Implementing Pull to Refresh (#28)

* Add swipe refresh layout
* Remove indicator and change border position
* Add snackbar for new grades and rename classes of synchronization
* Add info of the number of grades
* Add grades average average
* Add alert of new grades
* Fix crash while refreshing
* Add disappearing alerts
* Optimize res and fix crash
* Update API to version 27
This commit is contained in:
Rafał Borcz 2017-10-27 17:05:00 +02:00 committed by Mikołaj Pich
parent fe54fa71f3
commit f6e29490c3
35 changed files with 621 additions and 224 deletions

View File

@ -4,13 +4,13 @@ apply plugin: "io.github.ddimtirov.codacy"
apply plugin: 'org.greenrobot.greendao' apply plugin: 'org.greenrobot.greendao'
android { android {
compileSdkVersion 26 compileSdkVersion 27
buildToolsVersion "26.0.1" buildToolsVersion "27.0.0"
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 26 targetSdkVersion 27
versionCode 1 versionCode 1
versionName "0.1.0" versionName "0.1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -39,21 +39,19 @@ android {
} }
greendao { greendao {
schemaVersion 10 schemaVersion 13
generateTests = true generateTests = true
} }
dependencies { dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:27.0.0'
compile 'com.android.support:appcompat-v7:26.0.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'com.android.support:design:26.0.1' compile 'com.android.support:design:27.0.0'
compile 'com.android.support:support-vector-drawable:26.0.1' compile 'com.android.support:support-vector-drawable:27.0.0'
compile 'com.android.support:support-v4:26.0.1' compile 'com.android.support:support-v4:27.0.0'
compile 'com.android.support:recyclerview-v7:26.0.1' compile 'com.android.support:recyclerview-v7:27.0.0'
compile 'com.android.support:cardview-v7:26.0.1' compile 'com.android.support:cardview-v7:27.0.0'
compile 'com.firebase:firebase-jobdispatcher:0.8.1' compile 'com.firebase:firebase-jobdispatcher:0.8.4'
compile 'com.thoughtbot:expandablerecyclerview:1.3' compile 'com.thoughtbot:expandablerecyclerview:1.3'
compile 'org.apache.commons:commons-lang3:3.6' compile 'org.apache.commons:commons-lang3:3.6'
compile 'org.apache.commons:commons-collections4:4.1' compile 'org.apache.commons:commons-collections4:4.1'
@ -71,7 +69,7 @@ dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.1', { androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.1', {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
androidTestCompile 'com.android.support:support-annotations:26.0.1' androidTestCompile 'com.android.support:support-annotations:27.0.0'
androidTestCompile 'com.android.support.test:runner:1.0.1' androidTestCompile 'com.android.support.test:runner:1.0.1'
androidTestCompile 'com.android.support.test:rules:1.0.1' androidTestCompile 'com.android.support.test:rules:1.0.1'
androidTestCompile 'org.hamcrest:hamcrest-library:1.3' androidTestCompile 'org.hamcrest:hamcrest-library:1.3'

View File

@ -0,0 +1,50 @@
package io.github.wulkanowy.dao;
import android.support.test.InstrumentationRegistry;
import org.greenrobot.greendao.database.Database;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import io.github.wulkanowy.dao.entities.DaoMaster;
import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.dao.entities.Grade;
public class DatabaseAccessTest extends DatabaseAccess {
private static DaoSession daoSession;
@BeforeClass
public static void setUpClass() {
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext()
, "wulkanowyTest-database");
Database database = devOpenHelper.getWritableDb();
daoSession = new DaoMaster(database).newSession();
}
@Before
public void setUp() {
daoSession.getGradeDao().deleteAll();
daoSession.clear();
}
@Test
public void getNewGradesTest() {
daoSession.getGradeDao().insert(new Grade()
.setIsNew(true));
Assert.assertEquals(1, DatabaseAccess.getNewGrades(daoSession).size());
}
@AfterClass
public static void cleanUp() {
daoSession.getAccountDao().deleteAll();
daoSession.getGradeDao().deleteAll();
daoSession.getSubjectDao().deleteAll();
daoSession.clear();
}
}

View File

@ -40,7 +40,7 @@
android:label="@string/activity_dashboard_text" /> android:label="@string/activity_dashboard_text" />
<service <service
android:name=".services.jobs.GradesSync$GradeJob" android:name=".services.jobs.GradeJob$GradeService"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" /> <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE" />

View File

@ -3,12 +3,9 @@ package io.github.wulkanowy.activity.dashboard.grades;
import android.app.Activity; import android.app.Activity;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -21,16 +18,17 @@ import java.util.List;
import io.github.wulkanowy.R; import io.github.wulkanowy.R;
import io.github.wulkanowy.dao.entities.Grade; import io.github.wulkanowy.dao.entities.Grade;
import io.github.wulkanowy.utilities.AverageCalculator;
import static android.view.animation.Animation.RELATIVE_TO_SELF;
public class GradesAdapter extends ExpandableRecyclerViewAdapter<GradesAdapter.SubjectViewHolder, GradesAdapter.GradeViewHolder> { public class GradesAdapter extends ExpandableRecyclerViewAdapter<GradesAdapter.SubjectViewHolder, GradesAdapter.GradeViewHolder> {
private Activity activity; private Activity activity;
public GradesAdapter(List<? extends ExpandableGroup> groups, Context context) { private int numberOfNotReadGrade;
public GradesAdapter(List<? extends ExpandableGroup> groups, Activity activity) {
super(groups); super(groups);
activity = (Activity) context; this.activity = activity;
} }
@Override @Override
@ -59,80 +57,42 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter<GradesAdapter.S
private TextView subjectName; private TextView subjectName;
private ImageView indicatorDown; private TextView numberOfGrades;
private ImageView indicatorUp; private TextView averageGrades;
private ImageView subjectAlertNewGrades;
public SubjectViewHolder(View itemView) { public SubjectViewHolder(View itemView) {
super(itemView); super(itemView);
subjectName = itemView.findViewById(R.id.subject_text); subjectName = itemView.findViewById(R.id.subject_text);
indicatorDown = itemView.findViewById(R.id.group_indicator_down); numberOfGrades = itemView.findViewById(R.id.subject_number_of_grades);
indicatorUp = itemView.findViewById(R.id.group_indicator_up); subjectAlertNewGrades = itemView.findViewById(R.id.subject_new_grades_alert);
averageGrades = itemView.findViewById(R.id.subject_grades_average);
subjectAlertNewGrades.setVisibility(View.INVISIBLE);
} }
public void bind(ExpandableGroup group) { public void bind(ExpandableGroup group) {
subjectName.setText(group.getTitle()); int volumeGrades = group.getItemCount();
List<Grade> gradeList = group.getItems();
float average = AverageCalculator.calculate(gradeList);
if (isGroupExpanded(group)) { itemView.setTag(group.getTitle());
indicatorDown.setVisibility(View.INVISIBLE);
indicatorUp.setVisibility(View.VISIBLE); if (average < 0) {
averageGrades.setText(R.string.info_no_average);
} else { } else {
indicatorDown.setVisibility(View.VISIBLE); averageGrades.setText(activity.getResources().getString(R.string.info_average_grades, average));
indicatorUp.setVisibility(View.INVISIBLE);
}
} }
subjectName.setText(group.getTitle());
numberOfGrades.setText(activity.getResources().getQuantityString(R.plurals.numberOfGrades, volumeGrades, volumeGrades));
@Override for (Grade grade : gradeList) {
public void expand() { if (!grade.getRead()) {
RotateAnimation rotate = subjectAlertNewGrades.setVisibility(View.VISIBLE);
new RotateAnimation(-360, -180, RELATIVE_TO_SELF, 0.5f, RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(300);
rotate.setFillAfter(false);
rotate.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//Empty method definition
} }
@Override
public void onAnimationEnd(Animation animation) {
indicatorDown.setVisibility(View.INVISIBLE);
indicatorUp.setVisibility(View.VISIBLE);
} }
@Override
public void onAnimationRepeat(Animation animation) {
//Empty method definition
}
});
indicatorDown.setAnimation(rotate);
}
@Override
public void collapse() {
RotateAnimation rotate =
new RotateAnimation(360, 180, RELATIVE_TO_SELF, 0.5f, RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(300);
rotate.setFillAfter(false);
rotate.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//Empty method definition
}
@Override
public void onAnimationEnd(Animation animation) {
indicatorDown.setVisibility(View.VISIBLE);
indicatorUp.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
//Empty method definition
}
});
indicatorUp.setAnimation(rotate);
} }
} }
@ -144,32 +104,29 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter<GradesAdapter.S
private TextView dateGrade; private TextView dateGrade;
private Grade grade; private ImageView alertNewGrade;
public GradeViewHolder(final View itemView) { private View itemView;
private Grade gradeItem;
public GradeViewHolder(View itemView) {
super(itemView); super(itemView);
this.itemView = itemView;
gradeValue = itemView.findViewById(R.id.grade_text); gradeValue = itemView.findViewById(R.id.grade_text);
descriptionGrade = itemView.findViewById(R.id.description_grade_text); descriptionGrade = itemView.findViewById(R.id.description_grade_text);
dateGrade = itemView.findViewById(R.id.grade_date_text); dateGrade = itemView.findViewById(R.id.grade_date_text);
alertNewGrade = itemView.findViewById(R.id.grade_new_grades_alert);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GradesDialogFragment gradesDialogFragment = GradesDialogFragment.newInstance(grade);
gradesDialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
gradesDialogFragment.show(activity.getFragmentManager(), grade.toString());
}
});
} }
public void bind(Grade grade) { public void bind(Grade grade) {
this.grade = grade;
gradeValue.setText(grade.getValue()); gradeValue.setText(grade.getValue());
gradeValue.setBackgroundResource(grade.getValueColor()); gradeValue.setBackgroundResource(grade.getValueColor());
dateGrade.setText(grade.getDate()); dateGrade.setText(grade.getDate());
gradeItem = grade;
if (grade.getDescription().equals("") || grade.getDescription() == null) { if (grade.getDescription() == null || "".equals(grade.getDescription())) {
if (!grade.getSymbol().equals("")) { if (!"".equals(grade.getSymbol())) {
descriptionGrade.setText(grade.getSymbol()); descriptionGrade.setText(grade.getSymbol());
} else { } else {
descriptionGrade.setText(R.string.noDescription_text); descriptionGrade.setText(R.string.noDescription_text);
@ -178,6 +135,37 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter<GradesAdapter.S
descriptionGrade.setText(grade.getDescription()); descriptionGrade.setText(grade.getDescription());
} }
if (gradeItem.getRead()) {
alertNewGrade.setVisibility(View.INVISIBLE);
} else {
alertNewGrade.setVisibility(View.VISIBLE);
numberOfNotReadGrade++;
}
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GradesDialogFragment gradesDialogFragment = GradesDialogFragment.newInstance(gradeItem);
gradesDialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
gradesDialogFragment.show(activity.getFragmentManager(), gradeItem.toString());
if (!gradeItem.getRead()) {
numberOfNotReadGrade--;
}
if (numberOfNotReadGrade == 0) {
View subjectView = activity.findViewById(R.id.subject_grade_recycler).findViewWithTag(gradeItem.getSubject());
View subjectAlertNewGrade = subjectView.findViewById(R.id.subject_new_grades_alert);
subjectAlertNewGrade.setVisibility(View.INVISIBLE);
}
gradeItem.setRead(true);
gradeItem.setIsNew(false);
gradeItem.update();
alertNewGrade.setVisibility(View.INVISIBLE);
}
});
} }
} }
} }

View File

@ -3,70 +3,99 @@ package io.github.wulkanowy.activity.dashboard.grades;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.github.wulkanowy.R; import io.github.wulkanowy.R;
import io.github.wulkanowy.activity.WulkanowyApp; import io.github.wulkanowy.activity.WulkanowyApp;
import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.dao.DatabaseAccess;
import io.github.wulkanowy.dao.entities.Account; import io.github.wulkanowy.dao.entities.Account;
import io.github.wulkanowy.dao.entities.AccountDao; import io.github.wulkanowy.dao.entities.AccountDao;
import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.dao.entities.Grade; import io.github.wulkanowy.dao.entities.Grade;
import io.github.wulkanowy.dao.entities.Subject; import io.github.wulkanowy.dao.entities.Subject;
import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.VulcanSynchronization;
import io.github.wulkanowy.services.jobs.VulcanJobHelper;
import io.github.wulkanowy.utilities.ConnectionUtilities;
public class GradesFragment extends Fragment { public class GradesFragment extends Fragment {
private List<SubjectWithGrades> subjectWithGradesList = new ArrayList<>(); private List<SubjectWithGrades> subjectWithGradesList = new ArrayList<>();
private SwipeRefreshLayout swipeRefreshLayout;
private View view; private View view;
private RefreshTask refreshTask;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_grades, container, false); view = inflater.inflate(R.layout.fragment_grades, container, false);
DaoSession daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); swipeRefreshLayout = view.findViewById(R.id.grade_swipe_refresh);
swipeRefreshLayout.setColorSchemeResources(android.R.color.black,
android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (!ConnectionUtilities.isOnline(view.getContext())) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(view.getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show();
} else {
refreshTask = new RefreshTask();
refreshTask.execute(((WulkanowyApp) getActivity().getApplication()).getDaoSession());
}
}
});
if (new ArrayList<>().equals(subjectWithGradesList)) { if (new ArrayList<>().equals(subjectWithGradesList)) {
createExpListView(); createExpListView();
new GradesTask(daoSession).execute(); new GradesTask().execute(((WulkanowyApp) getActivity().getApplication()).getDaoSession());
} else if (subjectWithGradesList.size() > 0) { } else if (subjectWithGradesList.size() > 0) {
createExpListView(); createExpListView();
view.findViewById(R.id.loadingPanel).setVisibility(View.GONE); view.findViewById(R.id.loadingPanel).setVisibility(View.GONE);
} }
return view; return view;
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (refreshTask != null && refreshTask.getStatus() == AsyncTask.Status.RUNNING) {
refreshTask.cancel(true);
}
}
private void createExpListView() { private void createExpListView() {
RecyclerView recyclerView = view.findViewById(R.id.subject_grade_recycler); RecyclerView recyclerView = view.findViewById(R.id.subject_grade_recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, view.getContext()); GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, getActivity());
recyclerView.setAdapter(gradesAdapter); recyclerView.setAdapter(gradesAdapter);
} }
private class GradesTask extends AsyncTask<Void, Void, Void> { private void prepareSubjectsWithGradesList(DaoSession daoSession) {
subjectWithGradesList = new ArrayList<>();
private DaoSession daoSession; long userId = getContext().getSharedPreferences("LoginData", Context.MODE_PRIVATE)
GradesTask(DaoSession daoSession) {
this.daoSession = daoSession;
}
@Override
protected Void doInBackground(Void... params) {
long userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE)
.getLong("userId", 0); .getLong("userId", 0);
AccountDao accountDao = daoSession.getAccountDao(); AccountDao accountDao = daoSession.getAccountDao();
@ -79,6 +108,13 @@ public class GradesFragment extends Fragment {
subjectWithGradesList.add(subjectWithGrades); subjectWithGradesList.add(subjectWithGrades);
} }
} }
}
private class GradesTask extends AsyncTask<DaoSession, Void, Void> {
@Override
protected Void doInBackground(DaoSession... params) {
prepareSubjectsWithGradesList(params[0]);
return null; return null;
} }
@ -86,8 +122,51 @@ public class GradesFragment extends Fragment {
super.onPostExecute(result); super.onPostExecute(result);
createExpListView(); createExpListView();
view.findViewById(R.id.loadingPanel).setVisibility(View.GONE); view.findViewById(R.id.loadingPanel).setVisibility(View.GONE);
} }
} }
private class RefreshTask extends AsyncTask<DaoSession, Void, Boolean> {
@Override
protected Boolean doInBackground(DaoSession... params) {
VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession());
try {
vulcanSynchronization.loginCurrentUser(getContext(), params[0], new Vulcan());
vulcanSynchronization.syncGrades();
prepareSubjectsWithGradesList(params[0]);
return true;
} catch (Exception e) {
Log.e(VulcanJobHelper.DEBUG_TAG, "There was a synchronization problem", e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (result) {
prepareSubjectsWithGradesList(((WulkanowyApp) getActivity().getApplication()).getDaoSession());
createExpListView();
swipeRefreshLayout.setRefreshing(false);
int volumeGrades = DatabaseAccess.getNewGrades(((WulkanowyApp) getActivity().getApplication()).getDaoSession()).size();
if (volumeGrades == 0) {
Snackbar.make(getActivity().findViewById(R.id.fragment_container),
R.string.snackbar_no_grades,
Snackbar.LENGTH_SHORT).show();
} else {
Snackbar.make(getActivity().findViewById(R.id.fragment_container),
getString(R.string.snackbar_new_grade, volumeGrades),
Snackbar.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getContext(), R.string.refresh_error_text, Toast.LENGTH_SHORT).show();
swipeRefreshLayout.setRefreshing(false);
}
}
}
} }

View File

@ -19,7 +19,7 @@ import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.CryptoException;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.VulcanSynchronization; import io.github.wulkanowy.services.VulcanSynchronization;
import io.github.wulkanowy.services.jobs.GradesSync; import io.github.wulkanowy.services.jobs.GradeJob;
import io.github.wulkanowy.utilities.ConnectionUtilities; import io.github.wulkanowy.utilities.ConnectionUtilities;
public class LoginTask extends AsyncTask<String, Integer, Integer> { public class LoginTask extends AsyncTask<String, Integer, Integer> {
@ -74,7 +74,7 @@ public class LoginTask extends AsyncTask<String, Integer, Integer> {
protected void onPostExecute(Integer messageID) { protected void onPostExecute(Integer messageID) {
super.onPostExecute(messageID); super.onPostExecute(messageID);
GradesSync gradesSync = new GradesSync(); GradeJob gradesSync = new GradeJob();
gradesSync.scheduledJob(activity); gradesSync.scheduledJob(activity);
progress.dismiss(); progress.dismiss();

View File

@ -8,7 +8,7 @@ import android.widget.Toast;
import io.github.wulkanowy.R; import io.github.wulkanowy.R;
import io.github.wulkanowy.activity.dashboard.DashboardActivity; import io.github.wulkanowy.activity.dashboard.DashboardActivity;
import io.github.wulkanowy.activity.main.MainActivity; import io.github.wulkanowy.activity.main.MainActivity;
import io.github.wulkanowy.services.jobs.GradesSync; import io.github.wulkanowy.services.jobs.GradeJob;
import io.github.wulkanowy.utilities.ConnectionUtilities; import io.github.wulkanowy.utilities.ConnectionUtilities;
public class LoadingTask extends AsyncTask<Void, Void, Boolean> { public class LoadingTask extends AsyncTask<Void, Void, Boolean> {
@ -42,7 +42,7 @@ public class LoadingTask extends AsyncTask<Void, Void, Boolean> {
Intent intent = new Intent(context, MainActivity.class); Intent intent = new Intent(context, MainActivity.class);
context.startActivity(intent); context.startActivity(intent);
} else { } else {
GradesSync gradesSync = new GradesSync(); GradeJob gradesSync = new GradeJob();
gradesSync.scheduledJob(context); gradesSync.scheduledJob(context);
Intent intent = new Intent(context, DashboardActivity.class); Intent intent = new Intent(context, DashboardActivity.class);

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.dao;
import org.greenrobot.greendao.query.Query;
import java.util.List;
import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.dao.entities.Grade;
import io.github.wulkanowy.dao.entities.GradeDao;
public abstract class DatabaseAccess {
public static List<Grade> getNewGrades(DaoSession daoSession) {
Query<Grade> gradeQuery = daoSession.getGradeDao().queryBuilder()
.where(GradeDao.Properties.IsNew.eq(1))
.build();
return gradeQuery.list();
}
}

View File

@ -15,12 +15,24 @@ public abstract class EntitiesCompare {
.removeAll(newList, oldList)); .removeAll(newList, oldList));
List<Grade> updatedList = new ArrayList<>(CollectionUtils List<Grade> updatedList = new ArrayList<>(CollectionUtils
.removeAll(newList, addedOrUpdatedGradeList)); .removeAll(newList, addedOrUpdatedGradeList));
List<Grade> lastList = new ArrayList<>();
for (Grade grade : addedOrUpdatedGradeList) { for (Grade grade : addedOrUpdatedGradeList) {
grade.setIsNew(true); grade.setIsNew(true);
if (oldList.size() != 0) {
grade.setRead(false);
}
updatedList.add(grade); updatedList.add(grade);
} }
return updatedList; for (Grade grade : updatedList) {
for (Grade grade1 : oldList) {
if (grade.equals(grade1)) {
grade.setRead(grade1.getRead());
}
}
lastList.add(grade);
}
return lastList;
} }
} }

View File

@ -5,6 +5,7 @@ import android.os.Parcelable;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.greenrobot.greendao.DaoException;
import org.greenrobot.greendao.annotation.Entity; import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Id;
@ -12,7 +13,10 @@ import org.greenrobot.greendao.annotation.Property;
import io.github.wulkanowy.R; import io.github.wulkanowy.R;
@Entity(nameInDb = "Grades") @Entity(
nameInDb = "Grades",
active = true
)
public class Grade implements Parcelable { public class Grade implements Parcelable {
@Id(autoincrement = true) @Id(autoincrement = true)
@ -54,14 +58,18 @@ public class Grade implements Parcelable {
@Property(nameInDb = "IS_NEW") @Property(nameInDb = "IS_NEW")
private boolean isNew = false; private boolean isNew = false;
@Property(nameInDb = "READ")
private boolean read = true;
protected Grade(Parcel source) { protected Grade(Parcel source) {
value = source.readString(); value = source.readString();
} }
@Generated(hash = 1154096520) @Generated(hash = 568899968)
public Grade(Long id, Long subjectId, Long userId, String subject, String value, public Grade(Long id, Long subjectId, Long userId, String subject, String value,
String color, String symbol, String description, String weight, String color, String symbol, String description, String weight,
String date, String teacher, String semester, boolean isNew) { String date, String teacher, String semester, boolean isNew,
boolean read) {
this.id = id; this.id = id;
this.subjectId = subjectId; this.subjectId = subjectId;
this.userId = userId; this.userId = userId;
@ -75,6 +83,7 @@ public class Grade implements Parcelable {
this.teacher = teacher; this.teacher = teacher;
this.semester = semester; this.semester = semester;
this.isNew = isNew; this.isNew = isNew;
this.read = read;
} }
@Generated(hash = 2042976393) @Generated(hash = 2042976393)
@ -95,8 +104,8 @@ public class Grade implements Parcelable {
parcel.writeString(description); parcel.writeString(description);
parcel.writeString(weight); parcel.writeString(weight);
parcel.writeString(date); parcel.writeString(date);
parcel.writeString(value); parcel.writeString(teacher);
parcel.writeString(value); parcel.writeString(semester);
} }
public static final Creator<Grade> CREATOR = new Creator<Grade>() { public static final Creator<Grade> CREATOR = new Creator<Grade>() {
@ -111,6 +120,18 @@ public class Grade implements Parcelable {
} }
}; };
/**
* Used to resolve relations
*/
@Generated(hash = 2040040024)
private transient DaoSession daoSession;
/**
* Used for active entity operations.
*/
@Generated(hash = 681281562)
private transient GradeDao myDao;
public int getValueColor() { public int getValueColor() {
String replacedString = value.replaceAll("[^0-9]", ""); String replacedString = value.replaceAll("[^0-9]", "");
@ -284,7 +305,62 @@ public class Grade implements Parcelable {
return this.isNew; return this.isNew;
} }
public void setIsNew(boolean isNew) { public Grade setIsNew(boolean isNew) {
this.isNew = isNew; this.isNew = isNew;
return this;
}
public boolean getRead() {
return this.read;
}
public Grade setRead(boolean read) {
this.read = read;
return this;
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 128553479)
public void delete() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.delete(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 1942392019)
public void refresh() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.refresh(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 713229351)
public void update() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.update(this);
}
/**
* called by internal mechanisms, do not call yourself.
*/
@Generated(hash = 1187286414)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getGradeDao() : null;
} }
} }

View File

@ -1,6 +1,9 @@
package io.github.wulkanowy.services; package io.github.wulkanowy.services;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.api.Vulcan;
import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.dao.entities.DaoSession;
@ -38,4 +41,28 @@ public class LoginSession {
this.daoSession = daoSession; this.daoSession = daoSession;
return this; return this;
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoginSession that = (LoginSession) o;
return new EqualsBuilder()
.append(userId, that.userId)
.append(vulcan, that.vulcan)
.append(daoSession, that.daoSession)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(userId)
.append(vulcan)
.append(daoSession)
.toHashCode();
}
} }

View File

@ -12,7 +12,7 @@ import io.github.wulkanowy.api.login.LoginErrorException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException; import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.CryptoException;
import io.github.wulkanowy.services.jobs.VulcanSync; import io.github.wulkanowy.services.jobs.VulcanJobHelper;
import io.github.wulkanowy.services.synchronisation.AccountSynchronisation; import io.github.wulkanowy.services.synchronisation.AccountSynchronisation;
import io.github.wulkanowy.services.synchronisation.GradesSynchronisation; import io.github.wulkanowy.services.synchronisation.GradesSynchronisation;
import io.github.wulkanowy.services.synchronisation.SubjectsSynchronisation; import io.github.wulkanowy.services.synchronisation.SubjectsSynchronisation;
@ -47,7 +47,7 @@ public class VulcanSynchronization {
gradesSynchronisation.sync(loginSession); gradesSynchronisation.sync(loginSession);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.e(VulcanSync.DEBUG_TAG, "Synchronisation of grades failed", e); Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of grades failed", e);
return false; return false;
} }
} }
@ -60,7 +60,7 @@ public class VulcanSynchronization {
syncGrades(); syncGrades();
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.e(VulcanSync.DEBUG_TAG, "Synchronisation of subjects failed", e); Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of subjects failed", e);
return false; return false;
} }
} }

View File

@ -19,7 +19,7 @@ import io.github.wulkanowy.security.CryptoException;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.VulcanSynchronization; import io.github.wulkanowy.services.VulcanSynchronization;
public class GradesSync extends VulcanSync { public class GradeJob extends VulcanJobHelper {
public static final String UNIQUE_TAG = "GradesSync34512"; public static final String UNIQUE_TAG = "GradesSync34512";
@ -31,7 +31,7 @@ public class GradesSync extends VulcanSync {
protected Job createJob(FirebaseJobDispatcher dispatcher) { protected Job createJob(FirebaseJobDispatcher dispatcher) {
return dispatcher.newJobBuilder() return dispatcher.newJobBuilder()
.setLifetime(Lifetime.FOREVER) .setLifetime(Lifetime.FOREVER)
.setService(GradeJob.class) .setService(GradeService.class)
.setTag(UNIQUE_TAG) .setTag(UNIQUE_TAG)
.setRecurring(true) .setRecurring(true)
.setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END))
@ -41,7 +41,7 @@ public class GradesSync extends VulcanSync {
.build(); .build();
} }
public static class GradeJob extends VulcanJob { public static class GradeService extends VulcanService {
@Override @Override
public void workToBePerformed() throws CryptoException, BadCredentialsException, public void workToBePerformed() throws CryptoException, BadCredentialsException,

View File

@ -19,7 +19,7 @@ import io.github.wulkanowy.security.CryptoException;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.VulcanSynchronization; import io.github.wulkanowy.services.VulcanSynchronization;
public class SubjectsSync extends VulcanSync { public class SubjectJob extends VulcanJobHelper {
public static final String UNIQUE_TAG = "SubjectsSync34512"; public static final String UNIQUE_TAG = "SubjectsSync34512";
@ -31,7 +31,7 @@ public class SubjectsSync extends VulcanSync {
protected Job createJob(FirebaseJobDispatcher dispatcher) { protected Job createJob(FirebaseJobDispatcher dispatcher) {
return dispatcher.newJobBuilder() return dispatcher.newJobBuilder()
.setLifetime(Lifetime.UNTIL_NEXT_BOOT) .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
.setService(SubjectJob.class) .setService(SubjectService.class)
.setTag(UNIQUE_TAG) .setTag(UNIQUE_TAG)
.setRecurring(false) .setRecurring(false)
.setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END))
@ -41,7 +41,7 @@ public class SubjectsSync extends VulcanSync {
.build(); .build();
} }
private class SubjectJob extends VulcanJob { private class SubjectService extends VulcanService {
@Override @Override
public void workToBePerformed() throws CryptoException, BadCredentialsException, public void workToBePerformed() throws CryptoException, BadCredentialsException,

View File

@ -7,7 +7,7 @@ import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver; import com.firebase.jobdispatcher.GooglePlayDriver;
import com.firebase.jobdispatcher.Job; import com.firebase.jobdispatcher.Job;
public abstract class VulcanSync { public abstract class VulcanJobHelper {
public static final String DEBUG_TAG = "SynchronizationService"; public static final String DEBUG_TAG = "SynchronizationService";

View File

@ -13,20 +13,20 @@ import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException; import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.CryptoException;
public abstract class VulcanJob extends JobService { public abstract class VulcanService extends JobService {
private SyncTask syncTask = new SyncTask(); private SyncTask syncTask = new SyncTask();
@Override @Override
public boolean onStartJob(JobParameters params) { public boolean onStartJob(JobParameters params) {
Log.d(VulcanSync.DEBUG_TAG, "Wulkanowy services start"); Log.d(VulcanJobHelper.DEBUG_TAG, "Wulkanowy services start");
syncTask.execute(params); syncTask.execute(params);
return true; return true;
} }
@Override @Override
public boolean onStopJob(JobParameters params) { public boolean onStopJob(JobParameters params) {
Log.e(VulcanSync.DEBUG_TAG, "Wulkanowy serives stop"); Log.e(VulcanJobHelper.DEBUG_TAG, "Wulkanowy serives stop");
syncTask.cancel(true); syncTask.cancel(true);
return true; return true;
} }
@ -41,7 +41,7 @@ public abstract class VulcanJob extends JobService {
try { try {
workToBePerformed(); workToBePerformed();
} catch (Exception e) { } catch (Exception e) {
Log.e(VulcanSync.DEBUG_TAG, "User logging in the background failed", e); Log.e(VulcanJobHelper.DEBUG_TAG, "User logging in the background failed", e);
} finally { } finally {
jobFinished(params[0], false); jobFinished(params[0], false);
} }

View File

@ -18,7 +18,7 @@ import io.github.wulkanowy.dao.entities.DaoSession;
import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.CryptoException;
import io.github.wulkanowy.security.Safety; import io.github.wulkanowy.security.Safety;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.jobs.VulcanSync; import io.github.wulkanowy.services.jobs.VulcanJobHelper;
public class AccountSynchronisation { public class AccountSynchronisation {
@ -31,7 +31,7 @@ public class AccountSynchronisation {
if (userId != 0) { if (userId != 0) {
Log.d(VulcanSync.DEBUG_TAG, "Login current user id=" + String.valueOf(userId)); Log.d(VulcanJobHelper.DEBUG_TAG, "Login current user id=" + String.valueOf(userId));
Safety safety = new Safety(); Safety safety = new Safety();
Account account = accountDao.load(userId); Account account = accountDao.load(userId);
@ -46,7 +46,7 @@ public class AccountSynchronisation {
.setUserId(userId) .setUserId(userId)
.setVulcan(vulcan); .setVulcan(vulcan);
} else { } else {
Log.wtf(VulcanSync.DEBUG_TAG, "loginCurrentUser - USERID IS EMPTY"); Log.wtf(VulcanJobHelper.DEBUG_TAG, "loginCurrentUser - USERID IS EMPTY");
throw new IOException("Can't find user with index 0"); throw new IOException("Can't find user with index 0");
} }
} }
@ -70,7 +70,7 @@ public class AccountSynchronisation {
userId = accountDao.insert(account); userId = accountDao.insert(account);
Log.d(VulcanSync.DEBUG_TAG, "Login and save new user id=" + String.valueOf(userId)); Log.d(VulcanJobHelper.DEBUG_TAG, "Login and save new user id=" + String.valueOf(userId));
SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit(); SharedPreferences.Editor editor = sharedPreferences.edit();

View File

@ -19,7 +19,7 @@ import io.github.wulkanowy.dao.entities.GradeDao;
import io.github.wulkanowy.dao.entities.Subject; import io.github.wulkanowy.dao.entities.Subject;
import io.github.wulkanowy.dao.entities.SubjectDao; import io.github.wulkanowy.dao.entities.SubjectDao;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.jobs.VulcanSync; import io.github.wulkanowy.services.jobs.VulcanJobHelper;
import io.github.wulkanowy.utilities.ConversionVulcanObject; import io.github.wulkanowy.utilities.ConversionVulcanObject;
public class GradesSynchronisation { public class GradesSynchronisation {
@ -57,6 +57,6 @@ public class GradesSynchronisation {
gradeDao.insertInTx(lastList); gradeDao.insertInTx(lastList);
Log.d(VulcanSync.DEBUG_TAG, "Synchronization grades (amount = " + String.valueOf(lastList.size() + ")")); Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization grades (amount = " + String.valueOf(lastList.size() + ")"));
} }
} }

View File

@ -12,7 +12,7 @@ import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.dao.entities.Subject; import io.github.wulkanowy.dao.entities.Subject;
import io.github.wulkanowy.dao.entities.SubjectDao; import io.github.wulkanowy.dao.entities.SubjectDao;
import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.LoginSession;
import io.github.wulkanowy.services.jobs.VulcanSync; import io.github.wulkanowy.services.jobs.VulcanJobHelper;
import io.github.wulkanowy.utilities.ConversionVulcanObject; import io.github.wulkanowy.utilities.ConversionVulcanObject;
public class SubjectsSynchronisation { public class SubjectsSynchronisation {
@ -36,6 +36,6 @@ public class SubjectsSynchronisation {
subjectDao.insertInTx(preparedList); subjectDao.insertInTx(preparedList);
Log.d(VulcanSync.DEBUG_TAG, "Synchronization subjects (amount = " + String.valueOf(subjectEntitiesList.size() + ")")); Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization subjects (amount = " + String.valueOf(subjectEntitiesList.size() + ")"));
} }
} }

View File

@ -0,0 +1,53 @@
package io.github.wulkanowy.utilities;
import java.util.List;
import io.github.wulkanowy.dao.entities.Grade;
public abstract class AverageCalculator {
public static float calculate(List<Grade> gradeList) {
float counter = 0f;
float denominator = 0f;
for (Grade grade : gradeList) {
int integerWeight = getIntegerForWeightOfGrade(grade.getWeight());
float floatValue = getMathematicalValueOfGrade(grade.getValue());
if (floatValue != -1f) {
counter += floatValue * integerWeight;
denominator += integerWeight;
}
}
if (counter == 0f) {
return -1f;
} else {
return counter / denominator;
}
}
private static float getMathematicalValueOfGrade(String valueOfGrade) {
if (valueOfGrade.matches("[-|+|=]{0,2}[0-6]") || valueOfGrade.matches("[0-6][-|+|=]{0,2}")) {
if (valueOfGrade.matches("[-][0-6]") || valueOfGrade.matches("[0-6][-]")) {
String replacedValue = valueOfGrade.replaceAll("[-]", "");
return Float.valueOf(replacedValue) - 0.25f;
} else if (valueOfGrade.matches("[+][0-6]") || valueOfGrade.matches("[0-6][+]")) {
String replacedValue = valueOfGrade.replaceAll("[+]", "");
return Float.valueOf((replacedValue)) + 0.25f;
} else if (valueOfGrade.matches("[-|=]{1,2}[0-6]") || valueOfGrade.matches("[0-6][-|=]{1,2}")) {
String replacedValue = valueOfGrade.replaceAll("[-|=]{1,2}", "");
return Float.valueOf((replacedValue)) - 0.5f;
} else {
return Float.valueOf(valueOfGrade);
}
} else {
return -1;
}
}
private static int getIntegerForWeightOfGrade(String weightOfGrade) {
return Integer.valueOf(weightOfGrade.substring(0, weightOfGrade.length() - 3));
}
}

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="35dp"
android:height="35dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#000000"
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
<path android:pathData="M0-.75h24v24H0z" />
</vector>

View File

@ -4,5 +4,12 @@
android:width="24dp" android:width="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#ffffff" android:pathData="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /> <path
android:fillColor="#ffffff"
android:pathData="M19.4,13c0-0.3,0.1-0.7,0.1-1s0-0.7-0.1-1l2.1-1.5c0.2-0.1,0.3-0.4,0.1-0.7l-2-3.5C19.5,5,19.2,4.9,19,5l-2.4,1.1
c-0.5-0.4-1.2-0.8-1.8-1l-0.3-2.6C14.5,2.2,14.3,2,14,2H10C9.7,2,9.5,2.2,9.5,2.5L9.2,5c-0.7,0.3-1.3,0.6-1.8,1L5,5
C4.8,4.9,4.5,5,4.4,5.2l-2,3.5C2.2,9,2.2,9.3,2.5,9.4L4.6,11c0,0.3-0.1,0.7-0.1,1s0,0.7,0.1,1l-2.1,1.5c-0.2,0.1-0.3,0.4-0.1,0.7
l2,3.5C4.5,19,4.8,19.1,5,19l2.4-1.1c0.5,0.4,1.2,0.8,1.8,1l0.3,2.6c0,0.3,0.2,0.5,0.5,0.5H14c0.3,0,0.5-0.2,0.5-0.5l0.3-2.6
c0.7-0.3,1.3-0.6,1.8-1L19,19c0.2,0.1,0.5,0,0.6-0.2l2-3.5c0.1-0.2,0.1-0.5-0.1-0.7L19.4,13z M12,16c-2.2,0-4-1.8-4-4
c0-2.2,1.8-4,4-4s4,1.8,4,4C16,14.2,14.2,16,12,16z" />
</vector> </vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/colorPrimary"
android:pathData="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</vector>

View File

@ -2,7 +2,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<gradient <gradient
android:angle="90" android:angle="270"
android:centerColor="@android:color/transparent" android:centerColor="@android:color/transparent"
android:centerX="0.01" android:centerX="0.01"
android:startColor="#60606060" /> android:startColor="#60606060" />

View File

@ -9,12 +9,11 @@
android:weightSum="1" android:weightSum="1"
tools:context="io.github.wulkanowy.activity.dashboard.DashboardActivity"> tools:context="io.github.wulkanowy.activity.dashboard.DashboardActivity">
<FrameLayout <android.support.design.widget.CoordinatorLayout
android:id="@+id/fragment_container" android:id="@+id/fragment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:fitsSystemWindows="true"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior" />

View File

@ -1,13 +1,19 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_grade"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="io.github.wulkanowy.activity.dashboard.grades.GradesFragment"> tools:context="io.github.wulkanowy.activity.dashboard.grades.GradesFragment">
<android.support.v7.widget.RecyclerView <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/grade_swipe_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:id="@+id/subject_grade_recycler" />
<android.support.v7.widget.RecyclerView
android:id="@+id/subject_grade_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout <RelativeLayout
android:id="@+id/loadingPanel" android:id="@+id/loadingPanel"
@ -21,4 +27,6 @@
android:indeterminate="true" /> android:indeterminate="true" />
</RelativeLayout> </RelativeLayout>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout> </android.support.design.widget.CoordinatorLayout>

View File

@ -1,4 +1,5 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/grade_cardview" android:id="@+id/grade_cardview"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -23,27 +24,28 @@
<TextView <TextView
android:id="@+id/grade_text" android:id="@+id/grade_text"
android:layout_width="50dp" android:layout_width="45dp"
android:layout_height="50dp" android:layout_height="45dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:gravity="center" android:gravity="center"
android:textSize="19sp" /> android:maxLength="5"
android:textSize="16sp" />
<TextView <TextView
android:id="@+id/description_grade_text" android:id="@+id/description_grade_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_marginEnd="10dp" android:layout_marginEnd="40dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" android:layout_marginRight="40dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_toEndOf="@+id/grade_text" android:layout_toEndOf="@+id/grade_text"
android:layout_toRightOf="@+id/grade_text" android:layout_toRightOf="@+id/grade_text"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="19sp" /> android:textSize="15sp" />
<TextView <TextView
android:id="@+id/grade_date_text" android:id="@+id/grade_date_text"
@ -53,7 +55,16 @@
android:layout_alignLeft="@+id/description_grade_text" android:layout_alignLeft="@+id/description_grade_text"
android:layout_alignStart="@+id/description_grade_text" android:layout_alignStart="@+id/description_grade_text"
android:text="@string/grades_text" android:text="@string/grades_text"
android:textSize="13sp" /> android:textSize="12sp" />
<ImageView
android:id="@+id/grade_new_grades_alert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
app:srcCompat="@drawable/subject_alert_circle" />
</RelativeLayout> </RelativeLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>

View File

@ -179,6 +179,7 @@
android:layout_marginRight="10dp" android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:gravity="center" android:gravity="center"
android:text="@string/ok_text" android:text="@string/ok_text"
android:textSize="16sp" /> android:textSize="16sp" />

View File

@ -2,36 +2,51 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/border" android:background="@drawable/subject_border_1px"
android:foreground="?attr/selectableItemBackgroundBorderless"> android:foreground="?attr/selectableItemBackgroundBorderless"
android:padding="15dp">
<TextView <TextView
android:id="@+id/subject_text" android:id="@+id/subject_text"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="15dp" android:layout_marginEnd="35dp"
android:layout_marginRight="50dp" android:layout_marginRight="35dp"
android:layout_marginEnd="50dp" android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="19sp" /> android:textSize="17sp" />
<TextView
android:id="@+id/subject_grades_average"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/subject_text"
android:layout_marginTop="5dp"
android:text="@string/app_name"
android:textColor="#4C4C4C"
android:textSize="12sp" />
<TextView
android:id="@+id/subject_number_of_grades"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/subject_text"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@+id/subject_grades_average"
android:layout_toRightOf="@+id/subject_grades_average"
android:text="@string/app_name"
android:textColor="#4C4C4C"
android:textSize="12sp" />
<ImageView <ImageView
android:id="@+id/group_indicator_down" android:id="@+id/subject_new_grades_alert"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_marginTop="10dp"
app:srcCompat="@drawable/ic_arrow_down" /> app:srcCompat="@drawable/subject_alert_circle" />
<ImageView
android:id="@+id/group_indicator_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:rotation="180"
app:srcCompat="@drawable/ic_arrow_down" />
</RelativeLayout> </RelativeLayout>

View File

@ -20,11 +20,8 @@
<string name="lessonplan_text">Plan lekcji</string> <string name="lessonplan_text">Plan lekcji</string>
<string name="settings_text">Ustawienia</string> <string name="settings_text">Ustawienia</string>
<string name="noInternet_text">Brak połączenia z internetem</string> <string name="noInternet_text">Brak połączenia z internetem</string>
<string name="login_cookies_save_failed_text">Nie udało się zapisać sesji</string>
<string name="SQLite_ioError_text">W bazie danych wystąpił błąd. Zrestartuj aplikacje a także sprawdź iość wolnego miejsca w pamięci wewnętrznej</string>
<string name="root_failed_text">To urządzenie posiada posiada podwyższone uprawnienia (root). Automatyczne logowanie zosatło wyłączone.</string> <string name="root_failed_text">To urządzenie posiada posiada podwyższone uprawnienia (root). Automatyczne logowanie zosatło wyłączone.</string>
<string name="encrypt_failed_text">Szyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone</string> <string name="encrypt_failed_text">Szyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone</string>
<string name="decrypt_failed_text">Deszyfrowanie nie powiodło się. Automatyczne logowanie zostało wyłączone</string>
<string name="version_text">Wersja\u0020</string> <string name="version_text">Wersja\u0020</string>
<string name="dialog_description_text">Opis</string> <string name="dialog_description_text">Opis</string>
<string name="dialog_weight_text">Waga</string> <string name="dialog_weight_text">Waga</string>
@ -38,5 +35,16 @@
<string name="color_green_text">Zielony</string> <string name="color_green_text">Zielony</string>
<string name="noColor_text">Brak koloru</string> <string name="noColor_text">Brak koloru</string>
<string name="ok_text">OK</string> <string name="ok_text">OK</string>
<string name="dialog_subject_text">Przedmiot</string> <string name="refresh_error_text">"Podczas odświeżania zawartości wystąpił błąd. "</string>
<string name="snackbar_no_grades">Brak nowych ocen</string>
<string name="snackbar_new_grade">Ilość nowych ocen: %1$d</string>
<plurals name="numberOfGrades">
<item quantity="one">%d ocena</item>
<item quantity="few">%d oceny</item>
<item quantity="many">%d ocen</item>
<item quantity="other">%d ocen</item>
</plurals>
<string name="info_average_grades">Średnia: %1$.2f</string>
<string name="info_no_average">Brak średniej</string>
</resources> </resources>

View File

@ -20,11 +20,8 @@
<string name="lessonplan_text">Lesson Plan</string> <string name="lessonplan_text">Lesson Plan</string>
<string name="settings_text">Settings</string> <string name="settings_text">Settings</string>
<string name="noInternet_text">No internet connection</string> <string name="noInternet_text">No internet connection</string>
<string name="login_cookies_save_failed_text">Failed to save session</string>
<string name="SQLite_ioError_text">An error occurred in the database. Restart the applications and check the free space in the internal memory</string>
<string name="root_failed_text">This device is rooted. Automatic login has been disabled</string> <string name="root_failed_text">This device is rooted. Automatic login has been disabled</string>
<string name="encrypt_failed_text">Encryption failed. Automatic login has been disabled</string> <string name="encrypt_failed_text">Encryption failed. Automatic login has been disabled</string>
<string name="decrypt_failed_text">Decrypt is failed. Automatic login has been disable</string>
<string name="version_text">Version\u0020</string> <string name="version_text">Version\u0020</string>
<string name="dialog_description_text">Description</string> <string name="dialog_description_text">Description</string>
<string name="dialog_weight_text">Weight</string> <string name="dialog_weight_text">Weight</string>
@ -38,5 +35,14 @@
<string name="color_green_text">Green</string> <string name="color_green_text">Green</string>
<string name="noColor_text">No color</string> <string name="noColor_text">No color</string>
<string name="ok_text">OK</string> <string name="ok_text">OK</string>
<string name="dialog_subject_text">Subject</string> <string name="refresh_error_text">An error occurred while refreshing the content.</string>
<string name="snackbar_no_grades">No new grades</string>
<string name="snackbar_new_grade">Number of new grades: %1$d</string>
<plurals name="numberOfGrades">
<item quantity="one">%d grade</item>
<item quantity="other">%d grades</item>
</plurals>
<string name="info_average_grades">Average: %1$.2f</string>
<string name="info_no_average">No average</string>
</resources> </resources>

View File

@ -0,0 +1,42 @@
package io.github.wulkanowy.utilities;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.dao.entities.Grade;
public class AverageCalculatorTest extends AverageCalculator {
@Test
public void averageTest() {
List<Grade> gradeList = new ArrayList<>();
gradeList.add(new Grade().setValue("np.").setWeight("1,00"));
gradeList.add(new Grade().setValue("-5").setWeight("10,00"));
gradeList.add(new Grade().setValue("--5").setWeight("10,00"));
gradeList.add(new Grade().setValue("=5").setWeight("10,00"));
gradeList.add(new Grade().setValue("+5").setWeight("10,00"));
gradeList.add(new Grade().setValue("5").setWeight("10,00"));
List<Grade> gradeList1 = new ArrayList<>();
gradeList1.add(new Grade().setValue("np.").setWeight("1,00"));
gradeList1.add(new Grade().setValue("5-").setWeight("10,00"));
gradeList1.add(new Grade().setValue("5--").setWeight("10,00"));
gradeList1.add(new Grade().setValue("5=").setWeight("10,00"));
gradeList1.add(new Grade().setValue("5+").setWeight("10,00"));
gradeList1.add(new Grade().setValue("5").setWeight("10,00"));
Assert.assertEquals(4.8f, AverageCalculator.calculate(gradeList), 0.0f);
Assert.assertEquals(4.8f, AverageCalculator.calculate(gradeList1), 0.0f);
}
@Test
public void errorAverageTest() {
List<Grade> gradeList = new ArrayList<>();
gradeList.add(new Grade().setValue("np.").setWeight("1,00"));
Assert.assertEquals(-1f, AverageCalculator.calculate(gradeList), 0.0f);
}
}

View File

@ -8,7 +8,7 @@ buildscript {
maven { url 'https://maven.google.com' } maven { url 'https://maven.google.com' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -1,6 +1,6 @@
#Wed Mar 08 19:17:31 CET 2017 #Thu Oct 26 14:47:13 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip