diff --git a/.bettercodehub.yml b/.bettercodehub.yml index c7394280..cce850d7 100644 --- a/.bettercodehub.yml +++ b/.bettercodehub.yml @@ -1,5 +1,5 @@ exclude: -- /app/src/main/java/io/github/wulkanowy/db/dao/entities/.* +- /app/src/main/java/io/github/wulkanowy/data/db/dao/entities/.* component_depth: 1 languages: - java diff --git a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java index bae3a2f4..a44e868f 100644 --- a/api/src/main/java/io/github/wulkanowy/api/Vulcan.java +++ b/api/src/main/java/io/github/wulkanowy/api/Vulcan.java @@ -48,7 +48,7 @@ public class Vulcan { this.login = login; } - public String login(String email, String password, String symbol) + public void login(String email, String password, String symbol) throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException, VulcanOfflineException { @@ -56,8 +56,6 @@ public class Vulcan { login = getLogin(); this.symbol = login.login(this.email, password, symbol); - - return this.symbol; } public Vulcan login(String email, String password, String symbol, String id) @@ -82,6 +80,10 @@ public class Vulcan { return email; } + public String getSymbol() { + return symbol; + } + private void setFullEndpointInfo(String email) { String[] creds = email.split("\\\\"); diff --git a/api/src/main/java/io/github/wulkanowy/api/login/Login.java b/api/src/main/java/io/github/wulkanowy/api/login/Login.java index 97439395..e680804a 100644 --- a/api/src/main/java/io/github/wulkanowy/api/login/Login.java +++ b/api/src/main/java/io/github/wulkanowy/api/login/Login.java @@ -27,14 +27,6 @@ public class Login { this.client = client; } - private String getLoginPageUrl() { - return LOGIN_PAGE_URL; - } - - private String getLoginEndpointPageUrl() { - return LOGIN_ENDPOINT_PAGE_URL; - } - public String login(String email, String password, String symbol) throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException, VulcanOfflineException { @@ -47,7 +39,7 @@ public class Login { throws IOException, BadCredentialsException { this.symbol = symbol; - Document html = client.postPageByUrl(getLoginPageUrl(), new String[][]{ + Document html = client.postPageByUrl(LOGIN_PAGE_URL, new String[][]{ {"LoginName", email}, {"Password", password} }); @@ -64,8 +56,7 @@ public class Login { this.symbol = findSymbol(defaultSymbol, certificate); client.setSymbol(this.symbol); - Document html = client.postPageByUrl(getLoginEndpointPageUrl() - .replace("{symbol}", this.symbol), new String[][]{ + Document html = client.postPageByUrl(LOGIN_ENDPOINT_PAGE_URL, new String[][]{ {"wa", "wsignin1.0"}, {"wresult", certificate} }); @@ -103,4 +94,4 @@ public class Login { return els.get(1).text(); } -} +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 205a056d..66b90ef8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,29 +62,29 @@ android { } greendao { - schemaVersion 19 + schemaVersion 20 generateTests = true } dependencies { implementation project(':api') - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' - implementation 'com.android.support:support-v4:27.0.2' - implementation 'com.android.support:recyclerview-v7:27.0.2' - implementation 'com.android.support:cardview-v7:27.0.2' - implementation 'com.android.support:customtabs:27.0.2' + implementation 'com.android.support:appcompat-v7:27.1.0' + implementation 'com.android.support:design:27.1.0' + implementation 'com.android.support:support-v4:27.1.0' + implementation 'com.android.support:recyclerview-v7:27.1.0' + implementation 'com.android.support:cardview-v7:27.1.0' + implementation 'com.android.support:customtabs:27.1.0' implementation 'com.firebase:firebase-jobdispatcher:0.8.5' - implementation 'com.thoughtbot:expandablerecyclerview:1.3' implementation 'org.apache.commons:commons-lang3:3.7' - implementation 'eu.davidea:flexible-adapter:5.0.0-rc3' + implementation 'eu.davidea:flexible-adapter:5.0.0-rc4' + implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b1' implementation 'org.apache.commons:commons-collections4:4.1' implementation 'org.greenrobot:greendao:3.2.2' implementation 'com.jakewharton:butterknife:8.8.1' implementation 'joda-time:joda-time:2.9.9' - implementation 'com.github.javiersantos:AppUpdater:2.6.4' implementation 'com.google.dagger:dagger-android:2.14.1' implementation 'com.google.dagger:dagger-android-support:2.14.1' + implementation 'com.aurelhubert:ahbottomnavigation:2.1.0' implementation('com.crashlytics.sdk.android:crashlytics:2.8.0@aar') { transitive = true @@ -94,6 +94,7 @@ dependencies { } annotationProcessor 'com.google.dagger:dagger-android-processor:2.14.1' + annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' debugImplementation 'com.amitshekhar.android:debug-db:1.0.1' diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java index 78ea561d..e5330b62 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/AccountTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/AccountTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java index 7574addb..34c4c4c5 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/DayTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/DayTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java similarity index 89% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java index 7cabfa27..ea026559 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/GradeTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/GradeTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java similarity index 92% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java index 4e3523f7..c8690ff9 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/LessonTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/LessonTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java similarity index 88% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java index 85b986df..81a2e724 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/SubjectTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/SubjectTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java similarity index 87% rename from app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java rename to app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java index 6920ae20..86e7a8be 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/entities/WeekTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/data/db/dao/entities/WeekTest.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.test.AbstractDaoTestLongPk; diff --git a/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java b/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java deleted file mode 100644 index c9156676..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/db/dao/DatabaseAccessTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.wulkanowy.db.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.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.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-db"); - 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, new DatabaseAccess().getNewGrades(daoSession).size()); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java deleted file mode 100644 index 65da82e2..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/CurrentAccountLoginTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -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 org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -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.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -@RunWith(AndroidJUnit4.class) -public class CurrentAccountLoginTest { - - private static DaoSession daoSession; - - private Context context; - - private Context targetContext; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - context = InstrumentationRegistry.getContext(); - targetContext = InstrumentationRegistry.getTargetContext(); - - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - - setUserIdSharePreferences(0); - } - - @Test(expected = IOException.class) - public void emptyUserIdTest() throws CryptoException, BadCredentialsException, - AccountPermissionException, IOException, LoginErrorException, VulcanOfflineException { - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, new Vulcan()); - currentAccountLogin.loginCurrentUser(); - } - - @Test - public void loginCurrentUserTest() throws Exception { - AccountDao accountDao = daoSession.getAccountDao(); - - Safety safety = new Safety(); - - Long userId = accountDao.insert(new Account() - .setEmail("TEST@TEST") - .setPassword(safety.encrypt("TEST@TEST", "TEST", context)) - .setSymbol("")); - - setUserIdSharePreferences(userId); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.when(vulcan.login("TEST@TEST", "TEST", "TEST_SYMBOL", "TEST_ID")).thenReturn(new Vulcan()); - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(targetContext, daoSession, vulcan); - LoginSession loginSession = currentAccountLogin.loginCurrentUser(); - - Assert.assertNotNull(loginSession); - Assert.assertEquals(loginSession.getUserId(), userId); - Assert.assertNotNull(loginSession.getDaoSession()); - Assert.assertEquals(loginSession.getVulcan(), vulcan); - } - - private void setUserIdSharePreferences(long id) { - SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", id); - editor.apply(); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java deleted file mode 100644 index 5a4eb97d..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/FirstAccountLoginTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; -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 org.mockito.Mockito; - -import io.github.wulkanowy.api.StudentAndParent; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.user.BasicInformation; -import io.github.wulkanowy.api.user.PersonalData; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.Safety; - -public class FirstAccountLoginTest { - - private static DaoSession daoSession; - - private Context targetContext; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - targetContext = InstrumentationRegistry.getTargetContext(); - - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - - setUserIdSharePreferences(0); - } - - @Test - public void loginTest() throws Exception { - StudentAndParent snp = Mockito.mock(StudentAndParent.class); - Mockito.when(snp.getId()).thenReturn("TEST-ID"); - - PersonalData personalData = Mockito.mock(PersonalData.class); - Mockito.doReturn("NAME-TEST").when(personalData).getFirstAndLastName(); - - BasicInformation basicInformation = Mockito.mock(BasicInformation.class); - Mockito.doReturn(personalData).when(basicInformation).getPersonalData(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn("TEST-SYMBOL").when(vulcan).login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); - Mockito.doReturn(snp).when(vulcan).getStudentAndParent(); - Mockito.doReturn(basicInformation).when(vulcan).getBasicInformation(); - - FirstAccountLogin firstAccountLogin = new FirstAccountLogin(targetContext, daoSession, vulcan); - LoginSession loginSession = firstAccountLogin.login("TEST@TEST", "TEST-PASS", "default"); - - Long userId = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); - - Assert.assertNotNull(loginSession); - Assert.assertNotEquals(0, userId.longValue()); - Assert.assertEquals(loginSession.getUserId(), userId); - Assert.assertNotNull(loginSession.getDaoSession()); - Assert.assertEquals(loginSession.getVulcan(), vulcan); - - Safety safety = new Safety(); - Account account = daoSession.getAccountDao().load(userId); - Assert.assertNotNull(account); - Assert.assertEquals("TEST@TEST", account.getEmail()); - Assert.assertEquals("NAME-TEST", account.getName()); - Assert.assertEquals("TEST-PASS", safety.decrypt("TEST@TEST", account.getPassword())); - Assert.assertEquals("TEST-SYMBOL", account.getSymbol()); - Assert.assertEquals("TEST-ID", account.getSnpId()); - } - - private void setUserIdSharePreferences(long id) { - SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", id); - editor.apply(); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java deleted file mode 100644 index 11176719..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/GradeSyncTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -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 org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.grades.Grade; -import io.github.wulkanowy.api.grades.GradesList; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Subject; - -@RunWith(AndroidJUnit4.class) -public class GradeSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } - - @Test - public void syncGradesEmptyDatabaseTest() throws Exception { - Long userId = daoSession.getAccountDao().insert(new Account().setEmail("TEST@TEST")); - Long subjectId = daoSession.getSubjectDao().insert(new Subject().setName("Matematyka").setUserId(userId)); - - List gradeList = new ArrayList<>(); - gradeList.add(new Grade().setSubject("Matematyka").setValue("5")); - - GradesList gradesListApi = Mockito.mock(GradesList.class); - Mockito.doReturn(gradeList).when(gradesListApi).getAll(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn(gradesListApi).when(vulcan).getGradesList(); - - LoginSession loginSession = Mockito.mock(LoginSession.class); - Mockito.doReturn(vulcan).when(loginSession).getVulcan(); - Mockito.doReturn(daoSession).when(loginSession).getDaoSession(); - Mockito.doReturn(userId).when(loginSession).getUserId(); - - GradesSync gradesSync = new GradesSync(); - gradesSync.sync(loginSession); - - io.github.wulkanowy.db.dao.entities.Grade grade = daoSession.getGradeDao().load(1L); - - Assert.assertNotNull(grade); - Assert.assertEquals(userId, grade.getUserId()); - Assert.assertEquals(subjectId, grade.getSubjectId()); - Assert.assertEquals("Matematyka", grade.getSubject()); - Assert.assertEquals("5", grade.getValue()); - Assert.assertFalse(grade.getIsNew()); - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getGradeDao().deleteAll(); - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java deleted file mode 100644 index 38e70bc0..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/SubjectSyncTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -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 org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.grades.SubjectsList; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Subject; - -@RunWith(AndroidJUnit4.class) -public class SubjectSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Before - public void setUp() { - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } - - @Test - public void syncSubjectTest() throws Exception { - List subjectList = new ArrayList<>(); - subjectList.add(new io.github.wulkanowy.api.grades.Subject() - .setName("Matematyka") - .setFinalRating("5") - .setPredictedRating("4")); - - SubjectsList subjectsListApi = Mockito.mock(SubjectsList.class); - Mockito.doReturn(subjectList).when(subjectsListApi).getAll(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doReturn(subjectsListApi).when(vulcan).getSubjectsList(); - - LoginSession loginSession = Mockito.mock(LoginSession.class); - Mockito.doReturn(vulcan).when(loginSession).getVulcan(); - Mockito.doReturn(2L).when(loginSession).getUserId(); - Mockito.doReturn(daoSession).when(loginSession).getDaoSession(); - - SubjectsSync subjectsSync = new SubjectsSync(); - subjectsSync.sync(loginSession); - - Subject subject = daoSession.getSubjectDao().load(1L); - - Assert.assertNotNull(subject); - Assert.assertEquals(2, subject.getUserId().longValue()); - Assert.assertEquals("Matematyka", subject.getName()); - Assert.assertEquals("5", subject.getFinalRating()); - Assert.assertEquals("4", subject.getPredictedRating()); - - } - - @AfterClass - public static void cleanUp() { - daoSession.getSubjectDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java deleted file mode 100644 index d08ae4f0..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/TimetableSyncTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.greenrobot.greendao.database.Database; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.timetable.Day; -import io.github.wulkanowy.api.timetable.Lesson; -import io.github.wulkanowy.api.timetable.Timetable; -import io.github.wulkanowy.api.timetable.Week; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -@RunWith(AndroidJUnit4.class) -public class TimetableSyncTest { - - private static DaoSession daoSession; - - @BeforeClass - public static void setUpClass() { - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-db"); - Database database = devOpenHelper.getWritableDb(); - daoSession = new DaoMaster(database).newSession(); - - DaoMaster.dropAllTables(database, true); - DaoMaster.createAllTables(database, true); - } - - @Test - public void syncTimetableEmptyDatabaseTest() throws Exception { - Long userId = daoSession.getAccountDao().insert(new Account().setEmail("TEST@TEST")); - - List dayList = new ArrayList<>(); - dayList.add(new Day() - .setDate("20.12.2012") - .setLesson(new Lesson().setSubject("Matematyka").setRoom("20"))); - Week week = new Week().setDays(dayList); - - List nextDayList = new ArrayList<>(); - dayList.add(new Day() - .setDate("24.11.2013") - .setLesson(new Lesson().setSubject("Matematyka").setRoom("22"))); - Week nextWeek = new Week().setDays(nextDayList); - - Timetable timetable = mock(Timetable.class); - doReturn(week).when(timetable).getWeekTable(); - doReturn(nextWeek).when(timetable).getWeekTable(anyString()); - - Vulcan vulcan = mock(Vulcan.class); - doReturn(timetable).when(vulcan).getTimetable(); - - LoginSession loginSession = mock(LoginSession.class); - doReturn(vulcan).when(loginSession).getVulcan(); - doReturn(daoSession).when(loginSession).getDaoSession(); - doReturn(userId).when(loginSession).getUserId(); - - TimetableSync timetableSync = new TimetableSync(); - timetableSync.sync(loginSession, null); - - List dayEntityList = daoSession.getDayDao().loadAll(); - List lessonEntityList = dayEntityList.get(0).getLessons(); - - Assert.assertNotNull(dayEntityList.get(0)); - Assert.assertEquals(userId, dayEntityList.get(0).getUserId()); - Assert.assertEquals(1L, lessonEntityList.get(0).getDayId().longValue()); - Assert.assertEquals("Matematyka", lessonEntityList.get(0).getSubject()); - Assert.assertEquals("20", lessonEntityList.get(0).getRoom()); - Assert.assertEquals("20.12.2012", dayEntityList.get(0).getDate()); - - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.getDayDao().deleteAll(); - daoSession.getLessonDao().deleteAll(); - daoSession.getWeekDao().deleteAll(); - daoSession.clear(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java deleted file mode 100644 index df07d462..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/services/sync/VulcanSyncTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; - -@RunWith(AndroidJUnit4.class) -public class VulcanSyncTest { - - @Test(expected = IOException.class) - public void syncNoLoginSessionSubjectTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncSubjectsAndGrades(); - } - - @Test(expected = IOException.class) - public void syncNoLoginSessionGradeTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncGrades(); - } - - @Test(expected = IOException.class) - public void syncNoLoginSessionTimetableTest() throws IOException { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - vulcanSync.syncTimetable(); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java b/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java deleted file mode 100644 index 17813d42..00000000 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/SafetyTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.wulkanowy.utils.security; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SdkSuppress; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class SafetyTest { - - @Test - @SdkSuppress(minSdkVersion = 18) - public void encryptDecryptTest() throws Exception { - Context targetContext = InstrumentationRegistry.getTargetContext(); - - Safety safety = new Safety(); - Assert.assertEquals("PASS", safety.decrypt("TEST", safety.encrypt("TEST", "PASS", targetContext))); - } -} diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java index 16034ed4..2fd1904b 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.java @@ -1,59 +1,25 @@ package io.github.wulkanowy.utils.security; import android.content.Context; -import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SdkSuppress; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@SdkSuppress(minSdkVersion = 18) @RunWith(AndroidJUnit4.class) public class ScramblerTest { - private Context targetContext; - - private Scrambler scramblerLoad = new Scrambler(); - - @Before - public void setUp() throws CryptoException { - targetContext = InstrumentationRegistry.getTargetContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - scramblerLoad.loadKeyStore(); - } - } - - @Test(expected = CryptoException.class) - @SdkSuppress(maxSdkVersion = 17) - public void testNoSuchAlgorithm() throws CryptoException { - scramblerLoad.loadKeyStore(); - } - @Test - public void decryptEncryptStringTest() throws CryptoException { - scramblerLoad.generateNewKey("TEST", targetContext); - Assert.assertEquals("pass", - scramblerLoad.decryptString("TEST", scramblerLoad.encryptString("TEST", "pass"))); - } + @SdkSuppress(minSdkVersion = 18) + public void encryptDecryptTest() throws Exception { + Context targetContext = InstrumentationRegistry.getTargetContext(); - @Test(expected = CryptoException.class) - public void decryptEmptyTest() throws CryptoException { - scramblerLoad.decryptString("", ""); + Assert.assertEquals("PASS", Scrambler.decrypt("TEST", + Scrambler.encrypt("TEST", "PASS", targetContext))); } - - @Test(expected = CryptoException.class) - public void encryptEmptyTest() throws CryptoException { - scramblerLoad.encryptString("", ""); - } - - @Test(expected = CryptoException.class) - public void generateNewKeyEmptyTest() throws CryptoException { - scramblerLoad.generateNewKey("", targetContext); - } -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b919954..f5b89757 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ @@ -11,8 +10,6 @@ - - + android:theme="@style/WulkanowyTheme.SplashTheme"> @@ -38,12 +35,12 @@ android:label="@string/title_activity_login" android:windowSoftInputMode="adjustResize" /> diff --git a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java index 2c7c74ac..5065279a 100644 --- a/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java +++ b/app/src/main/java/io/github/wulkanowy/WulkanowyApp.java @@ -1,50 +1,48 @@ package io.github.wulkanowy; import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; import com.crashlytics.android.Crashlytics; import com.crashlytics.android.core.CrashlyticsCore; import org.greenrobot.greendao.query.QueryBuilder; +import javax.inject.Inject; + import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.utils.Log; import io.fabric.sdk.android.Fabric; -import io.github.wulkanowy.db.dao.entities.DaoMaster; -import io.github.wulkanowy.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.di.component.ApplicationComponent; +import io.github.wulkanowy.di.component.DaggerApplicationComponent; +import io.github.wulkanowy.di.modules.ApplicationModule; public class WulkanowyApp extends Application { - public static final String DEBUG_TAG = "WulaknowyActivity"; + protected ApplicationComponent applicationComponent; - private DaoSession daoSession; + @Inject + RepositoryContract repository; @Override public void onCreate() { super.onCreate(); + applicationComponent = DaggerApplicationComponent + .builder() + .applicationModule(new ApplicationModule(this)) + .build(); + applicationComponent.inject(this); + initializeFabric(); if (BuildConfig.DEBUG) { enableDebugLog(); } - initializeFabric(); - - DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "wulkanowy-db"); - - daoSession = new DaoMaster(devOpenHelper.getWritableDb()).newSession(); - - int schemaVersion = getSharedPreferences("LoginData", Context.MODE_PRIVATE).getInt("schemaVersion", 0); - - if (DaoMaster.SCHEMA_VERSION != schemaVersion) { - SharedPreferences sharedPreferences = getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", 0); - editor.putInt("schemaVersion", DaoMaster.SCHEMA_VERSION); - editor.apply(); - } + } + public ApplicationComponent getApplicationComponent() { + return applicationComponent; } private void enableDebugLog() { @@ -62,6 +60,6 @@ public class WulkanowyApp extends Application { } public DaoSession getDaoSession() { - return daoSession; + return null; } } diff --git a/app/src/main/java/io/github/wulkanowy/data/Repository.java b/app/src/main/java/io/github/wulkanowy/data/Repository.java new file mode 100644 index 00000000..04460a07 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/Repository.java @@ -0,0 +1,143 @@ +package io.github.wulkanowy.data; + + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.GradeDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; +import io.github.wulkanowy.di.annotations.SyncGrades; +import io.github.wulkanowy.di.annotations.SyncSubjects; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton +public class Repository implements RepositoryContract { + + private final SharedPrefContract sharedPref; + + private final ResourcesContract resources; + + private final DaoSession daoSession; + + private final LoginSyncContract loginSync; + + private final TimetableSyncContract timetableSync; + + private final SyncContract gradeSync; + + private final SyncContract subjectSync; + + @Inject + Repository(SharedPrefContract sharedPref, + ResourcesContract resources, + DaoSession daoSession, + LoginSyncContract loginSync, + TimetableSyncContract timetableSync, + @SyncGrades SyncContract gradeSync, + @SyncSubjects SyncContract subjectSync) { + this.sharedPref = sharedPref; + this.resources = resources; + this.daoSession = daoSession; + this.loginSync = loginSync; + this.timetableSync = timetableSync; + this.gradeSync = gradeSync; + this.subjectSync = subjectSync; + } + + @Override + public long getCurrentUserId() { + return sharedPref.getCurrentUserId(); + } + + @Override + public String[] getSymbolsKeysArray() { + return resources.getSymbolsKeysArray(); + } + + @Override + public String[] getSymbolsValuesArray() { + return resources.getSymbolsValuesArray(); + } + + @Override + public String getErrorLoginMessage(Exception e) { + return resources.getErrorLoginMessage(e); + } + + @Override + public void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException { + loginSync.loginUser(email, password, symbol); + } + + @Override + public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, + IOException, CryptoException, VulcanOfflineException, BadCredentialsException { + loginSync.loginCurrentUser(); + } + + @Override + public void syncGrades() throws NotLoggedInErrorException, IOException, ParseException { + gradeSync.sync(); + } + + @Override + public void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException { + subjectSync.sync(); + } + + @Override + public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException { + timetableSync.syncTimetable(); + } + + @Override + public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException { + timetableSync.syncTimetable(date); + } + + @Override + public void syncAll() throws NotLoggedInErrorException, IOException, ParseException { + syncSubjects(); + syncGrades(); + syncTimetable(); + } + + @Override + public Account getCurrentUser() { + return daoSession.getAccountDao().load(sharedPref.getCurrentUserId()); + } + + @Override + public Week getWeek(String date) { + return daoSession.getWeekDao().queryBuilder() + .where(WeekDao.Properties.StartDayDate.eq(date), + WeekDao.Properties.UserId.eq(getCurrentUserId())) + .unique(); + } + + @Override + public List getNewGrades() { + return daoSession.getGradeDao().queryBuilder() + .where(GradeDao.Properties.IsNew.eq(1)) + .list(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java new file mode 100644 index 00000000..fcdf29ed --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryContract.java @@ -0,0 +1,34 @@ +package io.github.wulkanowy.data; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import javax.inject.Singleton; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; + +@Singleton +public interface RepositoryContract extends ResourcesContract, LoginSyncContract, + TimetableSyncContract { + + long getCurrentUserId(); + + void syncGrades() throws NotLoggedInErrorException, IOException, ParseException; + + void syncSubjects() throws NotLoggedInErrorException, IOException, ParseException; + + void syncAll() throws NotLoggedInErrorException, IOException, ParseException; + + Account getCurrentUser(); + + Week getWeek(String date); + + List getNewGrades(); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java new file mode 100644 index 00000000..199a6c8b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/DbHelper.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.db.dao; + +import android.content.Context; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.DatabaseInfo; + +@Singleton +public class DbHelper extends DaoMaster.DevOpenHelper { + + @Inject + DbHelper(@ApplicationContext Context context, @DatabaseInfo String dbName) { + super(context, dbName); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java index e5ee45ad..20a47ac2 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Account.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Account.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java index 3ff68fe4..15a06309 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Day.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Day.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java similarity index 88% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java index 2a4adaed..632f9bcc 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Grade.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Grade.java @@ -1,7 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; - -import android.os.Parcel; -import android.os.Parcelable; +package io.github.wulkanowy.data.db.dao.entities; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -11,13 +8,15 @@ import org.greenrobot.greendao.annotation.Generated; import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Property; +import java.io.Serializable; + import io.github.wulkanowy.R; @Entity( nameInDb = "Grades", active = true ) -public class Grade implements Parcelable { +public class Grade implements Serializable { @Id(autoincrement = true) protected Long id; @@ -61,9 +60,7 @@ public class Grade implements Parcelable { @Property(nameInDb = "READ") private boolean read = true; - protected Grade(Parcel source) { - value = source.readString(); - } + private static final long serialVersionUID = 42L; @Generated(hash = 568899968) public Grade(Long id, Long subjectId, Long userId, String subject, String value, @@ -90,36 +87,6 @@ public class Grade implements Parcelable { 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(teacher); - parcel.writeString(semester); - } - - 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]; - } - }; - /** * Used to resolve relations */ diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java similarity index 97% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java index df0596a7..db9be944 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Lesson.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Lesson.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; @@ -7,12 +7,14 @@ import org.greenrobot.greendao.annotation.Id; import org.greenrobot.greendao.annotation.Index; import org.greenrobot.greendao.annotation.Property; +import java.io.Serializable; + @Entity( nameInDb = "Lessons", active = true, - indexes ={@Index(value = "dayId,date,startTime,endTime", unique = true)} + indexes = {@Index(value = "dayId,date,startTime,endTime", unique = true)} ) -public class Lesson { +public class Lesson implements Serializable { @Id(autoincrement = true) private Long id; @@ -65,6 +67,8 @@ public class Lesson { @Property(nameInDb = "IS_NEW_MOVED_IN_CANCELED") private boolean isNewMovedInOrChanged = false; + private static final long serialVersionUID = 42L; + /** * Used to resolve relations */ diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java similarity index 99% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java index 666257f3..6bcce622 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Subject.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Subject.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java similarity index 98% rename from app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java rename to app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java index c8dcbd75..c82f3277 100644 --- a/app/src/main/java/io/github/wulkanowy/db/dao/entities/Week.java +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/entities/Week.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.db.dao.entities; +package io.github.wulkanowy.data.db.dao.entities; import org.greenrobot.greendao.DaoException; import org.greenrobot.greendao.annotation.Entity; diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java new file mode 100644 index 00000000..8e7227d3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/AppResources.java @@ -0,0 +1,59 @@ +package io.github.wulkanowy.data.db.resources; + +import android.content.Context; +import android.content.res.Resources; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.security.CryptoException; + +@Singleton +public class AppResources implements ResourcesContract { + + private Resources resources; + + @Inject + AppResources(@ApplicationContext Context context) { + resources = context.getResources(); + } + + @Override + public String[] getSymbolsKeysArray() { + return resources.getStringArray(R.array.symbols); + } + + @Override + public String[] getSymbolsValuesArray() { + return resources.getStringArray(R.array.symbols_values); + } + + @Override + public String getErrorLoginMessage(Exception exception) { + LogUtils.error(AppConstant.APP_NAME + " encountered a error", exception); + + if (exception instanceof CryptoException) { + return resources.getString(R.string.encrypt_failed_text); + } else if (exception instanceof UnknownHostException) { + return resources.getString(R.string.noInternet_text); + } else if (exception instanceof SocketTimeoutException) { + return resources.getString(R.string.generic_timeout_error); + } else if (exception instanceof NotLoggedInErrorException || exception instanceof IOException) { + return resources.getString(R.string.login_denied_text); + } else if (exception instanceof VulcanOfflineException) { + return resources.getString(R.string.error_host_offline); + } else { + return exception.getMessage(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java new file mode 100644 index 00000000..3768ad2c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/resources/ResourcesContract.java @@ -0,0 +1,10 @@ +package io.github.wulkanowy.data.db.resources; + +public interface ResourcesContract { + + String[] getSymbolsKeysArray(); + + String[] getSymbolsValuesArray(); + + String getErrorLoginMessage(Exception e); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java new file mode 100644 index 00000000..ba437f88 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPref.java @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.db.shared; + +import android.content.Context; +import android.content.SharedPreferences; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; + +@Singleton +public class SharedPref implements SharedPrefContract { + + private static final String SHARED_KEY_USER_ID = "USER_ID"; + + private final SharedPreferences sharedPreferences; + + @Inject + SharedPref(@ApplicationContext Context context, @SharedPreferencesInfo String sharedName) { + sharedPreferences = context.getSharedPreferences(sharedName, Context.MODE_PRIVATE); + } + + @Override + public long getCurrentUserId() { + return sharedPreferences.getLong(SHARED_KEY_USER_ID, 0); + } + + @Override + public void setCurrentUserId(long userId) { + sharedPreferences.edit().putLong(SHARED_KEY_USER_ID, userId).apply(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java new file mode 100644 index 00000000..7f540acf --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/shared/SharedPrefContract.java @@ -0,0 +1,8 @@ +package io.github.wulkanowy.data.db.shared; + +public interface SharedPrefContract { + + long getCurrentUserId(); + + void setCurrentUserId(long userId); +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java new file mode 100644 index 00000000..43b37b7e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/SyncContract.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.data.sync; + +import java.io.IOException; +import java.text.ParseException; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; + +public interface SyncContract { + + void sync() throws NotLoggedInErrorException, IOException, ParseException; +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java new file mode 100644 index 00000000..e0b38884 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/grades/GradeSync.java @@ -0,0 +1,72 @@ +package io.github.wulkanowy.data.sync.grades; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.EntitiesCompare; +import io.github.wulkanowy.utils.LogUtils; + +@Singleton +public class GradeSync implements SyncContract { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + GradeSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void sync() throws IOException, NotLoggedInErrorException, ParseException { + + long userId = sharedPref.getCurrentUserId(); + + Account account = daoSession.getAccountDao().load(userId); + account.resetGradeList(); + account.resetSubjectList(); + + List gradesFromNet = DataObjectConverter + .gradesToGradeEntities(vulcan.getGradesList().getAll()); + List gradesFromDb = account.getGradeList(); + + List updatedGrades = EntitiesCompare.compareGradeList(gradesFromNet, gradesFromDb); + + daoSession.getGradeDao().deleteInTx(gradesFromDb); + + List lastList = new ArrayList<>(); + + for (Grade grade : updatedGrades) { + grade.setUserId(userId); + grade.setSubjectId(daoSession.getSubjectDao().queryBuilder() + .where(SubjectDao.Properties.Name.eq(grade.getSubject()), + SubjectDao.Properties.UserId.eq(userId)) + .build() + .uniqueOrThrow().getId()); + lastList.add(grade); + } + + daoSession.getGradeDao().insertInTx(lastList); + + LogUtils.debug("Synchronization grades (amount = " + lastList.size() + ")"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java new file mode 100644 index 00000000..71838ff5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSync.java @@ -0,0 +1,81 @@ +package io.github.wulkanowy.data.sync.login; + +import android.content.Context; + +import java.io.IOException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.data.db.dao.entities.Account; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.security.CryptoException; +import io.github.wulkanowy.utils.security.Scrambler; + +@Singleton +public class LoginSync implements LoginSyncContract { + + private final DaoSession daoSession; + + private final SharedPrefContract sharedPref; + + private final Vulcan vulcan; + + private final Context context; + + @Inject + LoginSync(DaoSession daoSession, SharedPrefContract sharedPref, + Vulcan vulcan, @ApplicationContext Context context) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + this.context = context; + } + + @Override + public void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException { + + LogUtils.debug("Login new user email=" + email); + + vulcan.login(email, password, symbol); + + Account account = new Account() + .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) + .setEmail(email) + .setPassword(Scrambler.encrypt(email, password, context)) + .setSymbol(vulcan.getSymbol()) + .setSnpId(vulcan.getStudentAndParent().getId()); + + sharedPref.setCurrentUserId(daoSession.getAccountDao().insert(account)); + } + + @Override + public void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, + IOException, CryptoException, VulcanOfflineException, BadCredentialsException { + + long userId = sharedPref.getCurrentUserId(); + + if (userId == 0) { + throw new IOException("Can't find logged user"); + } + + LogUtils.debug("Login current user id=" + userId); + + Account account = daoSession.getAccountDao().load(userId); + + vulcan.login(account.getEmail(), + Scrambler.decrypt(account.getEmail(), account.getPassword()), + account.getSymbol(), + account.getSnpId()); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java new file mode 100644 index 00000000..1aed8b1b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/login/LoginSyncContract.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.data.sync.login; + +import java.io.IOException; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.api.login.VulcanOfflineException; +import io.github.wulkanowy.utils.security.CryptoException; + +public interface LoginSyncContract { + + void loginUser(String email, String password, String symbol) + throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException; + + void loginCurrentUser() throws NotLoggedInErrorException, AccountPermissionException, IOException, + CryptoException, VulcanOfflineException, BadCredentialsException; +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java new file mode 100644 index 00000000..d888090b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/subjects/SubjectSync.java @@ -0,0 +1,61 @@ +package io.github.wulkanowy.data.sync.subjects; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.data.db.dao.entities.SubjectDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.LogUtils; + +@Singleton +public class SubjectSync implements SyncContract { + + private final SubjectDao subjectDao; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + SubjectSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.subjectDao = daoSession.getSubjectDao(); + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void sync() throws NotLoggedInErrorException, IOException, ParseException { + + long userId = sharedPref.getCurrentUserId(); + + List subjectsFromNet = DataObjectConverter + .subjectsToSubjectEntities(vulcan.getSubjectsList().getAll()); + + subjectDao.deleteInTx(subjectDao.queryBuilder() + .where(SubjectDao.Properties.UserId.eq(userId)) + .build() + .list()); + + List lastList = new ArrayList<>(); + + for (Subject subject : subjectsFromNet) { + subject.setUserId(userId); + lastList.add(subject); + } + + subjectDao.insertInTx(lastList); + + LogUtils.debug("Synchronization subjects (amount = " + lastList.size() + ")"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java new file mode 100644 index 00000000..f293f59d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSync.java @@ -0,0 +1,119 @@ +package io.github.wulkanowy.data.sync.timetable; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.DayDao; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.LessonDao; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.WeekDao; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.utils.DataObjectConverter; +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.TimeUtils; + +@Singleton +public class TimetableSync implements TimetableSyncContract { + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + private final SharedPrefContract sharedPref; + + @Inject + TimetableSync(DaoSession daoSession, SharedPrefContract sharedPref, Vulcan vulcan) { + this.daoSession = daoSession; + this.sharedPref = sharedPref; + this.vulcan = vulcan; + } + + @Override + public void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException { + long userId = sharedPref.getCurrentUserId(); + + io.github.wulkanowy.api.timetable.Week weekFromNet = date == null ? vulcan.getTimetable().getWeekTable() + : vulcan.getTimetable().getWeekTable(String.valueOf(TimeUtils.getNetTicks(date))); + + Week weekFromDb = daoSession.getWeekDao().queryBuilder() + .where(WeekDao.Properties.UserId.eq(userId), + WeekDao.Properties.StartDayDate.eq(weekFromNet.getStartDayDate())) + .unique(); + + Long weekId; + + if (weekFromDb == null) { + Week weekFromNetEntity = DataObjectConverter.weekToWeekEntity(weekFromNet).setUserId(userId); + weekId = daoSession.getWeekDao().insert(weekFromNetEntity); + } else { + weekId = weekFromDb.getId(); + } + + List dayListFromNet = weekFromNet.getDays(); + + List updatedLessonList = new ArrayList<>(); + + for (io.github.wulkanowy.api.timetable.Day dayFromNet : dayListFromNet) { + Day dayFromNetEntity = DataObjectConverter.dayToDayEntity(dayFromNet); + + Day dayFromDb = daoSession.getDayDao().queryBuilder() + .where(DayDao.Properties.UserId.eq(userId), + DayDao.Properties.WeekId.eq(weekId), + DayDao.Properties.Date.eq(dayFromNetEntity.getDate())) + .unique(); + + dayFromNetEntity.setUserId(userId); + dayFromNetEntity.setWeekId(weekId); + + Long dayId; + + if (dayFromDb != null) { + dayFromNetEntity.setId(dayFromDb.getId()); + daoSession.getDayDao().save(dayFromNetEntity); + dayId = dayFromNetEntity.getId(); + } else { + dayId = daoSession.getDayDao().insert(dayFromNetEntity); + } + + List lessonListFromNetEntities = DataObjectConverter + .lessonsToLessonsEntities(dayFromNet.getLessons()); + + for (Lesson lessonFromNetEntity : lessonListFromNetEntities) { + Lesson lessonFromDb = daoSession.getLessonDao().queryBuilder() + .where(LessonDao.Properties.DayId.eq(dayId), + LessonDao.Properties.Date.eq(lessonFromNetEntity.getDate()), + LessonDao.Properties.StartTime.eq(lessonFromNetEntity.getStartTime()), + LessonDao.Properties.EndTime.eq(lessonFromNetEntity.getEndTime())) + .unique(); + + if (lessonFromDb != null) { + lessonFromNetEntity.setId(lessonFromDb.getId()); + } + + lessonFromNetEntity.setDayId(dayFromNetEntity.getId()); + + if (!"".equals(lessonFromNetEntity.getSubject())) { + updatedLessonList.add(lessonFromNetEntity); + } + } + } + daoSession.getLessonDao().saveInTx(updatedLessonList); + + LogUtils.debug("Synchronization lessons (amount = " + updatedLessonList.size() + ")"); + } + + @Override + public void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException { + syncTimetable(null); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java new file mode 100644 index 00000000..4c37d2ec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/sync/timetable/TimetableSyncContract.java @@ -0,0 +1,13 @@ +package io.github.wulkanowy.data.sync.timetable; + +import java.io.IOException; +import java.text.ParseException; + +import io.github.wulkanowy.api.login.NotLoggedInErrorException; + +public interface TimetableSyncContract { + + void syncTimetable(String date) throws NotLoggedInErrorException, IOException, ParseException; + + void syncTimetable() throws NotLoggedInErrorException, IOException, ParseException; +} diff --git a/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java b/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java deleted file mode 100644 index 908a5110..00000000 --- a/app/src/main/java/io/github/wulkanowy/db/dao/DatabaseAccess.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.wulkanowy.db.dao; - -import org.greenrobot.greendao.query.Query; - -import java.util.List; - -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.GradeDao; - -public class DatabaseAccess { - - public List getNewGrades(DaoSession daoSession) { - Query gradeQuery = daoSession.getGradeDao().queryBuilder() - .where(GradeDao.Properties.IsNew.eq(1)) - .build(); - - return gradeQuery.list(); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java new file mode 100644 index 00000000..2a74c32d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/ActivityContext.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ActivityContext { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java b/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java new file mode 100644 index 00000000..04139d99 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/ApplicationContext.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ApplicationContext { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java new file mode 100644 index 00000000..fabcefba --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/DatabaseInfo.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface DatabaseInfo { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java b/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java new file mode 100644 index 00000000..f103994a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/PerActivity.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface PerActivity { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java b/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java new file mode 100644 index 00000000..98f364f7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/PerFragment.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface PerFragment { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java new file mode 100644 index 00000000..919c77a0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SharedPreferencesInfo.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SharedPreferencesInfo { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java new file mode 100644 index 00000000..90e6c02f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncGrades.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SyncGrades { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java new file mode 100644 index 00000000..81d351be --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/annotations/SyncSubjects.java @@ -0,0 +1,11 @@ +package io.github.wulkanowy.di.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface SyncSubjects { +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java new file mode 100644 index 00000000..3365a317 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/ActivityComponent.java @@ -0,0 +1,19 @@ +package io.github.wulkanowy.di.component; + +import dagger.Component; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.di.modules.ActivityModule; +import io.github.wulkanowy.ui.login.LoginActivity; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.ui.splash.SplashActivity; + +@PerActivity +@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) +public interface ActivityComponent { + + void inject(SplashActivity splashActivity); + + void inject(LoginActivity loginActivity); + + void inject(MainActivity mainActivity); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java new file mode 100644 index 00000000..3439a949 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/ApplicationComponent.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.di.component; + +import android.content.Context; + +import javax.inject.Singleton; + +import dagger.Component; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.modules.ApplicationModule; +import io.github.wulkanowy.services.SyncJob; + +@Singleton +@Component(modules = ApplicationModule.class) +public interface ApplicationComponent { + + @ApplicationContext + Context getContext(); + + RepositoryContract getRepository(); + + void inject(WulkanowyApp wulkanowyApp); + + void inject(SyncJob syncJob); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java b/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java new file mode 100644 index 00000000..01a0fc02 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/component/FragmentComponent.java @@ -0,0 +1,25 @@ +package io.github.wulkanowy.di.component; + +import dagger.Component; +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.di.modules.FragmentModule; +import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; +import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableTabFragment; + +@PerFragment +@Component(dependencies = ApplicationComponent.class, modules = FragmentModule.class) +public interface FragmentComponent { + + void inject(GradesFragment gradesFragment); + + void inject(AttendanceFragment attendanceFragment); + + void inject(DashboardFragment dashboardFragment); + + void inject(TimetableFragment timetableFragment); + + void inject(TimetableTabFragment timetableTabFragment); +} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java new file mode 100644 index 00000000..53dc1086 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/modules/ActivityModule.java @@ -0,0 +1,64 @@ +package io.github.wulkanowy.di.modules; + + +import android.content.Context; +import android.support.v7.app.AppCompatActivity; + +import dagger.Module; +import dagger.Provides; +import io.github.wulkanowy.di.annotations.ActivityContext; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.login.LoginContract; +import io.github.wulkanowy.ui.login.LoginPresenter; +import io.github.wulkanowy.ui.main.MainContract; +import io.github.wulkanowy.ui.main.MainPagerAdapter; +import io.github.wulkanowy.ui.main.MainPresenter; +import io.github.wulkanowy.ui.splash.SplashContract; +import io.github.wulkanowy.ui.splash.SplashPresenter; + +@Module +public class ActivityModule { + + private AppCompatActivity activity; + + public ActivityModule(AppCompatActivity activity) { + this.activity = activity; + } + + @ActivityContext + @Provides + Context provideContext() { + return activity; + } + + @Provides + AppCompatActivity provideActivity() { + return activity; + } + + @PerActivity + @Provides + SplashContract.Presenter provideSplashPresenter + (SplashPresenter splashPresenter) { + return splashPresenter; + } + + @PerActivity + @Provides + LoginContract.Presenter provideLoginPresenter + (LoginPresenter loginPresenter) { + return loginPresenter; + } + + @PerActivity + @Provides + MainContract.Presenter provideMainPresenter + (MainPresenter mainPresenter) { + return mainPresenter; + } + + @Provides + MainPagerAdapter provideMainPagerAdapter() { + return new MainPagerAdapter(activity.getSupportFragmentManager()); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java index 13234463..58e9909b 100644 --- a/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java +++ b/app/src/main/java/io/github/wulkanowy/di/modules/ApplicationModule.java @@ -3,13 +3,41 @@ package io.github.wulkanowy.di.modules; import android.app.Application; import android.content.Context; +import com.firebase.jobdispatcher.FirebaseJobDispatcher; +import com.firebase.jobdispatcher.GooglePlayDriver; + +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.data.Repository; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.DbHelper; +import io.github.wulkanowy.data.db.dao.entities.DaoMaster; +import io.github.wulkanowy.data.db.dao.entities.DaoSession; +import io.github.wulkanowy.data.db.resources.AppResources; +import io.github.wulkanowy.data.db.resources.ResourcesContract; +import io.github.wulkanowy.data.db.shared.SharedPref; +import io.github.wulkanowy.data.db.shared.SharedPrefContract; +import io.github.wulkanowy.data.sync.SyncContract; +import io.github.wulkanowy.data.sync.grades.GradeSync; +import io.github.wulkanowy.data.sync.login.LoginSync; +import io.github.wulkanowy.data.sync.login.LoginSyncContract; +import io.github.wulkanowy.data.sync.subjects.SubjectSync; +import io.github.wulkanowy.data.sync.timetable.TimetableSync; +import io.github.wulkanowy.data.sync.timetable.TimetableSyncContract; +import io.github.wulkanowy.di.annotations.ApplicationContext; +import io.github.wulkanowy.di.annotations.DatabaseInfo; +import io.github.wulkanowy.di.annotations.SharedPreferencesInfo; +import io.github.wulkanowy.di.annotations.SyncGrades; +import io.github.wulkanowy.di.annotations.SyncSubjects; +import io.github.wulkanowy.utils.AppConstant; @Module public class ApplicationModule { - protected final Application application; + private final Application application; public ApplicationModule(Application application) { this.application = application; @@ -20,8 +48,82 @@ public class ApplicationModule { return application; } + @ApplicationContext @Provides Context provideAppContext() { return application; } + + @DatabaseInfo + @Provides + String provideDatabaseName() { + return AppConstant.DATABASE_NAME; + } + + @SharedPreferencesInfo + @Provides + String provideSharedPreferencesName() { + return AppConstant.SHARED_PREFERENCES_NAME; + } + + @Singleton + @Provides + DaoSession provideDaoSession(DbHelper dbHelper) { + return new DaoMaster(dbHelper.getWritableDb()).newSession(); + } + + @Singleton + @Provides + Vulcan provideVulcan() { + return new Vulcan(); + } + + @Singleton + @Provides + RepositoryContract provideRepository(Repository repository) { + return repository; + } + + @Singleton + @Provides + SharedPrefContract provideSharedPref(SharedPref sharedPref) { + return sharedPref; + } + + @Singleton + @Provides + ResourcesContract provideAppResources(AppResources appResources) { + return appResources; + } + + @Singleton + @Provides + LoginSyncContract provideLoginSync(LoginSync loginSync) { + return loginSync; + } + + @SyncGrades + @Singleton + @Provides + SyncContract provideGradesSync(GradeSync gradeSync) { + return gradeSync; + } + + @SyncSubjects + @Singleton + @Provides + SyncContract provideSubjectSync(SubjectSync subjectSync) { + return subjectSync; + } + + @Singleton + @Provides + TimetableSyncContract provideTimetableSync(TimetableSync timetableSync) { + return timetableSync; + } + + @Provides + FirebaseJobDispatcher provideDispatcher() { + return new FirebaseJobDispatcher(new GooglePlayDriver(application)); + } } diff --git a/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java b/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java new file mode 100644 index 00000000..8999bca8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/di/modules/FragmentModule.java @@ -0,0 +1,76 @@ +package io.github.wulkanowy.di.modules; + +import android.support.v4.app.Fragment; + +import dagger.Module; +import dagger.Provides; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.ui.main.attendance.AttendanceContract; +import io.github.wulkanowy.ui.main.attendance.AttendancePresenter; +import io.github.wulkanowy.ui.main.dashboard.DashboardContract; +import io.github.wulkanowy.ui.main.dashboard.DashboardPresenter; +import io.github.wulkanowy.ui.main.grades.GradeHeaderItem; +import io.github.wulkanowy.ui.main.grades.GradesContract; +import io.github.wulkanowy.ui.main.grades.GradesPresenter; +import io.github.wulkanowy.ui.main.timetable.TimetableContract; +import io.github.wulkanowy.ui.main.timetable.TimetableHeaderItem; +import io.github.wulkanowy.ui.main.timetable.TimetablePagerAdapter; +import io.github.wulkanowy.ui.main.timetable.TimetablePresenter; +import io.github.wulkanowy.ui.main.timetable.TimetableTabContract; +import io.github.wulkanowy.ui.main.timetable.TimetableTabPresenter; + +@Module +public class FragmentModule { + + private final Fragment fragment; + + public FragmentModule(Fragment fragment) { + this.fragment = fragment; + } + + @PerFragment + @Provides + GradesContract.Presenter provideGradesPresenter(GradesPresenter gradesPresenter) { + return gradesPresenter; + } + + @PerFragment + @Provides + AttendanceContract.Presenter provideAttendancePresenter(AttendancePresenter attendancePresenter) { + return attendancePresenter; + } + + @PerFragment + @Provides + DashboardContract.Presenter provideDashboardPresenter(DashboardPresenter dashboardPresenter) { + return dashboardPresenter; + } + + @PerFragment + @Provides + TimetableContract.Presenter provideTimetablePresenter(TimetablePresenter timetablePresenter) { + return timetablePresenter; + } + + @PerFragment + @Provides + TimetableTabContract.Presenter provideTimetableTabPresenter(TimetableTabPresenter timetableTabPresenter) { + return timetableTabPresenter; + } + + @Provides + TimetablePagerAdapter provideTimetablePagerAdapter() { + return new TimetablePagerAdapter(fragment.getChildFragmentManager()); + } + + @Provides + FlexibleAdapter provideGradesAdapter() { + return new FlexibleAdapter<>(null); + } + + @Provides + FlexibleAdapter provideTimetableTabAdapter() { + return new FlexibleAdapter<>(null); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/NotificationService.java b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java new file mode 100644 index 00000000..ab721f46 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/NotificationService.java @@ -0,0 +1,55 @@ +package io.github.wulkanowy.services; + + +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.support.v4.app.NotificationCompat; + +import java.util.Random; + +class NotificationService { + + private static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; + + private static final String CHANNEL_NAME = "New Grade Channel"; + + private NotificationManager manager; + + private Context context; + + NotificationService(Context context) { + this.context = context; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createChannel(); + } + } + + void notify(Notification notification) { + getManager().notify(new Random().nextInt(1000), notification); + } + + NotificationCompat.Builder notificationBuilder() { + return new NotificationCompat.Builder(context, CHANNEL_ID); + } + + @TargetApi(26) + private void createChannel() { + NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.enableLights(true); + notificationChannel.enableVibration(true); + notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + getManager().createNotificationChannel(notificationChannel); + } + + private NotificationManager getManager() { + if (manager == null) { + manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + return manager; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/SyncJob.java b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java new file mode 100644 index 00000000..fad3025e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/SyncJob.java @@ -0,0 +1,114 @@ +package io.github.wulkanowy.services; + +import android.app.PendingIntent; +import android.content.Context; +import android.support.v4.app.NotificationCompat; + +import com.firebase.jobdispatcher.Constraint; +import com.firebase.jobdispatcher.FirebaseJobDispatcher; +import com.firebase.jobdispatcher.GooglePlayDriver; +import com.firebase.jobdispatcher.JobParameters; +import com.firebase.jobdispatcher.JobService; +import com.firebase.jobdispatcher.Lifetime; +import com.firebase.jobdispatcher.RetryStrategy; +import com.firebase.jobdispatcher.SimpleJobService; +import com.firebase.jobdispatcher.Trigger; + +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.utils.LogUtils; + +public class SyncJob extends SimpleJobService { + + private static final int DEFAULT_INTERVAL_START = 60 * 50; + + private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); + + public static final String EXTRA_INTENT_KEY = "cardId"; + + private List gradeList; + + @Inject + RepositoryContract repository; + + public static void start(Context context) { + FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); + + dispatcher.mustSchedule(dispatcher.newJobBuilder() + .setLifetime(Lifetime.FOREVER) + .setService(SyncJob.class) + .setTag("SyncJob") + .setRecurring(true) + .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) + .setConstraints(Constraint.ON_ANY_NETWORK) + .setReplaceCurrent(false) + .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) + .build()); + } + + @Override + public void onCreate() { + super.onCreate(); + ((WulkanowyApp) getApplication()).getApplicationComponent().inject(this); + } + + @Override + public int onRunJob(JobParameters job) { + try { + repository.loginCurrentUser(); + repository.syncAll(); + + gradeList = repository.getNewGrades(); + + if (!gradeList.isEmpty()) { + showNotification(); + } + return JobService.RESULT_SUCCESS; + } catch (Exception e) { + LogUtils.error("During background synchronization an error occurred", e); + return JobService.RESULT_FAIL_RETRY; + } + } + + private void showNotification() { + NotificationService service = new NotificationService(getApplicationContext()); + + service.notify(service.notificationBuilder() + .setContentTitle(getStringTitle()) + .setContentText(getStringContent()) + .setSmallIcon(R.drawable.ic_notification) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(getResources().getColor(R.color.colorPrimary)) + .setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, + MainActivity.getStartIntent(getApplicationContext()).putExtra(EXTRA_INTENT_KEY, 0) + , 0 + )) + .build()); + } + + private String getStringTitle() { + if (gradeList.size() == 1) { + return getResources().getQuantityString(R.plurals.newGradePlurals, 1); + } else { + return getResources().getQuantityString(R.plurals.newGradePlurals, 2); + } + } + + private String getStringContent() { + if (gradeList.size() == 1) { + return gradeList.get(0).getSubject(); + } else { + return getResources().getQuantityString(R.plurals.receivedNewGradePlurals, + gradeList.size(), gradeList.size()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/Updater.java b/app/src/main/java/io/github/wulkanowy/services/Updater.java deleted file mode 100644 index 5e4b90ac..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/Updater.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.github.wulkanowy.services; - -import android.Manifest; -import android.app.Activity; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.support.annotation.NonNull; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AlertDialog; -import android.util.Log; - -import com.github.javiersantos.appupdater.AppUpdaterUtils; -import com.github.javiersantos.appupdater.enums.AppUpdaterError; -import com.github.javiersantos.appupdater.enums.UpdateFrom; -import com.github.javiersantos.appupdater.objects.Update; - -import java.io.File; - -import io.github.wulkanowy.BuildConfig; -import io.github.wulkanowy.R; - -public class Updater { - - private static final String DEBUG_TAG = "WulkanowyUpdater"; - - private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0; - - private Activity activity; - - private Update update; - - private DownloadManager downloadManager; - - private BroadcastReceiver onComplete = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - DownloadManager.Query query = new DownloadManager.Query(); - Cursor c = downloadManager.query(query); - if (c.moveToFirst()) { - String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); - - if (uriString.substring(0, 7).matches("file://")) { - uriString = uriString.substring(7); - } - - File file = new File(uriString); - - Intent install; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - install = new Intent(Intent.ACTION_INSTALL_PACKAGE); - install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - install.setData(FileProvider.getUriForFile(context, - BuildConfig.APPLICATION_ID + ".fileprovider", file)); - } else { - install = new Intent(Intent.ACTION_VIEW); - install.setDataAndType(Uri.parse("file://" + file.getAbsolutePath()), - "application/vnd.android.package-archive"); - } - - context.startActivity(install); - } - } - }; - - public Updater(Activity activity) { - this.activity = activity; - - downloadManager = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE); - activity.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); - } - - private void downloadUpdate() { - if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED) { - startDownload(); - } else { - requestWriteStoragePermission(); - } - } - - private void requestWriteStoragePermission() { - ActivityCompat.requestPermissions(activity, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); - } - - private void startDownload() { - Snackbar.make(activity.findViewById(R.id.fragment_container), "Downloading started.", Snackbar.LENGTH_SHORT).show(); - - String path = Environment.getExternalStorageDirectory().toString() + File.separator + - Environment.DIRECTORY_DOWNLOADS + File.separator + "wulkanowy"; - - File dir = new File(path); - if(!dir.mkdirs()) { - for (String aChildren : dir.list()) { - new File(dir, aChildren).delete(); - } - } - - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(update.getUrlToDownload().toString())) - .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE) - .setAllowedOverRoaming(false) - .setTitle("Wulkanowy v" + update.getLatestVersionCode()) - .setDescription(update.getLatestVersion()) - .setVisibleInDownloadsUi(true) - .setMimeType("application/vnd.android.package-archive") - .setDestinationUri(Uri.fromFile(new File(path + File.separator + update.getLatestVersion() + ".apk"))); - - downloadManager.enqueue(request); - } - - public void onRequestPermissionsResult(int requestCode, @NonNull int[] grantResults) { - if (requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) { - if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadUpdate(); - } else { - Snackbar.make(activity.findViewById(R.id.fragment_container), - "Write storage permission request was denied.", - Snackbar.LENGTH_LONG) - .show(); - } - } - } - - public Updater checkForUpdates() { - AppUpdaterUtils appUpdaterUtils = new AppUpdaterUtils(activity) - .setUpdateFrom(UpdateFrom.JSON) - .setUpdateJSON(BuildConfig.UPDATE_URL) - .withListener(new AppUpdaterUtils.UpdateListener() { - - @Override - public void onSuccess(final Update currentUpdate, Boolean isUpdateAvailable) { - Log.d(DEBUG_TAG, "Latest Version: " + currentUpdate.getLatestVersion()); - Log.d(DEBUG_TAG, "Latest Version Code: " + currentUpdate.getLatestVersionCode().toString()); - Log.d(DEBUG_TAG, "URL: " + currentUpdate.getUrlToDownload().toString()); - Log.d(DEBUG_TAG, "Is update available?: " + Boolean.toString(isUpdateAvailable)); - - update = currentUpdate; - showDialog(isUpdateAvailable); - } - - @Override - public void onFailed(AppUpdaterError error) { - Log.e(DEBUG_TAG, "Something went wrong"); - Log.e(DEBUG_TAG, error.toString()); - } - }); - appUpdaterUtils.start(); - - return this; - } - - private void showDialog(boolean isUpdateAvailable) { - if (isUpdateAvailable) { - new AlertDialog.Builder(activity) - .setTitle("Update is available") - .setMessage("Update to version " + update.getLatestVersionCode().toString() + - " is available. Your version is " + BuildConfig.VERSION_CODE + ". Update?") - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - downloadUpdate(); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .show(); - } - } - - public void onDestroy(Activity activity) { - activity.unregisterReceiver(onComplete); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java b/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java deleted file mode 100644 index 0d8dd3a8..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/FullSyncJob.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.wulkanowy.services.jobs; - -import android.app.PendingIntent; -import android.content.Intent; -import android.support.v4.app.NotificationCompat; - -import com.firebase.jobdispatcher.Constraint; -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.Job; -import com.firebase.jobdispatcher.Lifetime; -import com.firebase.jobdispatcher.RetryStrategy; -import com.firebase.jobdispatcher.Trigger; - -import java.util.List; -import java.util.Random; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.db.dao.DatabaseAccess; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.services.notifications.NotificationBuilder; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.DashboardActivity; - -public class FullSyncJob extends VulcanJobHelper { - - private static final String UNIQUE_TAG = "FullSync"; - - private static final int DEFAULT_INTERVAL_START = 60 * 50; - - private static final int DEFAULT_INTERVAL_END = DEFAULT_INTERVAL_START + (60 * 40); - - @Override - protected Job createJob(FirebaseJobDispatcher dispatcher) { - return dispatcher.newJobBuilder() - .setLifetime(Lifetime.FOREVER) - .setService(SyncService.class) - .setTag(UNIQUE_TAG) - .setRecurring(true) - .setTrigger(Trigger.executionWindow(DEFAULT_INTERVAL_START, DEFAULT_INTERVAL_END)) - .setConstraints(Constraint.ON_ANY_NETWORK) - .setReplaceCurrent(false) - .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) - .build(); - } - - public static class SyncService extends VulcanService { - - @Override - public void workToBePerformed() throws Exception { - DaoSession daoSession = ((WulkanowyApp) getApplication()).getDaoSession(); - - VulcanSync synchronization = new VulcanSync() - .loginCurrentUser(getApplicationContext(), daoSession); - synchronization.syncAll(); - List newGradeList = new DatabaseAccess().getNewGrades(daoSession); - - if (newGradeList.size() == 1) { - buildNotify(getResources().getQuantityString(R.plurals.newGradePlurals, 1), - newGradeList.get(0).getSubject()); - } else if (newGradeList.size() > 1) { - buildNotify(getResources().getQuantityString(R.plurals.newGradePlurals, 2), - getResources().getQuantityString(R.plurals.receivedNewGradePlurals, newGradeList.size(), newGradeList.size())); - } - } - - private void buildNotify(String title, String bodyText) { - Intent intent = new Intent(getApplicationContext(), DashboardActivity.class); - intent.putExtra("cardID", 1); - PendingIntent pendingIntent = PendingIntent - .getActivity(getApplicationContext(), 0, intent, 0); - - NotificationBuilder notificationBuilder = new NotificationBuilder(getApplicationContext()); - NotificationCompat.Builder builder = notificationBuilder - .getNotifications(title, bodyText, pendingIntent); - notificationBuilder.getManager().notify(new Random().nextInt(10000), builder.build()); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java deleted file mode 100644 index f435c2e1..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanJobHelper.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.wulkanowy.services.jobs; - - -import android.content.Context; -import android.util.Log; - -import com.firebase.jobdispatcher.FirebaseJobDispatcher; -import com.firebase.jobdispatcher.GooglePlayDriver; -import com.firebase.jobdispatcher.Job; - -public abstract class VulcanJobHelper { - - public static final String DEBUG_TAG = "SynchronizationService"; - - public final void scheduledJob(Context context) { - FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); - dispatcher.mustSchedule(createJob(dispatcher)); - Log.i(DEBUG_TAG, "Wulkanowy Job is initiation: " + this.toString()); - } - - protected abstract Job createJob(FirebaseJobDispatcher dispatcher); -} diff --git a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java b/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java deleted file mode 100644 index 56c93e54..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/jobs/VulcanService.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.wulkanowy.services.jobs; - -import android.os.AsyncTask; -import android.util.Log; - -import com.firebase.jobdispatcher.JobParameters; -import com.firebase.jobdispatcher.JobService; - -import java.lang.ref.WeakReference; - -public abstract class VulcanService extends JobService { - - private SyncTask syncTask; - - @Override - public boolean onStartJob(JobParameters params) { - Log.d(VulcanJobHelper.DEBUG_TAG, "Wulkanowy services start"); - syncTask = new SyncTask(this, params); - syncTask.execute(); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Wulkanowy services stop"); - if (syncTask != null) { - syncTask.cancel(true); - } - return true; - } - - public abstract void workToBePerformed() throws Exception; - - private static class SyncTask extends AsyncTask { - - private JobParameters jobParameters; - - private WeakReference vulcanService; - - public SyncTask(VulcanService vulcanService, JobParameters jobParameters) { - this.jobParameters = jobParameters; - this.vulcanService = new WeakReference<>(vulcanService); - } - - @Override - protected Void doInBackground(Void... voids) { - try { - vulcanService.get().workToBePerformed(); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "User logging in the background failed", e); - } finally { - vulcanService.get().jobFinished(jobParameters, false); - } - return null; - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java b/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java deleted file mode 100644 index 74333f7e..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/notifications/NotificationBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.github.wulkanowy.services.notifications; - - -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.ContextWrapper; -import android.os.Build; -import android.support.v4.app.NotificationCompat; - -import io.github.wulkanowy.R; - -public class NotificationBuilder extends ContextWrapper { - - public static final String CHANNEL_ID = "Wulkanowy_New_Grade_Channel"; - - public static final String CHANNEL_NAME = "New Grade Channel"; - - private NotificationManager manager; - - public NotificationBuilder(Context context) { - super(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createChannel(); - } - } - - @TargetApi(26) - private void createChannel() { - NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - notificationChannel.enableLights(true); - notificationChannel.enableVibration(true); - notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - getManager().createNotificationChannel(notificationChannel); - } - - public NotificationCompat.Builder getNotifications(String title, String bodyText, PendingIntent pendingIntent) { - return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID) - .setContentTitle(title) - .setContentText(bodyText) - .setSmallIcon(R.drawable.ic_notification) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setChannelId(CHANNEL_ID) - .setDefaults(NotificationCompat.DEFAULT_ALL) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setColor(getResources().getColor(R.color.colorPrimary)); - } - - public NotificationManager getManager() { - if (manager == null) { - manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - } - return manager; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java b/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java deleted file mode 100644 index 30ec717c..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/CurrentAccountLogin.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.util.Log; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -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.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -public class CurrentAccountLogin { - - private final Context context; - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - public CurrentAccountLogin(Context context, DaoSession daoSession, Vulcan vulcan) { - this.context = context; - this.daoSession = daoSession; - this.vulcan = vulcan; - } - - public LoginSession loginCurrentUser() throws CryptoException, - BadCredentialsException, AccountPermissionException, IOException, LoginErrorException, VulcanOfflineException { - - AccountDao accountDao = daoSession.getAccountDao(); - - long userId = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); - - if (userId != 0) { - - Log.d(VulcanJobHelper.DEBUG_TAG, "Login current user id=" + String.valueOf(userId)); - - Safety safety = new Safety(); - Account account = accountDao.load(userId); - - vulcan.login( - account.getEmail(), - safety.decrypt(account.getEmail(), account.getPassword()), - account.getSymbol(), - account.getSnpId() - ); - - return new LoginSession() - .setDaoSession(daoSession) - .setUserId(userId) - .setVulcan(vulcan); - } else { - Log.wtf(VulcanJobHelper.DEBUG_TAG, "loginCurrentUser - USERID IS EMPTY"); - throw new IOException("Can't find user with index 0"); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java b/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java deleted file mode 100644 index bd0571d8..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/FirstAccountLogin.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.security.CryptoException; -import io.github.wulkanowy.utils.security.Safety; - -public class FirstAccountLogin { - - private final Context context; - - private final DaoSession daoSession; - - private final Vulcan vulcan; - - public FirstAccountLogin(Context context, DaoSession daoSession, Vulcan vulcan) { - this.context = context; - this.daoSession = daoSession; - this.vulcan = vulcan; - } - - public LoginSession login(String email, String password, String symbol) - throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException, VulcanOfflineException, BadCredentialsException { - - String realSymbol = vulcan.login(email, password, symbol); - - AccountDao accountDao = daoSession.getAccountDao(); - Safety safety = new Safety(); - Account account = new Account() - .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) - .setEmail(email) - .setPassword(safety.encrypt(email, password, context)) - .setSymbol(realSymbol) - .setSnpId(vulcan.getStudentAndParent().getId()); - - long userId = accountDao.insert(account); - - SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", userId); - editor.apply(); - - return new LoginSession() - .setVulcan(vulcan) - .setUserId(userId) - .setDaoSession(daoSession); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java deleted file mode 100644 index 78069b9d..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/GradesSync.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.wulkanowy.services.sync; - -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.NotLoggedInErrorException; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.GradeDao; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.SubjectDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.EntitiesCompare; - -public class GradesSync { - - public void sync(LoginSession loginSession) throws IOException, - ParseException, NotLoggedInErrorException { - - GradesList gradesList = loginSession.getVulcan().getGradesList(); - - GradeDao gradeDao = loginSession.getDaoSession().getGradeDao(); - AccountDao accountDao = loginSession.getDaoSession().getAccountDao(); - SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao(); - - Account account = accountDao.load(loginSession.getUserId()); - - account.resetGradeList(); - account.resetSubjectList(); - - List gradesFromDb = account.getGradeList(); - List gradeEntitiesList = DataObjectConverter.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(loginSession.getUserId()); - grade.setSubjectId((subjectQuery.uniqueOrThrow()).getId()); - - lastList.add(grade); - } - - gradeDao.insertInTx(lastList); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization grades (amount = " + String.valueOf(lastList.size() + ")")); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java b/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java deleted file mode 100644 index 1d3e91ba..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/LoginSession.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.wulkanowy.services.sync; - - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.db.dao.entities.DaoSession; - - -public class LoginSession { - - private Long userId; - - private Vulcan vulcan; - - private DaoSession daoSession; - - public Long getUserId() { - return userId; - } - - public LoginSession setUserId(Long userId) { - this.userId = userId; - return this; - } - - public Vulcan getVulcan() { - return vulcan; - } - - public LoginSession setVulcan(Vulcan vulcan) { - this.vulcan = vulcan; - return this; - } - - public DaoSession getDaoSession() { - return daoSession; - } - - public LoginSession setDaoSession(DaoSession daoSession) { - this.daoSession = daoSession; - 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(); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java deleted file mode 100644 index 065e8106..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/SubjectsSync.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.api.grades.SubjectsList; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.SubjectDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; - -public class SubjectsSync { - - public void sync(LoginSession loginSession) throws IOException, - NotLoggedInErrorException { - - SubjectsList subjectsList = loginSession.getVulcan().getSubjectsList(); - SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao(); - - List subjectEntitiesList = DataObjectConverter.subjectsToSubjectEntities(subjectsList.getAll()); - List preparedList = new ArrayList<>(); - - for (Subject subject : subjectEntitiesList) { - subject.setUserId(loginSession.getUserId()); - preparedList.add(subject); - } - - SubjectDao.dropTable(subjectDao.getDatabase(), true); - SubjectDao.createTable(subjectDao.getDatabase(), false); - subjectDao.insertInTx(preparedList); - - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization subjects (amount = " + String.valueOf(subjectEntitiesList.size() + ")")); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java deleted file mode 100644 index 0fd13723..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/TimetableSync.java +++ /dev/null @@ -1,121 +0,0 @@ -package io.github.wulkanowy.services.sync; - - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -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.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.timetable.Day; -import io.github.wulkanowy.api.timetable.Week; -import io.github.wulkanowy.db.dao.entities.DayDao; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.LessonDao; -import io.github.wulkanowy.db.dao.entities.WeekDao; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.DataObjectConverter; -import io.github.wulkanowy.utils.TimeUtils; - -public class TimetableSync { - - public void sync(@NonNull LoginSession loginSession, @Nullable String dateOfMonday) throws NotLoggedInErrorException, - IOException, ParseException { - DayDao dayDao = loginSession.getDaoSession().getDayDao(); - LessonDao lessonDao = loginSession.getDaoSession().getLessonDao(); - WeekDao weekDao = loginSession.getDaoSession().getWeekDao(); - - Long weekId; - - Week week = dateOfMonday == null ? loginSession.getVulcan().getTimetable().getWeekTable() - : loginSession.getVulcan().getTimetable() - .getWeekTable(String.valueOf(TimeUtils.getNetTicks(dateOfMonday, "yyyy-MM-dd"))); - - Query weekQuery = weekDao.queryBuilder() - .where(WeekDao.Properties.UserId.eq(loginSession.getUserId()), WeekDao.Properties.StartDayDate.eq(week.getStartDayDate())).build(); - - io.github.wulkanowy.db.dao.entities.Week week1 = weekQuery.unique(); - - if (week1 != null) { - weekId = week1.getId(); - } else { - weekId = weekDao.insert(DataObjectConverter.weekToWeekEntitie(week).setUserId(loginSession.getUserId())); - } - - List dayList = week.getDays(); - - dayDao.saveInTx(getPreparedDaysList(dayList, loginSession.getUserId(), weekId, dayDao)); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization days (amount = " + dayList.size() + ")"); - - List lessonList = new ArrayList<>(); - lessonList.addAll(getPreparedLessonsList(dayList, dayDao, lessonDao, loginSession.getUserId(), weekId)); - - lessonDao.saveInTx(lessonList); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Synchronization lessons (amount = " + lessonList.size() + ")"); - } - - private List getPreparedLessonsList(List dayList, DayDao dayDao, LessonDao lessonDao, long userId, long weekId) { - List allLessonsList = new ArrayList<>(); - - for (Day day : dayList) { - - Query dayQuery = dayDao.queryBuilder() - .where(DayDao.Properties.Date.eq(day.getDate()), - DayDao.Properties.UserId.eq(userId), - DayDao.Properties.WeekId.eq(weekId)) - .build(); - - List lessonEntityList = DataObjectConverter.lessonsToLessonsEntities(day.getLessons()); - List updatedLessonEntityList = new ArrayList<>(); - - for (Lesson lesson : lessonEntityList) { - Lesson lesson1 = lessonDao.queryBuilder() - .where(LessonDao.Properties.DayId.eq(dayQuery.uniqueOrThrow().getId()), - LessonDao.Properties.Date.eq(lesson.getDate()), - LessonDao.Properties.StartTime.eq(lesson.getStartTime()), - LessonDao.Properties.EndTime.eq(lesson.getEndTime())) - .unique(); - - if (lesson1 != null) { - lesson.setId(lesson1.getId()); - } - - lesson.setDayId(dayQuery.uniqueOrThrow().getId()); - if (!"".equals(lesson.getSubject())) { - updatedLessonEntityList.add(lesson); - } - } - allLessonsList.addAll(updatedLessonEntityList); - } - return allLessonsList; - } - - private List getPreparedDaysList(List dayList, long userId, long weekId, DayDao dayDao) { - List updatedDayList = new ArrayList<>(); - List dayEntityList = DataObjectConverter - .daysToDaysEntities(dayList); - for (io.github.wulkanowy.db.dao.entities.Day day : dayEntityList) { - - Query dayQuery = dayDao.queryBuilder().where(DayDao.Properties.UserId.eq(userId), DayDao.Properties.WeekId.eq(weekId), DayDao.Properties.Date.eq(day.getDate())).build(); - - io.github.wulkanowy.db.dao.entities.Day day1 = dayQuery.unique(); - - if (day1 != null) { - day.setId(day1.getId()); - } - - day.setUserId(userId); - day.setWeekId(weekId); - updatedDayList.add(day); - } - return updatedDayList; - } -} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java b/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java deleted file mode 100644 index ee9ee15d..00000000 --- a/app/src/main/java/io/github/wulkanowy/services/sync/VulcanSync.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.wulkanowy.services.sync; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.Log; - -import java.io.IOException; - -import io.github.wulkanowy.api.Vulcan; -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.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.utils.security.CryptoException; - -public class VulcanSync { - - private LoginSession loginSession; - - public VulcanSync(LoginSession loginSession) { - this.loginSession = loginSession; - } - - public VulcanSync() { - this.loginSession = new LoginSession(); - } - - public void firstLoginSignInStep(Context context, DaoSession daoSession, String email, String password, String symbol) - throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException, VulcanOfflineException, BadCredentialsException { - FirstAccountLogin firstAccountLogin = new FirstAccountLogin(context, daoSession, new Vulcan()); - loginSession = firstAccountLogin.login(email, password, symbol); - } - - public VulcanSync loginCurrentUser(Context context, DaoSession daoSession) throws CryptoException, - BadCredentialsException, AccountPermissionException, LoginErrorException, IOException, VulcanOfflineException { - return loginCurrentUser(context, daoSession, new Vulcan()); - } - - public VulcanSync loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) - throws CryptoException, BadCredentialsException, AccountPermissionException, - LoginErrorException, IOException, VulcanOfflineException { - - CurrentAccountLogin currentAccountLogin = new CurrentAccountLogin(context, daoSession, vulcan); - loginSession = currentAccountLogin.loginCurrentUser(); - return this; - } - - public void syncAll() throws IOException { - syncSubjectsAndGrades(); - syncTimetable(); - } - - public void syncGrades() throws IOException { - if (loginSession != null) { - GradesSync gradesSync = new GradesSync(); - try { - gradesSync.sync(loginSession); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of grades failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } - - public void syncSubjectsAndGrades() throws IOException { - if (loginSession != null) { - SubjectsSync subjectsSync = new SubjectsSync(); - try { - subjectsSync.sync(loginSession); - syncGrades(); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronisation of subjects failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } - - public void syncTimetable() throws IOException { - syncTimetable(null); - } - - public void syncTimetable(@Nullable String date) throws IOException { - if (loginSession != null) { - TimetableSync timetableSync = new TimetableSync(); - try { - timetableSync.sync(loginSession, date); - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "Synchronization of timetable failed", e); - throw new IOException(e.getCause()); - } - } else { - Log.e(VulcanJobHelper.DEBUG_TAG, "Before sync, should login user to log", - new UnsupportedOperationException()); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java new file mode 100644 index 00000000..3fd3dc7a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseActivity.java @@ -0,0 +1,67 @@ +package io.github.wulkanowy.ui.base; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; + +import butterknife.Unbinder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.di.component.ActivityComponent; +import io.github.wulkanowy.di.component.DaggerActivityComponent; +import io.github.wulkanowy.di.modules.ActivityModule; +import io.github.wulkanowy.utils.NetworkUtils; + +public abstract class BaseActivity extends AppCompatActivity implements BaseContract.View { + + private ActivityComponent activityComponent; + + private Unbinder unbinder; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + activityComponent = DaggerActivityComponent.builder() + .activityModule(new ActivityModule(this)) + .applicationComponent(((WulkanowyApp) getApplication()).getApplicationComponent()) + .build(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (unbinder != null) { + unbinder.unbind(); + } + } + + @Override + public void onError(int resId) { + onError(getString(resId)); + } + + @Override + public void onError(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNoNetworkError() { + onError(R.string.noInternet_text); + } + + @Override + public boolean isNetworkConnected() { + return NetworkUtils.isOnline(getApplicationContext()); + } + + public ActivityComponent getActivityComponent() { + return activityComponent; + } + + public void setButterKnife(Unbinder unbinder) { + this.unbinder = unbinder; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java new file mode 100644 index 00000000..2a4dc569 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseContract.java @@ -0,0 +1,27 @@ +package io.github.wulkanowy.ui.base; + +import android.support.annotation.StringRes; + +import io.github.wulkanowy.di.annotations.PerActivity; + +public interface BaseContract { + + interface View { + + void onError(@StringRes int resId); + + void onError(String message); + + void onNoNetworkError(); + + boolean isNetworkConnected(); + } + + @PerActivity + interface Presenter { + + void onStart(V view); + + void onDestroy(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java new file mode 100644 index 00000000..efb9d61a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.java @@ -0,0 +1,104 @@ +package io.github.wulkanowy.ui.base; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.View; + +import butterknife.Unbinder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.WulkanowyApp; +import io.github.wulkanowy.di.component.DaggerFragmentComponent; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.di.modules.FragmentModule; + +public abstract class BaseFragment extends Fragment implements BaseContract.View { + + private BaseActivity activity; + + private Unbinder unbinder; + + private FragmentComponent fragmentComponent; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof BaseActivity) { + activity = (BaseActivity) context; + } + + fragmentComponent = DaggerFragmentComponent.builder() + .fragmentModule(new FragmentModule(this)) + .applicationComponent(((WulkanowyApp) activity.getApplication()).getApplicationComponent()) + .build(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpOnViewCreated(view); + } + + @Override + public void onDetach() { + activity = null; + super.onDetach(); + } + + @Override + public void onDestroyView() { + if (unbinder != null) { + unbinder.unbind(); + } + super.onDestroyView(); + } + + @Override + public void onError(int resId) { + onError(getString(resId)); + } + + @Override + public void onError(String message) { + if (activity != null) { + activity.onError(message); + } + } + + @Override + public void onNoNetworkError() { + onError(R.string.noInternet_text); + } + + @Override + public boolean isNetworkConnected() { + return activity != null && activity.isNetworkConnected(); + } + + public void setButterKnife(Unbinder unbinder) { + this.unbinder = unbinder; + } + + public void setTitle(String title) { + if (activity != null) { + activity.setTitle(title); + } + } + + public FragmentComponent getFragmentComponent() { + return fragmentComponent; + } + + + protected void setUpOnViewCreated(View fragmentView) { + // do something on view created + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java new file mode 100644 index 00000000..dfb60b6f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.java @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.base; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; + +public class BasePresenter implements BaseContract.Presenter { + + private final RepositoryContract repository; + + private V view; + + @Inject + public BasePresenter(RepositoryContract repository) { + this.repository = repository; + } + + @Override + public void onStart(V view) { + this.view = view; + } + + @Override + public void onDestroy() { + view = null; + } + + public final RepositoryContract getRepository() { + return repository; + } + + public V getView() { + return view; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java index 713f71a5..26d1c146 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.java @@ -1,226 +1,223 @@ package io.github.wulkanowy.ui.login; -import android.app.Activity; -import android.net.Uri; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.customtabs.CustomTabsIntent; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.MotionEvent; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputLayout; import android.view.View; -import android.view.View.OnClickListener; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; -import android.widget.Button; import android.widget.EditText; import android.widget.TextView; -import java.util.LinkedHashMap; +import javax.inject.Inject; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnEditorAction; import io.github.wulkanowy.R; -import io.github.wulkanowy.services.Updater; +import io.github.wulkanowy.ui.base.BaseActivity; +import io.github.wulkanowy.ui.main.MainActivity; +import io.github.wulkanowy.utils.AppConstant; +import io.github.wulkanowy.utils.CommonUtils; import io.github.wulkanowy.utils.KeyboardUtils; -/** - * A login screen that offers login via email/password. - */ -public class LoginActivity extends Activity { +public class LoginActivity extends BaseActivity implements LoginContract.View { - private float touchPosition; + @BindView(R.id.login_activity_email_edit) + EditText emailView; - private EditText emailView; + @BindView(R.id.login_activity_pass_edit) + EditText passwordView; - private EditText passwordView; + @BindView(R.id.login_activity_symbol_edit) + AutoCompleteTextView symbolView; - private AutoCompleteTextView symbolView; + @BindView(R.id.login_activity_form_scroll) + View loginFormView; - private Updater updater; + @BindView(R.id.login_activity_progress_container) + View loadingBarView; + + @BindView(R.id.login_activity_progress_text) + TextView loginProgressText; + + @BindView(R.id.login_activity_symbol_text_input) + TextInputLayout symbolLayout; + + @Inject + LoginContract.Presenter presenter; + + private EditText requestedView; + + public static Intent getStartIntent(Context context) { + return new Intent(context, LoginActivity.class); + } @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); - updater = new Updater(this).checkForUpdates(); + setButterKnife(ButterKnife.bind(this)); + getActivityComponent().inject(this); - // Set up the login form. - emailView = findViewById(R.id.email); - passwordView = findViewById(R.id.password); - symbolView = findViewById(R.id.symbol); + presenter.onStart(this); - passwordView.setOnEditorActionListener(getTextViewSignInListener()); - symbolView.setOnEditorActionListener(getTextViewSignInListener()); + setUpOnCreate(); - populateAutoComplete(); + } - Button signInButton = findViewById(R.id.action_sign_in); - signInButton.setOnClickListener(new OnClickListener() { + protected void setUpOnCreate() { + symbolView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + getResources().getStringArray(R.array.symbols))); + } + + @OnClick(R.id.login_activity_sign_button) + void onLoginButtonClick() { + presenter.attemptLogin( + emailView.getText().toString(), + passwordView.getText().toString(), + symbolView.getText().toString()); + } + + @OnEditorAction(value = {R.id.login_activity_symbol_edit, R.id.login_activity_pass_edit}) + boolean onEditorAction(int id) { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + onLoginButtonClick(); + return true; + } + return false; + } + + @OnClick(R.id.login_activity_create_text) + void onCreateAccountButtonClick() { + CommonUtils.openInternalBrowserViewer(getApplicationContext(), + AppConstant.VULCAN_CREATE_ACCOUNT_URL); + } + + @OnClick(R.id.login_activity_forgot_text) + void onForgotPasswordButtonClick() { + CommonUtils.openInternalBrowserViewer(getApplicationContext(), + AppConstant.VULCAN_FORGOT_PASS_URL); + } + + @Override + public void setErrorEmailRequired() { + emailView.requestFocus(); + emailView.setError(getString(R.string.error_field_required)); + requestedView = emailView; + } + + @Override + public void setErrorEmailInvalid() { + emailView.requestFocus(); + emailView.setError(getString(R.string.error_invalid_email)); + requestedView = emailView; + } + + @Override + public void setErrorPassRequired() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_field_required)); + requestedView = passwordView; + } + + @Override + public void setErrorPassInvalid() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_invalid_password)); + requestedView = passwordView; + } + + @Override + public void setErrorPassIncorrect() { + passwordView.requestFocus(); + passwordView.setError(getString(R.string.error_incorrect_password)); + requestedView = passwordView; + } + + @Override + public void setErrorSymbolRequired() { + symbolLayout.setVisibility(View.VISIBLE); + symbolView.setError(getString(R.string.error_bad_account_permission)); + symbolView.requestFocus(); + requestedView = symbolView; + } + + @Override + public void resetViewErrors() { + emailView.setError(null); + passwordView.setError(null); + } + + @Override + public void showSoftInput() { + KeyboardUtils.showSoftInput(requestedView, this); + } + + @Override + public void hideSoftInput() { + KeyboardUtils.hideSoftInput(this); + } + + @Override + public void onError(String message) { + Snackbar.make(findViewById(R.id.login_activity_container), message, + Snackbar.LENGTH_LONG).show(); + } + + @Override + public void setStepOneLoginProgress() { + onLoginProgressUpdate("1", getString(R.string.step_login)); + } + + @Override + public void setStepTwoLoginProgress() { + onLoginProgressUpdate("2", getString(R.string.step_synchronization)); + } + + @Override + public void openMainActivity() { + startActivity(MainActivity.getStartIntent(this)); + finish(); + } + + @Override + public void showLoginProgress(final boolean show) { + int animTime = getResources().getInteger(android.R.integer.config_shortAnimTime); + + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + loginFormView.animate().setDuration(animTime).alpha( + show ? 0 : 1).setListener(new AnimatorListenerAdapter() { @Override - public void onClick(View view) { - attemptLogin(); + public void onAnimationEnd(Animator animation) { + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); } }); - findViewById(R.id.action_create_account).setOnClickListener(getButtonLinkListener( - "https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount" - )); - - findViewById(R.id.action_forgot_password).setOnClickListener(getButtonLinkListener( - "https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount" - )); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - updater.onRequestPermissionsResult(requestCode, grantResults); - } - - private TextView.OnEditorActionListener getTextViewSignInListener() { - return new TextView.OnEditorActionListener() { + loadingBarView.setVisibility(show ? View.VISIBLE : View.GONE); + loadingBarView.animate().setDuration(animTime).alpha( + show ? 1 : 0).setListener(new AnimatorListenerAdapter() { @Override - public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { - if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { - attemptLogin(); - return true; - } - return false; + public void onAnimationEnd(Animator animation) { + loadingBarView.setVisibility(show ? View.VISIBLE : View.GONE); } - }; - } - - private OnClickListener getButtonLinkListener(final String url) { - return new OnClickListener() { - @Override - public void onClick(View view) { - CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); - CustomTabsIntent customTabsIntent = builder.build(); - builder.setToolbarColor(getResources().getColor(R.color.colorPrimary)); - customTabsIntent.launchUrl(view.getContext(), Uri.parse(url)); - } - }; - } - - private void populateAutoComplete() { - // Get the string array - String[] countries = getResources().getStringArray(R.array.symbols); - // Create the adapter and set it to the AutoCompleteTextView - ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, - countries); - symbolView.setAdapter(adapter); - } - - /** - * Attempts to sign in the account specified by the login form. - */ - private void attemptLogin() { - // Reset errors. - emailView.setError(null); - passwordView.setError(null); - - // Store values at the time of the login attempt. - String email = emailView.getText().toString(); - String password = passwordView.getText().toString(); - String symbol = symbolView.getText().toString(); - - boolean cancel = false; - View focusView = null; - - // Check for a valid password. - if (TextUtils.isEmpty(password)) { - passwordView.setError(getString(R.string.error_field_required)); - focusView = passwordView; - cancel = true; - } else if (!isPasswordValid(password)) { - passwordView.setError(getString(R.string.error_invalid_password)); - focusView = passwordView; - cancel = true; - } - - // Check for a valid email address. - if (TextUtils.isEmpty(email)) { - emailView.setError(getString(R.string.error_field_required)); - focusView = emailView; - cancel = true; - } else if (!isEmailValid(email)) { - emailView.setError(getString(R.string.error_invalid_email)); - focusView = emailView; - cancel = true; - } - - // Check for a valid symbol. - if (TextUtils.isEmpty(symbol)) { - symbol = "Default"; - } - - String[] keys = getResources().getStringArray(R.array.symbols); - String[] values = getResources().getStringArray(R.array.symbols_values); - LinkedHashMap map = new LinkedHashMap<>(); - - for (int i = 0; i < Math.min(keys.length, values.length); ++i) { - map.put(keys[i], values[i]); - } - - if (map.containsKey(symbol)) { - symbol = map.get(symbol); - } - - if (cancel) { - // There was an error; don't attempt login and focus the first - // form field with an error. - focusView.requestFocus(); - } else { - // Show a progress spinner and kick off a background task to - // perform the user login attempt. - LoginTask authTask = new LoginTask(this, email, password, symbol); - authTask.showProgress(true); - authTask.execute(); - KeyboardUtils.hideSoftInput(this); - } - } - - private boolean isEmailValid(String email) { - return email.contains("@") || email.contains("\\\\"); - } - - private boolean isPasswordValid(String password) { - return password.length() > 4; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - touchPosition = ev.getY(); - } - if (ev.getAction() == MotionEvent.ACTION_UP) { - float releasePosition = ev.getY(); - - if (touchPosition - releasePosition == 0) { - View view = getCurrentFocus(); - if (view != null && (ev.getAction() == MotionEvent.ACTION_UP - || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText - && !view.getClass().getName().startsWith("android.webkit.")) { - - int[] coordinators = new int[2]; - view.getLocationOnScreen(coordinators); - float x = ev.getRawX() + view.getLeft() - coordinators[0]; - float y = ev.getRawY() + view.getTop() - coordinators[1]; - if (x < view.getLeft() || x > view.getRight() || y < view.getTop() - || y > view.getBottom()) { - KeyboardUtils.hideSoftInput(this); - } - } - } - } - return super.dispatchTouchEvent(ev); + }); } @Override public void onDestroy() { super.onDestroy(); - updater.onDestroy(this); + presenter.onDestroy(); + } + + private void onLoginProgressUpdate(String step, String message) { + loginProgressText.setText(String.format("%1$s/2 - %2$s...", step, message)); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java new file mode 100644 index 00000000..fa074896 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginContract.java @@ -0,0 +1,53 @@ +package io.github.wulkanowy.ui.login; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface LoginContract { + interface View extends BaseContract.View { + + void setErrorEmailRequired(); + + void setErrorPassRequired(); + + void setErrorSymbolRequired(); + + void setErrorEmailInvalid(); + + void setErrorPassInvalid(); + + void setErrorPassIncorrect(); + + void resetViewErrors(); + + void setStepOneLoginProgress(); + + void setStepTwoLoginProgress(); + + void openMainActivity(); + + void showLoginProgress(boolean show); + + void showSoftInput(); + + void hideSoftInput(); + + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void attemptLogin(String email, String password, String symbol); + + void onStartAsync(); + + void onLoginProgress(int step); + + void onEndAsync(boolean success, Exception exception); + + void onCanceledAsync(); + + RepositoryContract getRepository(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java new file mode 100644 index 00000000..c8e84725 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.java @@ -0,0 +1,143 @@ +package io.github.wulkanowy.ui.login; + +import android.text.TextUtils; + +import java.util.LinkedHashMap; + +import javax.inject.Inject; + +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.AppConstant; + +public class LoginPresenter extends BasePresenter + implements LoginContract.Presenter { + + private LoginTask loginAsync; + + @Inject + LoginPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (loginAsync != null) { + loginAsync.cancel(true); + loginAsync = null; + } + } + + @Override + public void attemptLogin(String email, String password, String symbol) { + getView().resetViewErrors(); + + if (!isAllFieldCorrect(password, email)) { + getView().showSoftInput(); + return; + } + + if (getView().isNetworkConnected()) { + // Dopóki używamy AsyncTask presenter będzie musiał "wiedzieć" o AsyncTaskach + loginAsync = new LoginTask(this, + email, + password, + getNormalizedSymbol(symbol)); + loginAsync.execute(); + + } else { + getView().onNoNetworkError(); + } + + getView().hideSoftInput(); + } + + @Override + public void onStartAsync() { + getView().showLoginProgress(true); + } + + @Override + public void onLoginProgress(int step) { + if (step == 1) { + getView().setStepOneLoginProgress(); + } else if (step == 2) { + getView().setStepTwoLoginProgress(); + } + } + + @Override + public void onEndAsync(boolean success, Exception exception) { + if (success) { + getView().openMainActivity(); + } else if (exception instanceof BadCredentialsException) { + getView().setErrorPassIncorrect(); + getView().showSoftInput(); + getView().showLoginProgress(false); + } else if (exception instanceof AccountPermissionException) { + getView().setErrorSymbolRequired(); + getView().showSoftInput(); + getView().showLoginProgress(false); + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + getView().showLoginProgress(false); + } + + } + + @Override + public void onCanceledAsync() { + getView().showLoginProgress(false); + } + + private boolean isEmailValid(String email) { + return email.contains("@") || email.contains("\\\\"); + } + + private boolean isPasswordValid(String password) { + return password.length() > 4; + } + + private String getNormalizedSymbol(String symbol) { + if (TextUtils.isEmpty(symbol)) { + return AppConstant.DEFAULT_SYMBOL; + } + + String[] keys = getRepository().getSymbolsKeysArray(); + String[] values = getRepository().getSymbolsValuesArray(); + LinkedHashMap map = new LinkedHashMap<>(); + + for (int i = 0; i < Math.min(keys.length, values.length); ++i) { + map.put(keys[i], values[i]); + } + + if (map.containsKey(symbol)) { + return map.get(symbol); + } + return AppConstant.DEFAULT_SYMBOL; + } + + private boolean isAllFieldCorrect(String password, String email) { + boolean correct = true; + + if (TextUtils.isEmpty(password)) { + getView().setErrorPassRequired(); + correct = false; + } else if (!isPasswordValid(password)) { + getView().setErrorPassInvalid(); + correct = false; + } + + if (TextUtils.isEmpty(email)) { + getView().setErrorEmailRequired(); + correct = false; + } else if (!isEmailValid(email)) { + getView().setErrorEmailInvalid(); + correct = false; + } + return correct; + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java index 3bf70284..441b8d8d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginTask.java @@ -1,64 +1,21 @@ package io.github.wulkanowy.ui.login; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; import android.os.AsyncTask; -import android.support.design.widget.Snackbar; -import android.support.design.widget.TextInputLayout; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; -import com.crashlytics.android.answers.CustomEvent; +public class LoginTask extends AsyncTask { -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; + private String email; -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.services.jobs.FullSyncJob; -import io.github.wulkanowy.services.sync.LoginSession; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.DashboardActivity; -import io.github.wulkanowy.utils.KeyboardUtils; -import io.github.wulkanowy.utils.NetworkUtils; -import io.github.wulkanowy.utils.security.CryptoException; + private String password; -/** - * Represents an asynchronous login/registration task used to authenticate - * the user. - */ -public class LoginTask extends AsyncTask { + private String symbol; - private final String email; + private LoginContract.Presenter presenter; - private final String password; + private Exception exception; - private final String symbol; - - private WeakReference activity; - - private WeakReference progressView; - - private WeakReference loginFormView; - - private WeakReference showText; - - LoginTask(Activity activity, String email, String password, String symbol) { - this.activity = new WeakReference<>(activity); + LoginTask(LoginContract.Presenter presenter, String email, String password, String symbol) { + this.presenter = presenter; this.email = email; this.password = password; this.symbol = symbol; @@ -66,159 +23,36 @@ public class LoginTask extends AsyncTask { @Override protected void onPreExecute() { - showText = new WeakReference<>((TextView) activity.get().findViewById(R.id.login_progress_text)); + presenter.onStartAsync(); } @Override - protected Integer doInBackground(Void... params) { - if (NetworkUtils.isOnline(activity.get())) { - DaoSession daoSession = ((WulkanowyApp) activity.get().getApplication()).getDaoSession(); - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); + protected Boolean doInBackground(Void... params) { + try { + publishProgress(1); + presenter.getRepository().loginUser(email, password, symbol); - try { - publishProgress("1", activity.get().getResources().getString(R.string.step_login)); - vulcanSync.firstLoginSignInStep(activity.get(), daoSession, email, password, symbol); - - publishProgress("2", activity.get().getResources().getString(R.string.step_synchronization)); - vulcanSync.syncAll(); - } catch (BadCredentialsException e) { - return R.string.login_bad_credentials_text; - } catch (AccountPermissionException e) { - return R.string.error_bad_account_permission; - } catch (CryptoException e) { - return R.string.encrypt_failed_text; - } catch (UnknownHostException e) { - return R.string.noInternet_text; - } catch (SocketTimeoutException e) { - return R.string.generic_timeout_error; - } catch (NotLoggedInErrorException | IOException e) { - return R.string.login_denied_text; - } catch (VulcanOfflineException e) { - return R.string.error_host_offline; - } catch (UnsupportedOperationException e) { - return -1; - } catch (Throwable e) { - Crashlytics.logException(e); - return R.string.login_denied_text; - } - - new FullSyncJob().scheduledJob(activity.get()); - - return R.string.login_accepted_text; - - } else { - return R.string.noInternet_text; + publishProgress(2); + presenter.getRepository().syncAll(); + } catch (Exception e) { + exception = e; + return false; } + return true; } @Override - protected void onProgressUpdate(String... progress) { - showText.get().setText(String.format("%1$s/2 - %2$s...", progress[0], progress[1])); + protected void onProgressUpdate(Integer... progress) { + presenter.onLoginProgress(progress[0]); } @Override - protected void onPostExecute(final Integer messageID) { - showProgress(false); - - switch (messageID) { - // if success - case R.string.login_accepted_text: - logFirstLoginAction(true, activity.get().getString(messageID)); - Intent intent = new Intent(activity.get(), DashboardActivity.class); - activity.get().finish(); - activity.get().startActivity(intent); - break; - - // if bad credentials entered - case R.string.login_bad_credentials_text: - logFirstLoginAction(false, activity.get().getString(messageID)); - EditText passwordView = activity.get().findViewById(R.id.password); - passwordView.setError(activity.get().getString(R.string.error_incorrect_password)); - passwordView.requestFocus(); - KeyboardUtils.showSoftInput(passwordView, activity.get()); - break; - - // if no permission - case R.string.error_bad_account_permission: - logFirstLoginAction(false, activity.get().getString(messageID)); - // Change to visible symbol input view - TextInputLayout symbolLayout = activity.get().findViewById(R.id.to_symbol_input_layout); - symbolLayout.setVisibility(View.VISIBLE); - - EditText symbolView = activity.get().findViewById(R.id.symbol); - symbolView.setError(activity.get().getString(R.string.error_bad_account_permission)); - symbolView.requestFocus(); - KeyboardUtils.showSoftInput(symbolView, activity.get()); - break; - - // if rooted and SDK < 18 - case -1: - logFirstLoginAction(false, "Device rooted"); - final AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity.get()) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.alert_dialog_blocked_app) - .setMessage(R.string.alert_dialog_blocked_app_message) - .setPositiveButton(R.string.generic_dialog_close, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); - alertDialog.show(); - break; - - default: - logFirstLoginAction(false, activity.get().getString(messageID)); - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - messageID, Snackbar.LENGTH_LONG).show(); - break; - } - } - - private void logFirstLoginAction(boolean success, String message) { - Answers.getInstance().logCustom(new CustomEvent("First login") - .putCustomAttribute("Symbol", symbol) - .putCustomAttribute("Success", success ? 1 : 0) - .putCustomAttribute("Message", message)); + protected void onPostExecute(Boolean success) { + presenter.onEndAsync(success, exception); } @Override protected void onCancelled() { - showProgress(false); - } - - /** - * Shows the progress UI and hides the login form. - */ - void showProgress(final boolean show) { - loginFormView = new WeakReference<>(activity.get().findViewById(R.id.login_form)); - progressView = new WeakReference<>(activity.get().findViewById(R.id.login_progress)); - - int animTime = activity.get().getResources().getInteger(android.R.integer.config_shortAnimTime); - - changeLoginFormVisibility(show, animTime); - changeProgressVisibility(show, animTime); - } - - private void changeLoginFormVisibility(final boolean show, final int animTime) { - loginFormView.get().setVisibility(show ? View.GONE : View.VISIBLE); - loginFormView.get().animate().setDuration(animTime).alpha( - show ? 0 : 1).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - loginFormView.get().setVisibility(show ? View.GONE : View.VISIBLE); - } - }); - } - - private void changeProgressVisibility(final boolean show, final int animTime) { - progressView.get().setVisibility(show ? View.VISIBLE : View.GONE); - progressView.get().animate().setDuration(animTime).alpha( - show ? 1 : 0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - progressView.get().setVisibility(show ? View.VISIBLE : View.GONE); - } - }); + presenter.onCanceledAsync(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java deleted file mode 100644 index 681aa751..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/AbstractFragment.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.github.wulkanowy.ui.main; - - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; -import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; -import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.utils.NetworkUtils; - -public abstract class AbstractFragment extends Fragment - implements AsyncResponse { - - private FlexibleAdapter flexibleAdapter; - - private List itemList = new ArrayList<>(); - - private WeakReference activityWeakReference; - - private SwipeRefreshLayout swipeRefreshLayout; - - private RecyclerView recyclerViewLayout; - - private DaoSession daoSession; - - private long userId; - - public AbstractFragment() { - //empty constructor for fragments - } - - public long getUserId() { - return userId; - } - - public SwipeRefreshLayout getRefreshLayoutView() { - return swipeRefreshLayout; - } - - public DaoSession getDaoSession() { - return daoSession; - } - - public Activity getActivityWeakReference() { - return activityWeakReference.get(); - } - - public abstract int getLayoutId(); - - public abstract int getRecyclerViewId(); - - public abstract int getLoadingBarId(); - - public abstract int getRefreshLayoutId(); - - public abstract List getItems() throws Exception; - - public abstract void onRefresh() throws Exception; - - public abstract void onPostRefresh(int stringResult); - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(getLayoutId(), container, false); - recyclerViewLayout = view.findViewById(getRecyclerViewId()); - swipeRefreshLayout = view.findViewById(getRefreshLayoutId()); - setUpRefreshLayout(swipeRefreshLayout); - return view; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - if (getActivity() != null && getView() != null) { - activityWeakReference = new WeakReference(getActivity()); - daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); - userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) - .getLong("userId", 0); - - if (itemList != null) - if (itemList.isEmpty()) { - flexibleAdapter = getFlexibleAdapter(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - if (getUserVisibleHint()) { - new DatabaseQueryTask(this).execute(); - } - } else { - setAdapterOnRecyclerView(recyclerViewLayout); - setLoadingBarInvisible(getView()); - } - } - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (isResumed() && isVisibleToUser && flexibleAdapter.getItemCount() == 0) { - new DatabaseQueryTask(this).execute(); - } - } - - @Override - public void onQuarryProcessFinish(@NonNull List resultItemList) { - itemList = resultItemList; - flexibleAdapter = getFlexibleAdapter(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - if (getView() != null) { - setLoadingBarInvisible(getView()); - } - } - - @Override - public void onRefreshProcessFinish(@Nullable List resultItemList, int stringEventId) { - if (resultItemList != null) { - itemList = resultItemList; - updateDataInRecyclerView(); - } - onPostRefresh(stringEventId); - getRefreshLayoutView().setRefreshing(false); - } - - @NonNull - protected FlexibleAdapter getFlexibleAdapter(@NonNull List itemList) { - return new FlexibleAdapter<>(itemList) - .setAutoCollapseOnExpand(true) - .setAutoScrollOnExpand(true) - .expandItemsAtStartUp(); - } - - @NonNull - private SwipeRefreshLayout.OnRefreshListener getDefaultRefreshListener() { - return new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - if (NetworkUtils.isOnline(getContext())) { - new RefreshTask(AbstractFragment.this).execute(); - } else { - Toast.makeText(getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show(); - swipeRefreshLayout.setRefreshing(false); - } - } - }; - } - - private void updateDataInRecyclerView() { - flexibleAdapter.updateDataSet(itemList); - setAdapterOnRecyclerView(recyclerViewLayout); - } - - protected void setUpRefreshLayout(@NonNull SwipeRefreshLayout swipeRefreshLayout) { - swipeRefreshLayout.setColorSchemeResources(android.R.color.black); - swipeRefreshLayout.setOnRefreshListener(getDefaultRefreshListener()); - } - - protected final void setLoadingBarInvisible(@NonNull View mainView) { - mainView.findViewById(getLoadingBarId()).setVisibility(View.INVISIBLE); - } - - protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { - recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(getActivityWeakReference())); - recyclerView.setAdapter(flexibleAdapter); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java b/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java deleted file mode 100644 index dc95e5a2..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/AsyncResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.List; - -import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; - -public interface AsyncResponse { - void onQuarryProcessFinish(@NonNull List resultItemList); - - void onRefreshProcessFinish(@Nullable List resultItemList, int stringErrorId); -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java deleted file mode 100644 index 6015de00..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/DashboardActivity.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.design.widget.BottomNavigationView; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.services.Updater; -import io.github.wulkanowy.ui.main.attendance.AttendanceFragment; -import io.github.wulkanowy.ui.main.board.BoardFragment; -import io.github.wulkanowy.ui.main.grades.GradesFragment; -import io.github.wulkanowy.ui.main.timetable.TimetableFragment; - -public class DashboardActivity extends AppCompatActivity { - - private Fragment currentFragment; - - private GradesFragment gradesFragment = new GradesFragment(); - - private AttendanceFragment attendanceFragment = new AttendanceFragment(); - - private BoardFragment boardFragment = new BoardFragment(); - - private TimetableFragment timetableFragment = new TimetableFragment(); - - private Updater updater; - - private boolean showed; - - private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener - = new BottomNavigationView.OnNavigationItemSelectedListener() { - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.navigation_marks: - setTitle(R.string.grades_text); - currentFragment = gradesFragment; - break; - - case R.id.navigation_attendance: - setTitle(R.string.attendance_text); - currentFragment = attendanceFragment; - break; - - case R.id.navigation_lessonplan: - setTitle(R.string.lessonplan_text); - currentFragment = timetableFragment; - break; - - case R.id.navigation_dashboard: - default: - setTitle(R.string.dashboard_text); - currentFragment = boardFragment; - break; - } - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment_container, currentFragment); - transaction.commit(); - return true; - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_dashboard); - - BottomNavigationView navigation = findViewById(R.id.navigation); - navigation.setSelectedItemId(R.id.navigation_marks); - navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); - - if (savedInstanceState != null) { - currentFragment = getSupportFragmentManager().getFragment(savedInstanceState, "currentFragment"); - setTitle(savedInstanceState.getString("activityTitle")); - } else { - currentFragment = gradesFragment; - setTitle(R.string.grades_text); - } - - int cardID = getIntent().getIntExtra("cardID", 0); - - if (cardID == 1) { - currentFragment = gradesFragment; - } - - getSupportFragmentManager().beginTransaction() - .replace(R.id.fragment_container, currentFragment).commit(); - } - - @Override - protected void onStart() { - super.onStart(); - if (!showed) { - updater = new Updater(this).checkForUpdates(); - showed = true; - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - updater.onRequestPermissionsResult(requestCode, grantResults); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString("activityTitle", getTitle().toString()); - getSupportFragmentManager().putFragment(outState, "currentFragment", currentFragment); - } - - public void onBackPressed() { - - BottomNavigationView navigation = findViewById(R.id.navigation); - - if (navigation.getSelectedItemId() != R.id.navigation_dashboard) { - navigation.setSelectedItemId(R.id.navigation_dashboard); - } else if (navigation.getSelectedItemId() == R.id.navigation_dashboard) { - moveTaskToBack(true); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - updater.onDestroy(this); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java deleted file mode 100644 index 7ce4b4fb..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/DatabaseQueryTask.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.AsyncTask; - -import java.util.List; - -public class DatabaseQueryTask extends AsyncTask> { - - private AbstractFragment abstractFragment; - - public DatabaseQueryTask(AbstractFragment abstractFragment) { - this.abstractFragment = abstractFragment; - } - - @Override - protected List doInBackground(Void... voids) { - try { - return abstractFragment.getItems(); - } catch (Exception e) { - return null; - } - } - - @SuppressWarnings("unchecked") - @Override - protected void onPostExecute(List objects) { - super.onPostExecute(objects); - abstractFragment.onQuarryProcessFinish(objects); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java new file mode 100644 index 00000000..6ad696d0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.java @@ -0,0 +1,153 @@ +package io.github.wulkanowy.ui.main; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.View; + +import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigationViewPager; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.services.SyncJob; +import io.github.wulkanowy.ui.base.BaseActivity; +import io.github.wulkanowy.ui.main.dashboard.DashboardFragment; +import io.github.wulkanowy.ui.main.grades.GradesFragment; +import io.github.wulkanowy.ui.main.timetable.TimetableFragment; + +public class MainActivity extends BaseActivity implements MainContract.View, + AHBottomNavigation.OnTabSelectedListener, OnFragmentIsReadyListener { + + private int initTabPosition = 0; + + @BindView(R.id.main_activity_nav) + AHBottomNavigation bottomNavigation; + + @BindView(R.id.main_activity_view_pager) + AHBottomNavigationViewPager viewPager; + + @BindView(R.id.main_activity_progress_bar) + View progressBar; + + @Inject + MainPagerAdapter pagerAdapter; + + @Inject + MainContract.Presenter presenter; + + public static Intent getStartIntent(Context context) { + return new Intent(context, MainActivity.class); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + initTabPosition = getIntent().getIntExtra(SyncJob.EXTRA_INTENT_KEY, initTabPosition); + + getActivityComponent().inject(this); + setButterKnife(ButterKnife.bind(this)); + + presenter.onStart(this); + + initiationViewPager(); + initiationBottomNav(); + } + + @Override + public void showProgressBar(boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + viewPager.setVisibility(show ? View.INVISIBLE : View.VISIBLE); + bottomNavigation.setVisibility(show ? View.INVISIBLE : View.VISIBLE); + } + + @Override + public void showActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + + @Override + public void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + @Override + public boolean onTabSelected(int position, boolean wasSelected) { + presenter.onTabSelected(position, wasSelected); + return true; + } + + @Override + public void setCurrentPage(int position) { + viewPager.setCurrentItem(position, false); + } + + @Override + public void onFragmentIsReady() { + presenter.onFragmentIsReady(); + } + + private void initiationBottomNav() { + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.grades_text), + getResources().getDrawable(R.drawable.icon_grade_26dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.attendance_text), + getResources().getDrawable(R.drawable.icon_attendance_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.dashboard_text), + getResources().getDrawable(R.drawable.ic_dashboard_black_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.lessonplan_text), + getResources().getDrawable(R.drawable.icon_lessonplan_24dp) + )); + bottomNavigation.addItem(new AHBottomNavigationItem( + getString(R.string.settings_text), + getResources().getDrawable(R.drawable.icon_other_24dp) + )); + + bottomNavigation.setAccentColor(getResources().getColor(R.color.colorPrimary)); + bottomNavigation.setInactiveColor(Color.BLACK); + bottomNavigation.setBackgroundColor(getResources().getColor(R.color.colorBackgroundBottomNavi)); + bottomNavigation.setTitleState(AHBottomNavigation.TitleState.ALWAYS_SHOW); + bottomNavigation.setOnTabSelectedListener(this); + bottomNavigation.setCurrentItem(initTabPosition); + bottomNavigation.setBehaviorTranslationEnabled(false); + } + + private void initiationViewPager() { + pagerAdapter.addFragment(new GradesFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + pagerAdapter.addFragment(new TimetableFragment()); + pagerAdapter.addFragment(new DashboardFragment()); + + viewPager.setPagingEnabled(false); + viewPager.setAdapter(pagerAdapter); + viewPager.setOffscreenPageLimit(4); + viewPager.setCurrentItem(initTabPosition, false); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + presenter.onDestroy(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java new file mode 100644 index 00000000..cb1d5d7c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainContract.java @@ -0,0 +1,26 @@ +package io.github.wulkanowy.ui.main; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface MainContract { + + interface View extends BaseContract.View { + + void setCurrentPage(int position); + + void showProgressBar(boolean show); + + void showActionBar(); + + void hideActionBar(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onTabSelected(int position, boolean wasSelected); + + void onFragmentIsReady(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java new file mode 100644 index 00000000..fcc51fb5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPagerAdapter.java @@ -0,0 +1,31 @@ +package io.github.wulkanowy.ui.main; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class MainPagerAdapter extends FragmentStatePagerAdapter { + + private List fragmentList = new ArrayList<>(); + + public MainPagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); + } + + void addFragment(Fragment fragment) { + fragmentList.add(fragment); + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + @Override + public int getCount() { + return fragmentList.size(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java new file mode 100644 index 00000000..149e49e2 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.java @@ -0,0 +1,44 @@ +package io.github.wulkanowy.ui.main; + + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; + +public class MainPresenter extends BasePresenter + implements MainContract.Presenter { + + private int fragmentCount = 0; + + @Inject + MainPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(MainContract.View view) { + super.onStart(view); + getView().showProgressBar(true); + getView().hideActionBar(); + } + + @Override + public void onTabSelected(int position, boolean wasSelected) { + if (!wasSelected) { + getView().setCurrentPage(position); + } + } + + @Override + public void onFragmentIsReady() { + if (fragmentCount < 5) { + fragmentCount++; + } + + if (fragmentCount == 5) { + getView().showActionBar(); + getView().showProgressBar(false); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java b/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java new file mode 100644 index 00000000..21e3ba12 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/OnFragmentIsReadyListener.java @@ -0,0 +1,6 @@ +package io.github.wulkanowy.ui.main; + +public interface OnFragmentIsReadyListener { + + void onFragmentIsReady(); +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java b/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java deleted file mode 100644 index 67697ba9..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/RefreshTask.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.wulkanowy.ui.main; - -import android.os.AsyncTask; -import android.util.Log; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.api.login.VulcanOfflineException; - -public class RefreshTask extends AsyncTask> { - - public static final String DEBUG_TAG = "RefreshTask"; - - private int stringEventId = 0; - - private AbstractFragment abstractFragment; - - public RefreshTask(AbstractFragment abstractFragment) { - this.abstractFragment = abstractFragment; - } - - @Override - protected List doInBackground(Void... voids) { - try { - abstractFragment.onRefresh(); - return abstractFragment.getItems(); - } catch (UnknownHostException e) { - stringEventId = R.string.noInternet_text; - Log.i(DEBUG_TAG, "Synchronization is failed because occur problem with internet", - e.getCause()); - return null; - } catch (SocketTimeoutException e) { - stringEventId = R.string.generic_timeout_error; - Log.i(DEBUG_TAG, "Too long wait for connection with internet", e); - return null; - } catch (VulcanOfflineException e) { - stringEventId = R.string.error_host_offline; - Log.i(DEBUG_TAG, "VULCAN services is offline"); - return null; - } catch (Exception e) { - stringEventId = R.string.refresh_error_text; - Log.e(DEBUG_TAG, "There was a sync problem", e); - return null; - } - } - - @SuppressWarnings("unchecked") - @Override - protected void onPostExecute(List objects) { - super.onPostExecute(objects); - abstractFragment.onRefreshProcessFinish(objects, stringEventId); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java new file mode 100644 index 00000000..fe403717 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceContract.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.main.attendance; + +import io.github.wulkanowy.di.annotations.PerFragment; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface AttendanceContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + boolean isMenuVisible(); + } + + @PerFragment + interface Presenter extends BaseContract.Presenter { + + void onStart(View view, OnFragmentIsReadyListener listener); + + void onFragmentVisible(boolean isVisible); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java index 0da2e99f..6010d108 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.java @@ -2,18 +2,58 @@ package io.github.wulkanowy.ui.main.attendance; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import io.github.wulkanowy.R; +import javax.inject.Inject; -public class AttendanceFragment extends Fragment { +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class AttendanceFragment extends BaseFragment implements AttendanceContract.View { + + @Inject + AttendanceContract.Presenter presenter; + + public AttendanceFragment() { + // empty constructor for fragment + } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_attendance, container, false); + View view = inflater.inflate(R.layout.fragment_attendance, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + + return view; + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); + } + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.dashboard_text)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + presenter.onDestroy(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java new file mode 100644 index 00000000..da43e320 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.attendance; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class AttendancePresenter extends BasePresenter + implements AttendanceContract.Presenter { + + private OnFragmentIsReadyListener listener; + + @Inject + AttendancePresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(AttendanceContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + this.listener.onFragmentIsReady(); + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java deleted file mode 100644 index f6b12c68..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/board/BoardFragment.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.wulkanowy.ui.main.board; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import io.github.wulkanowy.R; - -public class BoardFragment extends Fragment { - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_board, container, false); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java new file mode 100644 index 00000000..2488eb95 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardContract.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface DashboardContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + boolean isMenuVisible(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onStart(View view, OnFragmentIsReadyListener listener); + + void onFragmentVisible(boolean isVisible); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java new file mode 100644 index 00000000..0832b5fb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardFragment.java @@ -0,0 +1,48 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import javax.inject.Inject; + +import butterknife.ButterKnife; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class DashboardFragment extends BaseFragment implements DashboardContract.View { + + @Inject + DashboardContract.Presenter presenter; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_board, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + return view; + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); + } + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.dashboard_text)); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java new file mode 100644 index 00000000..58d89b39 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/dashboard/DashboardPresenter.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.dashboard; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public class DashboardPresenter extends BasePresenter + implements DashboardContract.Presenter { + + private OnFragmentIsReadyListener listener; + + @Inject + DashboardPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(DashboardContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + this.listener.onFragmentIsReady(); + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java new file mode 100644 index 00000000..5edabb7b --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradeHeaderItem.java @@ -0,0 +1,117 @@ +package io.github.wulkanowy.ui.main.grades; + + +import android.content.res.Resources; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; +import eu.davidea.viewholders.ExpandableViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.utils.AverageCalculator; + +public class GradeHeaderItem + extends AbstractExpandableHeaderItem { + + private Subject subject; + + GradeHeaderItem(Subject subject) { + this.subject = subject; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradeHeaderItem that = (GradeHeaderItem) o; + + return new EqualsBuilder() + .append(subject, that.subject) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(subject) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grade_header; + } + + @Override + public HeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new HeaderViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { + holder.onBind(subject, getSubItems()); + } + + static class HeaderViewHolder extends ExpandableViewHolder { + + @BindView(R.id.grade_header_subject_text) + TextView subjectName; + + @BindView(R.id.grade_header_average_text) + TextView averageText; + + @BindView(R.id.grade_header_number_of_grade_text) + TextView numberText; + + @BindView(R.id.grade_header_alert_image) + View alertImage; + + Resources resources; + + HeaderViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + resources = view.getResources(); + view.setOnClickListener(this); + } + + void onBind(Subject item, List subItems) { + subjectName.setText(item.getName()); + numberText.setText(resources.getQuantityString(R.plurals.numberOfGradesPlurals, + subItems.size(), subItems.size())); + averageText.setText(getGradesAverageString(item)); + alertImage.setVisibility(isSubItemsRead(subItems) ? View.INVISIBLE : View.VISIBLE); + alertImage.setTag(item.getName()); + } + + private boolean isSubItemsRead(List subItems) { + boolean isRead = true; + + for (GradesSubItem item : subItems) { + isRead = item.getGrade().getRead(); + } + return isRead; + } + + private String getGradesAverageString(Subject item) { + float average = AverageCalculator.calculate(item.getGradeList()); + + if (average < 0) { + return resources.getString(R.string.info_no_average); + } else { + return resources.getString(R.string.info_average_grades, average); + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java deleted file mode 100644 index 36742fec..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesAdapter.java +++ /dev/null @@ -1,183 +0,0 @@ -package io.github.wulkanowy.ui.main.grades; - - -import android.app.Activity; -import android.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter; -import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; -import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder; -import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder; - -import java.lang.ref.WeakReference; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.utils.AverageCalculator; - -public class GradesAdapter extends ExpandableRecyclerViewAdapter { - - private static int numberOfNotReadGrade; - - private Activity activity; - - public GradesAdapter(List groups, Activity activity) { - super(groups); - this.activity = activity; - } - - @Override - public SubjectViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.subject_item, parent, false); - return new SubjectViewHolder(view, activity); - } - - @Override - public GradeViewHolder onCreateChildViewHolder(ViewGroup child, int viewType) { - View view = LayoutInflater.from(child.getContext()).inflate(R.layout.grade_item, child, false); - return new GradeViewHolder(view, activity); - } - - @Override - public void onBindGroupViewHolder(SubjectViewHolder holder, int flatPosition, ExpandableGroup group) { - holder.bind(group); - } - - @Override - public void onBindChildViewHolder(GradeViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { - holder.bind((Grade) group.getItems().get(childIndex)); - } - - public static class SubjectViewHolder extends GroupViewHolder { - - private WeakReference activity; - - private TextView subjectName; - - private TextView numberOfGrades; - - private TextView averageGrades; - - private ImageView subjectAlertNewGrades; - - public SubjectViewHolder(View itemView, Activity activity) { - super(itemView); - this.activity = new WeakReference<>(activity); - - subjectName = itemView.findViewById(R.id.subject_text); - numberOfGrades = itemView.findViewById(R.id.subject_number_of_grades); - subjectAlertNewGrades = itemView.findViewById(R.id.subject_new_grades_alert); - averageGrades = itemView.findViewById(R.id.subject_grades_average); - } - - @SuppressWarnings("unchecked") - public void bind(ExpandableGroup group) { - int volumeGrades = group.getItemCount(); - List gradeList = group.getItems(); - float average = AverageCalculator.calculate(gradeList); - - itemView.setTag(group.getTitle()); - - if (average < 0) { - averageGrades.setText(R.string.info_no_average); - } else { - averageGrades.setText(activity.get().getResources().getString(R.string.info_average_grades, average)); - } - subjectName.setText(group.getTitle()); - numberOfGrades.setText(activity.get().getResources().getQuantityString(R.plurals.numberOfGradesPlurals, volumeGrades, volumeGrades)); - - for (Grade grade : gradeList) { - if (!grade.getRead()) { - subjectAlertNewGrades.setVisibility(View.VISIBLE); - } else { - subjectAlertNewGrades.setVisibility(View.INVISIBLE); - } - } - } - } - - public static class GradeViewHolder extends ChildViewHolder { - - private WeakReference activity; - - private TextView gradeValue; - - private TextView descriptionGrade; - - private TextView dateGrade; - - private ImageView alertNewGrade; - - private View itemView; - - private Grade gradeItem; - - public GradeViewHolder(View itemView, Activity activity) { - super(itemView); - this.itemView = itemView; - this.activity = new WeakReference<>(activity); - - gradeValue = itemView.findViewById(R.id.grade_text); - descriptionGrade = itemView.findViewById(R.id.description_grade_text); - dateGrade = itemView.findViewById(R.id.grade_date_text); - alertNewGrade = itemView.findViewById(R.id.grade_new_grades_alert); - } - - public void bind(Grade grade) { - gradeValue.setText(grade.getValue()); - gradeValue.setBackgroundResource(grade.getValueColor()); - dateGrade.setText(grade.getDate()); - gradeItem = grade; - - if (grade.getDescription() == null || "".equals(grade.getDescription())) { - if (!"".equals(grade.getSymbol())) { - descriptionGrade.setText(grade.getSymbol()); - } else { - descriptionGrade.setText(R.string.noDescription_text); - } - } else { - 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.get().getFragmentManager(), gradeItem.toString()); - - if (!gradeItem.getRead()) { - numberOfNotReadGrade--; - } - - if (numberOfNotReadGrade == 0) { - View subjectView = activity.get().findViewById(R.id.subject_grade_recycler).findViewWithTag(gradeItem.getSubject()); - if (subjectView != null) { - 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); - } - }); - - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java new file mode 100644 index 00000000..7deabb5e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesContract.java @@ -0,0 +1,40 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.support.v4.widget.SwipeRefreshLayout; + +import java.util.List; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface GradesContract { + + interface View extends BaseContract.View, SwipeRefreshLayout.OnRefreshListener { + + void updateAdapterList(List headerItems); + + void showNoItem(boolean show); + + void onRefreshSuccessNoGrade(); + + void onRefreshSuccess(int number); + + void hideRefreshingBar(); + + void setActivityTitle(); + + boolean isMenuVisible(); + + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onFragmentVisible(boolean isVisible); + + void onRefresh(); + + void onStart(View view, OnFragmentIsReadyListener listener); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java index e566d5c6..0d9a699f 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesDialogFragment.java @@ -1,107 +1,116 @@ package io.github.wulkanowy.ui.main.grades; -import android.app.Dialog; -import android.app.DialogFragment; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.TextView; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.utils.CommonUtils; public class GradesDialogFragment extends DialogFragment { + private static final String ARGUMENT_KEY = "Item"; + private Grade grade; + @BindView(R.id.grade_dialog_value) + TextView value; + + @BindView(R.id.grade_dialog_subject) + TextView subject; + + @BindView(R.id.grade_dialog_description_value) + TextView description; + + @BindView(R.id.grade_dialog_weight_value) + TextView weight; + + @BindView(R.id.grade_dialog_teacher_value) + TextView teacher; + + @BindView(R.id.grade_dialog_color_value) + TextView color; + + @BindView(R.id.grade_dialog_date_value) + TextView date; + public GradesDialogFragment() { - setRetainInstance(true); + //empty constructor for fragment } - public static final GradesDialogFragment newInstance(Grade grade) { - return new GradesDialogFragment().setGrade(grade); + public static GradesDialogFragment newInstance(Grade item) { + GradesDialogFragment dialogFragment = new GradesDialogFragment(); + + Bundle bundle = new Bundle(); + bundle.putSerializable(ARGUMENT_KEY, item); + + dialogFragment.setArguments(bundle); + + return dialogFragment; } - public static int colorHexToColorName(String hexColor) { - switch (hexColor) { - case "000000": - return R.string.color_black_text; - - case "F04C4C": - return R.string.color_red_text; - - case "20A4F7": - return R.string.color_blue_text; - - case "6ECD07": - return R.string.color_green_text; - - default: - return R.string.noColor_text; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + grade = (Grade) getArguments().getSerializable(ARGUMENT_KEY); } } - private GradesDialogFragment setGrade(Grade grade) { - this.grade = grade; - return this; - } - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.grades_dialog, container, false); + View view = inflater.inflate(R.layout.grade_dialog, container, false); - TextView gradeText = view.findViewById(R.id.dialog_grade_text); - TextView subjectText = view.findViewById(R.id.subject_dialog_text_value); - TextView descriptionText = view.findViewById(R.id.description_dialog_text_value); - TextView weightText = view.findViewById(R.id.weight_dialog_text_value); - TextView teacherText = view.findViewById(R.id.teacher_dialog_text_value); - TextView dateText = view.findViewById(R.id.date_dialog_text_value); - TextView colorText = view.findViewById(R.id.color_dialog_text_value); - Button closeDialog = view.findViewById(R.id.close_dialog); + ButterKnife.bind(this, view); - subjectText.setText(grade.getSubject()); - gradeText.setText(grade.getValue()); - gradeText.setBackgroundResource(grade.getValueColor()); - weightText.setText(grade.getWeight()); - dateText.setText(grade.getDate()); - colorText.setText(colorHexToColorName(grade.getColor())); + subject.setText(grade.getSubject()); + value.setText(grade.getValue()); + value.setBackgroundResource(grade.getValueColor()); + weight.setText(grade.getWeight()); + date.setText(grade.getDate()); + color.setText(CommonUtils.colorHexToColorName(grade.getColor())); + teacher.setText(getTeacherString()); + description.setText(getDescriptionString()); - if ("".equals(grade.getDescription())) { - if (!"".equals(grade.getSymbol())) { - descriptionText.setText(grade.getSymbol()); - } - } else if (!"".equals(grade.getSymbol())) { - descriptionText.setText(String.format("%1$s - %2$s", grade.getSymbol(), grade.getDescription())); - } else { - descriptionText.setText(grade.getDescription()); - } - - if (!"".equals(grade.getTeacher())) { - teacherText.setText(grade.getTeacher()); - } - - closeDialog.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); return view; } - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); + @OnClick(R.id.grade_dialog_close_button) + void onClickClose() { + dismiss(); + } + + private String getDescriptionString() { + if ("".equals(grade.getDescription())) { + if (!"".equals(grade.getSymbol())) { + return grade.getSymbol(); + } else { + return getString(R.string.noDescription_text); + } + } else if (!"".equals(grade.getSymbol())) { + return String.format("%1$s - %2$s", grade.getSymbol(), grade.getDescription()); + } else { + return grade.getDescription(); + } + } + + private String getTeacherString() { + if (grade.getTeacher() != null && !"".equals(grade.getTeacher())) { + return grade.getTeacher(); + } else { + return getString(R.string.generic_app_no_data); } - super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java index 07483776..bd44f298 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesFragment.java @@ -1,211 +1,133 @@ package io.github.wulkanowy.ui.main.grades; -import android.app.Activity; -import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; import io.github.wulkanowy.R; -import io.github.wulkanowy.WulkanowyApp; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.VulcanOfflineException; -import io.github.wulkanowy.db.dao.DatabaseAccess; -import io.github.wulkanowy.db.dao.entities.Account; -import io.github.wulkanowy.db.dao.entities.AccountDao; -import io.github.wulkanowy.db.dao.entities.DaoSession; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.services.sync.LoginSession; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.utils.NetworkUtils; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -public class GradesFragment extends Fragment { +public class GradesFragment extends BaseFragment implements GradesContract.View { - private static List subjectWithGradesList = new ArrayList<>(); + @BindView(R.id.grade_fragment_recycler) + RecyclerView recyclerView; - private static long userId; + @BindView(R.id.grade_fragment_no_item_container) + View noItemView; + + @BindView(R.id.grade_fragment_swipe_refresh) + SwipeRefreshLayout refreshLayout; + + @Inject + FlexibleAdapter adapter; + + @Inject + GradesContract.Presenter presenter; public GradesFragment() { - //empty constructor for fragments + // empty constructor for fragment } - private static void createExpList(View mainView, Activity activity) { + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_grades, container, false); - RecyclerView recyclerView = mainView.findViewById(R.id.subject_grade_recycler); - recyclerView.setLayoutManager(new LinearLayoutManager(activity)); - GradesAdapter gradesAdapter = new GradesAdapter(subjectWithGradesList, activity); - recyclerView.setAdapter(gradesAdapter); + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } + + return view; } - private static void downloadGradesFormDatabase(DaoSession daoSession) { + @Override + protected void setUpOnViewCreated(View fragmentView) { + noItemView.setVisibility(View.GONE); - subjectWithGradesList = new ArrayList<>(); + adapter.setAutoCollapseOnExpand(true); + adapter.setAutoScrollOnExpand(true); + adapter.expandItemsAtStartUp(); - AccountDao accountDao = daoSession.getAccountDao(); - Account account = accountDao.load(userId); + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setAdapter(adapter); - for (Subject subject : account.getSubjectList()) { - List gradeList = subject.getGradeList(); - if (!gradeList.isEmpty()) { - SubjectWithGrades subjectWithGrades = new SubjectWithGrades(subject.getName(), gradeList); - subjectWithGradesList.add(subjectWithGrades); - } + refreshLayout.setColorSchemeResources(android.R.color.black); + refreshLayout.setOnRefreshListener(this); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); } } @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_grades, container, false); - view.findViewById(R.id.fragment_no_grades).setVisibility(View.GONE); + public void setActivityTitle() { + setTitle(getString(R.string.grades_text)); + } + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void showNoItem(boolean show) { + noItemView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void hideRefreshingBar() { + refreshLayout.setRefreshing(false); + } + + @Override + public void updateAdapterList(List headerItems) { + adapter.updateDataSet(headerItems); + } + + @Override + public void onRefreshSuccessNoGrade() { + onError(R.string.snackbar_no_grades); + } + + @Override + public void onRefreshSuccess(int number) { + onError(getString(R.string.snackbar_new_grade, number)); + } + + @Override + public void onError(String message) { if (getActivity() != null) { - DaoSession daoSession = ((WulkanowyApp) getActivity().getApplication()).getDaoSession(); - userId = getActivity().getSharedPreferences("LoginData", Context.MODE_PRIVATE) - .getLong("userId", 0); - - prepareRefreshLayout(view, daoSession); - - if (subjectWithGradesList.equals(new ArrayList<>())) { - createExpList(view, getActivity()); - new GenerateListTask(getActivity(), view, daoSession).execute(); - } else { - createExpList(view, getActivity()); - view.findViewById(R.id.loadingPanel).setVisibility(View.INVISIBLE); - } - } - return view; - } - - private void prepareRefreshLayout(final View mainView, final DaoSession daoSession) { - - final SwipeRefreshLayout swipeRefreshLayout = mainView.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 (NetworkUtils.isOnline(getContext())) { - new RefreshTask(getActivity(), mainView, daoSession).execute(); - } else { - Toast.makeText(mainView.getContext(), R.string.noInternet_text, Toast.LENGTH_SHORT).show(); - swipeRefreshLayout.setRefreshing(false); - } - } - }); - } - - private static class GenerateListTask extends AsyncTask { - - private WeakReference mainView; - - private WeakReference activity; - - private DaoSession daoSession; - - public GenerateListTask(Activity activity, View mainView, DaoSession daoSession) { - this.activity = new WeakReference<>(activity); - this.mainView = new WeakReference<>(mainView); - this.daoSession = daoSession; - } - - @Override - protected Void doInBackground(Void... params) { - downloadGradesFormDatabase(daoSession); - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - createExpList(mainView.get(), activity.get()); - mainView.get().findViewById(R.id.loadingPanel).setVisibility(View.INVISIBLE); - if (subjectWithGradesList.isEmpty()) { - mainView.get().findViewById(R.id.fragment_no_grades).setVisibility(View.VISIBLE); - } + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); } } - private static class RefreshTask extends AsyncTask { - - private DaoSession daoSession; - - private WeakReference activity; - - private WeakReference mainView; - - public RefreshTask(Activity activity, View mainView, DaoSession daoSession) { - this.activity = new WeakReference<>(activity); - this.daoSession = daoSession; - this.mainView = new WeakReference<>(mainView); - } - - @Override - protected Integer doInBackground(Void... params) { - VulcanSync vulcanSync = new VulcanSync(new LoginSession()); - try { - vulcanSync.loginCurrentUser(activity.get(), daoSession, new Vulcan()); - vulcanSync.syncGrades(); - downloadGradesFormDatabase(daoSession); - return 1; - } catch (VulcanOfflineException e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "There was a sync problem, because vulcan is offline", e); - return R.string.error_host_offline; - } catch (Exception e) { - Log.e(VulcanJobHelper.DEBUG_TAG, "There was a sync problem", e); - return R.string.refresh_error_text; - } - } - - @Override - protected void onPostExecute(Integer messageID) { - super.onPostExecute(messageID); - - if (1 == messageID) { - if (mainView.get() != null && activity.get() != null) { - createExpList(mainView.get(), activity.get()); - } - - int volumeGrades = new DatabaseAccess().getNewGrades(daoSession).size(); - - if (volumeGrades == 0) { - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - R.string.snackbar_no_grades, - Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(activity.get().findViewById(R.id.fragment_container), - activity.get().getString(R.string.snackbar_new_grade, volumeGrades), - Snackbar.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(activity.get(), messageID, Toast.LENGTH_SHORT).show(); - } - - if (mainView.get() != null) { - SwipeRefreshLayout swipeRefreshLayout = mainView.get().findViewById(R.id.grade_swipe_refresh); - swipeRefreshLayout.setRefreshing(false); - } - } + @Override + public void onDestroyView() { + presenter.onDestroy(); + super.onDestroyView(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java new file mode 100644 index 00000000..cae2fbcb --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesPresenter.java @@ -0,0 +1,157 @@ +package io.github.wulkanowy.ui.main.grades; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class GradesPresenter extends BasePresenter + implements GradesContract.Presenter, AsyncListeners.OnRefreshListener, + AsyncListeners.OnFirstLoadingListener { + + private AbstractTask refreshTask; + + private AbstractTask loadingTask; + + private OnFragmentIsReadyListener listener; + + private List headerItems = new ArrayList<>(); + + private boolean isFirstSight = false; + + @Inject + GradesPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(GradesContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + if (!isFirstSight) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } + + @Override + public void onRefresh() { + if (getView().isNetworkConnected()) { + refreshTask = new AbstractTask(); + refreshTask.setOnRefreshListener(this); + refreshTask.execute(); + } else { + getView().onNoNetworkError(); + getView().hideRefreshingBar(); + } + } + + @Override + public void onDoInBackgroundRefresh() throws Exception { + getRepository().loginCurrentUser(); + getRepository().syncSubjects(); + getRepository().syncGrades(); + } + + @Override + public void onCanceledRefreshAsync() { + getView().hideRefreshingBar(); + } + + @Override + public void onEndRefreshAsync(boolean success, Exception exception) { + if (success) { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + + int numberOfNewGrades = getRepository().getNewGrades().size(); + + if (numberOfNewGrades <= 0) { + getView().onRefreshSuccessNoGrade(); + } else { + getView().onRefreshSuccess(numberOfNewGrades); + } + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + } + getView().hideRefreshingBar(); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + List subjectList = getRepository().getCurrentUser().getSubjectList(); + + headerItems = new ArrayList<>(); + + for (Subject subject : subjectList) { + List gradeList = subject.getGradeList(); + + if (!gradeList.isEmpty()) { + GradeHeaderItem headerItem = new GradeHeaderItem(subject); + + List subItems = new ArrayList<>(); + + for (Grade grade : gradeList) { + subItems.add(new GradesSubItem(headerItem, grade)); + } + + headerItem.setSubItems(subItems); + headerItem.setExpanded(false); + headerItems.add(headerItem); + } + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (headerItems.isEmpty()) { + getView().showNoItem(true); + } else { + getView().updateAdapterList(headerItems); + getView().showNoItem(false); + listener.onFragmentIsReady(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (refreshTask != null) { + refreshTask.cancel(true); + refreshTask = null; + } + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java new file mode 100644 index 00000000..c252bb2a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grades/GradesSubItem.java @@ -0,0 +1,150 @@ +package io.github.wulkanowy.ui.main.grades; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.View; +import android.widget.TextView; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.viewholders.FlexibleViewHolder; +import io.github.wulkanowy.R; +import io.github.wulkanowy.data.db.dao.entities.Grade; + +public class GradesSubItem + extends AbstractSectionableItem { + + private Grade grade; + + private static int numberOfNotReadGrade; + + GradesSubItem(GradeHeaderItem header, Grade grade) { + super(header); + this.grade = grade; + } + + public Grade getGrade() { + return grade; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + GradesSubItem that = (GradesSubItem) o; + + return new EqualsBuilder() + .append(grade, that.grade) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(grade) + .toHashCode(); + } + + @Override + public int getLayoutRes() { + return R.layout.grade_subitem; + } + + @Override + public SubItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new SubItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, SubItemViewHolder holder, int position, List payloads) { + holder.onBind(grade); + } + + static class SubItemViewHolder extends FlexibleViewHolder { + + @BindView(R.id.grade_subitem_value) + TextView value; + + @BindView(R.id.grade_subitem_description) + TextView description; + + @BindView(R.id.grade_subitem_date) + TextView date; + + @BindView(R.id.grade_subitem_alert_image) + View alert; + + private Context context; + + private Grade item; + + SubItemViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter); + ButterKnife.bind(this, view); + context = view.getContext(); + view.setOnClickListener(this); + } + + void onBind(Grade item) { + this.item = item; + + value.setText(item.getValue()); + value.setBackgroundResource(item.getValueColor()); + date.setText(item.getDate()); + description.setText(getDescriptionString()); + alert.setVisibility(item.getRead() ? View.INVISIBLE : View.VISIBLE); + + if (!item.getRead()) { + numberOfNotReadGrade++; + } + } + + @Override + public void onClick(View view) { + super.onClick(view); + showDialog(); + + if (!item.getRead()) { + numberOfNotReadGrade--; + + if (numberOfNotReadGrade == 0) { + ((Activity) context).findViewById(R.id.grade_fragment_container) + .findViewWithTag(item.getSubject()).setVisibility(View.INVISIBLE); + } + item.setIsNew(false); + item.setRead(true); + item.update(); + alert.setVisibility(View.INVISIBLE); + } + } + + private String getDescriptionString() { + if (item.getDescription() == null || "".equals(item.getDescription())) { + if (!"".equals(item.getSymbol())) { + return item.getSymbol(); + } else { + return context.getString(R.string.noDescription_text); + } + } else { + return item.getDescription(); + } + } + + private void showDialog() { + GradesDialogFragment dialogFragment = GradesDialogFragment.newInstance(item); + dialogFragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0); + dialogFragment.show(((FragmentActivity) context).getSupportFragmentManager(), item.toString()); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java b/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java deleted file mode 100644 index b343c200..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grades/SubjectWithGrades.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.wulkanowy.ui.main.grades; - - -import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup; - -import java.util.List; - -import io.github.wulkanowy.db.dao.entities.Grade; - -public class SubjectWithGrades extends ExpandableGroup { - - public SubjectWithGrades(String title, List items) { - super(title, items); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java new file mode 100644 index 00000000..156d1759 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TabsData.java @@ -0,0 +1,37 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.support.v4.app.Fragment; + +import java.util.ArrayList; +import java.util.List; + +class TabsData { + + private List fragments = new ArrayList<>(); + + private List titles = new ArrayList<>(); + + Fragment getFragment(int index) { + return fragments.get(index); + } + + void addFragment(Fragment fragment) { + if (fragment != null) { + fragments.add(fragment); + } + } + + int getFragmentsCount() { + return fragments.size(); + } + + String getTitle(int index) { + return titles.get(index); + } + + void addTitle(String title) { + if (title != null) { + titles.add(title); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java new file mode 100644 index 00000000..18fa33f9 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableContract.java @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.main.timetable; + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; + +public interface TimetableContract { + + interface View extends BaseContract.View { + + void setActivityTitle(); + + void scrollViewPagerToPosition(int position); + + void setTabDataToAdapter(TabsData tabsData); + + void setAdapterWithTabLayout(); + + void setChildFragmentSelected(int position, boolean selected); + + boolean isMenuVisible(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + + void onFragmentVisible(boolean isVisible); + + void onTabSelected(int position); + + void onTabUnselected(int position); + + void onStart(View view, OnFragmentIsReadyListener listener); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java index 3845d26c..cfc1e073 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialogFragment.java @@ -1,6 +1,5 @@ package io.github.wulkanowy.ui.main.timetable; -import android.app.Dialog; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -8,93 +7,121 @@ import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.TextView; import org.apache.commons.lang3.StringUtils; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Lesson; public class TimetableDialogFragment extends DialogFragment { + private static final String ARGUMENT_KEY = "Item"; + private Lesson lesson; + @BindView(R.id.timetable_dialog_lesson_value) + TextView lessonName; + + @BindView(R.id.timetable_dialog_teacher_value) + TextView teacher; + + @BindView(R.id.timetable_dialog_group_value) + TextView group; + + @BindView(R.id.timetable_dialog_room_value) + TextView room; + + @BindView(R.id.timetable_dialog_time_value) + TextView time; + + @BindView(R.id.timetable_dialog_description_value) + TextView description; + + @BindView(R.id.timetable_dialog_description) + View descriptionLabel; + + @BindView(R.id.timetable_dialog_teacher) + View teacherLabel; + + @BindView(R.id.timetable_dialog_group) + View groupLabel; + public TimetableDialogFragment() { //empty constructor for fragment } public static TimetableDialogFragment newInstance(Lesson lesson) { - return new TimetableDialogFragment().setLesson(lesson); + TimetableDialogFragment dialogFragment = new TimetableDialogFragment(); + + Bundle bundle = new Bundle(); + bundle.putSerializable(ARGUMENT_KEY, lesson); + + dialogFragment.setArguments(bundle); + + return dialogFragment; } - private TimetableDialogFragment setLesson(Lesson lesson) { - this.lesson = lesson; - return this; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + lesson = (Lesson) getArguments().getSerializable(ARGUMENT_KEY); + } } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.timetable_dialog, container, false); - setRetainInstance(true); - TextView lessonText = view.findViewById(R.id.timetable_dialog_lesson_value); - TextView teacherText = view.findViewById(R.id.timetable_dialog_teacher_value); - TextView groupText = view.findViewById(R.id.timetable_dialog_group_value); - TextView roomText = view.findViewById(R.id.timetable_dialog_room_value); - TextView timeText = view.findViewById(R.id.timetable_dialog_time_value); - TextView descriptionText = view.findViewById(R.id.timetable_dialog_description_value); - Button closeButton = view.findViewById(R.id.timetable_dialog_close); + ButterKnife.bind(this, view); if (!lesson.getSubject().isEmpty()) { - lessonText.setText(lesson.getSubject()); + lessonName.setText(lesson.getSubject()); } if (!lesson.getTeacher().isEmpty()) { - teacherText.setText(lesson.getTeacher()); + teacher.setText(lesson.getTeacher()); } else { - teacherText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_teacher).setVisibility(View.GONE); + teacher.setVisibility(View.GONE); + teacherLabel.setVisibility(View.GONE); } if (!lesson.getGroupName().isEmpty()) { - groupText.setText(lesson.getGroupName()); + group.setText(lesson.getGroupName()); } else { - groupText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_group).setVisibility(View.GONE); + group.setVisibility(View.GONE); + groupLabel.setVisibility(View.GONE); } if (!lesson.getRoom().isEmpty()) { - roomText.setText(lesson.getRoom()); + room.setText(lesson.getRoom()); } if (!lesson.getEndTime().isEmpty() && !lesson.getStartTime().isEmpty()) { - timeText.setText(String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime())); + time.setText(getTimeString()); } if (!lesson.getDescription().isEmpty()) { - descriptionText.setText(StringUtils.capitalize(lesson.getDescription())); + description.setText(StringUtils.capitalize(lesson.getDescription())); } else { - descriptionText.setVisibility(View.GONE); - view.findViewById(R.id.timetable_dialog_description).setVisibility(View.GONE); + description.setVisibility(View.GONE); + descriptionLabel.setVisibility(View.GONE); } - closeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dismiss(); - } - }); return view; } - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); - } - super.onDestroyView(); + private String getTimeString() { + return String.format("%1$s - %2$s", lesson.getStartTime(), lesson.getEndTime()); + } + + @OnClick(R.id.timetable_dialog_close) + void onClickCloseButton() { + dismiss(); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java index ec25c7dd..a9a83391 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.java @@ -1,121 +1,114 @@ package io.github.wulkanowy.ui.main.timetable; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; - -import java.util.ArrayList; -import java.util.List; +import javax.inject.Inject; +import butterknife.BindView; +import butterknife.ButterKnife; import io.github.wulkanowy.R; -import io.github.wulkanowy.utils.TimeUtils; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; -public class TimetableFragment extends Fragment { +public class TimetableFragment extends BaseFragment implements TimetableContract.View, TabLayout.OnTabSelectedListener { - private final String DATE_PATTERN = "yyyy-MM-dd"; + @BindView(R.id.timetable_fragment_viewpager) + ViewPager viewPager; - private List dateStringList = new ArrayList<>(); + @BindView(R.id.timetable_fragment_tab_layout) + TabLayout tabLayout; - private TimetablePagerAdapter pagerAdapter; + @Inject + TimetablePagerAdapter pagerAdapter; - private ViewPager viewPager; - - private TabLayout tabLayout; - - public TimetableFragment() { - //empty constructor for fragment - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } + @Inject + TimetableContract.Presenter presenter; @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_timetable, container, false); - viewPager = view.findViewById(R.id.timetable_fragment_viewpager); - tabLayout = view.findViewById(R.id.timetable_fragment_tab); - new CreateTabTask(this).execute(); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + presenter.onStart(this, (OnFragmentIsReadyListener) getActivity()); + } return view; } - private TimetablePagerAdapter getPagerAdapter() { - TimetablePagerAdapter pagerAdapter = new TimetablePagerAdapter(getChildFragmentManager()); - for (String date : dateStringList) { - pagerAdapter.addFragment(TimetableFragmentTab.newInstance(date), date); + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null) { + presenter.onFragmentVisible(menuVisible); } - return pagerAdapter; } - private String getDateOfCurrentMonday() { - DateTime currentDate = new DateTime(); - - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { - currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { - currentDate = currentDate.plusDays(1); - } else { - currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); - } - return currentDate.toString(DATE_PATTERN); + @Override + public void onTabSelected(TabLayout.Tab tab) { + presenter.onTabSelected(tab.getPosition()); } - private void setAdapterOnViewPager() { + @Override + public void onTabUnselected(TabLayout.Tab tab) { + presenter.onTabUnselected(tab.getPosition()); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + //do nothing + } + + @Override + public void setTabDataToAdapter(TabsData tabsData) { + pagerAdapter.setTabsData(tabsData); + } + + @Override + public void setAdapterWithTabLayout() { viewPager.setAdapter(pagerAdapter); - viewPager.setCurrentItem(dateStringList.indexOf(getDateOfCurrentMonday())); - } - private void setDateStringList() { - if (dateStringList.isEmpty()) { - dateStringList = TimeUtils.getMondaysFromCurrentSchoolYear(DATE_PATTERN); - } - } - - private void setViewPagerOnTabLayout() { tabLayout.setupWithViewPager(viewPager); + tabLayout.addOnTabSelectedListener(this); } - protected final void setLoadingBarInvisible() { - if (getView() != null) { - getView().findViewById(R.id.timetable_tab_progress_bar).setVisibility(View.GONE); + @Override + public void setChildFragmentSelected(int position, boolean selected) { + ((TimetableTabFragment) pagerAdapter.getItem(position)).setSelected(selected); + } + + @Override + public void scrollViewPagerToPosition(int position) { + viewPager.setCurrentItem(position, false); + } + + @Override + public void setActivityTitle() { + setTitle(getString(R.string.lessonplan_text)); + } + + @Override + public void onError(String message) { + if (getActivity() != null) { + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); } } - private static class CreateTabTask extends AsyncTask { - - private TimetableFragment fragment; - - public CreateTabTask(TimetableFragment fragment) { - this.fragment = fragment; - } - - @Override - protected Void doInBackground(Void... voids) { - fragment.setDateStringList(); - fragment.pagerAdapter = fragment.getPagerAdapter(); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - fragment.setAdapterOnViewPager(); - fragment.setViewPagerOnTabLayout(); - fragment.setLoadingBarInvisible(); - } + @Override + public void onDestroyView() { + presenter.onDestroy(); + super.onDestroyView(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java deleted file mode 100644 index 12a38daa..00000000 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragmentTab.java +++ /dev/null @@ -1,161 +0,0 @@ -package io.github.wulkanowy.ui.main.timetable; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; -import android.support.v7.widget.RecyclerView; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeComparator; -import org.joda.time.DateTimeConstants; -import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; - -import java.util.ArrayList; -import java.util.List; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Day; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.Week; -import io.github.wulkanowy.db.dao.entities.WeekDao; -import io.github.wulkanowy.services.sync.VulcanSync; -import io.github.wulkanowy.ui.main.AbstractFragment; - -public class TimetableFragmentTab extends AbstractFragment { - - private final String DATE_PATTERN = "yyyy-MM-dd"; - - private int positionToScroll; - - private String date; - - public static TimetableFragmentTab newInstance(String date) { - TimetableFragmentTab fragmentTab = new TimetableFragmentTab(); - - Bundle argument = new Bundle(); - argument.putString("date", date); - fragmentTab.setArguments(argument); - - return fragmentTab; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - date = getArguments().getString("date"); - } - } - - @Override - public int getLayoutId() { - return R.layout.fragment_timetable_tab; - } - - @Override - public int getRecyclerViewId() { - return R.id.timetable_recycler; - } - - @Override - public int getRefreshLayoutId() { - return R.id.timetable_refresh_layout; - } - - @Override - public int getLoadingBarId() { - return R.id.timetable_progress_bar; - } - - @NonNull - @Override - public List getItems() throws Exception { - Week week = getWeek(); - - if (week == null) { - onRefresh(); - return getItems(); - } - - List dayEntityList = week.getDayList(); - - List dayList = new ArrayList<>(); - - int iterator = -1; - - for (Day day : dayEntityList) { - List timetableSubItems = new ArrayList<>(); - - TimetableHeaderItem headerItem = new TimetableHeaderItem(day); - - for (Lesson lesson : day.getLessons()) { - TimetableSubItem subItem = new TimetableSubItem(headerItem, lesson, getFragmentManager()); - timetableSubItems.add(subItem); - } - - iterator++; - - boolean isExpanded = getExpanded(day.getDate()); - - if (isExpanded) { - positionToScroll = iterator; - } - - headerItem.setExpanded(isExpanded); - headerItem.setSubItems(timetableSubItems); - dayList.add(headerItem); - } - return dayList; - } - - @Override - protected void setAdapterOnRecyclerView(@NonNull RecyclerView recyclerView) { - super.setAdapterOnRecyclerView(recyclerView); - recyclerView.scrollToPosition(positionToScroll); - } - - @Override - public void onRefresh() throws Exception { - VulcanSync synchronization = new VulcanSync(); - synchronization.loginCurrentUser(getContext(), getDaoSession()); - synchronization.syncTimetable(date); - } - - @Override - public void onPostRefresh(int stringResult) { - if (stringResult == 0) { - stringResult = R.string.timetable_refresh_success; - } - Snackbar.make(getActivityWeakReference().findViewById(R.id.fragment_container), - stringResult, Snackbar.LENGTH_SHORT).show(); - } - - private boolean getExpanded(String dayDate) { - DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(DATE_PATTERN); - DateTime dayTime = dateTimeFormatter.parseDateTime(dayDate); - - DateTime currentDate = new DateTime(); - - if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY) { - currentDate = currentDate.plusDays(2); - } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY) { - currentDate = currentDate.plusDays(1); - } - - return DateTimeComparator.getDateOnlyInstance().compare(currentDate, dayTime) == 0; - } - - private Week getWeek() { - if (date == null) { - LocalDate currentMonday = new LocalDate().withDayOfWeek(DateTimeConstants.MONDAY); - date = currentMonday.toString(DATE_PATTERN); - } - return getDaoSession().getWeekDao().queryBuilder() - .where(WeekDao.Properties.StartDayDate.eq(date), - WeekDao.Properties.UserId.eq(getUserId())) - .unique(); - } -} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java index e40c6689..c418d9ac 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableHeaderItem.java @@ -5,6 +5,8 @@ import android.widget.ImageView; import android.widget.TextView; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.List; @@ -14,20 +16,35 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem; import eu.davidea.viewholders.ExpandableViewHolder; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Day; public class TimetableHeaderItem extends AbstractExpandableHeaderItem { private Day day; - public TimetableHeaderItem(Day day) { + TimetableHeaderItem(Day day) { this.day = day; } @Override public boolean equals(Object o) { - return this == o; + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + TimetableHeaderItem that = (TimetableHeaderItem) o; + + return new EqualsBuilder() + .append(day, that.day) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(day) + .toHashCode(); } @Override @@ -42,45 +59,42 @@ public class TimetableHeaderItem @Override public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List payloads) { - holder.dayName.setText(StringUtils.capitalize(day.getDayName())); - holder.date.setText(day.getDate()); - - boolean alertActive = false; - - for (TimetableSubItem subItem : getSubItems()) { - if (subItem.getLesson().getIsMovedOrCanceled() || - subItem.getLesson().getIsNewMovedInOrChanged()) { - alertActive = true; - } - } - - if (alertActive) { - holder.alert.setVisibility(View.VISIBLE); - } else { - holder.alert.setVisibility(View.GONE); - } + holder.onBind(day, getSubItems()); } - public static class HeaderViewHolder extends ExpandableViewHolder { + static class HeaderViewHolder extends ExpandableViewHolder { - @BindView(R.id.timetable_header_dayName_text) - public TextView dayName; + @BindView(R.id.timetable_header_day) + TextView dayName; - @BindView(R.id.timetable_header_date_text) - public TextView date; + @BindView(R.id.timetable_header_date) + TextView date; @BindView(R.id.timetable_header_alert_image) - public ImageView alert; + ImageView alert; - public HeaderViewHolder(View view, FlexibleAdapter adapter) { + HeaderViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - toggleExpansion(); - } - }); + view.setOnClickListener(this); ButterKnife.bind(this, view); } + + void onBind(Day item, List subItems) { + dayName.setText(StringUtils.capitalize(item.getDayName())); + date.setText(item.getDate()); + alert.setVisibility(isSubItemNewMovedInOrChanged(subItems) ? View.VISIBLE : View.INVISIBLE); + } + + private boolean isSubItemNewMovedInOrChanged(List subItems) { + boolean isAlertActive = false; + + for (TimetableSubItem subItem : subItems) { + if (subItem.getLesson().getIsMovedOrCanceled() || + subItem.getLesson().getIsNewMovedInOrChanged()) { + isAlertActive = true; + } + } + return isAlertActive; + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java index 8e404417..24e7582d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePagerAdapter.java @@ -1,48 +1,36 @@ package io.github.wulkanowy.ui.main.timetable; -import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; -import java.util.ArrayList; -import java.util.List; - public class TimetablePagerAdapter extends FragmentStatePagerAdapter { - private List fragmentList = new ArrayList<>(); + private TabsData tabsData; - private List titleList = new ArrayList<>(); - - public TimetablePagerAdapter(FragmentManager fm) { - super(fm); + public TimetablePagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); } - public void addFragment(Fragment fragment, String title) { - fragmentList.add(fragment); - titleList.add(title); + void setTabsData(TabsData tabsData) { + this.tabsData = tabsData; } @Override public Fragment getItem(int position) { - return fragmentList.get(position); + return tabsData.getFragment(position); } @Override public int getCount() { - return fragmentList.size(); + return tabsData.getFragmentsCount(); } @Nullable @Override public CharSequence getPageTitle(int position) { - return titleList.get(position); - } - - @Override - public void restoreState(Parcelable state, ClassLoader loader) { - // do nothing + return tabsData.getTitle(position); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java new file mode 100644 index 00000000..bee323dd --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.java @@ -0,0 +1,107 @@ +package io.github.wulkanowy.ui.main.timetable; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.ui.main.OnFragmentIsReadyListener; +import io.github.wulkanowy.utils.TimeUtils; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class TimetablePresenter extends BasePresenter + implements TimetableContract.Presenter, AsyncListeners.OnFirstLoadingListener { + + private AbstractTask loadingTask; + + private List dates = new ArrayList<>(); + + private TabsData tabsData = new TabsData(); + + private OnFragmentIsReadyListener listener; + + private int positionToScroll; + + private boolean isFirstSight = false; + + @Inject + TimetablePresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(TimetableContract.View view, OnFragmentIsReadyListener listener) { + super.onStart(view); + this.listener = listener; + + if (getView().isMenuVisible()) { + getView().setActivityTitle(); + } + + if (dates.isEmpty()) { + dates = TimeUtils.getMondaysFromCurrentSchoolYear(); + } + positionToScroll = dates.indexOf(TimeUtils.getDateOfCurrentMonday(true)); + + if (!isFirstSight) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onFragmentVisible(boolean isVisible) { + if (isVisible) { + getView().setActivityTitle(); + } + } + + @Override + public void onTabSelected(int position) { + getView().setChildFragmentSelected(position, true); + } + + @Override + public void onTabUnselected(int position) { + getView().setChildFragmentSelected(position, false); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + for (String date : dates) { + tabsData.addTitle(date); + tabsData.addFragment(TimetableTabFragment.newInstance(date)); + } + } + + @Override + public void onCanceledLoadingAsync() { + //do nothing + + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + if (result) { + getView().setTabDataToAdapter(tabsData); + getView().setAdapterWithTabLayout(); + getView().scrollViewPagerToPosition(positionToScroll); + listener.onFragmentIsReady(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java index 025f5031..b4212940 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableSubItem.java @@ -1,8 +1,9 @@ package io.github.wulkanowy.ui.main.timetable; +import android.content.Context; import android.graphics.Paint; import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentActivity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -15,19 +16,17 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractSectionableItem; import eu.davidea.viewholders.FlexibleViewHolder; import io.github.wulkanowy.R; -import io.github.wulkanowy.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Lesson; -public class TimetableSubItem extends AbstractSectionableItem { +public class TimetableSubItem + extends AbstractSectionableItem { private Lesson lesson; - private FragmentManager fragmentManager; - - public TimetableSubItem(TimetableHeaderItem header, Lesson lesson, FragmentManager fragmentManager) { + public TimetableSubItem(TimetableHeaderItem header, Lesson lesson) { super(header); this.lesson = lesson; - this.fragmentManager = fragmentManager; } public Lesson getLesson() { @@ -51,63 +50,74 @@ public class TimetableSubItem extends AbstractSectionableItem headerItems); + + void onRefreshSuccess(); + + void hideRefreshingBar(); + + void showProgressBar(boolean show); + } + + interface Presenter extends BaseContract.Presenter { + + void onFragmentSelected(boolean isSelected); + + void setArgumentDate(String date); + + void onStart(View view, boolean isPrimary); + + void onRefresh(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java new file mode 100644 index 00000000..10f3163a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabFragment.java @@ -0,0 +1,145 @@ +package io.github.wulkanowy.ui.main.timetable; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager; +import io.github.wulkanowy.R; +import io.github.wulkanowy.di.component.FragmentComponent; +import io.github.wulkanowy.ui.base.BaseFragment; + +public class TimetableTabFragment extends BaseFragment implements TimetableTabContract.View, + SwipeRefreshLayout.OnRefreshListener { + + private static final String ARGUMENT_KEY = "Date"; + + private boolean isPrimary = false; + + private boolean isSelected = false; + + @BindView(R.id.timetable_tab_fragment_recycler) + RecyclerView recyclerView; + + @BindView(R.id.timetable_tab_fragment_swipe_refresh) + SwipeRefreshLayout refreshLayout; + + @BindView(R.id.timetable_tab_fragment_progress_bar) + View progressBar; + + @Inject + TimetableTabContract.Presenter presenter; + + @Inject + FlexibleAdapter adapter; + + public static TimetableTabFragment newInstance(String date) { + TimetableTabFragment fragmentTab = new TimetableTabFragment(); + + Bundle bundle = new Bundle(); + bundle.putString(ARGUMENT_KEY, date); + fragmentTab.setArguments(bundle); + + return fragmentTab; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_timetable_tab, container, false); + + FragmentComponent component = getFragmentComponent(); + if (component != null) { + component.inject(this); + setButterKnife(ButterKnife.bind(this, view)); + + if (getArguments() != null) { + presenter.setArgumentDate(getArguments().getString(ARGUMENT_KEY)); + } + + presenter.onStart(this, isPrimary); + } + return view; + } + + @Override + protected void setUpOnViewCreated(View fragmentView) { + adapter.setAutoCollapseOnExpand(true); + adapter.setAutoScrollOnExpand(true); + adapter.expandItemsAtStartUp(); + + recyclerView.setLayoutManager(new SmoothScrollLinearLayoutManager(fragmentView.getContext())); + recyclerView.setAdapter(adapter); + + refreshLayout.setColorSchemeResources(android.R.color.black); + refreshLayout.setOnRefreshListener(this); + + } + + @Override + public void updateAdapterList(List headerItems) { + adapter.updateDataSet(headerItems); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (presenter != null && getView() != null) { + presenter.onFragmentSelected(isSelected); + } else if (isSelected) { + isPrimary = true; + } + } + + @Override + public void onRefresh() { + presenter.onRefresh(); + } + + @Override + public void onRefreshSuccess() { + onError(R.string.timetable_refresh_success); + } + + @Override + public void hideRefreshingBar() { + refreshLayout.setRefreshing(false); + } + + @Override + public void showProgressBar(boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + @Override + public void onError(String message) { + if (getActivity() != null) { + Snackbar.make(getActivity().findViewById(R.id.main_activity_view_pager), + message, Snackbar.LENGTH_LONG).show(); + } + } + + @Override + public void onDestroyView() { + isPrimary = false; + presenter.onDestroy(); + super.onDestroyView(); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java new file mode 100644 index 00000000..5872b4d0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableTabPresenter.java @@ -0,0 +1,155 @@ +package io.github.wulkanowy.ui.main.timetable; + + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Week; +import io.github.wulkanowy.ui.base.BasePresenter; +import io.github.wulkanowy.utils.async.AbstractTask; +import io.github.wulkanowy.utils.async.AsyncListeners; + +public class TimetableTabPresenter extends BasePresenter + implements TimetableTabContract.Presenter, AsyncListeners.OnRefreshListener, + AsyncListeners.OnFirstLoadingListener { + + private AbstractTask refreshTask; + + private AbstractTask loadingTask; + + private List headerItems = new ArrayList<>(); + + private String date; + + private boolean isFirstSight = false; + + @Inject + TimetableTabPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(TimetableTabContract.View view, boolean isPrimary) { + super.onStart(view); + getView().showProgressBar(true); + onFragmentSelected(isPrimary); + } + + @Override + public void onFragmentSelected(boolean isSelected) { + if (!isFirstSight && isSelected) { + isFirstSight = true; + + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + } + } + + @Override + public void onRefresh() { + if (getView().isNetworkConnected()) { + refreshTask = new AbstractTask(); + refreshTask.setOnRefreshListener(this); + refreshTask.execute(); + } else { + getView().onNoNetworkError(); + getView().hideRefreshingBar(); + } + } + + @Override + public void onDoInBackgroundRefresh() throws Exception { + syncData(); + } + + @Override + public void onCanceledRefreshAsync() { + // do nothing + } + + @Override + public void onEndRefreshAsync(boolean result, Exception exception) { + if (result) { + loadingTask = new AbstractTask(); + loadingTask.setOnFirstLoadingListener(this); + loadingTask.execute(); + + getView().onRefreshSuccess(); + } else { + getView().onError(getRepository().getErrorLoginMessage(exception)); + } + getView().hideRefreshingBar(); + } + + @Override + public void onDoInBackgroundLoading() throws Exception { + Week week = getRepository().getWeek(date); + + if (week == null) { + syncData(); + week = getRepository().getWeek(date); + } + + List dayList = week.getDayList(); + + headerItems = new ArrayList<>(); + + for (Day day : dayList) { + TimetableHeaderItem headerItem = new TimetableHeaderItem(day); + + List lessonList = day.getLessons(); + + List subItems = new ArrayList<>(); + + for (Lesson lesson : lessonList) { + subItems.add(new TimetableSubItem(headerItem, lesson)); + } + + headerItem.setSubItems(subItems); + headerItem.setExpanded(false); + headerItems.add(headerItem); + } + } + + @Override + public void onCanceledLoadingAsync() { + // do nothing + } + + @Override + public void onEndLoadingAsync(boolean result, Exception exception) { + getView().updateAdapterList(headerItems); + getView().showProgressBar(false); + } + + @Override + public void setArgumentDate(String date) { + this.date = date; + } + + private void syncData() throws Exception { + getRepository().loginCurrentUser(); + getRepository().syncTimetable(date); + } + + @Override + public void onDestroy() { + super.onDestroy(); + isFirstSight = false; + + if (refreshTask != null) { + refreshTask.cancel(true); + refreshTask = null; + } + if (loadingTask != null) { + loadingTask.cancel(true); + loadingTask = null; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java index a4d39e06..c9f98d5e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.java @@ -1,47 +1,50 @@ package io.github.wulkanowy.ui.splash; -import android.content.Context; -import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.widget.TextView; -import io.github.wulkanowy.BuildConfig; -import io.github.wulkanowy.R; -import io.github.wulkanowy.services.jobs.FullSyncJob; +import javax.inject.Inject; + +import butterknife.ButterKnife; +import io.github.wulkanowy.services.SyncJob; +import io.github.wulkanowy.ui.base.BaseActivity; import io.github.wulkanowy.ui.login.LoginActivity; -import io.github.wulkanowy.ui.main.DashboardActivity; +import io.github.wulkanowy.ui.main.MainActivity; -public class SplashActivity extends AppCompatActivity { +public class SplashActivity extends BaseActivity implements SplashContract.View { + + @Inject + SplashContract.Presenter presenter; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_splash); - TextView versionName = findViewById(R.id.rawText); - versionName.setText(getString(R.string.version_text, BuildConfig.VERSION_NAME)); + getActivityComponent().inject(this); + setButterKnife(ButterKnife.bind(this)); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - executeOnRunApp(); - } - }, 500); + presenter.onStart(this); } - private void executeOnRunApp() { - Intent intent; + @Override + protected void onDestroy() { + presenter.onDestroy(); + super.onDestroy(); + } - if (getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0) == 0) { - intent = new Intent(this, LoginActivity.class); - } else { - new FullSyncJob().scheduledJob(getApplicationContext()); + @Override + public void openLoginActivity() { + startActivity(LoginActivity.getStartIntent(this)); + finish(); + } - intent = new Intent(this, DashboardActivity.class); - } + @Override + public void openMainActivity() { + startActivity(MainActivity.getStartIntent(this)); + finish(); + } - startActivity(intent); + @Override + public void startSyncService() { + SyncJob.start(getApplicationContext()); } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java new file mode 100644 index 00000000..a69deb03 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashContract.java @@ -0,0 +1,21 @@ +package io.github.wulkanowy.ui.splash; + + +import io.github.wulkanowy.di.annotations.PerActivity; +import io.github.wulkanowy.ui.base.BaseContract; + +public interface SplashContract { + + interface View extends BaseContract.View { + + void openLoginActivity(); + + void openMainActivity(); + + void startSyncService(); + } + + @PerActivity + interface Presenter extends BaseContract.Presenter { + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java new file mode 100644 index 00000000..c3d81215 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.java @@ -0,0 +1,29 @@ +package io.github.wulkanowy.ui.splash; + +import android.support.annotation.NonNull; + +import javax.inject.Inject; + +import io.github.wulkanowy.data.RepositoryContract; +import io.github.wulkanowy.ui.base.BasePresenter; + +public class SplashPresenter extends BasePresenter + implements SplashContract.Presenter { + + @Inject + SplashPresenter(RepositoryContract repository) { + super(repository); + } + + @Override + public void onStart(@NonNull SplashContract.View activity) { + super.onStart(activity); + getView().startSyncService(); + + if (getRepository().getCurrentUserId() == 0) { + getView().openLoginActivity(); + } else { + getView().openMainActivity(); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java new file mode 100644 index 00000000..27d76e75 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AppConstant.java @@ -0,0 +1,25 @@ +package io.github.wulkanowy.utils; + +public final class AppConstant { + + public static final String APP_NAME = "Wulkanowy"; + + public static final String DATABASE_NAME = "wulkanowy_db"; + + public static final String SHARED_PREFERENCES_NAME = "user_data"; + + + public static final String VULCAN_CREATE_ACCOUNT_URL = + "https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount"; + + public static final String VULCAN_FORGOT_PASS_URL = + "https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount"; + + public static final String DEFAULT_SYMBOL = "Default"; + + public static final String DATE_PATTERN = "yyyy-MM-dd"; + + private AppConstant() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java b/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java index 0d3326da..cc33e65a 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java +++ b/app/src/main/java/io/github/wulkanowy/utils/AverageCalculator.java @@ -2,7 +2,7 @@ package io.github.wulkanowy.utils; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; public final class AverageCalculator { diff --git a/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java new file mode 100644 index 00000000..9680817e --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/CommonUtils.java @@ -0,0 +1,40 @@ +package io.github.wulkanowy.utils; + +import android.content.Context; +import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; + +import io.github.wulkanowy.R; + +public final class CommonUtils { + + private CommonUtils() { + throw new IllegalStateException("Utility class"); + } + + public static void openInternalBrowserViewer(Context context, String url) { + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + builder.setToolbarColor(context.getResources().getColor(R.color.colorPrimary)); + CustomTabsIntent customTabsIntent = builder.build(); + customTabsIntent.launchUrl(context, Uri.parse(url)); + } + + public static int colorHexToColorName(String hexColor) { + switch (hexColor) { + case "000000": + return R.string.color_black_text; + + case "F04C4C": + return R.string.color_red_text; + + case "20A4F7": + return R.string.color_blue_text; + + case "6ECD07": + return R.string.color_green_text; + + default: + return R.string.noColor_text; + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java index fcede01d..99006d1a 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java +++ b/app/src/main/java/io/github/wulkanowy/utils/DataObjectConverter.java @@ -4,11 +4,11 @@ package io.github.wulkanowy.utils; import java.util.ArrayList; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Day; -import io.github.wulkanowy.db.dao.entities.Grade; -import io.github.wulkanowy.db.dao.entities.Lesson; -import io.github.wulkanowy.db.dao.entities.Subject; -import io.github.wulkanowy.db.dao.entities.Week; +import io.github.wulkanowy.data.db.dao.entities.Day; +import io.github.wulkanowy.data.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Lesson; +import io.github.wulkanowy.data.db.dao.entities.Subject; +import io.github.wulkanowy.data.db.dao.entities.Week; public final class DataObjectConverter { @@ -52,50 +52,54 @@ public final class DataObjectConverter { return gradeEntityList; } - public static Week weekToWeekEntitie(io.github.wulkanowy.api.timetable.Week week) { + public static Week weekToWeekEntity(io.github.wulkanowy.api.timetable.Week week) { return new Week().setStartDayDate(week.getStartDayDate()); } + public static Day dayToDayEntity(io.github.wulkanowy.api.timetable.Day day) { + return new Day() + .setDate(day.getDate()) + .setDayName(day.getDayName()) + .setFreeDay(day.isFreeDay()) + .setFreeDayName(day.getFreeDayName()); + } + public static List daysToDaysEntities(List dayList) { List dayEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.timetable.Day day : dayList) { - Day dayEntity = new Day() - .setDate(day.getDate()) - .setDayName(day.getDayName()) - .setFreeDay(day.isFreeDay()) - .setFreeDayName(day.getFreeDayName()); - - dayEntityList.add(dayEntity); + dayEntityList.add(dayToDayEntity(day)); } return dayEntityList; } + public static Lesson lessonToLessonEntity(io.github.wulkanowy.api.timetable.Lesson lesson) { + return new Lesson() + .setNumber(lesson.getNumber()) + .setSubject(lesson.getSubject()) + .setTeacher(lesson.getTeacher()) + .setRoom(lesson.getRoom()) + .setDescription(lesson.getDescription()) + .setGroupName(lesson.getGroupName()) + .setStartTime(lesson.getStartTime()) + .setEndTime(lesson.getEndTime()) + .setDate(lesson.getDate()) + .setEmpty(lesson.isEmpty()) + .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) + .setPlanning(lesson.isPlanning()) + .setRealized(lesson.isRealized()) + .setMovedOrCanceled(lesson.isMovedOrCanceled()) + .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); + } + public static List lessonsToLessonsEntities(List lessonList) { List lessonEntityList = new ArrayList<>(); for (io.github.wulkanowy.api.timetable.Lesson lesson : lessonList) { - Lesson lessonEntity = new Lesson() - .setNumber(lesson.getNumber()) - .setSubject(lesson.getSubject()) - .setTeacher(lesson.getTeacher()) - .setRoom(lesson.getRoom()) - .setDescription(lesson.getDescription()) - .setGroupName(lesson.getGroupName()) - .setStartTime(lesson.getStartTime()) - .setEndTime(lesson.getEndTime()) - .setDate(lesson.getDate()) - .setEmpty(lesson.isEmpty()) - .setDivisionIntoGroups(lesson.isDivisionIntoGroups()) - .setPlanning(lesson.isPlanning()) - .setRealized(lesson.isRealized()) - .setMovedOrCanceled(lesson.isMovedOrCanceled()) - .setNewMovedInOrChanged(lesson.isNewMovedInOrChanged()); - - lessonEntityList.add(lessonEntity); + lessonEntityList.add(lessonToLessonEntity(lesson)); } return lessonEntityList; } diff --git a/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java b/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java index 878db5c3..5681bad1 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java +++ b/app/src/main/java/io/github/wulkanowy/utils/EntitiesCompare.java @@ -5,7 +5,7 @@ import org.apache.commons.collections4.CollectionUtils; import java.util.ArrayList; import java.util.List; -import io.github.wulkanowy.db.dao.entities.Grade; +import io.github.wulkanowy.data.db.dao.entities.Grade; public final class EntitiesCompare { @@ -24,8 +24,8 @@ public final class EntitiesCompare { for (Grade grade : addedOrUpdatedGradeList) { if (!oldList.isEmpty()) { grade.setRead(false); - grade.setIsNew(true); } + grade.setIsNew(true); updatedList.add(grade); } diff --git a/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java b/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java new file mode 100644 index 00000000..f3b5b3ec --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/LogUtils.java @@ -0,0 +1,22 @@ +package io.github.wulkanowy.utils; + +import android.util.Log; + +public final class LogUtils { + + private LogUtils() { + throw new IllegalStateException("Utility class"); + } + + public static void debug(String message) { + Log.d(AppConstant.APP_NAME, message); + } + + public static void error(String message, Throwable throwable) { + Log.e(AppConstant.APP_NAME, message, throwable); + } + + public static void error(String message) { + Log.e(AppConstant.APP_NAME, message); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java index 70a8ec5f..717ed42a 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeUtils.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import static io.github.wulkanowy.utils.AppConstant.DATE_PATTERN; + public final class TimeUtils { private static final long TICKS_AT_EPOCH = 621355968000000000L; @@ -31,7 +33,7 @@ public final class TimeUtils { } public static long getNetTicks(String dateString) throws ParseException { - return getNetTicks(dateString, "dd.MM.yyyy"); + return getNetTicks(dateString, DATE_PATTERN); } public static long getNetTicks(String dateString, String dateFormat) throws ParseException { @@ -46,7 +48,7 @@ public final class TimeUtils { return new Date((netTicks - TICKS_AT_EPOCH) / TICKS_PER_MILLISECOND); } - public static List getMondaysFromCurrentSchoolYear(String dateFormat) { + public static List getMondaysFromCurrentSchoolYear() { LocalDate startDate = new LocalDate(getCurrentSchoolYear(), 9, 1); LocalDate endDate = new LocalDate(getCurrentSchoolYear() + 1, 8, 31); @@ -61,7 +63,7 @@ public final class TimeUtils { } while (startDate.isBefore(endDate)) { - dateList.add(startDate.toString(dateFormat)); + dateList.add(startDate.toString(DATE_PATTERN)); startDate = startDate.plusWeeks(1); } return dateList; @@ -71,4 +73,17 @@ public final class TimeUtils { DateTime dateTime = new DateTime(); return dateTime.getMonthOfYear() <= 8 ? dateTime.getYear() - 1 : dateTime.getYear(); } + + public static String getDateOfCurrentMonday(boolean normalize) { + DateTime currentDate = new DateTime(); + + if (currentDate.getDayOfWeek() == DateTimeConstants.SATURDAY && normalize) { + currentDate = currentDate.plusDays(2); + } else if (currentDate.getDayOfWeek() == DateTimeConstants.SUNDAY && normalize) { + currentDate = currentDate.plusDays(1); + } else { + currentDate = currentDate.withDayOfWeek(DateTimeConstants.MONDAY); + } + return currentDate.toString(DATE_PATTERN); + } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java new file mode 100644 index 00000000..5106d641 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/async/AbstractTask.java @@ -0,0 +1,63 @@ +package io.github.wulkanowy.utils.async; + +import android.os.AsyncTask; + +import io.github.wulkanowy.utils.LogUtils; + +public class AbstractTask extends AsyncTask { + + private Exception exception; + + private AsyncListeners.OnRefreshListener onRefreshListener; + + private AsyncListeners.OnFirstLoadingListener onFirstLoadingListener; + + public void setOnFirstLoadingListener(AsyncListeners.OnFirstLoadingListener onFirstLoadingListener) { + this.onFirstLoadingListener = onFirstLoadingListener; + } + + public void setOnRefreshListener(AsyncListeners.OnRefreshListener onRefreshListener) { + this.onRefreshListener = onRefreshListener; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onDoInBackgroundLoading(); + } else if (onRefreshListener != null) { + onRefreshListener.onDoInBackgroundRefresh(); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + return true; + } catch (Exception e) { + exception = e; + return false; + } + } + + @Override + protected void onCancelled() { + super.onCancelled(); + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onCanceledLoadingAsync(); + } else if (onRefreshListener != null) { + onRefreshListener.onCanceledRefreshAsync(); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (onFirstLoadingListener != null) { + onFirstLoadingListener.onEndLoadingAsync(result, exception); + } else if (onRefreshListener != null) { + onRefreshListener.onEndRefreshAsync(result, exception); + } else { + LogUtils.error("AbstractTask does not have a listener assigned"); + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java b/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java new file mode 100644 index 00000000..0d33c57c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/async/AsyncListeners.java @@ -0,0 +1,23 @@ +package io.github.wulkanowy.utils.async; + +public interface AsyncListeners { + + interface OnRefreshListener { + + void onDoInBackgroundRefresh() throws Exception; + + void onCanceledRefreshAsync(); + + void onEndRefreshAsync(boolean result, Exception exception); + + } + + interface OnFirstLoadingListener { + + void onDoInBackgroundLoading() throws Exception; + + void onCanceledLoadingAsync(); + + void onEndLoadingAsync(boolean result, Exception exception); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java b/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java deleted file mode 100644 index ca780875..00000000 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Safety.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.wulkanowy.utils.security; - -import android.content.Context; -import android.os.Build; -import android.util.Base64; - -import io.github.wulkanowy.utils.RootChecker; - -public class Safety extends Scrambler { - - public String encrypt(String email, String plainText, Context context) throws CryptoException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - loadKeyStore(); - generateNewKey(email, context); - return encryptString(email, plainText); - } else { - if (RootChecker.isRooted()) { - return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); - } else { - throw new UnsupportedOperationException("Stored data in this devices isn't safe because android is rooted"); - } - } - } - - public String decrypt(String email, String encryptedText) throws CryptoException { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - loadKeyStore(); - return decryptString(email, encryptedText); - } else { - return new String(Base64.decode(encryptedText, Base64.DEFAULT)); - } - - } -} diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java index 62ffbd72..93da1d0b 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.java @@ -8,7 +8,6 @@ import android.security.KeyPairGeneratorSpec; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.util.Base64; -import android.util.Log; import org.apache.commons.lang3.ArrayUtils; @@ -27,29 +26,57 @@ import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.security.auth.x500.X500Principal; -public class Scrambler { +import io.github.wulkanowy.utils.LogUtils; +import io.github.wulkanowy.utils.RootChecker; - public static final String DEBUG_TAG = "WulkanowySecurity"; +public final class Scrambler { private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - private KeyStore keyStore; + private static KeyStore keyStore; - protected void loadKeyStore() throws CryptoException { + private Scrambler() { + throw new IllegalStateException("Utility class"); + } + public static String encrypt(String email, String plainText, Context context) + throws CryptoException { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + loadKeyStore(); + generateNewKey(email, context); + return encryptString(email, plainText); + } else { + if (RootChecker.isRooted()) { + return new String(Base64.encode(plainText.getBytes(), Base64.DEFAULT)); + } else { + throw new UnsupportedOperationException("Stored data in this devices " + + "isn't safe because android is rooted"); + } + } + } + + public static String decrypt(String email, String encryptedText) throws CryptoException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + loadKeyStore(); + return decryptString(email, encryptedText); + } else { + return new String(Base64.decode(encryptedText, Base64.DEFAULT)); + } + } + + private static void loadKeyStore() throws CryptoException { try { keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); keyStore.load(null); } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } - } @SuppressWarnings("deprecation") @TargetApi(18) - protected void generateNewKey(String alias, Context context) throws CryptoException { + private static void generateNewKey(String alias, Context context) throws CryptoException { Calendar start = Calendar.getInstance(); Calendar end = Calendar.getInstance(); @@ -61,7 +88,8 @@ public class Scrambler { try { if (!keyStore.containsAlias(alias)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + spec = new KeyGenParameterSpec.Builder(alias, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setDigests(KeyProperties.DIGEST_SHA256) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) @@ -79,28 +107,23 @@ public class Scrambler { .build(); } - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", ANDROID_KEYSTORE); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", + ANDROID_KEYSTORE); keyPairGenerator.initialize(spec); keyPairGenerator.generateKeyPair(); - - } else { - Log.w(DEBUG_TAG, "GenerateNewKey - " + alias + " is exist"); } } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "GenerateNewKey - String is empty"); throw new CryptoException("GenerateNewKey - String is empty"); } - - Log.d(DEBUG_TAG, "Key pair are create"); + LogUtils.debug("Key pair are create"); } - protected String encryptString(String alias, String text) throws CryptoException { + private static String encryptString(String alias, String text) throws CryptoException { if (!alias.isEmpty() && !text.isEmpty()) { try { @@ -121,16 +144,14 @@ public class Scrambler { return Base64.encodeToString(values, Base64.DEFAULT); } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "EncryptString - String is empty"); throw new CryptoException("EncryptString - String is empty"); } } - protected String decryptString(String alias, String text) throws CryptoException { + private static String decryptString(String alias, String text) throws CryptoException { if (!alias.isEmpty() && !text.isEmpty()) { try { @@ -153,15 +174,11 @@ public class Scrambler { Byte[] bytes = values.toArray(new Byte[values.size()]); return new String(ArrayUtils.toPrimitive(bytes), 0, bytes.length, "UTF-8"); - } catch (Exception e) { - Log.e(DEBUG_TAG, e.getMessage()); throw new CryptoException(e.getMessage()); } } else { - Log.e(DEBUG_TAG, "EncryptString - String is empty"); throw new CryptoException("EncryptString - String is empty"); - } } } diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml deleted file mode 100644 index 5a609d65..00000000 --- a/app/src/main/res/drawable/ic_arrow_down.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_logo_splash.png b/app/src/main/res/drawable/ic_logo_splash.png deleted file mode 100644 index cb62c0e5..00000000 Binary files a/app/src/main/res/drawable/ic_logo_splash.png and /dev/null differ diff --git a/app/src/main/res/drawable/icon_grade_26dp.xml b/app/src/main/res/drawable/icon_grade_26dp.xml index 68964c33..13f24206 100644 --- a/app/src/main/res/drawable/icon_grade_26dp.xml +++ b/app/src/main/res/drawable/icon_grade_26dp.xml @@ -5,6 +5,6 @@ android:viewportWidth="26" android:viewportHeight="26"> \ No newline at end of file diff --git a/app/src/main/res/drawable/img_splash_512px.png b/app/src/main/res/drawable/img_splash_512px.png new file mode 100644 index 00000000..9d8f403c Binary files /dev/null and b/app/src/main/res/drawable/img_splash_512px.png differ diff --git a/app/src/main/res/drawable/splash_background.xml b/app/src/main/res/drawable/splash_background.xml new file mode 100644 index 00000000..0a7c4523 --- /dev/null +++ b/app/src/main/res/drawable/splash_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml deleted file mode 100644 index 0bb788bd..00000000 --- a/app/src/main/res/layout/activity_dashboard.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 86e67f94..67095982 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,152 +1,137 @@ + tools:context="io.github.wulkanowy.ui.login.LoginActivity"> + android:layout_height="wrap_content"> + android:orientation="vertical"> + android:text="@string/login_heading" /> + android:layout_marginBottom="12dp"> + android:maxLines="1" /> + android:layout_height="wrap_content"> + android:inputType="textPassword" + android:maxLines="1" /> + android:visibility="gone"> + android:inputType="textAutoComplete" + android:maxLines="1" />