From 690b7304944f3bae896db09080e84fc8c1730e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 17 Sep 2017 18:04:28 +0200 Subject: [PATCH] Migrate to greenDAO (#23) * Migration of database operation to greenDAO * Disable crashlitics for debug builds * Remove unused drawable * Fix crash when user have one grade --- app/build.gradle | 14 +- .../wulkanowy/dao/entities/AccountTest.java | 18 ++ .../wulkanowy/dao/entities/GradeTest.java | 19 ++ .../wulkanowy/dao/entities/SubjectTest.java | 18 ++ app/src/main/AndroidManifest.xml | 1 + .../wulkanowy/activity/WulkanowyApp.java | 43 +++ .../activity/dashboard/DashboardActivity.java | 4 +- .../activity/dashboard/grades/GradeItem.java | 59 ---- .../dashboard/grades/GradesAdapter.java | 19 +- .../grades/GradesDialogFragment.java | 23 +- .../dashboard/grades/GradesFragment.java | 51 +-- .../dashboard/grades/SubjectWithGrades.java | 6 +- .../wulkanowy/activity/main/LoginTask.java | 30 +- .../wulkanowy/activity/main/MainActivity.java | 2 +- .../activity/started/LoadingTask.java | 2 +- .../activity/started/StartedActivity.java | 2 +- .../io/github/wulkanowy/api/grades/Grade.java | 86 ----- .../github/wulkanowy/dao/EntitiesCompare.java | 26 ++ .../wulkanowy/dao/entities/Account.java | 211 ++++++++++++ .../github/wulkanowy/dao/entities/Grade.java | 299 ++++++++++++++++++ .../wulkanowy/dao/entities/Subject.java | 191 +++++++++++ .../wulkanowy/database/DatabaseAdapter.java | 90 ------ .../wulkanowy/database/DatabaseComparer.java | 25 -- .../wulkanowy/database/DatabaseHelper.java | 65 ---- .../wulkanowy/database/accounts/Account.java | 60 ---- .../database/accounts/AccountsDatabase.java | 81 ----- .../database/grades/GradesDatabase.java | 155 --------- .../database/subjects/SubjectsDatabase.java | 144 --------- .../wulkanowy/security/CryptoException.java | 3 - .../github/wulkanowy/security/Scrambler.java | 42 +-- .../wulkanowy/services/jobs/GradesSync.java | 8 +- .../wulkanowy/services/jobs/SubjectsSync.java | 10 +- .../wulkanowy/services/jobs/VulcanJob.java | 4 +- .../synchronisation/DataSynchronisation.java | 26 +- .../GradesSynchronisation.java | 53 +++- .../SubjectsSynchronisation.java | 32 +- .../VulcanSynchronisation.java | 64 ++-- .../utilities/ConversionVulcanObject.java | 48 +++ app/src/main/res/drawable/sample_0.png | Bin 37861 -> 0 bytes app/src/main/res/layout/subject_item.xml | 4 +- app/src/main/res/values/styles.xml | 2 +- .../wulkanowy/dao/EntitiesCompareTest.java | 80 +++++ .../wulkanowy/dao/entities/GradeTest.java | 35 ++ build.gradle | 1 + 44 files changed, 1222 insertions(+), 934 deletions(-) create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/AccountTest.java create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/GradeTest.java create mode 100644 app/src/androidTest/java/io/github/wulkanowy/dao/entities/SubjectTest.java create mode 100644 app/src/main/java/io/github/wulkanowy/activity/WulkanowyApp.java delete mode 100644 app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradeItem.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/EntitiesCompare.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Account.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java create mode 100644 app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/DatabaseAdapter.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/DatabaseComparer.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/DatabaseHelper.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/accounts/Account.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/accounts/AccountsDatabase.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/grades/GradesDatabase.java delete mode 100644 app/src/main/java/io/github/wulkanowy/database/subjects/SubjectsDatabase.java create mode 100644 app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java delete mode 100644 app/src/main/res/drawable/sample_0.png create mode 100644 app/src/test/java/io/github/wulkanowy/dao/EntitiesCompareTest.java create mode 100644 app/src/test/java/io/github/wulkanowy/dao/entities/GradeTest.java diff --git a/app/build.gradle b/app/build.gradle index 0881e8ba4..277334181 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,14 @@ apply plugin: 'com.android.application' apply plugin: 'jacoco-android' apply plugin: "io.github.ddimtirov.codacy" +apply plugin: 'org.greenrobot.greendao' android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "io.github.wulkanowy" + testApplicationId "io.github.tests.wulkanowy" minSdkVersion 14 targetSdkVersion 26 versionCode 1 @@ -21,10 +23,16 @@ android { } debug { testCoverageEnabled = true + ext.enableCrashlytics = false } } } +greendao { + schemaVersion 10 + generateTests = true +} + dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { @@ -43,7 +51,11 @@ dependencies { compile 'org.apache.commons:commons-lang3:3.6' compile 'org.apache.commons:commons-collections4:4.1' compile 'org.jsoup:jsoup:1.10.3' + compile 'org.greenrobot:greendao:3.2.2' + + debugCompile 'com.amitshekhar.android:debug-db:1.0.1' + debugCompile 'net.zetetic:android-database-sqlcipher:3.5.7@aar' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.9.0' + testCompile 'org.mockito:mockito-core:2.10.0' } diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/AccountTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/AccountTest.java new file mode 100644 index 000000000..4b9ddc5aa --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/AccountTest.java @@ -0,0 +1,18 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class AccountTest extends AbstractDaoTestLongPk { + + public AccountTest() { + super(AccountDao.class); + } + + @Override + protected Account createEntity(Long key) { + Account entity = new Account(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/GradeTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/GradeTest.java new file mode 100644 index 000000000..31d30c977 --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/GradeTest.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class GradeTest extends AbstractDaoTestLongPk { + + public GradeTest() { + super(GradeDao.class); + } + + @Override + protected Grade createEntity(Long key) { + Grade entity = new Grade(); + entity.setId(key); + entity.setIsNew(false); + return entity; + } + +} diff --git a/app/src/androidTest/java/io/github/wulkanowy/dao/entities/SubjectTest.java b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/SubjectTest.java new file mode 100644 index 000000000..9d2464edf --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/dao/entities/SubjectTest.java @@ -0,0 +1,18 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.test.AbstractDaoTestLongPk; + +public class SubjectTest extends AbstractDaoTestLongPk { + + public SubjectTest() { + super(SubjectDao.class); + } + + @Override + protected Subject createEntity(Long key) { + Subject entity = new Subject(); + entity.setId(key); + return entity; + } + +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0bebe51b..244a06b1d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ CREATOR = new Creator() { - @Override - public GradeItem createFromParcel(Parcel source) { - return new GradeItem(source); - } - - @Override - public GradeItem[] newArray(int size) { - return new GradeItem[size]; - } - }; -} diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java b/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java index a50bad1d3..9a4ca82f6 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java +++ b/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/GradesAdapter.java @@ -20,6 +20,7 @@ import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder; import java.util.List; import io.github.wulkanowy.R; +import io.github.wulkanowy.dao.entities.Grade; import static android.view.animation.Animation.RELATIVE_TO_SELF; @@ -51,7 +52,7 @@ public class GradesAdapter extends ExpandableRecyclerViewAdapter 1) { + DaoSession daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); + + if (subjectWithGradesList.equals(new ArrayList<>())) { + new GradesTask(daoSession).execute(); + } else if (subjectWithGradesList.size() > 0) { createExpListView(); view.findViewById(R.id.loadingPanel).setVisibility(View.GONE); } @@ -42,48 +47,46 @@ public class GradesFragment extends Fragment { public void createExpListView() { - RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.subject_grade_recycler); + RecyclerView recyclerView = view.findViewById(R.id.subject_grade_recycler); recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, view.getContext()); recyclerView.setAdapter(gradesAdapter); } - public class MarksTask extends AsyncTask { + private class GradesTask extends AsyncTask { - private Context context; + private DaoSession daoSession; - MarksTask(Context context) { - this.context = context; + GradesTask(DaoSession daoSession) { + this.daoSession = daoSession; } @Override protected Void doInBackground(Void... params) { - SubjectsDatabase subjectsDatabase = new SubjectsDatabase(context); - GradesDatabase gradesDatabase = new GradesDatabase(context); + long userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) + .getLong("userId", 0); - gradesDatabase.open(); + AccountDao accountDao = daoSession.getAccountDao(); + Account account = accountDao.load(userId); - for (Subject subject : subjectsDatabase.getAllSubjectsNames()) { - List gradeItems = gradesDatabase.getSubjectGrades(context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("isLogin", 0), - SubjectsDatabase.getSubjectId(subject.getName())); - if (gradeItems.size() > 0) { - subjectWithGradesList.add(new SubjectWithGrades(subject.getName(), gradeItems)); + for (Subject subject : account.getSubjectList()) { + List gradeList = subject.getGradeList(); + if (gradeList.size() != 0) { + SubjectWithGrades subjectWithGrades = new SubjectWithGrades(subject.getName(), gradeList); + subjectWithGradesList.add(subjectWithGrades); } } - - gradesDatabase.close(); - return null; } protected void onPostExecute(Void result) { + super.onPostExecute(result); + createExpListView(); view.findViewById(R.id.loadingPanel).setVisibility(View.GONE); - - super.onPostExecute(result); } } } diff --git a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java b/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java index 5e60e5919..3938a3fd8 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java +++ b/app/src/main/java/io/github/wulkanowy/activity/dashboard/grades/SubjectWithGrades.java @@ -5,9 +5,11 @@ import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; import java.util.List; -public class SubjectWithGrades extends ExpandableGroup { +import io.github.wulkanowy.dao.entities.Grade; - public SubjectWithGrades(String title, List items) { +public class SubjectWithGrades extends ExpandableGroup { + + public SubjectWithGrades(String title, List items) { super(title, items); } } diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java b/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java index d04756f71..558429e61 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java @@ -1,7 +1,7 @@ package io.github.wulkanowy.activity.main; +import android.app.Activity; import android.app.ProgressDialog; -import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.widget.Toast; @@ -21,21 +21,21 @@ import io.github.wulkanowy.utilities.ConnectionUtilities; public class LoginTask extends AsyncTask { - private Context context; + private Activity activity; private ProgressDialog progress; - public LoginTask(Context context) { - this.context = context; - this.progress = new ProgressDialog(context); + public LoginTask(Activity activity) { + this.activity = activity; + this.progress = new ProgressDialog(activity); } @Override protected void onPreExecute() { super.onPreExecute(); - progress.setTitle(context.getText(R.string.login_text)); - progress.setMessage(context.getText(R.string.please_wait_text)); + progress.setTitle(activity.getText(R.string.login_text)); + progress.setMessage(activity.getText(R.string.please_wait_text)); progress.setCancelable(false); progress.show(); } @@ -43,10 +43,10 @@ public class LoginTask extends AsyncTask { @Override protected Integer doInBackground(String... credentials) { - if (ConnectionUtilities.isOnline(context)) { + if (ConnectionUtilities.isOnline(activity)) { VulcanSynchronisation vulcanSynchronisation = new VulcanSynchronisation(); try { - vulcanSynchronisation.loginNewUser(credentials[0], credentials[1], credentials[2], context); + vulcanSynchronisation.loginNewUser(credentials[0], credentials[1], credentials[2], activity); } catch (BadCredentialsException e) { return R.string.login_bad_credentials_text; } catch (AccountPermissionException e) { @@ -57,8 +57,8 @@ public class LoginTask extends AsyncTask { return R.string.login_denied_text; } - DataSynchronisation dataSynchronisation = new DataSynchronisation(context); - dataSynchronisation.syncGradesAndSubjects(vulcanSynchronisation); + DataSynchronisation dataSynchronisation = new DataSynchronisation(activity); + dataSynchronisation.syncSubjectsAndGrades(vulcanSynchronisation); return R.string.login_accepted_text; @@ -71,16 +71,16 @@ public class LoginTask extends AsyncTask { super.onPostExecute(messageID); GradesSync gradesSync = new GradesSync(); - gradesSync.scheduledJob(context); + gradesSync.scheduledJob(activity); progress.dismiss(); - Toast.makeText(context, context.getString(messageID), Toast.LENGTH_LONG).show(); + Toast.makeText(activity, activity.getString(messageID), Toast.LENGTH_LONG).show(); if (messageID == R.string.login_accepted_text || messageID == R.string.root_failed_text || messageID == R.string.encrypt_failed_text) { - Intent intent = new Intent(context, DashboardActivity.class); - context.startActivity(intent); + Intent intent = new Intent(activity, DashboardActivity.class); + activity.startActivity(intent); } } } diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java index a7ee5f1b8..1d162f4f3 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java +++ b/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java @@ -45,7 +45,7 @@ public class MainActivity extends AppCompatActivity { private void autoComplete() { // Get a reference to the AutoCompleteTextView in the layout - AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.symbolText); + AutoCompleteTextView textView = findViewById(R.id.symbolText); // Get the string array String[] countries = getResources().getStringArray(R.array.symbols); // Create the adapter and set it to the AutoCompleteTextView diff --git a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java b/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java index 363d747f5..7ca8b59ea 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java +++ b/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java @@ -38,7 +38,7 @@ public class LoadingTask extends AsyncTask { Toast.makeText(context, R.string.noInternet_text, Toast.LENGTH_LONG).show(); } - if (context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("isLogin", 0) == 0) { + if (context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0) == 0) { Intent intent = new Intent(context, MainActivity.class); context.startActivity(intent); } else { diff --git a/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java b/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java index 143aa4e83..3e3c90014 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java +++ b/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java @@ -14,7 +14,7 @@ public class StartedActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_started); - TextView versionName = (TextView) findViewById(R.id.rawText); + TextView versionName = findViewById(R.id.rawText); versionName.setText(getText(R.string.version_text) + BuildConfig.VERSION_NAME); new LoadingTask(this).execute(); diff --git a/app/src/main/java/io/github/wulkanowy/api/grades/Grade.java b/app/src/main/java/io/github/wulkanowy/api/grades/Grade.java index 2654917da..0e0c85246 100644 --- a/app/src/main/java/io/github/wulkanowy/api/grades/Grade.java +++ b/app/src/main/java/io/github/wulkanowy/api/grades/Grade.java @@ -1,14 +1,6 @@ package io.github.wulkanowy.api.grades; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - public class Grade { - protected int id; - - private int userID; - - private int subjectID; private String subject = ""; @@ -28,38 +20,6 @@ public class Grade { private String semester = ""; - private boolean isNew; - - public int getId() { - return id; - } - - public Grade setId(int id) { - this.id = id; - - return this; - } - - public int getUserID() { - return userID; - } - - public Grade setUserID(int userID) { - this.userID = userID; - - return this; - } - - public int getSubjectID() { - return subjectID; - } - - public Grade setSubjectID(int subjectID) { - this.subjectID = subjectID; - - return this; - } - public String getSubject() { return subject; } @@ -149,50 +109,4 @@ public class Grade { return this; } - - public boolean isNew() { - return isNew; - } - - public Grade setIsNew(boolean isNew) { - this.isNew = isNew; - - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - Grade grade = (Grade) o; - - return new EqualsBuilder() - .append(subject, grade.subject) - .append(value, grade.value) - .append(color, grade.color) - .append(symbol, grade.symbol) - .append(description, grade.description) - .append(weight, grade.weight) - .append(date, grade.date) - .append(teacher, grade.teacher) - .append(semester, grade.semester) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(subject) - .append(value) - .append(color) - .append(symbol) - .append(description) - .append(weight) - .append(date) - .append(teacher) - .append(semester) - .toHashCode(); - } } diff --git a/app/src/main/java/io/github/wulkanowy/dao/EntitiesCompare.java b/app/src/main/java/io/github/wulkanowy/dao/EntitiesCompare.java new file mode 100644 index 000000000..4ded7fd75 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/EntitiesCompare.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.dao; + +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.dao.entities.Grade; + +public class EntitiesCompare { + + public static List compareGradeList(List newList, List oldList) { + + List addedOrUpdatedGradeList = new ArrayList<>(CollectionUtils + .removeAll(newList, oldList)); + List updatedList = new ArrayList<>(CollectionUtils + .removeAll(newList, addedOrUpdatedGradeList)); + + for (Grade grade : addedOrUpdatedGradeList) { + grade.setNew(true); + updatedList.add(grade); + } + + return updatedList; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java new file mode 100644 index 000000000..a4607acdf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java @@ -0,0 +1,211 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity(nameInDb = "Accounts") +public class Account { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "NAME") + private String name; + + @Property(nameInDb = "E-MAIL") + private String email; + + @Property(nameInDb = "PASSWORD") + private String password; + + @Property(nameInDb = "SYMBOL") + private String symbol; + + @ToMany(referencedJoinProperty = "userId") + private List subjectList; + + @ToMany(referencedJoinProperty = "userId") + private List gradeList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 335469827) + private transient AccountDao myDao; + + @Generated(hash = 1514643300) + public Account(Long id, String name, String email, String password, + String symbol) { + this.id = id; + this.name = name; + this.email = email; + this.password = password; + this.symbol = symbol; + } + + @Generated(hash = 882125521) + public Account() { + } + + public Long getId() { + return id; + } + + public Account setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public Account setName(String name) { + this.name = name; + return this; + } + + public String getEmail() { + return email; + } + + public Account setEmail(String email) { + this.email = email; + return this; + } + + public String getPassword() { + return password; + } + + public Account setPassword(String password) { + this.password = password; + return this; + } + + public String getSymbol() { + return symbol; + } + + public Account setSymbol(String symbol) { + this.symbol = symbol; + return this; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1800750450) + public List getSubjectList() { + if (subjectList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + SubjectDao targetDao = daoSession.getSubjectDao(); + List subjectListNew = targetDao._queryAccount_SubjectList(id); + synchronized (this) { + if (subjectList == null) { + subjectList = subjectListNew; + } + } + } + return subjectList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 594294258) + public synchronized void resetSubjectList() { + subjectList = null; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1040074549) + public List getGradeList() { + if (gradeList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + GradeDao targetDao = daoSession.getGradeDao(); + List gradeListNew = targetDao._queryAccount_GradeList(id); + synchronized (this) { + if (gradeList == null) { + gradeList = gradeListNew; + } + } + } + return gradeList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1939990047) + public synchronized void resetGradeList() { + gradeList = null; + } + + /** + * 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 = 1812283172) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getAccountDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java new file mode 100644 index 000000000..eaf934b6b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java @@ -0,0 +1,299 @@ +package io.github.wulkanowy.dao.entities; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Property; + +import io.github.wulkanowy.R; + +@Entity(nameInDb = "Grades") +public class Grade implements Parcelable { + + @Id(autoincrement = true) + protected Long id; + + @Property(nameInDb = "SUBJECT_ID") + private Long subjectId; + + @Property(nameInDb = "USER_ID") + private Long userId; + + @Property(nameInDb = "SUBJECT") + private String subject = ""; + + @Property(nameInDb = "VALUE") + protected String value = ""; + + @Property(nameInDb = "COLOR") + private String color = ""; + + @Property(nameInDb = "SYMBOL") + private String symbol = ""; + + @Property(nameInDb = "DESCRIPTION") + private String description = ""; + + @Property(nameInDb = "WEIGHT") + private String weight = ""; + + @Property(nameInDb = "DATE") + private String date = ""; + + @Property(nameInDb = "TEACHER") + private String teacher = ""; + + @Property(nameInDb = "SEMESTER") + private String semester = ""; + + @Property(nameInDb = "IS_NEW") + private boolean isNew = false; + + protected Grade(Parcel source) { + value = source.readString(); + } + + @Generated(hash = 1154096520) + public Grade(Long id, Long subjectId, Long userId, String subject, String value, + String color, String symbol, String description, String weight, + String date, String teacher, String semester, boolean isNew) { + this.id = id; + this.subjectId = subjectId; + this.userId = userId; + this.subject = subject; + this.value = value; + this.color = color; + this.symbol = symbol; + this.description = description; + this.weight = weight; + this.date = date; + this.teacher = teacher; + this.semester = semester; + this.isNew = isNew; + } + + @Generated(hash = 2042976393) + public Grade() { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(subject); + parcel.writeString(value); + parcel.writeString(color); + parcel.writeString(symbol); + parcel.writeString(description); + parcel.writeString(weight); + parcel.writeString(date); + parcel.writeString(value); + parcel.writeString(value); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Grade createFromParcel(Parcel source) { + return new Grade(source); + } + + @Override + public Grade[] newArray(int size) { + return new Grade[size]; + } + }; + + public int getValueColor() { + + String replacedString = value.replaceAll("[^0-9]", ""); + + if (!"".equals(replacedString)) { + switch (Integer.parseInt(replacedString)) { + case 6: + return R.color.six_grade; + case 5: + return R.color.five_grade; + case 4: + return R.color.four_grade; + case 3: + return R.color.three_grade; + case 2: + return R.color.two_grade; + case 1: + return R.color.one_grade; + default: + return R.color.default_grade; + } + } + return R.color.default_grade; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + Grade grade = (Grade) o; + + return new EqualsBuilder() + .append(subject, grade.subject) + .append(value, grade.value) + .append(color, grade.color) + .append(symbol, grade.symbol) + .append(description, grade.description) + .append(weight, grade.weight) + .append(date, grade.date) + .append(teacher, grade.teacher) + .append(semester, grade.semester) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .append(value) + .append(color) + .append(symbol) + .append(description) + .append(weight) + .append(date) + .append(teacher) + .append(semester) + .toHashCode(); + } + + public Long getId() { + return id; + } + + public Grade setId(Long id) { + this.id = id; + return this; + } + + public Long getSubjectId() { + return subjectId; + } + + public Grade setSubjectId(Long subjectId) { + this.subjectId = subjectId; + return this; + } + + public Long getUserId() { + return userId; + } + + public Grade setUserId(Long userId) { + this.userId = userId; + return this; + } + + public String getSubject() { + return subject; + } + + public Grade setSubject(String subject) { + this.subject = subject; + return this; + } + + public String getValue() { + return value; + } + + public Grade setValue(String value) { + this.value = value; + return this; + } + + public String getColor() { + return color; + } + + public Grade setColor(String color) { + this.color = color; + return this; + } + + public String getSymbol() { + return symbol; + } + + public Grade setSymbol(String symbol) { + this.symbol = symbol; + return this; + } + + public String getDescription() { + return description; + } + + public Grade setDescription(String description) { + this.description = description; + return this; + } + + public String getWeight() { + return weight; + } + + public Grade setWeight(String weight) { + this.weight = weight; + return this; + } + + public String getDate() { + return date; + } + + public Grade setDate(String date) { + this.date = date; + return this; + } + + public String getTeacher() { + return teacher; + } + + public Grade setTeacher(String teacher) { + this.teacher = teacher; + return this; + } + + public String getSemester() { + return semester; + } + + public Grade setSemester(String semester) { + this.semester = semester; + return this; + } + + public boolean isNew() { + return isNew; + } + + public Grade setNew(boolean aNew) { + isNew = aNew; + return this; + } + + public boolean getIsNew() { + return this.isNew; + } + + public void setIsNew(boolean isNew) { + this.isNew = isNew; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java new file mode 100644 index 000000000..dc942c83b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Subject.java @@ -0,0 +1,191 @@ +package io.github.wulkanowy.dao.entities; + +import org.greenrobot.greendao.DaoException; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.ToMany; + +import java.util.List; + +@Entity(nameInDb = "Subjects") +public class Subject { + + @Id(autoincrement = true) + private Long id; + + @Property(nameInDb = "USER_ID") + private Long userId; + + @Property(nameInDb = "NAME") + private String name; + + @Property(nameInDb = "PREDICTED_RATING") + private String predictedRating; + + @Property(nameInDb = "FINAL_RATING") + private String finalRating; + + @Property(nameInDb = "SEMESTER") + private String semester; + + @ToMany(referencedJoinProperty = "subjectId") + private List gradeList; + + /** + * Used to resolve relations + */ + @Generated(hash = 2040040024) + private transient DaoSession daoSession; + + /** + * Used for active entity operations. + */ + @Generated(hash = 1644932788) + private transient SubjectDao myDao; + + @Generated(hash = 396325764) + public Subject(Long id, Long userId, String name, String predictedRating, + String finalRating, String semester) { + this.id = id; + this.userId = userId; + this.name = name; + this.predictedRating = predictedRating; + this.finalRating = finalRating; + this.semester = semester; + } + + @Generated(hash = 1617906264) + public Subject() { + } + + public Long getId() { + return id; + } + + public Subject setId(Long id) { + this.id = id; + return this; + } + + public Long getUserId() { + return userId; + } + + public Subject setUserId(Long userId) { + this.userId = userId; + return this; + } + + public String getName() { + return name; + } + + public Subject setName(String name) { + this.name = name; + return this; + } + + public String getPredictedRating() { + return predictedRating; + } + + public Subject setPredictedRating(String predictedRating) { + this.predictedRating = predictedRating; + return this; + } + + public String getFinalRating() { + return finalRating; + } + + public Subject setFinalRating(String finalRating) { + this.finalRating = finalRating; + return this; + } + + public String getSemester() { + return semester; + } + + public Subject setSemester(String semester) { + this.semester = semester; + return this; + } + + /** + * To-many relationship, resolved on first access (and after reset). + * Changes to to-many relations are not persisted, make changes to the target entity. + */ + @Generated(hash = 1358847893) + public List getGradeList() { + if (gradeList == null) { + final DaoSession daoSession = this.daoSession; + if (daoSession == null) { + throw new DaoException("Entity is detached from DAO context"); + } + GradeDao targetDao = daoSession.getGradeDao(); + List gradeListNew = targetDao._querySubject_GradeList(id); + synchronized (this) { + if (gradeList == null) { + gradeList = gradeListNew; + } + } + } + return gradeList; + } + + /** + * Resets a to-many relationship, making the next get call to query for a fresh result. + */ + @Generated(hash = 1939990047) + public synchronized void resetGradeList() { + gradeList = null; + } + + /** + * 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 = 937984622) + public void __setDaoSession(DaoSession daoSession) { + this.daoSession = daoSession; + myDao = daoSession != null ? daoSession.getSubjectDao() : null; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/database/DatabaseAdapter.java b/app/src/main/java/io/github/wulkanowy/database/DatabaseAdapter.java deleted file mode 100644 index 82fa0fe2c..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/DatabaseAdapter.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.wulkanowy.database; - -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -public class DatabaseAdapter { - - private final String DATABASE_NAME = "accountdatabase.db"; - - private final int DATABASE_VERSION = 6; - - public static SQLiteDatabase database; - - private DatabaseHelper databaseHelper; - - public Context context; - - public DatabaseAdapter(Context context) { - this.context = context; - } - - public DatabaseAdapter open() { - - databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION); - - try { - database = databaseHelper.getWritableDatabase(); - } catch (SQLException e) { - database = databaseHelper.getReadableDatabase(); - Log.w(DatabaseHelper.DEBUG_TAG, "Database in read-only"); - } - - Log.d(DatabaseHelper.DEBUG_TAG, "Open database"); - - return this; - } - - public void close() { - databaseHelper.close(); - - Log.d(DatabaseHelper.DEBUG_TAG, "Close database"); - } - - protected boolean checkExist(String tableName, String dbfield, String fieldValue) { - - Cursor cursor; - - if (dbfield == null && fieldValue == null && tableName != null) { - cursor = database.rawQuery("SELECT COUNT(*) FROM " + tableName, null); - Log.d(DatabaseHelper.DEBUG_TAG, "Check exist " + tableName + " table"); - } else if (dbfield != null && fieldValue != null && tableName != null) { - cursor = database.rawQuery("SELECT COUNT(*) FROM " + tableName + " WHERE " + dbfield + "=?", new String[]{fieldValue}); - Log.d(DatabaseHelper.DEBUG_TAG, "Check exist " + fieldValue + " row"); - } else { - cursor = null; - } - - if (cursor != null) { - cursor.moveToFirst(); - - int count = cursor.getInt(0); - - if (count > 0) { - return true; - } - - cursor.close(); - } - - return false; - } - - protected boolean checkExist(String tableName) { - return checkExist(tableName, null, null); - } - - protected void deleteAndCreate(String tableName) { - - database.execSQL(databaseHelper.DROP_TABLE + tableName); - database.execSQL(databaseHelper.SUBJECT_TABLE); - database.execSQL(databaseHelper.ACCOUNT_TABLE); - database.execSQL(databaseHelper.GRADE_TABLE); - - Log.d(DatabaseHelper.DEBUG_TAG, "Recreate table " + tableName); - - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/DatabaseComparer.java b/app/src/main/java/io/github/wulkanowy/database/DatabaseComparer.java deleted file mode 100644 index 294176d5b..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/DatabaseComparer.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.database; - - -import org.apache.commons.collections4.CollectionUtils; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.grades.Grade; - -public class DatabaseComparer { - - public static List compareGradesLists(List newList, List oldList) { - - List addedOrUpdatedGradesList = new ArrayList<>(CollectionUtils.removeAll(newList, oldList)); - List updatedList = new ArrayList<>(CollectionUtils.removeAll(newList, addedOrUpdatedGradesList)); - - for (Grade grade : addedOrUpdatedGradesList) { - grade.setIsNew(true); - updatedList.add(grade); - } - - return updatedList; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/DatabaseHelper.java b/app/src/main/java/io/github/wulkanowy/database/DatabaseHelper.java deleted file mode 100644 index 3011eff6f..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/DatabaseHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.wulkanowy.database; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDatabase.CursorFactory; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -public class DatabaseHelper extends SQLiteOpenHelper { - - public final static String DEBUG_TAG = "SQLiteWulkanowyDatabase"; - - public final String ACCOUNT_TABLE = "CREATE TABLE IF NOT EXISTS accounts( " + - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "name TEXT, " + - "email TEXT," + - "password TEXT, " + - "symbol TEXT );"; - - public final String SUBJECT_TABLE = "CREATE TABLE IF NOT EXISTS subjects( " + - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "name TEXT, " + - "predictedRating1 TEXT, " + - "finalRating1 TEXT, " + - "predictedRating2 TEXT, " + - "finalRating2 TEXT );"; - - public final String GRADE_TABLE = "CREATE TABLE IF NOT EXISTS grades( " + - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "userID INTEGER, " + - "subjectID INTEGER, " + - "subject TEXT, " + - "value TEXT, " + - "color TEXT, " + - "symbol TEXT, " + - "description TEXT, " + - "weight TEXT, " + - "date DATE, " + - "teacher TEXT, " + - "semester INTEGER, " + - "isNew INTEGER );"; - - public final String DROP_TABLE = "DROP TABLE IF EXISTS "; - - public DatabaseHelper(Context context, String name, CursorFactory factory, int version) { - super(context, name, factory, version); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(ACCOUNT_TABLE); - db.execSQL(SUBJECT_TABLE); - db.execSQL(GRADE_TABLE); - Log.d(DEBUG_TAG, "Create database"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(DROP_TABLE + "accounts"); - db.execSQL(DROP_TABLE + "subjects"); - db.execSQL(DROP_TABLE + "grades"); - onCreate(db); - Log.d(DEBUG_TAG, "Database upgrade from ver." + oldVersion + " to ver." + newVersion); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/accounts/Account.java b/app/src/main/java/io/github/wulkanowy/database/accounts/Account.java deleted file mode 100644 index 8bd46476c..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/accounts/Account.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.database.accounts; - - -public class Account { - - private int id; - - private String name; - - private String email; - - private String password; - - private String symbol; - - public int getId() { - return id; - } - - public Account setId(int id) { - this.id = id; - return this; - } - - public String getName() { - return name; - } - - public Account setName(String name) { - this.name = name; - return this; - } - - public String getEmail() { - return email; - } - - public Account setEmail(String email) { - this.email = email; - return this; - } - - public String getPassword() { - return password; - } - - public Account setPassword(String password) { - this.password = password; - return this; - } - - public String getSymbol() { - return symbol; - } - - public Account setSymbol(String symbol) { - this.symbol = symbol; - return this; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/accounts/AccountsDatabase.java b/app/src/main/java/io/github/wulkanowy/database/accounts/AccountsDatabase.java deleted file mode 100644 index b367bdbad..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/accounts/AccountsDatabase.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.wulkanowy.database.accounts; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.CursorIndexOutOfBoundsException; -import android.database.SQLException; -import android.util.Log; - -import io.github.wulkanowy.database.DatabaseAdapter; -import io.github.wulkanowy.database.DatabaseHelper; - -public class AccountsDatabase extends DatabaseAdapter { - - private String name = "name"; - - private String email = "email"; - - private String password = "password"; - - private String symbol = "symbol"; - - private String idText = "id"; - - private String accounts = "accounts"; - - public AccountsDatabase(Context context) { - super(context); - } - - public long put(Account account) throws SQLException { - - ContentValues newAccount = new ContentValues(); - newAccount.put(name, account.getName()); - newAccount.put(email, account.getEmail()); - newAccount.put(password, account.getPassword()); - newAccount.put(symbol, account.getSymbol()); - - if (!database.isReadOnly()) { - long newId = database.insertOrThrow(accounts, null, newAccount); - Log.d(DatabaseHelper.DEBUG_TAG, "Put account " + newId + " into database"); - return newId; - } - - Log.e(DatabaseHelper.DEBUG_TAG, "Attempt to write on read-only database"); - throw new SQLException("Attempt to write on read-only database"); - } - - public Account getAccount(long id) throws SQLException { - - Account account = new Account(); - - String[] columns = {idText, name, email, password, symbol}; - String args[] = {id + ""}; - - try { - Cursor cursor = database.query(accounts, columns, "id=?", args, null, null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - account.setId(cursor.getInt(0)); - account.setName(cursor.getString(1)); - account.setEmail(cursor.getString(2)); - account.setPassword(cursor.getString(3)); - account.setSymbol(cursor.getString(4)); - cursor.close(); - } - } catch (SQLException e) { - - Log.e(DatabaseHelper.DEBUG_TAG, e.getMessage()); - throw e; - } catch (CursorIndexOutOfBoundsException e) { - - Log.e(DatabaseHelper.DEBUG_TAG, e.getMessage()); - throw new SQLException(e.getMessage()); - } - - Log.d(DatabaseHelper.DEBUG_TAG, "Extract account " + id + " from database"); - - return account; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/grades/GradesDatabase.java b/app/src/main/java/io/github/wulkanowy/database/grades/GradesDatabase.java deleted file mode 100644 index b8a2af74b..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/grades/GradesDatabase.java +++ /dev/null @@ -1,155 +0,0 @@ -package io.github.wulkanowy.database.grades; - - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.activity.dashboard.grades.GradeItem; -import io.github.wulkanowy.api.grades.Grade; -import io.github.wulkanowy.database.DatabaseAdapter; -import io.github.wulkanowy.database.DatabaseComparer; -import io.github.wulkanowy.database.DatabaseHelper; -import io.github.wulkanowy.database.subjects.SubjectsDatabase; - -public class GradesDatabase extends DatabaseAdapter { - - private String userIdText = "userID"; - - private String subjectIdText = "subjectID"; - - private String subject = "subject"; - - private String value = "value"; - - private String color = "color"; - - private String symbol = "symbol"; - - private String description = "description"; - - private String weight = "weight"; - - private String date = "date"; - - private String teacher = "teacher"; - - private String isNew = "isNew"; - - private String semester = "semester"; - - private String grades = "grades"; - - public GradesDatabase(Context context) { - super(context); - } - - public long put(Grade grade) throws SQLException { - - ContentValues newGrade = new ContentValues(); - newGrade.put(userIdText, context.getSharedPreferences("LoginData", context.MODE_PRIVATE).getLong("isLogin", 0)); - newGrade.put(subjectIdText, SubjectsDatabase.getSubjectId(grade.getSubject())); - newGrade.put(subject, grade.getSubject()); - newGrade.put(value, grade.getValue()); - newGrade.put(color, grade.getColor()); - newGrade.put(symbol, grade.getSymbol()); - newGrade.put(description, grade.getDescription()); - newGrade.put(weight, grade.getWeight()); - newGrade.put(date, grade.getDate()); - newGrade.put(teacher, grade.getTeacher()); - newGrade.put(semester, grade.getSemester()); - newGrade.put(isNew, grade.isNew() ? 1 : 0); - - if (!database.isReadOnly()) { - long newId = database.insertOrThrow(grades, null, newGrade); - Log.d(DatabaseHelper.DEBUG_TAG, "Put grade " + newId + " into database"); - return newId; - } - - Log.e(DatabaseHelper.DEBUG_TAG, "Attempt to write on read-only database"); - throw new SQLException("Attempt to write on read-only database"); - } - - public List put(List gradeList) throws SQLException { - - List newIdList = new ArrayList<>(); - List preparedList; - - if (checkExist(grades)) { - preparedList = DatabaseComparer.compareGradesLists(gradeList, getAllUserGrades()); - deleteAndCreate(grades); - } else { - preparedList = gradeList; - } - - for (Grade grade : preparedList) { - - newIdList.add(put(grade)); - } - return newIdList; - } - - public List getSubjectGrades(long userId, long subjectId) throws SQLException { - - String exec = "SELECT " + grades + ".*, strftime('%d.%m.%Y', " + date + ") " + - "FROM " + grades + " WHERE " + userIdText + "=? AND " - + subjectIdText + "=? ORDER BY " + date + " DESC"; - - List gradesList = new ArrayList<>(); - - Cursor cursor = database.rawQuery(exec, new String[]{String.valueOf(userId), String.valueOf(subjectId)}); - - while (cursor.moveToNext()) { - GradeItem grade = new GradeItem(); - grade.setId(cursor.getInt(0)); - grade.setUserID(cursor.getInt(1)); - grade.setSubjectID(cursor.getInt(2)); - grade.setSubject(cursor.getString(3)); - grade.setValue(cursor.getString(4)); - grade.setColor(cursor.getString(5)); - grade.setSymbol(cursor.getString(6)); - grade.setDescription(cursor.getString(7)); - grade.setWeight(cursor.getString(8)); - grade.setDate(cursor.getString(13)); // last, because reformatted date is last - grade.setTeacher(cursor.getString(10)); - grade.setSemester(cursor.getString(11)); - grade.setIsNew(cursor.getInt(12) != 0); - gradesList.add(grade); - } - - cursor.close(); - return gradesList; - } - - public List getAllUserGrades() { - - List gradesList = new ArrayList<>(); - - String exec = "SELECT " + grades + ".*, strftime('%d.%m.%Y', " + date + ") " + - " FROM " + grades + " WHERE " + userIdText + "=? ORDER BY " + date + " DESC"; - - Cursor cursor = database.rawQuery(exec, new String[]{String.valueOf(context.getSharedPreferences("LoginData", context.MODE_PRIVATE).getLong("isLogin", 0))}); - - while (cursor.moveToNext()) { - Grade grade = new Grade(); - grade.setSubject(cursor.getString(3)); - grade.setValue(cursor.getString(4)); - grade.setColor(cursor.getString(5)); - grade.setSymbol(cursor.getString(6)); - grade.setDescription(cursor.getString(7)); - grade.setWeight(cursor.getString(8)); - grade.setDate(cursor.getString(9)); - grade.setTeacher(cursor.getString(10)); - grade.setSemester(cursor.getString(11)); - grade.setIsNew(cursor.getInt(12) != 0); - gradesList.add(grade); - } - cursor.close(); - return gradesList; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/database/subjects/SubjectsDatabase.java b/app/src/main/java/io/github/wulkanowy/database/subjects/SubjectsDatabase.java deleted file mode 100644 index 093b99997..000000000 --- a/app/src/main/java/io/github/wulkanowy/database/subjects/SubjectsDatabase.java +++ /dev/null @@ -1,144 +0,0 @@ -package io.github.wulkanowy.database.subjects; - - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.CursorIndexOutOfBoundsException; -import android.database.SQLException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.grades.Subject; -import io.github.wulkanowy.database.DatabaseAdapter; -import io.github.wulkanowy.database.DatabaseHelper; - -public class SubjectsDatabase extends DatabaseAdapter { - - private static String idText = "id"; - - private static String name = "name"; - - private static String predictedRating1 = "predictedRating1"; - - private static String finalRating1 = "finalRating1"; - - private static String predictedRating2 = "predictedRating2"; - - private static String finalRating2 = "finalRating2"; - - private static String subjects = "subjects"; - - public SubjectsDatabase(Context context) { - super(context); - } - - public long put(Subject subject) throws SQLException { - - ContentValues newSubject = new ContentValues(); - newSubject.put(name, subject.getName()); - newSubject.put(predictedRating1, subject.getPredictedRating()); - newSubject.put(finalRating1, subject.getFinalRating()); - - if (!database.isReadOnly()) { - long newId = database.insertOrThrow(subjects, null, newSubject); - Log.d(DatabaseHelper.DEBUG_TAG, "Put subject " + newId + " into database"); - return newId; - } - - Log.e(DatabaseHelper.DEBUG_TAG, "Attempt to write on read-only database"); - throw new SQLException("Attempt to write on read-only database"); - } - - public List put(List subjectList) throws SQLException { - - List newIdList = new ArrayList<>(); - - for (Subject subject : subjectList) { - if (!checkExist(subjects, name, subject.getName())) { - newIdList.add(put(subject)); - } - } - return newIdList; - } - - public long update(Subject subject) throws SQLException { - - ContentValues updateSubject = new ContentValues(); - updateSubject.put(name, subject.getName()); - updateSubject.put(predictedRating1, subject.getPredictedRating()); - updateSubject.put(finalRating1, subject.getFinalRating()); - String args[] = {subject.getId() + ""}; - - if (!database.isReadOnly()) { - long updateId = database.update(subjects, updateSubject, "id=?", args); - Log.d(DatabaseHelper.DEBUG_TAG, "Update subject " + updateId + " into database"); - return updateId; - } - - Log.e(DatabaseHelper.DEBUG_TAG, "Attempt to write on read-only database"); - throw new SQLException("Attempt to write on read-only database"); - } - - public Subject getSubject(long id) throws SQLException { - - Subject subject = new Subject(); - - String[] columns = {idText, name, predictedRating1, finalRating1, predictedRating2, finalRating2}; - String args[] = {id + ""}; - - try { - Cursor cursor = database.query(subjects, columns, "id=?", args, null, null, null, null); - if (cursor != null) { - cursor.moveToFirst(); - subject.setId(cursor.getInt(0)); - subject.setName(cursor.getString(1)); - subject.setPredictedRating(cursor.getString(2)); - subject.setFinalRating(cursor.getString(3)); - cursor.close(); - } - } catch (SQLException e) { - - Log.e(DatabaseHelper.DEBUG_TAG, e.getMessage()); - throw e; - } catch (CursorIndexOutOfBoundsException e) { - - Log.e(DatabaseHelper.DEBUG_TAG, e.getMessage()); - throw new SQLException(e.getMessage()); - } - - Log.d(DatabaseHelper.DEBUG_TAG, "Extract subject " + id + " from database"); - - return subject; - } - - public List getAllSubjectsNames() { - - List subjectsList = new ArrayList<>(); - - String exec = "SELECT " + name + " FROM " + subjects; - - Cursor cursor = database.rawQuery(exec, null); - - while (cursor.moveToNext()) { - Subject subject = new Subject(); - subject.setName(cursor.getString(0)); - subjectsList.add(subject); - } - cursor.close(); - return subjectsList; - } - - public static long getSubjectId(String nameSubject) throws SQLException { - - String whereExec = "SELECT " + idText + " FROM " + subjects + " WHERE " + name + " =?"; - - Cursor cursor = database.rawQuery(whereExec, new String[]{nameSubject}); - cursor.moveToFirst(); - int idSubject = cursor.getInt(0); - cursor.close(); - return idSubject; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/security/CryptoException.java b/app/src/main/java/io/github/wulkanowy/security/CryptoException.java index 89254a495..62abcf76e 100644 --- a/app/src/main/java/io/github/wulkanowy/security/CryptoException.java +++ b/app/src/main/java/io/github/wulkanowy/security/CryptoException.java @@ -3,9 +3,6 @@ package io.github.wulkanowy.security; public class CryptoException extends Exception { - public CryptoException() { - } - public CryptoException(String message) { super(message); } diff --git a/app/src/main/java/io/github/wulkanowy/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java index 1bf344c77..aa02bbf60 100644 --- a/app/src/main/java/io/github/wulkanowy/security/Scrambler.java +++ b/app/src/main/java/io/github/wulkanowy/security/Scrambler.java @@ -21,7 +21,6 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Calendar; -import java.util.Enumeration; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -34,7 +33,7 @@ public class Scrambler { private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - public final static String DEBUG_TAG = "KeyStoreSecurity"; + public final static String DEBUG_TAG = "WulkanowySecurity"; public Context context; @@ -54,22 +53,6 @@ public class Scrambler { } - public ArrayList getAllAliases() throws CryptoException { - - ArrayList keyAliases = new ArrayList<>(); - try { - Enumeration aliases = keyStore.aliases(); - while (aliases.hasMoreElements()) { - keyAliases.add(aliases.nextElement()); - } - } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); - throw new CryptoException(e.getMessage()); - } - - return keyAliases; - } - @TargetApi(18) public void generateNewKey(String alias) throws CryptoException { @@ -122,21 +105,6 @@ public class Scrambler { } - public void deleteKey(String alias) throws CryptoException { - - if (!alias.isEmpty()) { - try { - keyStore.deleteEntry(alias); - Log.d(DEBUG_TAG, "Key" + alias + "is delete"); - } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); - } - } else { - Log.e(DEBUG_TAG, "DeleteKey - String is empty"); - throw new CryptoException("DeleteKey - String is empty"); - } - } - public String encryptString(String alias, String text) throws CryptoException { if (!alias.isEmpty() && !text.isEmpty()) { @@ -153,13 +121,9 @@ public class Scrambler { cipherOutputStream.write(text.getBytes("UTF-8")); cipherOutputStream.close(); - Log.d(DEBUG_TAG, "String is encrypt"); - byte[] vals = outputStream.toByteArray(); - String encryptedText = Base64.encodeToString(vals, Base64.DEFAULT); - Log.d(DEBUG_TAG, encryptedText); - return encryptedText; + return Base64.encodeToString(vals, Base64.DEFAULT); } catch (Exception e) { Log.e(DEBUG_TAG, e.getMessage()); @@ -193,8 +157,6 @@ public class Scrambler { Byte[] bytes = values.toArray(new Byte[values.size()]); - Log.d(DEBUG_TAG, "String is decrypt"); - return new String(ArrayUtils.toPrimitive(bytes), 0, bytes.length, "UTF-8"); } catch (Exception e) { diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/GradesSync.java b/app/src/main/java/io/github/wulkanowy/services/jobs/GradesSync.java index 29a0835b8..e65ec165e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/GradesSync.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/GradesSync.java @@ -9,9 +9,11 @@ import com.firebase.jobdispatcher.Trigger; import java.io.IOException; +import io.github.wulkanowy.activity.WulkanowyApp; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.LoginErrorException; +import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.services.synchronisation.DataSynchronisation; import io.github.wulkanowy.services.synchronisation.VulcanSynchronisation; @@ -44,9 +46,11 @@ public class GradesSync extends VulcanSync { public void workToBePerformed() throws CryptoException, BadCredentialsException, LoginErrorException, AccountPermissionException, IOException { + DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); + VulcanSynchronisation vulcanSynchronisation = new VulcanSynchronisation(); - DataSynchronisation dataSynchronisation = new DataSynchronisation(getApplicationContext()); - vulcanSynchronisation.loginCurrentUser(getApplicationContext()); + DataSynchronisation dataSynchronisation = new DataSynchronisation(daoSession); + vulcanSynchronisation.loginCurrentUser(getApplicationContext(), daoSession); dataSynchronisation.syncGrades(vulcanSynchronisation); } } diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectsSync.java b/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectsSync.java index f6882fc8e..3360065b3 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectsSync.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/SubjectsSync.java @@ -9,9 +9,11 @@ import com.firebase.jobdispatcher.Trigger; import java.io.IOException; +import io.github.wulkanowy.activity.WulkanowyApp; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.LoginErrorException; +import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.services.synchronisation.DataSynchronisation; import io.github.wulkanowy.services.synchronisation.VulcanSynchronisation; @@ -44,10 +46,12 @@ public class SubjectsSync extends VulcanSync { public void workToBePerformed() throws CryptoException, BadCredentialsException, LoginErrorException, AccountPermissionException, IOException { + DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); + VulcanSynchronisation vulcanSynchronisation = new VulcanSynchronisation(); - DataSynchronisation dataSynchronisation = new DataSynchronisation(getApplicationContext()); - vulcanSynchronisation.loginCurrentUser(getApplicationContext()); - dataSynchronisation.syncSubjects(vulcanSynchronisation); + DataSynchronisation dataSynchronisation = new DataSynchronisation(daoSession); + vulcanSynchronisation.loginCurrentUser(getApplicationContext(), daoSession); + dataSynchronisation.syncSubjectsAndGrades(vulcanSynchronisation); } } diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJob.java index 9c787e577..eccb612fd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJob.java +++ b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJob.java @@ -19,14 +19,14 @@ public abstract class VulcanJob extends JobService { @Override public boolean onStartJob(JobParameters params) { - Log.d(VulcanSync.DEBUG_TAG, "Start job"); + Log.d(VulcanSync.DEBUG_TAG, "Wulkanowy services start"); syncTask.execute(params); return true; } @Override public boolean onStopJob(JobParameters params) { - Log.d(VulcanSync.DEBUG_TAG, "Stop job"); + Log.e(VulcanSync.DEBUG_TAG, "Wulkanowy serives stop"); syncTask.cancel(true); return true; } diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/DataSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/DataSynchronisation.java index d77607f49..ff764beca 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/DataSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/DataSynchronisation.java @@ -1,40 +1,42 @@ package io.github.wulkanowy.services.synchronisation; -import android.content.Context; +import android.app.Activity; import android.util.Log; +import io.github.wulkanowy.activity.WulkanowyApp; +import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.services.jobs.VulcanSync; public class DataSynchronisation { - private Context context; + private DaoSession daoSession; - public DataSynchronisation(Context context) { - this.context = context; + public DataSynchronisation(DaoSession daoSession) { + this.daoSession = daoSession; + } + + public DataSynchronisation(Activity activity) { + daoSession = ((WulkanowyApp) activity.getApplication()).getDaoSession(); } public void syncGrades(VulcanSynchronisation vulcanSynchronisation) { GradesSynchronisation gradesSynchronisation = new GradesSynchronisation(); try { - gradesSynchronisation.sync(vulcanSynchronisation, context); + gradesSynchronisation.sync(vulcanSynchronisation, daoSession); } catch (Exception e) { Log.e(VulcanSync.DEBUG_TAG, "Synchronisation of grades failed", e); } } - public void syncSubjects(VulcanSynchronisation vulcanSynchronisation) { + public void syncSubjectsAndGrades(VulcanSynchronisation vulcanSynchronisation) { SubjectsSynchronisation subjectsSynchronisation = new SubjectsSynchronisation(); try { - subjectsSynchronisation.sync(vulcanSynchronisation, context); + subjectsSynchronisation.sync(vulcanSynchronisation, daoSession); + syncGrades(vulcanSynchronisation); } catch (Exception e) { Log.e(VulcanSync.DEBUG_TAG, "Synchronisation of subjects failed", e); } } - - public void syncGradesAndSubjects(VulcanSynchronisation vulcanSynchronisation) { - syncSubjects(vulcanSynchronisation); - syncGrades(vulcanSynchronisation); - } } diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/GradesSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/GradesSynchronisation.java index 91507b9fc..fccc4d56a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/GradesSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/GradesSynchronisation.java @@ -1,23 +1,62 @@ package io.github.wulkanowy.services.synchronisation; -import android.content.Context; +import android.util.Log; + +import org.greenrobot.greendao.query.Query; import java.io.IOException; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import io.github.wulkanowy.api.grades.GradesList; import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.database.grades.GradesDatabase; +import io.github.wulkanowy.dao.EntitiesCompare; +import io.github.wulkanowy.dao.entities.Account; +import io.github.wulkanowy.dao.entities.AccountDao; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.dao.entities.Grade; +import io.github.wulkanowy.dao.entities.GradeDao; +import io.github.wulkanowy.dao.entities.Subject; +import io.github.wulkanowy.dao.entities.SubjectDao; +import io.github.wulkanowy.services.jobs.VulcanSync; +import io.github.wulkanowy.utilities.ConversionVulcanObject; public class GradesSynchronisation { - public void sync(VulcanSynchronisation vulcanSynchronisation, Context context) throws IOException, ParseException, LoginErrorException { + public void sync(VulcanSynchronisation vulcanSynchronisation, DaoSession daoSession) throws IOException, + ParseException, LoginErrorException { GradesList gradesList = new GradesList(vulcanSynchronisation.getStudentAndParent()); - GradesDatabase gradesDatabase = new GradesDatabase(context); - gradesDatabase.open(); - gradesDatabase.put(gradesList.getAll()); - gradesDatabase.close(); + GradeDao gradeDao = daoSession.getGradeDao(); + AccountDao accountDao = daoSession.getAccountDao(); + SubjectDao subjectDao = daoSession.getSubjectDao(); + + Account account = accountDao.load(vulcanSynchronisation.getUserId()); + + List gradesFromDb = account.getGradeList(); + List gradeEntitiesList = ConversionVulcanObject.gradesToGradeEntities(gradesList.getAll()); + List updatedList = EntitiesCompare.compareGradeList(gradeEntitiesList, gradesFromDb); + List lastList = new ArrayList<>(); + + GradeDao.dropTable(gradeDao.getDatabase(), true); + GradeDao.createTable(gradeDao.getDatabase(), false); + + for (Grade grade : updatedList) { + + Query subjectQuery = subjectDao.queryBuilder() + .where(SubjectDao.Properties.Name.eq(grade.getSubject())) + .build(); + + grade.setUserId(vulcanSynchronisation.getUserId()); + grade.setSubjectId((subjectQuery.uniqueOrThrow()).getId()); + + lastList.add(grade); + } + + gradeDao.insertInTx(lastList); + + Log.d(VulcanSync.DEBUG_TAG, "Synchronization grades (amount = " + String.valueOf(lastList.size() + ")")); } } diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java index a3d33a0ca..d894bc67f 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java @@ -1,23 +1,41 @@ package io.github.wulkanowy.services.synchronisation; -import android.content.Context; +import android.util.Log; import java.io.IOException; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import io.github.wulkanowy.api.grades.SubjectsList; import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.database.subjects.SubjectsDatabase; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.dao.entities.Subject; +import io.github.wulkanowy.dao.entities.SubjectDao; +import io.github.wulkanowy.services.jobs.VulcanSync; +import io.github.wulkanowy.utilities.ConversionVulcanObject; public class SubjectsSynchronisation { - public void sync(VulcanSynchronisation vulcanSynchronisation, Context context) throws IOException, ParseException, LoginErrorException { + public void sync(VulcanSynchronisation vulcanSynchronisation, DaoSession daoSession) throws IOException, + ParseException, LoginErrorException { SubjectsList subjectsList = new SubjectsList(vulcanSynchronisation.getStudentAndParent()); + SubjectDao subjectDao = daoSession.getSubjectDao(); - SubjectsDatabase subjectsDatabase = new SubjectsDatabase(context); - subjectsDatabase.open(); - subjectsDatabase.put(subjectsList.getAll()); - subjectsDatabase.close(); + List subjectEntitiesList = ConversionVulcanObject.subjectsToSubjectEntities(subjectsList.getAll()); + List preparedList = new ArrayList<>(); + + for (Subject subject : subjectEntitiesList) { + subject.setUserId(vulcanSynchronisation.getUserId()); + preparedList.add(subject); + } + + SubjectDao.dropTable(subjectDao.getDatabase(), true); + SubjectDao.createTable(subjectDao.getDatabase(), false); + subjectDao.insertInTx(preparedList); + + + Log.d(VulcanSync.DEBUG_TAG, "Synchronization subjects (amount = " + String.valueOf(subjectEntitiesList.size() + ")")); } } diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/VulcanSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/VulcanSynchronisation.java index e7114dfc4..28cd7adf2 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/VulcanSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/VulcanSynchronisation.java @@ -1,5 +1,6 @@ package io.github.wulkanowy.services.synchronisation; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -7,6 +8,7 @@ import android.util.Log; import java.io.IOException; import java.util.Map; +import io.github.wulkanowy.activity.WulkanowyApp; import io.github.wulkanowy.api.Cookies; import io.github.wulkanowy.api.StudentAndParent; import io.github.wulkanowy.api.login.AccountPermissionException; @@ -15,8 +17,9 @@ import io.github.wulkanowy.api.login.Login; import io.github.wulkanowy.api.login.LoginErrorException; import io.github.wulkanowy.api.user.BasicInformation; import io.github.wulkanowy.api.user.PersonalData; -import io.github.wulkanowy.database.accounts.Account; -import io.github.wulkanowy.database.accounts.AccountsDatabase; +import io.github.wulkanowy.dao.entities.Account; +import io.github.wulkanowy.dao.entities.AccountDao; +import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.Safety; import io.github.wulkanowy.services.jobs.VulcanSync; @@ -25,17 +28,21 @@ public class VulcanSynchronisation { private StudentAndParent studentAndParent; - public void loginCurrentUser(Context context) throws CryptoException, BadCredentialsException, LoginErrorException, AccountPermissionException, IOException { + private Long userId = 0L; - long userId = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("isLogin", 0); + public void loginCurrentUser(Context context, DaoSession daoSession) throws CryptoException, + BadCredentialsException, LoginErrorException, AccountPermissionException, IOException { + + AccountDao accountDao = daoSession.getAccountDao(); + + userId = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); if (userId != 0) { - AccountsDatabase accountsDatabase = new AccountsDatabase(context); - accountsDatabase.open(); - Account account = accountsDatabase.getAccount(userId); - accountsDatabase.close(); - Safety safety = new Safety(context); + Log.d(VulcanSync.DEBUG_TAG, "Login current user id=" + String.valueOf(userId)); + + Safety safety = new Safety(context); + Account account = accountDao.load(userId); Login login = loginUser( account.getEmail(), safety.decrypt(account.getEmail(), account.getPassword()), @@ -47,12 +54,14 @@ public class VulcanSynchronisation { } } - public void loginNewUser(String email, String password, String symbol, Context context) throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException, CryptoException { + public void loginNewUser(String email, String password, String symbol, Context context, DaoSession daoSession) + throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException, CryptoException { + + AccountDao accountDao = daoSession.getAccountDao(); Login login = loginUser(email, password, symbol); Safety safety = new Safety(context); - AccountsDatabase accountsDatabase = new AccountsDatabase(context); BasicInformation basicInformation = new BasicInformation(getAndSetStudentAndParentFromApi(symbol, login.getCookies())); PersonalData personalData = basicInformation.getPersonalData(); @@ -62,25 +71,23 @@ public class VulcanSynchronisation { .setPassword(safety.encrypt(email, password)) .setSymbol(symbol); - accountsDatabase.open(); - long idNewUser = accountsDatabase.put(account); - accountsDatabase.close(); + userId = accountDao.insert(account); + + Log.d(VulcanSync.DEBUG_TAG, "Login and save new user id=" + String.valueOf(userId)); SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("isLogin", idNewUser); + editor.putLong("userId", userId); editor.apply(); } - public StudentAndParent getStudentAndParent() { - return studentAndParent; + public void loginNewUser(String email, String password, String symbol, Activity activity) + throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException, CryptoException { + loginNewUser(email, password, symbol, activity, ((WulkanowyApp) activity.getApplication()).getDaoSession()); } - private void setStudentAndParent(StudentAndParent studentAndParent) { - this.studentAndParent = studentAndParent; - } - - private Login loginUser(String email, String password, String symbol) throws BadCredentialsException, LoginErrorException, AccountPermissionException { + private Login loginUser(String email, String password, String symbol) throws BadCredentialsException, + LoginErrorException, AccountPermissionException { Cookies cookies = new Cookies(); Login login = new Login(cookies); @@ -89,7 +96,16 @@ public class VulcanSynchronisation { } - private StudentAndParent getAndSetStudentAndParentFromApi(String symbol, Map cookiesMap) throws IOException, LoginErrorException { + public Long getUserId() { + return userId; + } + + public StudentAndParent getStudentAndParent() { + return studentAndParent; + } + + private StudentAndParent getAndSetStudentAndParentFromApi(String symbol, Map cookiesMap) + throws IOException, LoginErrorException { if (studentAndParent == null) { Cookies cookies = new Cookies(); @@ -97,7 +113,7 @@ public class VulcanSynchronisation { StudentAndParent snp = new StudentAndParent(cookies, symbol); - setStudentAndParent(snp); + studentAndParent = snp; return snp; } else { return studentAndParent; diff --git a/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java b/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java new file mode 100644 index 000000000..707454ba7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utilities/ConversionVulcanObject.java @@ -0,0 +1,48 @@ +package io.github.wulkanowy.utilities; + + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import io.github.wulkanowy.dao.entities.Grade; +import io.github.wulkanowy.dao.entities.Subject; + +public abstract class ConversionVulcanObject { + + public static List subjectsToSubjectEntities(List subjectList) { + + List subjectEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.grades.Subject subject : subjectList) { + Subject subjectEntity = new Subject() + .setName(subject.getName()) + .setPredictedRating(subject.getPredictedRating()) + .setFinalRating(subject.getFinalRating()); + subjectEntityList.add(subjectEntity); + } + + return subjectEntityList; + } + + public static List gradesToGradeEntities(List gradeList) throws ParseException { + + List gradeEntityList = new ArrayList<>(); + + for (io.github.wulkanowy.api.grades.Grade grade : gradeList) { + Grade gradeEntity = new Grade() + .setSubject(grade.getSubject()) + .setValue(grade.getValue()) + .setColor(grade.getColor()) + .setSymbol(grade.getSymbol()) + .setDescription(grade.getDescription()) + .setWeight(grade.getWeight()) + .setDate(grade.getDate()) + .setTeacher(grade.getTeacher()) + .setSemester(grade.getSemester()); + + gradeEntityList.add(gradeEntity); + } + return gradeEntityList; + } +} diff --git a/app/src/main/res/drawable/sample_0.png b/app/src/main/res/drawable/sample_0.png deleted file mode 100644 index db882ce637b4265ad16857edd8cfb77597e55147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37861 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 DHAZ diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index cca4bb674..af68f01b9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ -