diff --git a/README.md b/README.md index e2c9045f..196bd5b6 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,8 @@ Book Dash is an Android App for the NPO where you can download books in differen 1. MaterialHelpTutorial - https://github.com/spongebobrf/MaterialIntroTutorial 2. FabButton - https://github.com/ckurtm/FabButton -3. Firebase - https://firebase.google.com/ -4. Fabric - https://fabric.io/dashboard -5. Glide Image Loading - https://github.com/bumptech/glide +3. Firebase - https://firebase.google.com/ +4. Glide Image Loading - https://github.com/bumptech/glide # Setup @@ -55,14 +54,13 @@ google-services.json file. This must then be placed into the app folder of this # [OPTIONAL setup] 1. If you wish to build a release version you will need to create your own keystore file and edit the password values in the following file - (create a version of the file without the .sample extension): release-keystore.properties.sample -2. Setup a Fabric Account. https://fabric.io/dashboard -3. Get your Fabric API Key and Client key, change it in the file: /app/fabric-sample.properties and rename the file to fabric.properties +2. If you want to add Firebase Crashlytics to your app: Setup a Firebase Account. https://firebase.google.com/docs/crashlytics # Contributions Contributions are welcome. Please read the [contributions guide](CONTRIBUTING.md) for more information. # License -Copyright 2016 Book Dash. +Copyright 2023 Book Dash. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for diff --git a/app/.gitignore b/app/.gitignore index e7afaf48..2abde4aa 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,2 @@ /build /google-services.json -/fabric.properties diff --git a/app/build.gradle b/app/build.gradle index 74b172b4..c2df5047 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,26 +1,14 @@ -buildscript { - repositories { - maven { url 'https://maven.fabric.io/public' } - } - - dependencies { - classpath 'io.fabric.tools:gradle:1.31.2' - } -} apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'io.fabric' apply plugin: 'com.github.triplet.play' apply plugin: 'com.google.firebase.firebase-perf' +apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'kotlin-kapt' repositories { - maven { url 'https://maven.fabric.io/public' } mavenCentral() } - - android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -100,14 +88,17 @@ android { } } + // Avoid crash on platform method calls that's not mocked. E.g. FirebaseCrashlytics. + testOptions { + unitTests.returnDefaultValues = true + } + buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - ext.enableCrashlytics = true } debug { - ext.enableCrashlytics = false // Run code coverage reports by default on debug builds. testCoverageEnabled = true } @@ -123,57 +114,58 @@ dependencies { testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion" - testImplementation("org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito") + testImplementation("org.powermock:powermock-module-junit4:2.0.7") testImplementation("org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito") // Android Testing Library's runner and rules - androidTestImplementation 'androidx.test.espresso:espresso-web:3.2.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.3.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:rules:1.3.0' // Espresso UI Testing - androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0') { + androidTestImplementation('androidx.test.espresso:espresso-core:3.3.0') { exclude group: "javax.inject" } - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0' - androidTestImplementation 'androidx.appcompat:appcompat:1.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' + androidTestImplementation 'androidx.appcompat:appcompat:1.2.0' androidTestImplementation "com.google.android.material:material:$rootProject.ext.materialVersion" implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.palette:palette:1.0.0' implementation "com.google.android.material:material:$rootProject.ext.materialVersion" implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.percentlayout:percentlayout:1.0.0' - implementation 'androidx.preference:preference:1.1.0' + implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation "com.github.bumptech.glide:glide:$rootProject.ext.glideVersion" kapt "com.github.bumptech.glide:compiler:$rootProject.ext.glideVersion" - implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.github.castorflex.smoothprogressbar:library-circular:1.2.0' - - implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { - transitive = true - } - - implementation 'com.google.firebase:firebase-analytics:17.2.1' - implementation 'com.google.firebase:firebase-auth:19.2.0' - implementation 'com.google.firebase:firebase-database:19.2.0' - implementation 'com.google.firebase:firebase-messaging:20.0.1' - implementation 'com.google.firebase:firebase-config:19.0.3' - implementation 'com.google.firebase:firebase-storage:19.1.0' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.github.castorflex.smoothprogressbar:library-circular:1.3.0' + + // Import the Firebase Bill of Materials (BoM) + implementation platform('com.google.firebase:firebase-bom:26.0.0') + + // Firebase Dependencies + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.firebase:firebase-auth' + implementation 'com.google.firebase:firebase-config' + implementation 'com.google.firebase:firebase-crashlytics' + implementation 'com.google.firebase:firebase-database' + implementation 'com.google.firebase:firebase-messaging' + implementation 'com.google.firebase:firebase-perf' + implementation 'com.google.firebase:firebase-storage' implementation 'com.firebaseui:firebase-ui-storage:6.1.0' - implementation 'com.google.firebase:firebase-perf:19.0.2' implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxjava:1.2.5' - implementation 'com.jakewharton.threetenabp:threetenabp:1.0.5' - implementation 'com.jakewharton.timber:timber:4.7.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' + implementation 'com.jakewharton.threetenabp:threetenabp:1.3.0' + implementation 'com.jakewharton.timber:timber:4.7.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.multidex:multidex:2.0.1' @@ -181,8 +173,5 @@ dependencies { implementation project(':fabbutton') } - - - apply plugin: 'kotlin-kapt' apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/app/fabric-sample.properties b/app/fabric-sample.properties deleted file mode 100644 index 48938ae8..00000000 --- a/app/fabric-sample.properties +++ /dev/null @@ -1,2 +0,0 @@ -apiSecret=fabricApiSecret -apiKey=fabricApiKey \ No newline at end of file diff --git a/app/prod/release/output.json b/app/prod/release/output.json new file mode 100644 index 00000000..3f3b3421 --- /dev/null +++ b/app/prod/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"2.9.2","enabled":true,"outputFile":"app-prod-release.apk","fullName":"prodRelease","baseName":"prod-release","dirName":""},"path":"app-prod-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index b88bccbd..ec830b17 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -20,17 +20,18 @@ -keep class me.zhanghai.android.materialprogressbar.** { *; } -keep class com.joanzapata.** { *; } - - - -keep public class * implements com.bumptech.glide.module.GlideModule --keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { - **[] $VALUES; - public *; +-keep class * extends com.bumptech.glide.module.AppGlideModule { + (...); +} +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { + *** rewind(); } --keep class com.crashlytics.** { *; } --keep class com.crashlytics.android.** -keepattributes SourceFile,LineNumberTable -keepattributes Signature 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 8742446c..3f8604da 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 @@ -43,7 +43,7 @@ public void tearDown() { @Test public void languageItemClick_ShowLanguageChooser() { - onView(withId(R.id.action_language_choice)).perform(click()); + onView(withId(R.id.container_language)).perform(click()); //Then onView(withText(R.string.language_selection_heading)).inRoot(isDialog()).check(matches(isDisplayed())); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b981bfa6..e3b068bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,18 +30,21 @@ + android:theme="@style/AppTheme.NoActionBar" + android:exported="false"> + android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen" + android:exported="false"> + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> @@ -61,7 +64,8 @@ + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> @@ -71,7 +75,8 @@ + android:theme="@style/AppTheme.NoActionBar" + android:exported="false"> @@ -79,15 +84,13 @@ + android:theme="@style/AppTheme.NoActionBar" + android:exported="false"> - \ No newline at end of file diff --git a/app/src/main/java/org/bookdash/android/BookDashApplication.java b/app/src/main/java/org/bookdash/android/BookDashApplication.java index d501b478..aa41d909 100644 --- a/app/src/main/java/org/bookdash/android/BookDashApplication.java +++ b/app/src/main/java/org/bookdash/android/BookDashApplication.java @@ -5,15 +5,13 @@ import androidx.multidex.MultiDex; import androidx.multidex.MultiDexApplication; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.core.CrashlyticsCore; import com.google.firebase.analytics.FirebaseAnalytics; -import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.crashlytics.FirebaseCrashlytics; +import com.google.firebase.installations.FirebaseInstallations; import com.jakewharton.threetenabp.AndroidThreeTen; import org.bookdash.android.config.CrashlyticsTree; -import io.fabric.sdk.android.Fabric; import rx.Subscriber; import timber.log.Timber; @@ -42,13 +40,12 @@ public void onCreate() { super.onCreate(); AndroidThreeTen.init(this); - Crashlytics crashlyticsKit = new Crashlytics.Builder() - .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()).build(); - Fabric.with(this, crashlyticsKit); + + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG); if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); - Timber.d("Firebase Debug Info:" + FirebaseInstanceId.getInstance().getToken()); + Timber.d("Firebase Debug Info:%s", FirebaseInstallations.getInstance().getId()); } else { Timber.plant(new CrashlyticsTree()); } diff --git a/app/src/main/java/org/bookdash/android/config/CrashlyticsTree.java b/app/src/main/java/org/bookdash/android/config/CrashlyticsTree.java index 8229bfb9..25901cf5 100644 --- a/app/src/main/java/org/bookdash/android/config/CrashlyticsTree.java +++ b/app/src/main/java/org/bookdash/android/config/CrashlyticsTree.java @@ -1,9 +1,7 @@ package org.bookdash.android.config; import android.util.Log; - -import com.crashlytics.android.Crashlytics; - +import com.google.firebase.crashlytics.FirebaseCrashlytics; import timber.log.Timber; /** @@ -17,10 +15,10 @@ protected void log(final int priority, final String tag, final String message, f return; } - Crashlytics.log(priority, tag, message); + FirebaseCrashlytics.getInstance().log("Priority: " + priority + ", TAG: " + tag + ", Message: " + message); if (t != null) { - Crashlytics.logException(t); + FirebaseCrashlytics.getInstance().recordException(t); } } } diff --git a/app/src/main/java/org/bookdash/android/config/FirebaseConfig.java b/app/src/main/java/org/bookdash/android/config/FirebaseConfig.java index 59005323..9ef440a2 100644 --- a/app/src/main/java/org/bookdash/android/config/FirebaseConfig.java +++ b/app/src/main/java/org/bookdash/android/config/FirebaseConfig.java @@ -6,15 +6,15 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.BuildConfig; import com.google.firebase.remoteconfig.FirebaseRemoteConfig; import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings; -import org.bookdash.android.BuildConfig; import org.bookdash.android.R; public class FirebaseConfig implements RemoteConfigSettingsApi { - private static final int CACHE_EXPIRATION_IN_SECONDS = 3600; + private static final long CACHE_EXPIRATION_IN_SECONDS = 3600; private static final String DEFAULT_LANGUAGE_ID = "default_language_id"; private static final String DEFAULT_LANGUAGE_NAME = "default_language_name"; private static final String DEFAULT_LANGUAGE_ABBREVIATION = "default_language_abbreviation"; @@ -29,10 +29,10 @@ private FirebaseConfig(FirebaseRemoteConfig firebaseRemoteConfig) { public static FirebaseConfig newInstance() { final FirebaseRemoteConfig firebaseRemoteConfig = FirebaseRemoteConfig.getInstance(); - FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder() - .setDeveloperModeEnabled(BuildConfig.DEBUG).build(); - firebaseRemoteConfig.setConfigSettings(configSettings); - firebaseRemoteConfig.setDefaults(R.xml.firebase_remote_config_defaults); + FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder(). + setMinimumFetchIntervalInSeconds(BuildConfig.DEBUG ? 0 : CACHE_EXPIRATION_IN_SECONDS).build(); + firebaseRemoteConfig.setConfigSettingsAsync(configSettings); + firebaseRemoteConfig.setDefaultsAsync(R.xml.firebase_remote_config_defaults); return new FirebaseConfig(firebaseRemoteConfig); } @@ -40,7 +40,7 @@ public FirebaseConfig init() { firebaseRemoteConfig.fetch(CACHE_EXPIRATION_IN_SECONDS).addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { - firebaseRemoteConfig.activateFetched(); + firebaseRemoteConfig.activate(); } }).addOnFailureListener(new OnFailureListener() { @Override diff --git a/app/src/main/java/org/bookdash/android/data/book/DownloadServiceImpl.java b/app/src/main/java/org/bookdash/android/data/book/DownloadServiceImpl.java index 49df0e05..a0aff899 100644 --- a/app/src/main/java/org/bookdash/android/data/book/DownloadServiceImpl.java +++ b/app/src/main/java/org/bookdash/android/data/book/DownloadServiceImpl.java @@ -6,6 +6,7 @@ import androidx.annotation.WorkerThread; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.google.firebase.storage.FileDownloadTask; import com.google.firebase.storage.FirebaseStorage; import com.google.gson.Gson; @@ -99,8 +100,12 @@ private BookPages getBookPages(String fileName) { Log.e(TAG, "EX: ", e); } + FirebaseCrashlytics.getInstance().log("File path: " + fileName); // TODO Remove in next release if book downloading bug is fixed. + FirebaseCrashlytics.getInstance().recordException(e); Log.e(TAG, "Ex:" + e.getMessage(), e); } catch (Exception e) { + FirebaseCrashlytics.getInstance().log("File path: " + fileName); // TODO Remove in next release if book downloading bug is fixed. + FirebaseCrashlytics.getInstance().recordException(e); Log.e(TAG, "error parsing book: " + fileName, e); } return bookPages; diff --git a/app/src/main/java/org/bookdash/android/domain/model/firebase/FireBookDetails.java b/app/src/main/java/org/bookdash/android/domain/model/firebase/FireBookDetails.java index 864fbf00..990a1dcf 100644 --- a/app/src/main/java/org/bookdash/android/domain/model/firebase/FireBookDetails.java +++ b/app/src/main/java/org/bookdash/android/domain/model/firebase/FireBookDetails.java @@ -137,7 +137,18 @@ private String getFolderLocation(File file) { if (files == null || files.length == 0 || files[0] == null) { return null; } - return files[0].getAbsoluteFile().toString(); + for (File aFile : files) { + if (!aFile.getAbsoluteFile().toString().contains("MACOSX")) { + // This is a workaround bugfix (no other solution possible). Previously the app + // assumed that a book's root folder only contains one folder (containing the + // book images and .json file). However, in the CMS content conversion process + // where PDF books are converted into .json and images so that the app can + // consume it, mac inserted a "__MACOSX" folder in certain cases. This causes + // this method to return the MACOSX folder as the book folder on certain devices + // as file.listFiles() does not guarantee any specific ordering. + return aFile.getAbsoluteFile().toString(); + } + } } return null; } diff --git a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoActivity.java b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoActivity.java index 37866740..af29a5b5 100644 --- a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoActivity.java @@ -1,6 +1,7 @@ package org.bookdash.android.presentation.bookinfo; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; @@ -42,7 +43,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; -import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.CustomTarget; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.snackbar.Snackbar; @@ -269,7 +270,7 @@ protected void onNewIntent(Intent intent) { private void loadImage(StorageReference url) { GlideApp.with(this).load(url) .transition(DrawableTransitionOptions.withCrossFade()) - .into(new SimpleTarget() { + .into(new CustomTarget() { @Override public void onResourceReady(@NonNull Drawable resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { @@ -277,6 +278,12 @@ public void onResourceReady(@NonNull Drawable resource, @Nullable com.bumptech.g onImageLoaded(bitmap); extractPaletteColors(bitmap); } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + // Clear the view. + onImageLoaded(null); + } }); } public static Bitmap drawableToBitmap (Drawable drawable) { @@ -300,7 +307,7 @@ public static Bitmap drawableToBitmap (Drawable drawable) { drawable.draw(canvas); return bitmap; } - private void onImageLoaded(Bitmap bitmap) { + private void onImageLoaded(@Nullable Bitmap bitmap) { imageViewBook.setImageBitmap(bitmap); } @@ -464,11 +471,15 @@ public void showContributors(List list) { @Override public void sendShareEvent(String bookTitle) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.sharing_book_title, bookTitle)); - sendIntent.setType("text/plain"); - startActivity(sendIntent); + try { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.sharing_book_title, bookTitle)); + sendIntent.setType("text/plain"); + startActivity(sendIntent); + } catch (ActivityNotFoundException anfe) { + showSnackBarMessage(R.string.share_book_error_no_apps_found); + } } @Override diff --git a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoPresenter.java b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoPresenter.java index e418f6ce..910602ea 100644 --- a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoPresenter.java +++ b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoPresenter.java @@ -2,6 +2,8 @@ import androidx.annotation.NonNull; +import com.google.firebase.crashlytics.FirebaseCrashlytics; + import org.bookdash.android.R; import org.bookdash.android.data.book.BookService; import org.bookdash.android.data.book.DownloadService; @@ -96,15 +98,16 @@ public void onError(Throwable e) { public void onNext(DownloadProgressItem downloadProgressItem) { getView().showDownloadProgress(downloadProgressItem.getDownloadProgress()); if (downloadProgressItem.isComplete()) { + bookInfo.setIsDownloading(false); analytics.trackViewBook(bookInfo); BookPages bookPages = downloadProgressItem.getBookPages(); if (bookPages == null) { getView().showSnackBarMessage(R.string.failed_to_open_book); + FirebaseCrashlytics.getInstance().recordException(new Exception("Book pages null after completed download.")); // TODO Remove in next release if book downloading bug is fixed. analytics.trackDownloadBookFailed(bookInfo, "failed_to_open_book"); return; } getView().showDownloadFinished(); - bookInfo.setIsDownloading(false); getView().openBook(bookInfo, bookPages, bookInfo.getFolderLocation()); } 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 e1738f9f..063eb093 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 @@ -24,6 +24,8 @@ interface View extends MvpView { void showLanguagePopover(String[] languages, int selected); void startSearchActivity(); + + void onSelectedLanguageChanged(String selectedLanguage); } interface Presenter extends MvpPresenter{ 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 index 4d4ed375..35d8f603 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/ListBooksFragment.java @@ -51,6 +51,8 @@ public class ListBooksFragment extends Fragment implements ListBooksContract.Vie private CircularProgressBar circularProgressBar; private LinearLayout linearLayoutErrorScreen; private TextView textViewErrorMessage; + private LinearLayout linearLayoutContainerLanguage; + private TextView textViewCurrentLanguage; private NavDrawerInterface navDrawerInterface; private BookAdapter bookAdapter; private View.OnClickListener bookClickListener = new View.OnClickListener() { @@ -117,6 +119,8 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { linearLayoutErrorScreen = view.findViewById(R.id.linear_layout_error); buttonRetry = view.findViewById(R.id.button_retry); textViewErrorMessage = view.findViewById(R.id.text_view_error_screen); + linearLayoutContainerLanguage = view.findViewById(R.id.container_language); + textViewCurrentLanguage = view.findViewById(R.id.text_current_language); recyclerViewBooks = view.findViewById(R.id.recycler_view_books); recyclerViewBooks.setLayoutManager( new GridLayoutManager(getActivity(), getContext().getResources().getInteger(R.integer.book_span))); @@ -127,6 +131,12 @@ public void onClick(View v) { listBooksPresenter.loadBooksForLanguagePreference(); } }); + linearLayoutContainerLanguage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + listBooksPresenter.clickOpenLanguagePopover(); + } + }); Toolbar toolbar = view.findViewById(R.id.toolbar); if (navDrawerInterface != null) { navDrawerInterface.setToolbar(toolbar); @@ -164,10 +174,6 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_language_choice) { - listBooksPresenter.clickOpenLanguagePopover(); - return true; - } if (item.getItemId() == R.id.action_search_books) { listBooksPresenter.openSearchScreen(); return true; @@ -275,5 +281,13 @@ public void startSearchActivity() { SearchActivity.start(getActivity()); } - + @Override + public void onSelectedLanguageChanged(final String selectedLanguage) { + runUiThread(new Runnable() { + @Override + public void run() { + textViewCurrentLanguage.setText(selectedLanguage); + } + }); + } } 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 028ab358..0e51b2d6 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 @@ -1,6 +1,6 @@ package org.bookdash.android.presentation.listbooks; -import com.crashlytics.android.Crashlytics; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import org.bookdash.android.R; import org.bookdash.android.data.book.BookService; @@ -51,7 +51,7 @@ public void onCompleted() { @Override public void onError(final Throwable e) { getView().showSnackBarError(R.string.error_loading_languages); - Crashlytics.logException(e); + FirebaseCrashlytics.getInstance().recordException(e); } @Override @@ -73,16 +73,17 @@ public void onCompleted() { @Override public void onError(final Throwable e) { getView().showSnackBarError(R.string.error_saving_selected_language); - Crashlytics.logException(e); + FirebaseCrashlytics.getInstance().recordException(e); } @Override public void onNext(final Boolean aBoolean) { - analytics.trackLanguageChange(languages.get(indexOfLanguage).getLanguageName()); - loadBooksForLanguage(languages.get(indexOfLanguage)); + FireLanguage selectedLanguage = languages.get(indexOfLanguage); + analytics.trackLanguageChange(selectedLanguage.getLanguageName()); + getView().onSelectedLanguageChanged(selectedLanguage.getLanguageName()); + loadBooksForLanguage(selectedLanguage); } })); - } @Override @@ -104,6 +105,7 @@ public void onError(final Throwable e) { @Override public void onNext(final FireLanguage fireLanguage) { analytics.setUserLanguage(fireLanguage.getLanguageName()); + getView().onSelectedLanguageChanged(fireLanguage.getLanguageName()); loadBooksForLanguage(fireLanguage); } })); @@ -113,6 +115,7 @@ public void onNext(final FireLanguage fireLanguage) { @Override public void clickOpenLanguagePopover() { if (languages == null) { + getView().showSnackBarError(R.string.error_loading_languages_try_again); return; } addSubscription(settingsRepository.getLanguagePreference().observeOn(ioScheduler).subscribeOn(mainScheduler) @@ -124,7 +127,7 @@ public void onCompleted() { @Override public void onError(final Throwable e) { getView().showSnackBarError(R.string.error_opening_languagepopover); - Crashlytics.logException(e); + FirebaseCrashlytics.getInstance().recordException(e); } @Override diff --git a/app/src/main/java/org/bookdash/android/presentation/readbook/BookDetailActivity.kt b/app/src/main/java/org/bookdash/android/presentation/readbook/BookDetailActivity.kt index eeae7bc3..45db3570 100644 --- a/app/src/main/java/org/bookdash/android/presentation/readbook/BookDetailActivity.kt +++ b/app/src/main/java/org/bookdash/android/presentation/readbook/BookDetailActivity.kt @@ -3,9 +3,11 @@ package org.bookdash.android.presentation.readbook import android.os.Bundle import android.view.ViewTreeObserver import android.view.WindowManager +import android.widget.Toast import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.viewpager.widget.ViewPager +import com.google.firebase.crashlytics.FirebaseCrashlytics import org.bookdash.android.R import org.bookdash.android.domain.model.gson.BookPages import org.bookdash.android.presentation.activity.BaseAppCompatActivity @@ -32,6 +34,10 @@ class BookDetailActivity : BaseAppCompatActivity() { }) //val book = intent.getStringExtra(BOOK_ARG) val bookPages = intent.getParcelableExtra(BOOK_PAGES) + if (bookPages == null) { + onBookPagesNull() + return + } bookPages.pages.add(0, null) val bookLocation = intent.getStringExtra(LOCATION_BOOK) pageAdapter = PageAdapter(supportFragmentManager, bookPages.pages, @@ -51,12 +57,22 @@ class BookDetailActivity : BaseAppCompatActivity() { } }) readBookViewModel.pageForwardEventTrigger.observe(this, Observer { - if (viewPager.currentItem != viewPager.childCount -1) { + if (viewPager.currentItem != viewPager.childCount - 1) { viewPager.setCurrentItem(viewPager.currentItem + 1, true) } }) } + /** + * Called when this Activity was launched with no book pages. + * TODO Remove this method once bookPages == null bug is fixed. + */ + private fun onBookPagesNull() { + FirebaseCrashlytics.getInstance().recordException(Exception("Book pages null when opening book detail.")) + Toast.makeText(applicationContext, R.string.failed_to_open_book, Toast.LENGTH_LONG).show() + finish() + } + override fun getScreenName(): String { return "BookDetailScreen" } diff --git a/app/src/main/java/org/bookdash/android/presentation/readbook/PageAdapter.java b/app/src/main/java/org/bookdash/android/presentation/readbook/PageAdapter.java index 283e6ba6..c693b2d9 100644 --- a/app/src/main/java/org/bookdash/android/presentation/readbook/PageAdapter.java +++ b/app/src/main/java/org/bookdash/android/presentation/readbook/PageAdapter.java @@ -16,7 +16,7 @@ public class PageAdapter extends FragmentStatePagerAdapter { public PageAdapter(FragmentManager fm, List pages, String rootFileLocation) { - super(fm); + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); this.pages = pages; this.rootFileLocation = rootFileLocation; } diff --git a/app/src/main/res/drawable-v21/language_selector.xml b/app/src/main/res/drawable-v21/language_selector.xml new file mode 100644 index 00000000..e798f1e6 --- /dev/null +++ b/app/src/main/res/drawable-v21/language_selector.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_down_24.xml b/app/src/main/res/drawable/ic_arrow_down_24.xml new file mode 100644 index 00000000..fba0a470 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_public_black_24dp.xml b/app/src/main/res/drawable/ic_public_black_24dp.xml deleted file mode 100644 index 60e024f5..00000000 --- a/app/src/main/res/drawable/ic_public_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/language_selector.xml b/app/src/main/res/drawable/language_selector.xml new file mode 100644 index 00000000..c127c330 --- /dev/null +++ b/app/src/main/res/drawable/language_selector.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_list_books.xml b/app/src/main/res/layout/fragment_list_books.xml index 3b029089..a7b0d41c 100644 --- a/app/src/main/res/layout/fragment_list_books.xml +++ b/app/src/main/res/layout/fragment_list_books.xml @@ -1,5 +1,4 @@ - + android:src="@drawable/appbackground" /> - + app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> + + + + + app:cpb_sweep_speed="1.0" /> @@ -69,7 +91,7 @@ android:layout_width="150dp" android:layout_height="150dp" android:layout_gravity="center_horizontal" - android:src="@drawable/lb_ic_sad_cloud"/> + android:src="@drawable/lb_ic_sad_cloud" /> + android:text="@string/error_loading_books" />