From a1b27a733ae036c54e12cdb162aca19e28168ba3 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 4 Apr 2016 19:02:01 +0200 Subject: [PATCH 01/52] added the booting up of an emulator & running of connected tests --- circle.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index e77ab07..0837b9a 100644 --- a/circle.yml +++ b/circle.yml @@ -10,6 +10,13 @@ dependencies: - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.2" test: + pre: + - emulator -avd circleci-android22 -no-audio -no-window: + background: true + parallel: true + - circle-android wait-for-boot override: - ./gradlew assembleDebug - - cp -r app/build/outputs $CIRCLE_ARTIFACTS \ No newline at end of file + - cp -r app/build/outputs $CIRCLE_ARTIFACTS + - ./gradlew connectedCheck + - cp -r app/build/test-results/prodDebug $CIRCLE_TEST_REPORTS \ No newline at end of file From 5851012ef564599b8ad86fb41c4c291958fc0cdc Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 4 Apr 2016 19:33:47 +0200 Subject: [PATCH 02/52] only running mock debug test --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 0837b9a..3910fbd 100644 --- a/circle.yml +++ b/circle.yml @@ -18,5 +18,5 @@ test: override: - ./gradlew assembleDebug - cp -r app/build/outputs $CIRCLE_ARTIFACTS - - ./gradlew connectedCheck + - ./gradlew connectedMockDebugAndroidTest - cp -r app/build/test-results/prodDebug $CIRCLE_TEST_REPORTS \ No newline at end of file From 289253299ecf2001e3e41f83c9ab02afec0b166c Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 5 Apr 2016 17:30:56 +0200 Subject: [PATCH 03/52] Unlock android device --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 3910fbd..ca5255f 100644 --- a/circle.yml +++ b/circle.yml @@ -18,5 +18,6 @@ test: override: - ./gradlew assembleDebug - cp -r app/build/outputs $CIRCLE_ARTIFACTS + - adb shell input keyevent 82 - ./gradlew connectedMockDebugAndroidTest - cp -r app/build/test-results/prodDebug $CIRCLE_TEST_REPORTS \ No newline at end of file From 47912118c896510f0c85eac03527c952e9efcb06 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 5 Apr 2016 18:46:41 +0200 Subject: [PATCH 04/52] Archiving different folder for androidTest results. --- .../listbooks/OverflowMenuOptionsTest.java | 12 ------------ circle.yml | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java b/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java index 6907aa5..cbbc87a 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java @@ -79,18 +79,6 @@ public void aboutMenuClick_ShowAboutBookDashScreen() { } - @Test - public void rateThisAppClick_ShowPlayStoreDetail() { - //Given - //When - selectNavDrawItem(R.id.action_rate_app); - //Then - intended(allOf(hasAction(Intent.ACTION_VIEW), - hasData(Uri.parse("market://details?id=" + org.bookdash.android.BuildConfig.APPLICATION_ID)) - ) - ); - } - @Test public void contributorsClicked_ShowThanksPopover() { diff --git a/circle.yml b/circle.yml index ca5255f..d8d01cb 100644 --- a/circle.yml +++ b/circle.yml @@ -20,4 +20,4 @@ test: - cp -r app/build/outputs $CIRCLE_ARTIFACTS - adb shell input keyevent 82 - ./gradlew connectedMockDebugAndroidTest - - cp -r app/build/test-results/prodDebug $CIRCLE_TEST_REPORTS \ No newline at end of file + - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS \ No newline at end of file From fdd2f4389d6b5f277a04a7e4beab557b036b0c7a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 5 Apr 2016 19:10:19 +0200 Subject: [PATCH 05/52] Added running of more tests and archiving their results --- circle.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index d8d01cb..f053485 100644 --- a/circle.yml +++ b/circle.yml @@ -20,4 +20,8 @@ test: - cp -r app/build/outputs $CIRCLE_ARTIFACTS - adb shell input keyevent 82 - ./gradlew connectedMockDebugAndroidTest - - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS \ No newline at end of file + - ./gradlew connectedProdDebugAndroidTest + - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS + - ./gradlew test + - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS + - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS \ No newline at end of file From 7651dbdc8846bad04bbbd300defcf9eba96418a0 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 5 Apr 2016 19:40:19 +0200 Subject: [PATCH 06/52] Added cache of directories --- circle.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index f053485..a123c99 100644 --- a/circle.yml +++ b/circle.yml @@ -3,6 +3,9 @@ machine: ANDROID_HOME: /usr/local/android-sdk-linux dependencies: + cache_directories: + - ~/.android + - ~/android pre: - touch app/google-services.json - echo $GOOGLE_SERVICES_JSON > app/google-services.json @@ -19,8 +22,7 @@ test: - ./gradlew assembleDebug - cp -r app/build/outputs $CIRCLE_ARTIFACTS - adb shell input keyevent 82 - - ./gradlew connectedMockDebugAndroidTest - - ./gradlew connectedProdDebugAndroidTest + - ./gradlew connectedAndroidTest - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS - ./gradlew test - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS From 7eafb6a381a07dc4eaceb37df82eb40eadd0ffda Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 8 Apr 2016 16:47:40 +0200 Subject: [PATCH 07/52] Archiving the coverage report too. --- circle.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index a123c99..3863734 100644 --- a/circle.yml +++ b/circle.yml @@ -5,7 +5,6 @@ machine: dependencies: cache_directories: - ~/.android - - ~/android pre: - touch app/google-services.json - echo $GOOGLE_SERVICES_JSON > app/google-services.json @@ -22,8 +21,10 @@ test: - ./gradlew assembleDebug - cp -r app/build/outputs $CIRCLE_ARTIFACTS - adb shell input keyevent 82 - - ./gradlew connectedAndroidTest + - ./gradlew createMockDebugCoverageReport + - ./gradlew createProdDebugCoverageReport - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS + - cp -r app/build/reports/coverage/ $CIRCLE_TEST_REPORTS - ./gradlew test - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS \ No newline at end of file From 6846c35f5f20b76c27df70504563f873d5b99ad7 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 8 Apr 2016 17:13:53 +0200 Subject: [PATCH 08/52] Added uploading code coverage reports to codecov --- circle.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 3863734..6d56c98 100644 --- a/circle.yml +++ b/circle.yml @@ -10,6 +10,7 @@ dependencies: - echo $GOOGLE_SERVICES_JSON > app/google-services.json - echo y | android update sdk --no-ui --all --filter "tools,platform-tools,android-23" - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.2" + - pip install codecov test: pre: @@ -17,14 +18,17 @@ test: background: true parallel: true - circle-android wait-for-boot + override: - - ./gradlew assembleDebug - - cp -r app/build/outputs $CIRCLE_ARTIFACTS - adb shell input keyevent 82 - ./gradlew createMockDebugCoverageReport - ./gradlew createProdDebugCoverageReport + - ./gradlew test + + post: + - cp -r app/build/outputs $CIRCLE_ARTIFACTS - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS - cp -r app/build/reports/coverage/ $CIRCLE_TEST_REPORTS - - ./gradlew test - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS - - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS \ No newline at end of file + - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS + - codecov --token=$CODE_COV_TOKEN From a02f172f0890d9d4c3bfcebf8f41a9e1d59713bf Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 13:01:25 +0200 Subject: [PATCH 09/52] Using coveralls to upload test coverage. --- circle.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 6d56c98..62078f5 100644 --- a/circle.yml +++ b/circle.yml @@ -10,7 +10,6 @@ dependencies: - echo $GOOGLE_SERVICES_JSON > app/google-services.json - echo y | android update sdk --no-ui --all --filter "tools,platform-tools,android-23" - echo y | android update sdk --no-ui --all --filter "build-tools-23.0.2" - - pip install codecov test: pre: @@ -31,4 +30,4 @@ test: - cp -r app/build/reports/coverage/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS - - codecov --token=$CODE_COV_TOKEN + - coveralls From 7b57711a28ddf2bd8ca67cb20b6621ebf50b9acd Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 13:42:12 +0200 Subject: [PATCH 10/52] Disabling animations on the emulator to produce more reliable test results. --- circle.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 62078f5..0228a6c 100644 --- a/circle.yml +++ b/circle.yml @@ -13,10 +13,13 @@ dependencies: test: pre: - - emulator -avd circleci-android22 -no-audio -no-window: + - emulator -avd circleci-android22 -no-audio -no-window -no-boot-anim: background: true parallel: true - circle-android wait-for-boot + - adb shell settings put global window_animation_scale 0 + - adb shell settings put global transition_animation_scale 0 + - adb shell settings put global animator_duration_scale 0 override: - adb shell input keyevent 82 From 9a6857ccba156127dcf71c74181cef3fc1140572 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 14:30:02 +0200 Subject: [PATCH 11/52] Using a new emulator to run tests as the old emulator didn't have google apis. Also added keep awake option --- circle.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 0228a6c..634bf3b 100644 --- a/circle.yml +++ b/circle.yml @@ -13,16 +13,18 @@ dependencies: test: pre: - - emulator -avd circleci-android22 -no-audio -no-window -no-boot-anim: + - android create avd -n google-sysimg-22 -t sys-img-armeabi-v7a-addon-google_apis-google-22 + - emulator -avd google-sysimg-22 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot + - adb shell input keyevent 82 + - adb shell svc power stayon true - adb shell settings put global window_animation_scale 0 - adb shell settings put global transition_animation_scale 0 - adb shell settings put global animator_duration_scale 0 override: - - adb shell input keyevent 82 - ./gradlew createMockDebugCoverageReport - ./gradlew createProdDebugCoverageReport - ./gradlew test From 5c24bd4cdba4f0651d448ef270683ccbb8cfeced Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 14:37:18 +0200 Subject: [PATCH 12/52] specifying the target using text not name. --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 634bf3b..41b3eb4 100644 --- a/circle.yml +++ b/circle.yml @@ -13,7 +13,7 @@ dependencies: test: pre: - - android create avd -n google-sysimg-22 -t sys-img-armeabi-v7a-addon-google_apis-google-22 + - android create avd -n google-sysimg-22 -t "Google Inc.:Google APIs:22" - emulator -avd google-sysimg-22 -no-audio -no-window : background: true parallel: true From a6ab5cde7e013770172ba7a9470a903ed4f9fd4b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 14:41:42 +0200 Subject: [PATCH 13/52] list android targets --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 41b3eb4..cff82c6 100644 --- a/circle.yml +++ b/circle.yml @@ -13,6 +13,7 @@ dependencies: test: pre: + - android list targets - android create avd -n google-sysimg-22 -t "Google Inc.:Google APIs:22" - emulator -avd google-sysimg-22 -no-audio -no-window : background: true From a6f7d7bef04245eb02ce6f2090a6d03a5254f5e6 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 14:46:15 +0200 Subject: [PATCH 14/52] Adding target name without brackets --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index cff82c6..1e84f5a 100644 --- a/circle.yml +++ b/circle.yml @@ -14,7 +14,7 @@ dependencies: test: pre: - android list targets - - android create avd -n google-sysimg-22 -t "Google Inc.:Google APIs:22" + - android create avd -n google-sysimg-22 -t Google Inc.:Google APIs:22 - emulator -avd google-sysimg-22 -no-audio -no-window : background: true parallel: true From a070277d3e70829a4ee752368b6695cf45118b21 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 14:49:21 +0200 Subject: [PATCH 15/52] replacing with correct target id. --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 1e84f5a..7f06619 100644 --- a/circle.yml +++ b/circle.yml @@ -14,7 +14,7 @@ dependencies: test: pre: - android list targets - - android create avd -n google-sysimg-22 -t Google Inc.:Google APIs:22 + - android create avd -n google-sysimg-22 -t 12 - emulator -avd google-sysimg-22 -no-audio -no-window : background: true parallel: true From ea561fada4c0384e52aa310b698e1d0f68cb5eb6 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 15:16:17 +0200 Subject: [PATCH 16/52] name with no dashes in it for building. --- circle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 7f06619..6113fcd 100644 --- a/circle.yml +++ b/circle.yml @@ -14,8 +14,8 @@ dependencies: test: pre: - android list targets - - android create avd -n google-sysimg-22 -t 12 - - emulator -avd google-sysimg-22 -no-audio -no-window : + - android create avd -n emulatorwithgoogleapi22 -t 12 + - emulator -avd emulatorwithgoogleapi22 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot From f51eb9cb5302cbd68b97cac5f410e79c78d5aac4 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 15:35:31 +0200 Subject: [PATCH 17/52] added tag attribute --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 6113fcd..6ed7ae0 100644 --- a/circle.yml +++ b/circle.yml @@ -14,7 +14,7 @@ dependencies: test: pre: - android list targets - - android create avd -n emulatorwithgoogleapi22 -t 12 + - android create avd -n emulatorwithgoogleapi22 -t 12 --tag google_apis/armeabi-v7a - emulator -avd emulatorwithgoogleapi22 -no-audio -no-window : background: true parallel: true From 6e0e1092a2fb6b11f1a490f3ed658c5f7a230484 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 15:38:04 +0200 Subject: [PATCH 18/52] Only using google_apis as tag --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 6ed7ae0..04e74ec 100644 --- a/circle.yml +++ b/circle.yml @@ -14,7 +14,7 @@ dependencies: test: pre: - android list targets - - android create avd -n emulatorwithgoogleapi22 -t 12 --tag google_apis/armeabi-v7a + - android create avd -n emulatorwithgoogleapi22 -t 12 --tag google_apis - emulator -avd emulatorwithgoogleapi22 -no-audio -no-window : background: true parallel: true From c13b4aab0285696bd92e9cf263bae67b6e93d101 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 16:23:33 +0200 Subject: [PATCH 19/52] switched to using api 21 --- circle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 04e74ec..b1e390b 100644 --- a/circle.yml +++ b/circle.yml @@ -14,8 +14,8 @@ dependencies: test: pre: - android list targets - - android create avd -n emulatorwithgoogleapi22 -t 12 --tag google_apis - - emulator -avd emulatorwithgoogleapi22 -no-audio -no-window : + - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis + - emulator -avd emulatorwithgoogleapi21 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot From 701581cf207f02768391401abfcc14906043e9ae Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 17:42:21 +0200 Subject: [PATCH 20/52] removed cache directories --- circle.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/circle.yml b/circle.yml index b1e390b..1d4c988 100644 --- a/circle.yml +++ b/circle.yml @@ -3,8 +3,6 @@ machine: ANDROID_HOME: /usr/local/android-sdk-linux dependencies: - cache_directories: - - ~/.android pre: - touch app/google-services.json - echo $GOOGLE_SERVICES_JSON > app/google-services.json From c4839e2e9b0a7abe7c5cd99dad2bec726b010447 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sat, 9 Apr 2016 18:54:00 +0200 Subject: [PATCH 21/52] Added a custom test runner to support running of Espresso tests - disabling the keyguard, the fact that the phone goes into sleep mode and disabling animations. --- app/build.gradle | 48 ++++++--- .../presentation/CustomTestRunner.java | 99 +++++++++++++++++++ app/src/mock/AndroidManifest.xml | 14 +++ build.gradle | 2 +- circle.yml | 2 - set_animation_permissions.sh | 24 +++++ 6 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java create mode 100644 app/src/mock/AndroidManifest.xml create mode 100644 set_animation_permissions.sh diff --git a/app/build.gradle b/app/build.gradle index e583099..56b0105 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ repositories { android { - dataBinding { enabled = true} + dataBinding { enabled = true } compileSdkVersion 23 buildToolsVersion "23.0.2" @@ -28,7 +28,7 @@ android { targetSdkVersion 23 versionCode 17 versionName "1.0.17" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "org.bookdash.android.presentation.CustomTestRunner" buildConfigField "String", "PARSE_APPLICATION_ID", "\"${BOOKDASH_PARSE_APP_ID}\"" buildConfigField "String", "PARSE_CLIENT_KEY", "\"${BOOKDASH_PARSE_CLIENT_ID}\"" @@ -74,7 +74,7 @@ android { } // Remove mockRelease as it's not needed. android.variantFilter { variant -> - if(variant.buildType.name.equals('release') + if (variant.buildType.name.equals('release') && variant.getFlavors().get(0).name.equals('mock')) { variant.setIgnore(true); } @@ -90,7 +90,7 @@ android { release { shrinkResources false minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' ext.enableCrashlytics = true } debug { @@ -110,37 +110,37 @@ dependencies { testCompile "junit:junit:$rootProject.ext.junitVersion" testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion" - testCompile ("org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito") - testCompile ("org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito") + testCompile("org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito") + testCompile("org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito") // Android Testing Support Library's runner and rules - androidTestCompile ('com.android.support.test.espresso:espresso-web:2.2.1'){ + androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.1') { exclude module: 'support-annotations' exclude module: 'support-v4' } - androidTestCompile ("com.android.support.test:runner:$rootProject.ext.runnerVersion"){ + androidTestCompile("com.android.support.test:runner:$rootProject.ext.runnerVersion") { exclude module: 'support-annotations' exclude module: 'support-v4' } - androidTestCompile ("com.android.support.test:rules:$rootProject.ext.runnerVersion"){ + androidTestCompile("com.android.support.test:rules:$rootProject.ext.runnerVersion") { exclude module: 'support-annotations' exclude module: 'support-v4' } // Espresso UI Testing - androidTestCompile ("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion"){ + androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion") { exclude module: 'recyclerview-v7' exclude module: 'support-annotations' exclude module: 'support-v4' exclude group: "javax.inject" } - androidTestCompile ("com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion") { + androidTestCompile("com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion") { exclude module: 'recyclerview-v7' exclude module: 'support-annotations' exclude module: 'support-v4' } - androidTestCompile ("com.android.support.test.espresso:espresso-intents:$rootProject.ext.espressoVersion"){ + androidTestCompile("com.android.support.test.espresso:espresso-intents:$rootProject.ext.espressoVersion") { exclude module: 'recyclerview-v7' exclude module: 'support-annotations' exclude module: 'support-v4' @@ -159,8 +159,8 @@ dependencies { compile 'com.google.code.gson:gson:2.4' compile 'com.github.castorflex.smoothprogressbar:library-circular:1.2.0' - // compile 'com.parse.bolts:bolts-android:1.3.0' - // compile 'com.parse:parse-android:1.11.0' + // compile 'com.parse.bolts:bolts-android:1.3.0' + // compile 'com.parse:parse-android:1.11.0' compile('com.crashlytics.sdk.android:crashlytics:2.5.3@aar') { transitive = true; } @@ -170,3 +170,23 @@ dependencies { compile "com.android.support:design:$rootProject.ext.googlePlayServicesVersion" compile "com.google.android.gms:play-services-analytics:$rootProject.ext.googlePlayServicesVersion" } + +// Grant animation permissions to avoid test failure because of ui sync. +task grantAnimationPermissions(type: Exec, dependsOn: 'installMock') { + group = 'test' + description = 'Grant permissions for testing.' + + def absolutePath = file('..') // Get project absolute path + commandLine "$absolutePath/set_animation_permissions.sh org.bookdash.android".split(" ") +} + +// Source: http://stackoverflow.com/q/29908110/112705 +afterEvaluate { + // When launching individual tests from Android Studio, it seems that only the assemble tasks + // get called directly, not the install* versions + tasks.each { task -> + if (task.name.startsWith('assembleMockAndroidTest')) { + task.dependsOn grantAnimationPermissions + } + } +} diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java b/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java new file mode 100644 index 0000000..c594dc2 --- /dev/null +++ b/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java @@ -0,0 +1,99 @@ +package org.bookdash.android.presentation; + +import android.Manifest; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PowerManager; +import android.support.test.runner.AndroidJUnitRunner; +import android.util.Log; + +import java.lang.reflect.Method; + +/** + * Tests can fail for other reasons than code, it´ because of the animations and espresso sync and + * emulator state (screen off or locked) + *

+ * Before all the tests prepare the device to run tests and avoid these problems. + *

+ * - Disable animations + * - Disable keyguard lock + * - Set it to be awake all the time (dont let the processor sleep) + * + * @see https://github.com/JakeWharton/u2020 + * @see https://gist.github.com/daj/7b48f1b8a92abf960e7b + * @see https://code.google.com/p/android-test-kit/wiki/DisablingAnimations + */ +public final class CustomTestRunner extends AndroidJUnitRunner { + + @Override + public void onStart() { + + runOnMainSync(new Runnable() { + @Override + public void run() { + Context app = CustomTestRunner.this.getTargetContext().getApplicationContext(); + + CustomTestRunner.this.disableAnimations(app); + + String name = CustomTestRunner.class.getSimpleName(); + // Unlock the device so that the tests can input keystrokes. + KeyguardManager keyguard = (KeyguardManager) app.getSystemService(Context.KEYGUARD_SERVICE); + keyguard.newKeyguardLock(name).disableKeyguard(); + // Wake up the screen. + PowerManager power = (PowerManager) app.getSystemService(Context.POWER_SERVICE); + power.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name) + .acquire(); + } + }); + + super.onStart(); + } + + @Override + public void finish(int resultCode, Bundle results) { + super.finish(resultCode, results); + enableAnimations(getContext()); + } + + // + void disableAnimations(Context context) { + int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE); + if (permStatus == PackageManager.PERMISSION_GRANTED) { + setSystemAnimationsScale(0.0f); + } + } + + void enableAnimations(Context context) { + int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE); + if (permStatus == PackageManager.PERMISSION_GRANTED) { + setSystemAnimationsScale(1.0f); + } + } + + private void setSystemAnimationsScale(float animationScale) { + try { + Class windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub"); + Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class); + Class serviceManagerClazz = Class.forName("android.os.ServiceManager"); + Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class); + Class windowManagerClazz = Class.forName("android.view.IWindowManager"); + Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class); + Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales"); + + IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window"); + Object windowManagerObj = asInterface.invoke(null, windowManagerBinder); + float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj); + for (int i = 0; i < currentScales.length; i++) { + currentScales[i] = animationScale; + } + setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales}); + Log.d("SystemAnimation", "changed permissions"); + } catch (Exception e) { + Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'("); + } + } + // +} diff --git a/app/src/mock/AndroidManifest.xml b/app/src/mock/AndroidManifest.xml new file mode 100644 index 0000000..878af0a --- /dev/null +++ b/app/src/mock/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1a0d55f..a69117c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0-beta7' + classpath 'com.android.tools.build:gradle:2.1.0-alpha5' classpath 'com.google.gms:google-services:1.5.0-beta2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' diff --git a/circle.yml b/circle.yml index 1d4c988..ec4a376 100644 --- a/circle.yml +++ b/circle.yml @@ -25,7 +25,6 @@ test: override: - ./gradlew createMockDebugCoverageReport - - ./gradlew createProdDebugCoverageReport - ./gradlew test post: @@ -34,4 +33,3 @@ test: - cp -r app/build/reports/coverage/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS - - coveralls diff --git a/set_animation_permissions.sh b/set_animation_permissions.sh new file mode 100644 index 0000000..75212fc --- /dev/null +++ b/set_animation_permissions.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# source https://github.com/zielmicha/adb-wrapper +# +# argument: apk package +# Set permission android.permission.SET_ANIMATION_SCALE for each device. +# ex: sh set_animation_permissions.sh +# + +adb=$ANDROID_HOME/platform-tools/adb +package=$1 + +if [ "$#" = 0 ]; then + echo "No parameters found, run with sh set_animation_permissions.sh " + exit 0 +fi + +# get all the devices +devices=$($adb devices | grep -v 'List of devices' | cut -f1 | grep '.') + +for device in $devices; do + echo "Setting permissions to device" $device "for package" $package + $adb -s $device shell pm grant $package android.permission.SET_ANIMATION_SCALE +done \ No newline at end of file From 481fd63239bb2d545b78c9096798ee38c607cddc Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 11:35:42 +0200 Subject: [PATCH 22/52] Code Cleanup and adding coveralls. --- app/build.gradle | 1 + .../presentation/CustomTestRunner.java | 29 +++++++++++-------- app/src/mock/AndroidManifest.xml | 11 +++---- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 56b0105..45ea173 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,6 +33,7 @@ android { buildConfigField "String", "PARSE_CLIENT_KEY", "\"${BOOKDASH_PARSE_CLIENT_ID}\"" } + File signFile = rootProject.file('release-keystore.properties') if (signFile.exists()) { signingConfigs { diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java b/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java index c594dc2..053fcf7 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java @@ -24,10 +24,11 @@ * * @see https://github.com/JakeWharton/u2020 * @see https://gist.github.com/daj/7b48f1b8a92abf960e7b - * @see https://code.google.com/p/android-test-kit/wiki/DisablingAnimations */ public final class CustomTestRunner extends AndroidJUnitRunner { + private static final String TAG = "CustomTestRunner"; + @Override public void onStart() { @@ -39,26 +40,31 @@ public void run() { CustomTestRunner.this.disableAnimations(app); String name = CustomTestRunner.class.getSimpleName(); - // Unlock the device so that the tests can input keystrokes. - KeyguardManager keyguard = (KeyguardManager) app.getSystemService(Context.KEYGUARD_SERVICE); - keyguard.newKeyguardLock(name).disableKeyguard(); - // Wake up the screen. - PowerManager power = (PowerManager) app.getSystemService(Context.POWER_SERVICE); - power.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name) - .acquire(); + unlockScreen(app, name); + keepSceenAwake(app, name); } }); super.onStart(); } + @Override public void finish(int resultCode, Bundle results) { super.finish(resultCode, results); enableAnimations(getContext()); } + private void keepSceenAwake(Context app, String name) { + PowerManager power = (PowerManager) app.getSystemService(Context.POWER_SERVICE); + power.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, name) + .acquire(); + } + + private void unlockScreen(Context app, String name) { + KeyguardManager keyguard = (KeyguardManager) app.getSystemService(Context.KEYGUARD_SERVICE); + keyguard.newKeyguardLock(name).disableKeyguard(); + } - // void disableAnimations(Context context) { int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE); if (permStatus == PackageManager.PERMISSION_GRANTED) { @@ -90,10 +96,9 @@ private void setSystemAnimationsScale(float animationScale) { currentScales[i] = animationScale; } setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales}); - Log.d("SystemAnimation", "changed permissions"); + Log.d(TAG, "Changed permissions of animations"); } catch (Exception e) { - Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'("); + Log.e(TAG, "Could not change animation scale to " + animationScale + " :'("); } } - // } diff --git a/app/src/mock/AndroidManifest.xml b/app/src/mock/AndroidManifest.xml index 878af0a..4bf6091 100644 --- a/app/src/mock/AndroidManifest.xml +++ b/app/src/mock/AndroidManifest.xml @@ -4,11 +4,12 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="org.bookdash.android"> + + + - - - + tests. Adding this permission to the manifest is not sufficient - you must also grant the + permission over adb! --> + \ No newline at end of file From 89d785bf55b6189605c8ef0ac17ce9d7cf542164 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 14:00:14 +0200 Subject: [PATCH 23/52] giving the emulator more memory --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index ec4a376..f4da4ac 100644 --- a/circle.yml +++ b/circle.yml @@ -13,7 +13,7 @@ test: pre: - android list targets - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis - - emulator -avd emulatorwithgoogleapi21 -no-audio -no-window : + - emulator -memory 1024 -avd emulatorwithgoogleapi21 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot From 09314849df76a418a298c569216f687867465e50 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 14:48:39 +0200 Subject: [PATCH 24/52] outputting the values of the .ini file for the emulator. --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index f4da4ac..3e92a2d 100644 --- a/circle.yml +++ b/circle.yml @@ -13,6 +13,7 @@ test: pre: - android list targets - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis + - cat ~/.android/avd/emulatorwithgoogleapi21.ini - emulator -memory 1024 -avd emulatorwithgoogleapi21 -no-audio -no-window : background: true parallel: true @@ -32,4 +33,3 @@ test: - cp -r app/build/outputs/androidTest-results/ $CIRCLE_TEST_REPORTS - cp -r app/build/reports/coverage/ $CIRCLE_TEST_REPORTS - cp -r app/build/test-results/mockDebug/ $CIRCLE_TEST_REPORTS - - cp -r app/build/test-results/prodDebug/ $CIRCLE_TEST_REPORTS From 4998bbd523b24d831b2e9ba08f51fbdd4a28c450 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 14:53:46 +0200 Subject: [PATCH 25/52] Added heapSize and ramSize to emulator config. --- circle.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/circle.yml b/circle.yml index 3e92a2d..f8788cf 100644 --- a/circle.yml +++ b/circle.yml @@ -13,6 +13,8 @@ test: pre: - android list targets - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis + - echo 'vm.heapSize=128' >> ~/.android/avd/emulatorwithgoogleapi21.ini + - echo 'hw.ramSize=1024' >> ~/.android/avd/emulatorwithgoogleapi21.ini - cat ~/.android/avd/emulatorwithgoogleapi21.ini - emulator -memory 1024 -avd emulatorwithgoogleapi21 -no-audio -no-window : background: true From 6c2cdc6a02def5a138331b9d1a746a9555975fc5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 15:16:47 +0200 Subject: [PATCH 26/52] up'ed emulator memory to 512 --- circle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index f8788cf..c6398dd 100644 --- a/circle.yml +++ b/circle.yml @@ -13,10 +13,10 @@ test: pre: - android list targets - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis - - echo 'vm.heapSize=128' >> ~/.android/avd/emulatorwithgoogleapi21.ini + - echo 'vm.heapSize=512' >> ~/.android/avd/emulatorwithgoogleapi21.ini - echo 'hw.ramSize=1024' >> ~/.android/avd/emulatorwithgoogleapi21.ini - cat ~/.android/avd/emulatorwithgoogleapi21.ini - - emulator -memory 1024 -avd emulatorwithgoogleapi21 -no-audio -no-window : + - emulator -avd emulatorwithgoogleapi21 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot From da551ed86783f92462c6e47e9bd274e26fb4ba39 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Sun, 10 Apr 2016 16:16:30 +0200 Subject: [PATCH 27/52] switching to using a version 22 of emulator --- circle.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/circle.yml b/circle.yml index c6398dd..2980b90 100644 --- a/circle.yml +++ b/circle.yml @@ -12,11 +12,11 @@ dependencies: test: pre: - android list targets - - android create avd -n emulatorwithgoogleapi21 -t 11 --tag google_apis - - echo 'vm.heapSize=512' >> ~/.android/avd/emulatorwithgoogleapi21.ini - - echo 'hw.ramSize=1024' >> ~/.android/avd/emulatorwithgoogleapi21.ini - - cat ~/.android/avd/emulatorwithgoogleapi21.ini - - emulator -avd emulatorwithgoogleapi21 -no-audio -no-window : + - android create avd -n emulatorwithgoogleapi22 -t 12 --tag google_apis + - echo 'vm.heapSize=512' >> ~/.android/avd/emulatorwithgoogleapi22.ini + - echo 'hw.ramSize=1024' >> ~/.android/avd/emulatorwithgoogleapi22.ini + - cat ~/.android/avd/emulatorwithgoogleapi22.ini + - emulator -avd emulatorwithgoogleapi22 -no-audio -no-window : background: true parallel: true - circle-android wait-for-boot From a34f4520fd50ad0e46836d4e217ea8e9f48ce08b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 20 Apr 2016 19:44:11 +0200 Subject: [PATCH 28/52] Split out the fragment for a list of books and the downloads section. --- app/build.gradle | 1 + .../listbooks/OverflowMenuOptionsTest.java | 13 +- app/src/main/AndroidManifest.xml | 121 ++++++----- .../android/data/books/BookDetailApi.java | 2 + .../android/data/books/BookDetailApiImpl.java | 41 +++- .../data/books/BookDetailRepository.java | 5 +- .../data/books/BookDetailRepositoryImpl.java | 31 +-- .../downloads/DownloadsAdapter.java | 53 +++++ .../downloads/DownloadsContract.java | 28 +++ .../downloads/DownloadsFragment.java | 93 ++++++++ .../downloads/DownloadsPresenter.java | 38 ++++ .../downloads/DownloadsViewHolder.java | 24 +++ .../listbooks/ListBooksContract.java | 7 +- .../listbooks/ListBooksFragment.java | 159 ++++++++++++++ .../listbooks/ListBooksPresenter.java | 14 +- .../MainActivity.java} | 200 +++--------------- .../presentation/main/MainContract.java | 25 +++ .../presentation/main/MainPresenter.java | 30 +++ .../presentation/splash/SplashActivity.java | 5 +- app/src/main/res/drawable/ic_delete.xml | 9 + app/src/main/res/layout/activity_main.xml | 106 +--------- .../main/res/layout/fragment_downloads.xml | 108 ++++++++++ .../main/res/layout/fragment_list_books.xml | 114 ++++++++++ .../main/res/layout/list_item_download.xml | 57 +++++ .../downloads/DownloadsPresenterTest.java | 72 +++++++ 25 files changed, 990 insertions(+), 366 deletions(-) create mode 100644 app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsContract.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsPresenter.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java rename app/src/main/java/org/bookdash/android/presentation/{listbooks/ListBooksActivity.java => main/MainActivity.java} (56%) create mode 100644 app/src/main/java/org/bookdash/android/presentation/main/MainContract.java create mode 100644 app/src/main/java/org/bookdash/android/presentation/main/MainPresenter.java create mode 100644 app/src/main/res/drawable/ic_delete.xml create mode 100644 app/src/main/res/layout/fragment_downloads.xml create mode 100644 app/src/main/res/layout/fragment_list_books.xml create mode 100644 app/src/main/res/layout/list_item_download.xml create mode 100644 app/src/test/java/org/bookdash/android/presentation/downloads/DownloadsPresenterTest.java diff --git a/app/build.gradle b/app/build.gradle index 45ea173..84afa26 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,6 +11,7 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' apply plugin: 'io.fabric' apply plugin: 'com.github.triplet.play' + repositories { maven { url 'https://maven.fabric.io/public' } } diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java b/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java index cbbc87a..33ffc26 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/listbooks/OverflowMenuOptionsTest.java @@ -1,20 +1,13 @@ package org.bookdash.android.presentation.listbooks; -import android.content.Intent; -import android.net.Uri; import android.support.design.widget.NavigationView; -import android.support.test.InstrumentationRegistry; import android.support.test.espresso.intent.Intents; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; -import android.view.MenuItem; -import org.bookdash.android.BuildConfig; import org.bookdash.android.R; import org.bookdash.android.presentation.about.AboutActivity; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -22,13 +15,11 @@ import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasData; import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.RootMatchers.*; import static android.support.test.espresso.matcher.ViewMatchers.isClickable; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; @@ -49,8 +40,8 @@ @LargeTest public class OverflowMenuOptionsTest { @Rule - public ActivityTestRule mActivityTestRule = - new ActivityTestRule<>(ListBooksActivity.class); + public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(ListBooksFragment.class); @Before diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8df01d..c93b397 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,38 +1,39 @@ - + - - - + + + - - - - + + + + + - + + + - - + android:value=".presentation.listbooks.ListBooksFragment"/> + - - - + + + + - - - - + + + + - + android:pathPrefix="/books" + android:scheme="http"/> + android:parentActivityName=".presentation.listbooks.ListBooksFragment"> + android:value=".presentation.listbooks.ListBooksFragment"/> - - - + + + + + - - + + - + - - + + - + - + - - - + + + - + + android:value="@integer/google_play_services_version"/> + android:value="b2579d751611dc3b58788bad80a51ad37e140ecb"/> diff --git a/app/src/main/java/org/bookdash/android/data/books/BookDetailApi.java b/app/src/main/java/org/bookdash/android/data/books/BookDetailApi.java index 2ad0f8c..d1825b2 100644 --- a/app/src/main/java/org/bookdash/android/data/books/BookDetailApi.java +++ b/app/src/main/java/org/bookdash/android/data/books/BookDetailApi.java @@ -16,6 +16,8 @@ public interface BookDetailApi { void getBooksForLanguages(String language, BookServiceCallback> bookServiceCallback); + void getDownloadedBooks(BookServiceCallback> bookServiceCallback); + interface BookServiceCallback { void onLoaded(T result); diff --git a/app/src/main/java/org/bookdash/android/data/books/BookDetailApiImpl.java b/app/src/main/java/org/bookdash/android/data/books/BookDetailApiImpl.java index a7ee645..9a82bb5 100644 --- a/app/src/main/java/org/bookdash/android/data/books/BookDetailApiImpl.java +++ b/app/src/main/java/org/bookdash/android/data/books/BookDetailApiImpl.java @@ -12,19 +12,20 @@ import com.parse.ProgressCallback; import org.bookdash.android.BookDashApplication; +import org.bookdash.android.data.utils.FileManager; +import org.bookdash.android.data.utils.ZipManager; import org.bookdash.android.domain.pojo.Book; import org.bookdash.android.domain.pojo.BookContributor; import org.bookdash.android.domain.pojo.BookDetail; import org.bookdash.android.domain.pojo.Language; import org.bookdash.android.domain.pojo.gson.BookPages; -import org.bookdash.android.data.utils.FileManager; -import org.bookdash.android.data.utils.ZipManager; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Executor; @@ -65,6 +66,38 @@ public void done(List list, ParseException e) { }); } + private List filterOnlyDownloadedBooks(List bookDetails) { + List bookDetailsDownloaded = new ArrayList<>(); + + for (BookDetail b : bookDetails) { + if (b.isDownloadedAlready() || b.isDownloading()) { + bookDetailsDownloaded.add(b); + } + } + return bookDetailsDownloaded; + } + + @Override + public void getDownloadedBooks(final BookServiceCallback> bookServiceCallback) { + + ParseQuery queryBookDetail = ParseQuery.getQuery(BookDetail.class); + queryBookDetail.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE); + queryBookDetail.include(BookDetail.BOOK_LANGUAGE_COL); + queryBookDetail.include(BookDetail.BOOK_ID_COL); + queryBookDetail.whereEqualTo(BookDetail.BOOK_ENABLED_COL, true); + queryBookDetail.addDescendingOrder(BookDetail.CREATED_AT_COL); + queryBookDetail.findInBackground(new FindCallback() { + @Override + public void done(List list, ParseException e) { + if (e != null) { + bookServiceCallback.onError(e); + return; + } + bookServiceCallback.onLoaded(filterOnlyDownloadedBooks(list)); + } + }); + } + @Override public void getBookDetail(String bookDetailId, final BookServiceCallback bookServiceCallback) { ParseQuery queryBookDetail = ParseQuery.getQuery(BookDetail.class); @@ -127,9 +160,9 @@ public void done(List list, ParseException e) { @Override public void downloadBook(final BookDetail bookInfo, @NonNull final BookServiceCallback downloadBookCallback, @NonNull final BookServiceProgressCallback progressCallback) { - if (bookInfo.isDownloadedAlready()){ + if (bookInfo.isDownloadedAlready()) { progressCallback.onProgressChanged(100); - downloadBookCallback.onLoaded(getBookPages(bookInfo.getFolderLocation(BookDashApplication.FILES_DIR) + File.separator + BookDetail.BOOK_INFO_FILE_NAME )); + downloadBookCallback.onLoaded(getBookPages(bookInfo.getFolderLocation(BookDashApplication.FILES_DIR) + File.separator + BookDetail.BOOK_INFO_FILE_NAME)); return; } bookInfo.getBookFile().getDataInBackground(new GetDataCallback() { diff --git a/app/src/main/java/org/bookdash/android/data/books/BookDetailRepository.java b/app/src/main/java/org/bookdash/android/data/books/BookDetailRepository.java index 3717b1a..7842bc9 100644 --- a/app/src/main/java/org/bookdash/android/data/books/BookDetailRepository.java +++ b/app/src/main/java/org/bookdash/android/data/books/BookDetailRepository.java @@ -16,7 +16,10 @@ */ public interface BookDetailRepository { - void getBooksForLanguage(String language, boolean downloadedOnly, @NonNull GetBooksForLanguageCallback booksForLanguageCallback); + + void getBooksForLanguage(@NonNull String language, @NonNull GetBooksForLanguageCallback booksForLanguageCallback); + + void getDownloadedBooks(GetBooksForLanguageCallback getBooksForLanguageCallback); interface GetBooksForLanguageCallback { void onBooksLoaded(List books); diff --git a/app/src/main/java/org/bookdash/android/data/books/BookDetailRepositoryImpl.java b/app/src/main/java/org/bookdash/android/data/books/BookDetailRepositoryImpl.java index 6bd32d6..c7c6182 100644 --- a/app/src/main/java/org/bookdash/android/data/books/BookDetailRepositoryImpl.java +++ b/app/src/main/java/org/bookdash/android/data/books/BookDetailRepositoryImpl.java @@ -8,7 +8,6 @@ import org.bookdash.android.domain.pojo.Language; import org.bookdash.android.domain.pojo.gson.BookPages; -import java.util.ArrayList; import java.util.List; /** @@ -26,16 +25,12 @@ public BookDetailRepositoryImpl(@NonNull BookDetailApi bookDetailApi) { } @Override - public void getBooksForLanguage(@NonNull String language, final boolean downloadedOnly, @NonNull final GetBooksForLanguageCallback booksForLanguageCallback) { + public void getBooksForLanguage(@NonNull String language, @NonNull final GetBooksForLanguageCallback booksForLanguageCallback) { bookDetailApi.getBooksForLanguages(language, new BookDetailApi.BookServiceCallback>() { @Override public void onLoaded(List result) { - if (downloadedOnly) { - booksForLanguageCallback.onBooksLoaded(filterOnlyDownloadedBooks(result)); - } else { - booksForLanguageCallback.onBooksLoaded(result); - } + booksForLanguageCallback.onBooksLoaded(result); } @Override @@ -45,16 +40,24 @@ public void onError(Exception error) { }); } - private List filterOnlyDownloadedBooks(List bookDetails){ - List bookDetailsDownloaded = new ArrayList<>(); + @Override + public void getDownloadedBooks(final GetBooksForLanguageCallback getBooksForLanguageCallback) { + bookDetailApi.getDownloadedBooks(new BookDetailApi.BookServiceCallback>() { - for (BookDetail b: bookDetails){ - if (b.isDownloadedAlready() || b.isDownloading()){ - bookDetailsDownloaded.add(b); + @Override + public void onLoaded(List result) { + getBooksForLanguageCallback.onBooksLoaded(result); } - } - return bookDetailsDownloaded; + + @Override + public void onError(Exception error) { + getBooksForLanguageCallback.onBooksLoadError(error); + } + }); + } + + @Override public void getBookDetail(String bookDetailId, @NonNull final GetBookDetailCallback bookDetailCallback) { bookDetailApi.getBookDetail(bookDetailId, new BookDetailApi.BookServiceCallback() { diff --git a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java new file mode 100644 index 0000000..ca432ae --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java @@ -0,0 +1,53 @@ +package org.bookdash.android.presentation.downloads; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; + +import org.bookdash.android.R; +import org.bookdash.android.domain.pojo.BookDetail; + +import java.util.List; + +public class DownloadsAdapter extends RecyclerView.Adapter { + + private List bookList; + private Context context; + + public DownloadsAdapter(List bookList, Context context) { + this.bookList = bookList; + this.context = context; + } + + @Override + public DownloadsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_download, parent, false); + + return new DownloadsViewHolder(v); + } + + @Override + public void onBindViewHolder(DownloadsViewHolder holder, int position) { + BookDetail book = bookList.get(position); + holder.downloadTitleTextView.setText(book.getBookTitle()); + Glide.with(context).load(book.getBookCoverUrl()).into(holder.downloadImageTextView); + + } + + @Override + public int getItemCount() { + if (bookList == null) { + return 0; + } + return bookList.size(); + } + + public void setBooks(List books) { + this.bookList = books; + } +} diff --git a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsContract.java b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsContract.java new file mode 100644 index 0000000..5ebea65 --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsContract.java @@ -0,0 +1,28 @@ +package org.bookdash.android.presentation.downloads; + + +import org.bookdash.android.domain.pojo.BookDetail; + +import java.util.List; + +public interface DownloadsContract { + + interface View { + + void showDownloadedBooks(List books); + + void showLoading(boolean show); + + void showErrorScreen(boolean show, String errorMessage, boolean showRetryButton); + + void showSnackBarError(int message); + + } + + interface UserActions { + void loadListDownloads(); + + void deleteDownload(); + } + +} diff --git a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java new file mode 100644 index 0000000..9db1c6f --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java @@ -0,0 +1,93 @@ +package org.bookdash.android.presentation.downloads; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.bookdash.android.Injection; +import org.bookdash.android.R; +import org.bookdash.android.domain.pojo.BookDetail; + +import java.util.List; + +import fr.castorflex.android.circularprogressbar.CircularProgressBar; + + +public class DownloadsFragment extends Fragment implements DownloadsContract.View { + + private RecyclerView listDownloadsRecyclerView; + private DownloadsContract.UserActions downloadsPresenter; + private DownloadsAdapter downloadsAdapter; + private LinearLayout linearLayoutErrorScreen; + private Button buttonRetry; + private TextView textViewErrorMessage; + private CircularProgressBar circularProgressBar; + + public static DownloadsFragment newInstance() { + return new DownloadsFragment(); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_downloads, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + listDownloadsRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_list_downloads); + downloadsPresenter = new DownloadsPresenter(Injection.provideBookRepo(), this); + downloadsAdapter = new DownloadsAdapter(null, getActivity()); + listDownloadsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + listDownloadsRecyclerView.setAdapter(downloadsAdapter); + + linearLayoutErrorScreen = (LinearLayout) view.findViewById(R.id.linear_layout_error); + buttonRetry = (Button) view.findViewById(R.id.button_retry); + textViewErrorMessage = (TextView) view.findViewById(R.id.text_view_error_screen); + circularProgressBar = (CircularProgressBar) view.findViewById(R.id.fragment_loading_downloads); + downloadsPresenter.loadListDownloads(); + + } + + @Override + public void showDownloadedBooks(List books) { + downloadsAdapter.setBooks(books); + downloadsAdapter.notifyDataSetChanged(); + } + + + @Override + public void showErrorScreen(boolean show, String errorMessage, boolean showRetryButton) { + if (show) { + linearLayoutErrorScreen.setVisibility(View.VISIBLE); + } else { + linearLayoutErrorScreen.setVisibility(View.GONE); + } + buttonRetry.setVisibility(showRetryButton ? View.VISIBLE : View.GONE); + textViewErrorMessage.setText(errorMessage); + + } + + @Override + public void showLoading(boolean visible) { + circularProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); + listDownloadsRecyclerView.setVisibility(visible ? View.GONE : View.VISIBLE); + + } + + @Override + public void showSnackBarError(int message) { + Snackbar.make(listDownloadsRecyclerView, message, Snackbar.LENGTH_LONG).show(); + } + +} diff --git a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsPresenter.java b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsPresenter.java new file mode 100644 index 0000000..faf9b77 --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsPresenter.java @@ -0,0 +1,38 @@ +package org.bookdash.android.presentation.downloads; + +import org.bookdash.android.data.books.BookDetailRepository; +import org.bookdash.android.domain.pojo.BookDetail; + +import java.util.List; + +public class DownloadsPresenter implements DownloadsContract.UserActions{ + private final BookDetailRepository bookRepository; + private final DownloadsContract.View view; + + public DownloadsPresenter(BookDetailRepository bookRepository, DownloadsContract.View downloadsView) { + this.bookRepository = bookRepository; + this.view = downloadsView; + } + + public void loadListDownloads() { + view.showLoading(true); + bookRepository.getDownloadedBooks(new BookDetailRepository.GetBooksForLanguageCallback() { + @Override + public void onBooksLoaded(List books) { + view.showDownloadedBooks(books); + view.showLoading(false); + } + + @Override + public void onBooksLoadError(Exception e) { + view.showErrorScreen(true, e.getMessage(), true); + view.showLoading(false); + } + }); + } + + @Override + public void deleteDownload() { + + } +} diff --git a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java new file mode 100644 index 0000000..06894f4 --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java @@ -0,0 +1,24 @@ +package org.bookdash.android.presentation.downloads; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import org.bookdash.android.R; + +public class DownloadsViewHolder extends RecyclerView.ViewHolder { + TextView downloadTitleTextView; + TextView downloadProgressTextView; + ImageView downloadImageTextView; + ImageButton downloadActionTextView; + + public DownloadsViewHolder(View itemView) { + super(itemView); + downloadActionTextView = (ImageButton) itemView.findViewById(R.id.image_button_delete_book); + downloadImageTextView = (ImageView) itemView.findViewById(R.id.image_view_download_book_cover); + downloadProgressTextView = (TextView) itemView.findViewById(R.id.download_progress); + downloadTitleTextView = (TextView) itemView.findViewById(R.id.text_view_book_title_download); + } +} diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksContract.java b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksContract.java index ccbf536..ebb30f2 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksContract.java +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksContract.java @@ -11,9 +11,6 @@ public interface ListBooksContract { interface View { - void showThanksPopover(); - void showAboutPage(); - void showRatingPlayStore(); void showErrorScreen(boolean show, String errorMessage, boolean showRetryButton); void showLoading(boolean visible); void showBooks(List bookDetailList); @@ -23,8 +20,8 @@ interface View { interface UserActionsListener { void loadLanguages(); - void saveSelectedLanguage(int indexOfLanguage, boolean downloadOnly); - void loadBooksForLanguagePreference(boolean downloadedOnly); + void saveSelectedLanguage(int indexOfLanguage); + void loadBooksForLanguagePreference(); void clickOpenLanguagePopover(); } diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java new file mode 100644 index 0000000..8391fcf --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java @@ -0,0 +1,159 @@ +package org.bookdash.android.presentation.listbooks; + +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +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.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.bookdash.android.Injection; +import org.bookdash.android.R; +import org.bookdash.android.domain.pojo.BookDetail; +import org.bookdash.android.presentation.bookinfo.BookInfoActivity; +import org.bookdash.android.presentation.view.AutofitRecyclerView; + +import java.util.List; + +import fr.castorflex.android.circularprogressbar.CircularProgressBar; + + +public class ListBooksFragment extends Fragment implements ListBooksContract.View { + + private static final String TAG = ListBooksFragment.class.getCanonicalName(); + private ListBooksContract.UserActionsListener actionsListener; + private Button buttonRetry; + private AutofitRecyclerView mRecyclerView; + private CircularProgressBar circularProgressBar; + private LinearLayout linearLayoutErrorScreen; + private TextView textViewErrorMessage; + + public static Fragment newInstance() { + return new ListBooksFragment(); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_list_books, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + actionsListener = new ListBooksPresenter(this, Injection.provideBookRepo(), Injection.provideSettingsRepo(getActivity())); + + circularProgressBar = (CircularProgressBar) view.findViewById(R.id.activity_loading_books); + linearLayoutErrorScreen = (LinearLayout) view.findViewById(R.id.linear_layout_error); + buttonRetry = (Button) view.findViewById(R.id.button_retry); + textViewErrorMessage = (TextView) view.findViewById(R.id.text_view_error_screen); + mRecyclerView = (AutofitRecyclerView) view.findViewById(R.id.recycler_view_books); + + mRecyclerView.setHasFixedSize(true); + mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.bottom = 8; + outRect.right = 8; + outRect.left = 8; + outRect.top = 8; + } + }); + buttonRetry.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "Retry button clicked"); + actionsListener.loadBooksForLanguagePreference(); + } + }); + + actionsListener.loadLanguages(); + actionsListener.loadBooksForLanguagePreference(); + } + + + private View.OnClickListener bookClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + openBookDetails(v); + } + }; + + + public void openBookDetails(View v) { + Intent intent = new Intent(getActivity(), BookInfoActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + BookViewHolder viewHolder = (BookViewHolder) v.getTag(); + BookDetail bookDetailResult = viewHolder.bookDetail; + intent.putExtra(BookInfoActivity.BOOK_PARCEL, bookDetailResult.toBookParcelable()); + startActivity(intent); + + } + + + @Override + public void showErrorScreen(boolean show, String errorMessage, boolean showRetryButton) { + if (show) { + linearLayoutErrorScreen.setVisibility(View.VISIBLE); + } else { + linearLayoutErrorScreen.setVisibility(View.GONE); + } + buttonRetry.setVisibility(showRetryButton ? View.VISIBLE : View.GONE); + textViewErrorMessage.setText(errorMessage); + + } + + @Override + public void showLoading(boolean visible) { + circularProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); + mRecyclerView.setVisibility(visible ? View.GONE : View.VISIBLE); + + } + + @Override + public void showBooks(List bookDetailList) { + if (bookDetailList.isEmpty()) { + showErrorScreen(true, getString(R.string.no_books_available), true); + } + RecyclerView.Adapter mAdapter = new BookAdapter(bookDetailList, ListBooksFragment.this.getActivity(), bookClickListener); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.scheduleLayoutAnimation(); + } + + @Override + public void showSnackBarError(int message) { + Snackbar.make(mRecyclerView, message, Snackbar.LENGTH_LONG).show(); + } + + private DialogInterface.OnClickListener languageClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (dialog != null) { + dialog.dismiss(); + } + + actionsListener.saveSelectedLanguage(which); + + } + }; + + @Override + public void showLanguagePopover(String[] languages, int selected) { + AlertDialog alertDialogLanguages = new AlertDialog.Builder(getActivity()) + .setTitle(getString(R.string.language_selection_heading)) + .setSingleChoiceItems(languages, selected, languageClickListener).create(); + alertDialogLanguages.show(); + } + + +} diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksPresenter.java b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksPresenter.java index f24280f..59d1e2e 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksPresenter.java +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksPresenter.java @@ -25,16 +25,17 @@ public ListBooksPresenter(ListBooksContract.View listBooksView, BookDetailReposi this.bookDetailRepository = bookDetailRepository; this.settingsRepository = settingsRepository; } + @Override - public void loadBooksForLanguagePreference(boolean downloadedOnly) { + public void loadBooksForLanguagePreference() { String languagePreference = settingsRepository.getLanguagePreference(); - loadBooksForLanguage(languagePreference, downloadedOnly); + loadBooksForLanguage(languagePreference); } - private void loadBooksForLanguage(String language, boolean downloadedOnly) { + private void loadBooksForLanguage(String language) { listBooksView.showLoading(true); listBooksView.showErrorScreen(false, "", false); - bookDetailRepository.getBooksForLanguage(language,downloadedOnly, new BookDetailRepository.GetBooksForLanguageCallback() { + bookDetailRepository.getBooksForLanguage(language, new BookDetailRepository.GetBooksForLanguageCallback() { @Override public void onBooksLoaded(List books) { listBooksView.showLoading(false); @@ -69,10 +70,10 @@ public void onLanguagesLoadError(Exception e) { } @Override - public void saveSelectedLanguage(int indexOfLanguage, boolean downloadOnly) { + public void saveSelectedLanguage(int indexOfLanguage) { settingsRepository.saveLanguagePreference(languages.get(indexOfLanguage).getLanguageName()); - loadBooksForLanguage(languages.get(indexOfLanguage).getLanguageName(), downloadOnly); + loadBooksForLanguage(languages.get(indexOfLanguage).getLanguageName()); } @Override @@ -95,5 +96,4 @@ public void clickOpenLanguagePopover() { } - } diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksActivity.java b/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java similarity index 56% rename from app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksActivity.java rename to app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java index 9186f50..886a8a1 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java @@ -1,18 +1,18 @@ -package org.bookdash.android.presentation.listbooks; +package org.bookdash.android.presentation.main; import android.content.ActivityNotFoundException; -import android.content.DialogInterface; import android.content.Intent; -import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; -import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.method.LinkMovementMethod; @@ -20,11 +20,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; import android.widget.TextView; - import com.google.android.gms.appinvite.AppInvite; import com.google.android.gms.appinvite.AppInviteInvitation; import com.google.android.gms.appinvite.AppInviteInvitationResult; @@ -33,38 +30,32 @@ import com.google.android.gms.common.api.ResultCallback; import org.bookdash.android.BuildConfig; -import org.bookdash.android.Injection; import org.bookdash.android.R; import org.bookdash.android.presentation.about.AboutActivity; import org.bookdash.android.presentation.activity.BaseAppCompatActivity; -import org.bookdash.android.presentation.bookinfo.BookInfoActivity; -import org.bookdash.android.domain.pojo.BookDetail; -import org.bookdash.android.presentation.view.AutofitRecyclerView; - -import java.util.List; +import org.bookdash.android.presentation.downloads.DownloadsFragment; +import org.bookdash.android.presentation.listbooks.ListBooksFragment; -import fr.castorflex.android.circularprogressbar.CircularProgressBar; - -public class ListBooksActivity extends BaseAppCompatActivity implements ListBooksContract.View { +public class MainActivity extends BaseAppCompatActivity implements MainContract.MainView { private static final int INVITE_REQUEST_CODE = 1; - private ListBooksContract.UserActionsListener actionsListener; + private static final String TAG = "MainActivity"; private GoogleApiClient googleApiClient; private Toolbar toolbar; private DrawerLayout drawerLayout; private NavigationView navigationView; - private Button buttonRetry; + private MainContract.MainUserActions actionsListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - actionsListener = new ListBooksPresenter(this, Injection.provideBookRepo(), Injection.provideSettingsRepo(this)); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); toolbar = (Toolbar) findViewById(R.id.toolbar); navigationView = (NavigationView) findViewById(R.id.navigation_view); setSupportActionBar(toolbar); + actionsListener = new MainPresenter(this); final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { @@ -73,32 +64,6 @@ protected void onCreate(Bundle savedInstanceState) { } setUpNavDrawer(); - circularProgressBar = (CircularProgressBar) findViewById(R.id.activity_loading_books); - linearLayoutErrorScreen = (LinearLayout) findViewById(R.id.linear_layout_error); - buttonRetry = (Button) findViewById(R.id.button_retry); - textViewErrorMessage = (TextView) findViewById(R.id.text_view_error_screen); - mRecyclerView = (AutofitRecyclerView) findViewById(R.id.recycler_view_books); - - mRecyclerView.setHasFixedSize(true); - mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - outRect.bottom = 8; - outRect.right = 8; - outRect.left = 8; - outRect.top = 8; - } - }); - buttonRetry.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Log.d(TAG, "Retry button clicked"); - actionsListener.loadBooksForLanguagePreference(false); - } - }); - - actionsListener.loadLanguages(); - actionsListener.loadBooksForLanguagePreference(false); checkIfComingFromInvite(); } @@ -158,14 +123,11 @@ public boolean onNavigationItemSelected(MenuItem menuItem) { } private void showDownloadedBooks() { - downloadOnly = true; - - actionsListener.loadBooksForLanguagePreference(downloadOnly); + actionsListener.clickViewDownloadBooks(); } private void showAllBooks() { - downloadOnly = false; - actionsListener.loadBooksForLanguagePreference(downloadOnly); + actionsListener.clickViewAllBooks(); } private void showSettingsScreen() { @@ -182,13 +144,6 @@ public void onConnectionFailed(ConnectionResult connectionResult) { } }) - /* .enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() { - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - Log.d(TAG, "onConnectionFailed: onResult:" + connectionResult.toString()); - - } - })*/ .build(); if (googleApiClient != null) { googleApiClient.connect(); @@ -204,12 +159,6 @@ public void onResult(AppInviteInvitationResult result) { } } - private static final String TAG = ListBooksActivity.class.getCanonicalName(); - - private AutofitRecyclerView mRecyclerView; - private CircularProgressBar circularProgressBar; - private LinearLayout linearLayoutErrorScreen; - private TextView textViewErrorMessage; @Override protected void onStop() { @@ -229,19 +178,10 @@ protected void onStart() { } } - private View.OnClickListener bookClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - - // final BookDetail bookDetail = (BookDetail) v.getTag(); - - openBookDetails(v); - } - }; @Override protected String getScreenName() { - return "BookListingScreen"; + return "MainActivity"; } @Override @@ -259,10 +199,6 @@ public boolean onOptionsItemSelected(MenuItem item) { showAboutPage(); return true; } - if (id == R.id.action_language_choice) { - actionsListener.clickOpenLanguagePopover(); - return true; - } if (id == R.id.action_rate_app) { showRatingPlayStore(); return true; @@ -287,7 +223,7 @@ private void openInvitePage() { .build(); startActivityForResult(intent, INVITE_REQUEST_CODE); } catch (ActivityNotFoundException ac) { - Snackbar.make(mRecyclerView, R.string.common_google_play_services_api_unavailable_text, Snackbar.LENGTH_LONG).show(); + Snackbar.make(navigationView, R.string.common_google_play_services_api_unavailable_text, Snackbar.LENGTH_LONG).show(); } } @@ -311,34 +247,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } - private boolean downloadOnly = false; - private DialogInterface.OnClickListener languageClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (dialog != null) { - dialog.dismiss(); - } - - actionsListener.saveSelectedLanguage(which, downloadOnly); - - /* tracker.send(new HitBuilders.EventBuilder() - .setCategory("LanguageChange") - .setAction(languages.get(which).getLanguageName()) - .build());*/ - - - } - }; - - @Override - public void showLanguagePopover(String[] languages, int selected) { - AlertDialog alertDialogLanguages = new AlertDialog.Builder(this) - .setTitle(getString(R.string.language_selection_heading)) - .setSingleChoiceItems(languages, selected, languageClickListener).create(); - alertDialogLanguages.show(); - } - - @Override public void showThanksPopover() { AlertDialog.Builder thanksDialog = new AlertDialog.Builder(this); @@ -353,39 +261,9 @@ public void showThanksPopover() { @Override public void showAboutPage() { - Intent intent = new Intent(ListBooksActivity.this, AboutActivity.class); - - startActivity(intent); - } - - - public void openBookDetails(View v) { - /* if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { - ImageView imageView = - ((BookViewHolder) v.getTag()).bookCover; - imageView.setTransitionName(getString(R.string.transition_book)); - v.setBackgroundColor( - ContextCompat.getColor(ListBooksActivity.this, android.R.color.transparent)); - ActivityOptions options = - ActivityOptions.makeSceneTransitionAnimation(ListBooksActivity.this, - Pair.create((View) imageView, getString(R.string.transition_book))); - + Intent intent = new Intent(MainActivity.this, AboutActivity.class); - Intent intent = new Intent(ListBooksActivity.this, BookInfoActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(BookInfoActivity.BOOK_PARCEL, ((BookViewHolder) v.getTag()).bookDetail.toBookParcelable()); - startActivity(intent, options.toBundle()); - - } else {*/ - - Intent intent = new Intent(ListBooksActivity.this, BookInfoActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - BookViewHolder viewHolder = (BookViewHolder) v.getTag(); - BookDetail bookDetailResult = viewHolder.bookDetail; - intent.putExtra(BookInfoActivity.BOOK_PARCEL, bookDetailResult.toBookParcelable()); startActivity(intent); - - /* }*/ } @Override @@ -403,44 +281,22 @@ public void showRatingPlayStore() { } @Override - public void showErrorScreen(boolean show, String errorMessage, boolean showRetryButton) { - if (show) { - linearLayoutErrorScreen.setVisibility(View.VISIBLE); - } else { - linearLayoutErrorScreen.setVisibility(View.GONE); - } - buttonRetry.setVisibility(showRetryButton ? View.VISIBLE : View.GONE); - textViewErrorMessage.setText(errorMessage); - + public void showAllBooksPage() { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction ft = fragmentManager.beginTransaction(); + Fragment f = ListBooksFragment.newInstance(); + ft.replace(R.id.fragment_content, f, "ALLBOOKS"); + ft.commit(); } @Override - public void showLoading(boolean visible) { - circularProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); - mRecyclerView.setVisibility(visible ? View.GONE : View.VISIBLE); - + public void showDownloadedBooksPage() { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction ft = fragmentManager.beginTransaction(); + Fragment f = DownloadsFragment.newInstance(); + ft.replace(R.id.fragment_content, f, "DOWNLOADED_BOOKS"); + ft.commit(); } - @Override - public void showBooks(List bookDetailList) { - if (bookDetailList.isEmpty()) { - if (downloadOnly) { - showErrorScreen(true, getString(R.string.no_books_downloaded), false); - - } else { - showErrorScreen(true, getString(R.string.no_books_available), true); - - } - // return; - } - RecyclerView.Adapter mAdapter = new BookAdapter(bookDetailList, ListBooksActivity.this, bookClickListener); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.scheduleLayoutAnimation(); - } - - @Override - public void showSnackBarError(int message) { - Snackbar.make(mRecyclerView, message, Snackbar.LENGTH_LONG).show(); - } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/bookdash/android/presentation/main/MainContract.java b/app/src/main/java/org/bookdash/android/presentation/main/MainContract.java new file mode 100644 index 0000000..836fd40 --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/main/MainContract.java @@ -0,0 +1,25 @@ +package org.bookdash.android.presentation.main; + + +public interface MainContract { + interface MainView { + + void showThanksPopover(); + + void showAboutPage(); + + void showRatingPlayStore(); + + void showAllBooksPage(); + + void showDownloadedBooksPage(); + } + + interface MainUserActions { + + void clickViewDownloadBooks(); + + void clickViewAllBooks(); + } +} + diff --git a/app/src/main/java/org/bookdash/android/presentation/main/MainPresenter.java b/app/src/main/java/org/bookdash/android/presentation/main/MainPresenter.java new file mode 100644 index 0000000..fdac318 --- /dev/null +++ b/app/src/main/java/org/bookdash/android/presentation/main/MainPresenter.java @@ -0,0 +1,30 @@ +package org.bookdash.android.presentation.main; + +import org.bookdash.android.data.settings.SettingsApi; +import org.bookdash.android.domain.pojo.Language; + +import java.util.List; + +/** + * Created by rebeccafranks on 16/04/19. + */ +public class MainPresenter implements MainContract.MainUserActions { + + private MainContract.MainView mainView; + + public MainPresenter(MainContract.MainView mainView) { + this.mainView = mainView; + } + + @Override + public void clickViewDownloadBooks() { + mainView.showDownloadedBooksPage(); + } + + @Override + public void clickViewAllBooks() { + mainView.showAllBooksPage(); + } + + +} diff --git a/app/src/main/java/org/bookdash/android/presentation/splash/SplashActivity.java b/app/src/main/java/org/bookdash/android/presentation/splash/SplashActivity.java index 7b23df1..91472c0 100644 --- a/app/src/main/java/org/bookdash/android/presentation/splash/SplashActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/splash/SplashActivity.java @@ -7,7 +7,8 @@ import org.bookdash.android.Injection; import org.bookdash.android.R; -import org.bookdash.android.presentation.listbooks.ListBooksActivity; +import org.bookdash.android.presentation.listbooks.ListBooksFragment; +import org.bookdash.android.presentation.main.MainActivity; import za.co.riggaroo.materialhelptutorial.tutorial.MaterialTutorialActivity; @@ -47,7 +48,7 @@ public void loadTutorial() { @Override public void loadMainScreen() { - Intent mainAct = new Intent(SplashActivity.this, ListBooksActivity.class); + Intent mainAct = new Intent(SplashActivity.this, MainActivity.class); startActivity(mainAct); finish(); } diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..39e64d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cc0d2ca..c35e91b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,114 +1,26 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/drawer_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - - - - - - - - - - - - - - - - - - - - - -