diff --git a/app/build.gradle b/app/build.gradle index bcda2c7..74b172b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'io.fabric.tools:gradle:1.25.1' + classpath 'io.fabric.tools:gradle:1.31.2' } } apply plugin: 'com.android.application' @@ -23,17 +23,17 @@ repositories { android { compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion '27.0.3' defaultConfig { applicationId "org.bookdash.android" - minSdkVersion 16 + minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.compileSdkVersion versionCode rootProject.ext.versionCode versionName "$rootProject.ext.versionName" testInstrumentationRunner "org.bookdash.android.presentation.CustomTestRunner" resConfigs "en" vectorDrawables.useSupportLibrary = true + multiDexEnabled true } dataBinding { @@ -126,91 +126,63 @@ dependencies { testImplementation("org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito") testImplementation("org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito") - // Android Testing Support Library's runner and rules - androidTestImplementation('com.android.support.test.espresso:espresso-web:2.2.1') { - exclude module: 'support-annotations' - exclude module: 'support-v4' - } - androidTestImplementation("com.android.support.test:runner:$rootProject.ext.runnerVersion") { - exclude module: 'support-annotations' - exclude module: 'support-v4' - } - androidTestImplementation("com.android.support.test:rules:$rootProject.ext.runnerVersion") { - exclude module: 'support-annotations' - exclude module: 'support-v4' - } + // 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' // Espresso UI Testing - androidTestImplementation("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion") { - exclude module: 'recyclerview-v7' - exclude module: 'support-annotations' - exclude module: 'support-v4' + androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0') { exclude group: "javax.inject" - - } - androidTestImplementation("com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion") { - exclude module: 'support-annotations' - exclude group: 'com.android.support', module: 'appcompat' - exclude group: 'com.android.support', module: 'support-v4' - exclude module: 'recyclerview-v7' } - androidTestImplementation("com.android.support.test.espresso:espresso-intents:$rootProject.ext.espressoVersion") { - exclude module: 'recyclerview-v7' - exclude module: 'support-annotations' - exclude module: 'support-v4' - } - androidTestImplementation "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" - androidTestImplementation "com.android.support:design:$rootProject.ext.supportLibraryVersion" + 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 "com.google.android.material:material:$rootProject.ext.materialVersion" implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':fabbutton') - implementation "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:cardview-v7:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:palette-v7:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:design:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:percent:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:preference-v7:$rootProject.ext.supportLibraryVersion" - implementation "com.android.support:preference-v14:$rootProject.ext.supportLibraryVersion" - implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation 'com.google.code.gson:gson:2.8.2' + implementation 'androidx.appcompat:appcompat:1.1.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.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.5.3@aar') { + implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { transitive = true } - implementation('za.co.riggaroo:materialhelptutorial:1.6.0') { - exclude module: 'support-v4' - } - implementation("com.google.firebase:firebase-invites:${rootProject.ext.googlePlayServicesVersion}") { - exclude module: 'support-v4' - } - implementation("com.google.firebase:firebase-core:${rootProject.ext.googlePlayServicesVersion}") { - exclude module: 'support-v4' - } - implementation "com.google.firebase:firebase-database:${rootProject.ext.googlePlayServicesVersion}" - implementation "com.google.firebase:firebase-messaging:${rootProject.ext.googlePlayServicesVersion}" - implementation("com.google.firebase:firebase-config:${rootProject.ext.googlePlayServicesVersion}") { - exclude module: 'support-v4' - } - implementation("com.google.firebase:firebase-storage:${rootProject.ext.googlePlayServicesVersion}") { - exclude module: 'support-v4' - } - implementation('com.firebaseui:firebase-ui-storage:0.6.1') { - exclude module: 'support-v4' - } - implementation "com.google.firebase:firebase-perf:${rootProject.ext.googlePlayServicesVersion}" + 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.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.6.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta5' - implementation "android.arch.lifecycle:extensions:1.1.0" + implementation 'com.jakewharton.timber:timber:4.7.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.multidex:multidex:2.0.1' + + implementation project(path: ':materialhelptutorial') + implementation project(':fabbutton') } + + +apply plugin: 'kotlin-kapt' apply plugin: 'com.google.gms.google-services' \ No newline at end of file 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 053fcf7..2bc1161 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/CustomTestRunner.java @@ -7,9 +7,10 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; -import android.support.test.runner.AndroidJUnitRunner; import android.util.Log; +import androidx.test.runner.AndroidJUnitRunner; + import java.lang.reflect.Method; /** diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/about/AboutFragmentTest.java b/app/src/androidTest/java/org/bookdash/android/presentation/about/AboutFragmentTest.java index 13be463..725c7cd 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/about/AboutFragmentTest.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/about/AboutFragmentTest.java @@ -2,13 +2,14 @@ import android.content.Intent; import android.net.Uri; -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.SmallTest; import android.text.Html; +import androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + import junit.framework.Assert; import org.bookdash.android.R; @@ -20,15 +21,15 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.scrollTo; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.intent.Intents.intended; -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.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; /** @@ -55,8 +56,8 @@ public void tearDown() { @Test public void loadAboutBookDash_SeeInformation() throws Throwable { - CharSequence about = Html.fromHtml(InstrumentationRegistry.getTargetContext().getString(R.string.why_bookdash)); - String headingAbout = InstrumentationRegistry.getTargetContext().getString(R.string.heading_about); + CharSequence about = Html.fromHtml(InstrumentationRegistry.getInstrumentation().getTargetContext().getString(R.string.why_bookdash)); + String headingAbout = InstrumentationRegistry.getInstrumentation().getTargetContext().getString(R.string.heading_about); onView(withText(headingAbout)).check(matches(isDisplayed())); 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 49f2a3b..8742446 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,9 +1,9 @@ package org.bookdash.android.presentation.listbooks; -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 androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; import org.bookdash.android.R; import org.bookdash.android.presentation.main.MainActivity; @@ -14,13 +14,13 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; @RunWith(AndroidJUnit4.class) diff --git a/app/src/androidTest/java/org/bookdash/android/presentation/utils/NavigationUtils.java b/app/src/androidTest/java/org/bookdash/android/presentation/utils/NavigationUtils.java index 81fe5f9..e934dd8 100644 --- a/app/src/androidTest/java/org/bookdash/android/presentation/utils/NavigationUtils.java +++ b/app/src/androidTest/java/org/bookdash/android/presentation/utils/NavigationUtils.java @@ -1,14 +1,14 @@ package org.bookdash.android.presentation.utils; import android.app.Activity; -import android.support.design.widget.NavigationView; +import com.google.android.material.navigation.NavigationView; import org.bookdash.android.R; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.isClickable; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.isClickable; +import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; diff --git a/app/src/androidTestMock/java/org.bookdash.android/presentation/bookinfo/BookInfoActivityTest.java b/app/src/androidTestMock/java/org.bookdash.android/presentation/bookinfo/BookInfoActivityTest.java index 1053a62..e3a8957 100644 --- a/app/src/androidTestMock/java/org.bookdash.android/presentation/bookinfo/BookInfoActivityTest.java +++ b/app/src/androidTestMock/java/org.bookdash.android/presentation/bookinfo/BookInfoActivityTest.java @@ -1,12 +1,13 @@ package org.bookdash.android.presentation.bookinfo; import android.content.Intent; -import android.support.test.espresso.intent.Intents; -import android.support.test.espresso.matcher.ViewMatchers; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; +import androidx.test.espresso.intent.Intents; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + import org.bookdash.android.domain.model.firebase.FireBookDetails; import org.junit.After; import org.junit.Before; @@ -14,10 +15,10 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; +import static androidx.test.espresso.matcher.ViewMatchers.withText; /** * @author rebeccafranks diff --git a/app/src/androidTestMock/java/org.bookdash.android/presentation/listbooks/ListBooksActivityTest.java b/app/src/androidTestMock/java/org.bookdash.android/presentation/listbooks/ListBooksActivityTest.java index 43bc582..f79e4fa 100644 --- a/app/src/androidTestMock/java/org.bookdash.android/presentation/listbooks/ListBooksActivityTest.java +++ b/app/src/androidTestMock/java/org.bookdash.android/presentation/listbooks/ListBooksActivityTest.java @@ -1,12 +1,12 @@ package org.bookdash.android.presentation.listbooks; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.ViewInteraction; -import android.support.test.espresso.intent.Intents; -import android.support.test.espresso.matcher.BoundedMatcher; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; +import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.intent.Intents; +import androidx.test.espresso.matcher.BoundedMatcher; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; import junit.framework.Assert; @@ -24,16 +24,16 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; -import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.is; /** diff --git a/app/src/androidTestMock/java/org.bookdash.android/presentation/search/SearchActivityTest.java b/app/src/androidTestMock/java/org.bookdash.android/presentation/search/SearchActivityTest.java index ec5869c..42e7e8e 100644 --- a/app/src/androidTestMock/java/org.bookdash.android/presentation/search/SearchActivityTest.java +++ b/app/src/androidTestMock/java/org.bookdash.android/presentation/search/SearchActivityTest.java @@ -1,14 +1,15 @@ package org.bookdash.android.presentation.search; -import android.support.test.espresso.ViewInteraction; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import androidx.test.espresso.ViewInteraction; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + import org.bookdash.android.R; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -17,14 +18,14 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; -import static android.support.test.espresso.action.ViewActions.replaceText; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withParent; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withParent; +import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; @LargeTest diff --git a/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/SplashScreenTest.java b/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/SplashScreenTest.java index 363f365..0537e8c 100644 --- a/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/SplashScreenTest.java +++ b/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/SplashScreenTest.java @@ -1,10 +1,10 @@ package org.bookdash.android.presentation.splash; -import android.support.test.espresso.Espresso; -import android.support.test.espresso.IdlingResource; -import android.support.test.espresso.intent.Intents; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.IdlingResource; +import androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; import org.bookdash.android.data.settings.FakeSettingsApiImpl; import org.bookdash.android.presentation.main.MainActivity; @@ -17,8 +17,8 @@ import za.co.riggaroo.materialhelptutorial.tutorial.MaterialTutorialActivity; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; /** * @author rebeccafranks diff --git a/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/util/ElapsedTimeIdlingResource.java b/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/util/ElapsedTimeIdlingResource.java index 0fb4d5f..05ef971 100644 --- a/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/util/ElapsedTimeIdlingResource.java +++ b/app/src/androidTestMock/java/org.bookdash.android/presentation/splash/util/ElapsedTimeIdlingResource.java @@ -1,6 +1,6 @@ package org.bookdash.android.presentation.splash.util; -import android.support.test.espresso.IdlingResource; +import androidx.test.espresso.IdlingResource; /** * @author rebeccafranks diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 955aec2..b981bfa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,9 +26,6 @@ android:label="@string/app_name" android:largeHeap="true" android:theme="@style/AppTheme"> - () + val keys = ArrayList() if (dataSnapshot.child(FireContributor.ROLES_SECTION).hasChildren()) { val children = dataSnapshot.child(FireContributor.ROLES_SECTION).children children.mapTo(keys) { it.key } @@ -77,7 +72,7 @@ class FirebaseBookDatabase(firebaseDatabase: FirebaseDatabase, Log.d(TAG, "Book Details:" + bookDetails?.bookTitle + ". Book URL:" + bookDetails ?.bookCoverPageUrl) bookDetails?.bookId = snap.key - val keys = ArrayList() + val keys = ArrayList() if (snap.child(FireBookDetails.CONTRIBUTORS_ITEM_NAME).hasChildren()) { val children = snap.child(FireBookDetails.CONTRIBUTORS_ITEM_NAME) .children diff --git a/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositories.java b/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositories.java index 175eb39..5fbc3dc 100644 --- a/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositories.java +++ b/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositories.java @@ -1,6 +1,6 @@ package org.bookdash.android.data.settings; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.firebase.messaging.FirebaseMessaging; diff --git a/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositoryImpl.java b/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositoryImpl.java index 16d1bea..595fa9e 100644 --- a/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositoryImpl.java +++ b/app/src/main/java/org/bookdash/android/data/settings/SettingsRepositoryImpl.java @@ -1,6 +1,6 @@ package org.bookdash.android.data.settings; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.firebase.messaging.FirebaseMessaging; diff --git a/app/src/main/java/org/bookdash/android/data/tracking/BookDashFirebaseAnalytics.java b/app/src/main/java/org/bookdash/android/data/tracking/BookDashFirebaseAnalytics.java index 9229939..beae538 100644 --- a/app/src/main/java/org/bookdash/android/data/tracking/BookDashFirebaseAnalytics.java +++ b/app/src/main/java/org/bookdash/android/data/tracking/BookDashFirebaseAnalytics.java @@ -1,7 +1,7 @@ package org.bookdash.android.data.tracking; import android.os.Bundle; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.firebase.analytics.FirebaseAnalytics; diff --git a/app/src/main/java/org/bookdash/android/data/tutorial/TutorialsRepositories.java b/app/src/main/java/org/bookdash/android/data/tutorial/TutorialsRepositories.java index 74becc4..8111cfc 100644 --- a/app/src/main/java/org/bookdash/android/data/tutorial/TutorialsRepositories.java +++ b/app/src/main/java/org/bookdash/android/data/tutorial/TutorialsRepositories.java @@ -1,7 +1,7 @@ package org.bookdash.android.data.tutorial; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; public class TutorialsRepositories { 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 5ba87c8..864fbf0 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 @@ -14,6 +14,7 @@ import org.threeten.bp.ZonedDateTime; import org.threeten.bp.format.DateTimeFormatter; import org.threeten.bp.format.FormatStyle; +import org.threeten.bp.zone.ZoneRulesException; import java.io.File; import java.util.Comparator; @@ -53,7 +54,7 @@ public int compare(final FireBookDetails bookDetails, final FireBookDetails book private String bookLanguageAbbreviation = "en"; private String bookId; private String bookDescription; - private List contributors; + private List contributorsIndexedKeys; private boolean isDownloading; private String bookUrl; private long createdDate; @@ -82,7 +83,7 @@ protected FireBookDetails(Parcel in) { this.bookLanguage = in.readString(); this.bookId = in.readString(); this.bookDescription = in.readString(); - this.contributors = in.createStringArrayList(); + this.contributorsIndexedKeys = in.createStringArrayList(); this.isDownloading = in.readByte() != 0; this.createdDate = in.readLong(); this.bookLanguageAbbreviation = in.readString(); @@ -191,7 +192,7 @@ public void setBookDescription(String bookDescription) { } public List getContributorsIndexList() { - return contributors; + return contributorsIndexedKeys; } @Override @@ -208,7 +209,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.bookLanguage); dest.writeString(this.bookId); dest.writeString(this.bookDescription); - dest.writeStringList(this.contributors); + dest.writeStringList(this.contributorsIndexedKeys); dest.writeByte(this.isDownloading ? (byte) 1 : (byte) 0); dest.writeLong(this.createdDate); dest.writeString(this.bookLanguageAbbreviation); @@ -223,13 +224,20 @@ public void setBookLanguage(String bookLanguage) { } public void setContributorsIndexedKeys(List contributorsIndexedKeys) { - this.contributors = contributorsIndexedKeys; + this.contributorsIndexedKeys = contributorsIndexedKeys; } public String getCreatedDateFormatted() { Log.d("bookdateials", "getCreatedDateFormatted() called:" + createdDate); Instant i = Instant.ofEpochMilli(createdDate); - ZonedDateTime z = ZonedDateTime.ofInstant(i, ZoneId.systemDefault()); + ZoneId zoneId; + try { + zoneId = ZoneId.systemDefault(); + } catch (ZoneRulesException zre) { + // Fallback. Just show the date to the user as if they're in South Africa. + zoneId = ZoneId.of("UTC+02:00"); + } + ZonedDateTime z = ZonedDateTime.ofInstant(i, zoneId); return z.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)); } diff --git a/app/src/main/java/org/bookdash/android/presentation/about/AboutFragment.java b/app/src/main/java/org/bookdash/android/presentation/about/AboutFragment.java index a18390e..6e8da40 100644 --- a/app/src/main/java/org/bookdash/android/presentation/about/AboutFragment.java +++ b/app/src/main/java/org/bookdash/android/presentation/about/AboutFragment.java @@ -4,11 +4,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -17,6 +12,12 @@ import android.widget.Button; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; + import org.bookdash.android.R; import org.bookdash.android.presentation.main.NavDrawerInterface; diff --git a/app/src/main/java/org/bookdash/android/presentation/activity/BaseAppCompatActivity.java b/app/src/main/java/org/bookdash/android/presentation/activity/BaseAppCompatActivity.java index 0761f5e..fa4cd5e 100644 --- a/app/src/main/java/org/bookdash/android/presentation/activity/BaseAppCompatActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/activity/BaseAppCompatActivity.java @@ -1,8 +1,7 @@ package org.bookdash.android.presentation.activity; -import android.content.Context; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; 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 aec4ab0..3786674 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 @@ -3,27 +3,15 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.databinding.DataBindingUtil; import android.graphics.Bitmap; -import android.graphics.Typeface; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.annotation.StringRes; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CollapsingToolbarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.ColorUtils; -import android.support.v7.app.ActionBar; -import android.support.v7.graphics.Palette; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.transition.Transition; import android.util.DisplayMetrics; import android.util.Log; @@ -39,14 +27,30 @@ import android.widget.ProgressBar; import android.widget.TextView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.Toolbar; +import androidx.cardview.widget.CardView; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.ColorUtils; +import androidx.databinding.DataBindingUtil; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.bumptech.glide.request.target.SimpleTarget; -import com.firebase.ui.storage.images.FirebaseImageLoader; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.snackbar.Snackbar; import com.google.firebase.storage.StorageReference; import org.bookdash.android.Injection; import org.bookdash.android.R; +import org.bookdash.android.config.GlideApp; import org.bookdash.android.databinding.ActivityBookInformationBinding; import org.bookdash.android.domain.model.firebase.FireBookDetails; import org.bookdash.android.domain.model.firebase.FireContributor; @@ -263,15 +267,39 @@ protected void onNewIntent(Intent intent) { } private void loadImage(StorageReference url) { - Glide.with(this).using(new FirebaseImageLoader()).load(url).asBitmap().into(new SimpleTarget() { - @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { - onImageLoaded(resource); - extractPaletteColors(resource); - } - }); + GlideApp.with(this).load(url) + .transition(DrawableTransitionOptions.withCrossFade()) + .into(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { + + Bitmap bitmap = drawableToBitmap(resource); + onImageLoaded(bitmap); + extractPaletteColors(bitmap); + } + }); } + public static Bitmap drawableToBitmap (Drawable drawable) { + Bitmap bitmap = null; + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if(bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } private void onImageLoaded(Bitmap bitmap) { imageViewBook.setImageBitmap(bitmap); diff --git a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoContract.java b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoContract.java index 30484e6..c55b30e 100644 --- a/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoContract.java +++ b/app/src/main/java/org/bookdash/android/presentation/bookinfo/BookInfoContract.java @@ -1,6 +1,6 @@ package org.bookdash.android.presentation.bookinfo; -import android.support.annotation.StringRes; +import androidx.annotation.StringRes; import org.bookdash.android.domain.model.firebase.FireBookDetails; import org.bookdash.android.domain.model.firebase.FireContributor; 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 45b9347..e418f6c 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 @@ -1,6 +1,6 @@ package org.bookdash.android.presentation.bookinfo; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.bookdash.android.R; import org.bookdash.android.data.book.BookService; @@ -9,6 +9,7 @@ import org.bookdash.android.domain.model.DownloadProgressItem; import org.bookdash.android.domain.model.firebase.FireBookDetails; import org.bookdash.android.domain.model.firebase.FireContributor; +import org.bookdash.android.domain.model.gson.BookPages; import org.bookdash.android.presentation.base.BasePresenter; import java.util.List; @@ -96,14 +97,15 @@ public void onNext(DownloadProgressItem downloadProgressItem) { getView().showDownloadProgress(downloadProgressItem.getDownloadProgress()); if (downloadProgressItem.isComplete()) { analytics.trackViewBook(bookInfo); - if (downloadProgressItem.getBookPages() == null) { + BookPages bookPages = downloadProgressItem.getBookPages(); + if (bookPages == null) { getView().showSnackBarMessage(R.string.failed_to_open_book); analytics.trackDownloadBookFailed(bookInfo, "failed_to_open_book"); return; } getView().showDownloadFinished(); bookInfo.setIsDownloading(false); - getView().openBook(bookInfo, downloadProgressItem.getBookPages(), + getView().openBook(bookInfo, bookPages, bookInfo.getFolderLocation()); } } diff --git a/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorAdapter.java b/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorAdapter.java index bca1f3f..4201355 100644 --- a/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorAdapter.java +++ b/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorAdapter.java @@ -1,15 +1,14 @@ package org.bookdash.android.presentation.bookinfo; 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 com.firebase.ui.storage.images.FirebaseImageLoader; +import androidx.recyclerview.widget.RecyclerView; import org.bookdash.android.R; +import org.bookdash.android.config.GlideApp; import org.bookdash.android.domain.model.firebase.FireContributor; import java.util.List; @@ -35,7 +34,7 @@ public void onBindViewHolder(ContributorViewHolder holder, int position) { holder.textViewContributor.setText(item.getName()); holder.textViewRole.setText(item.getActualRolesFormatted()); - Glide.with(context).using(new FirebaseImageLoader()).load(item.getFirebaseAvatar()) + GlideApp.with(context).load(item.getFirebaseAvatar()) .placeholder(R.drawable.placeholder_avatar).error(R.drawable.placeholder_avatar) .into(holder.imageViewContributorAvatar); diff --git a/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorViewHolder.java b/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorViewHolder.java index 2dc8619..b6d4273 100644 --- a/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorViewHolder.java +++ b/app/src/main/java/org/bookdash/android/presentation/bookinfo/ContributorViewHolder.java @@ -1,10 +1,11 @@ package org.bookdash.android.presentation.bookinfo; -import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + import org.bookdash.android.R; 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 index 0450f0e..05b6393 100644 --- a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsAdapter.java @@ -1,15 +1,14 @@ 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 com.firebase.ui.storage.images.FirebaseImageLoader; +import androidx.recyclerview.widget.RecyclerView; import org.bookdash.android.R; +import org.bookdash.android.config.GlideApp; import org.bookdash.android.domain.model.firebase.FireBookDetails; import java.util.List; @@ -40,7 +39,7 @@ public DownloadsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public void onBindViewHolder(DownloadsViewHolder holder, int position) { FireBookDetails book = bookList.get(position); holder.downloadTitleTextView.setText(book.getBookTitle()); - Glide.with(context).using(new FirebaseImageLoader()).load(book.getFirebaseBookCoverUrl()).into(holder.downloadImageTextView); + GlideApp.with(context).load(book.getFirebaseBookCoverUrl()).into(holder.downloadImageTextView); holder.downloadActionButtonView.setOnClickListener(deleteClickListener); holder.book = book; holder.downloadActionButtonView.setTag(holder); 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 index 70ba81f..297d948 100644 --- a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsFragment.java @@ -4,15 +4,6 @@ import android.content.DialogInterface; import android.content.Intent; 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.ActionBar; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -23,6 +14,17 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; + import org.bookdash.android.Injection; import org.bookdash.android.R; import org.bookdash.android.domain.model.firebase.FireBookDetails; 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 index 6f3effe..6e4b8c5 100644 --- a/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java +++ b/app/src/main/java/org/bookdash/android/presentation/downloads/DownloadsViewHolder.java @@ -1,11 +1,12 @@ 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 androidx.recyclerview.widget.RecyclerView; + import org.bookdash.android.R; import org.bookdash.android.domain.model.firebase.FireBookDetails; diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/BookAdapter.java b/app/src/main/java/org/bookdash/android/presentation/listbooks/BookAdapter.java index de4158e..4a2cba0 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/BookAdapter.java +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/BookAdapter.java @@ -1,16 +1,17 @@ package org.bookdash.android.presentation.listbooks; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.bumptech.glide.Glide; -import com.firebase.ui.storage.images.FirebaseImageLoader; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import org.bookdash.android.R; +import org.bookdash.android.config.GlideApp; import org.bookdash.android.domain.model.firebase.FireBookDetails; import java.util.List; @@ -42,7 +43,8 @@ public void onBindViewHolder(BookViewHolder holder, int position) { FireBookDetails bookDetail = bookDetails.get(position); holder.bookTitle.setText(bookDetail.getBookTitle()); Log.d("BookAdapter", "Book url:" + bookDetail.getBookCoverPageUrl()); - Glide.with(context).using(new FirebaseImageLoader()).load(bookDetail.getFirebaseBookCoverUrl()) + GlideApp.with(context).load(bookDetail.getFirebaseBookCoverUrl()) + .transition(DrawableTransitionOptions.withCrossFade()) .placeholder(R.drawable.bookdash_placeholder).error(R.drawable.bookdash_placeholder) .into(holder.bookCover); holder.bookDetail = bookDetail; diff --git a/app/src/main/java/org/bookdash/android/presentation/listbooks/BookViewHolder.java b/app/src/main/java/org/bookdash/android/presentation/listbooks/BookViewHolder.java index 6ab4b0e..2879d0b 100644 --- a/app/src/main/java/org/bookdash/android/presentation/listbooks/BookViewHolder.java +++ b/app/src/main/java/org/bookdash/android/presentation/listbooks/BookViewHolder.java @@ -1,11 +1,12 @@ package org.bookdash.android.presentation.listbooks; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; + import org.bookdash.android.R; import org.bookdash.android.domain.model.firebase.FireBookDetails; 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 e1f7544..4d4ed37 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 @@ -5,15 +5,6 @@ import android.content.DialogInterface; import android.content.Intent; 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.ActionBar; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -25,6 +16,17 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; + import org.bookdash.android.Injection; import org.bookdash.android.R; import org.bookdash.android.domain.model.firebase.FireBookDetails; 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 109410a..028ab35 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 @@ -161,8 +161,14 @@ public void onCompleted() { @Override public void onError(final Throwable e) { - getView().showLoading(false); - getView().showErrorScreen(true, e.getMessage(), true); + + ListBooksContract.View view = getView(); + if (view == null) { + return; + } + + view.showLoading(false); + view.showErrorScreen(true, e.getMessage(), true); } @Override diff --git a/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java b/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java index 30e1a7f..300b98b 100644 --- a/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/main/MainActivity.java @@ -4,29 +4,23 @@ import android.content.Intent; 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.Toolbar; import android.text.Html; import android.text.method.LinkMovementMethod; -import android.util.Log; import android.view.MenuItem; import android.view.View; 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; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.google.android.material.navigation.NavigationView; +import com.google.android.material.snackbar.Snackbar; import org.bookdash.android.BuildConfig; import org.bookdash.android.Injection; @@ -44,7 +38,6 @@ public class MainActivity extends BaseAppCompatActivity implements MainContract. private static final String TAG = "MainActivity"; private static final String GOOGLE_PLAY_STORE_URL = "http://play.google.com/store/apps/details?id="; private static final String GOOGLE_PLAY_MARKET_URL = "market://details?id="; - private GoogleApiClient googleApiClient; private DrawerLayout drawerLayout; private NavigationView navigationView; private MainContract.MainUserActions mainPresenter; @@ -66,7 +59,6 @@ protected void onCreate(Bundle savedInstanceState) { } setUpNavDrawer(); - checkIfComingFromInvite(); showAllBooks(); } @@ -121,28 +113,6 @@ public boolean onNavigationItemSelected(MenuItem menuItem) { }); } - private void checkIfComingFromInvite() { - googleApiClient = new GoogleApiClient.Builder(this).addApi(AppInvite.API) - .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - Log.d(TAG, "onConnectionFailed: onResult:" + connectionResult.toString()); - - } - }).build(); - if (googleApiClient != null) { - googleApiClient.connect(); - - AppInvite.AppInviteApi.getInvitation(googleApiClient, this, true) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(AppInviteInvitationResult result) { - Log.d(TAG, "getInvitation:onResult:" + result.getStatus()); - } - }); - } - } - private void showAllBooks() { mainPresenter.clickViewAllBooks(); } @@ -184,8 +154,12 @@ public void showRatingPlayStore() { try { startActivity(goToMarket); } catch (ActivityNotFoundException e) { - startActivity( - new Intent(Intent.ACTION_VIEW, Uri.parse(GOOGLE_PLAY_STORE_URL + BuildConfig.APPLICATION_ID))); + try { + startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(GOOGLE_PLAY_STORE_URL + BuildConfig.APPLICATION_ID))); + } catch (ActivityNotFoundException anfe) { + Snackbar.make(navigationView, R.string.error_opening_app_rating, Snackbar.LENGTH_LONG); + } } } @@ -210,14 +184,15 @@ public void showDownloadedBooksPage() { @Override public void inviteFriends() { try { - Intent intent = new AppInviteInvitation.IntentBuilder(getString(R.string.invitation_title)) - .setMessage(getString(R.string.invitation_message)) - .setCallToActionText(getString(R.string.invitation_cta)) - // .setDeepLink(Uri.parse("http://bookdash.org/books/dK5BJWxPIf")) - .build(); - startActivityForResult(intent, INVITE_REQUEST_CODE); + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.invitation_message)); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.invitation_subject)); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, + getResources().getText(R.string.invite_using))); } catch (ActivityNotFoundException ac) { - Snackbar.make(navigationView, R.string.common_google_play_services_unsupported_text, Snackbar.LENGTH_LONG) + Snackbar.make(navigationView, R.string.invite_error_no_apps_found, Snackbar.LENGTH_LONG) .show(); } } @@ -227,44 +202,6 @@ public String getScreenName() { return "MainActivity"; } - @Override - protected void onStart() { - super.onStart(); - if (googleApiClient != null) { - googleApiClient.connect(); - } - } - - @Override - protected void onStop() { - super.onStop(); - if (googleApiClient != null) { - - googleApiClient.disconnect(); - - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - Log.d(TAG, "onActivityResult: requestCode=" + requestCode + ", resultCode=" + resultCode); - - if (requestCode == INVITE_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - // Check how many invitations were sent and log a message - // The ids array contains the unique invitation ids for each invitation sent - // (one for each contact select by the user). You can use these for analytics - // as the ID will be consistent on the sending and receiving devices. - String[] ids = AppInviteInvitation.getInvitationIds(resultCode, data); - Log.d(TAG, getString(R.string.sent_invitations_fmt, ids.length)); - } else { - // Sending failed or it was canceled, show failure message to the user - Log.d(TAG, "invite send failed:" + requestCode + ",resultCode:" + resultCode); - } - } - } - @Override public void openNavDrawer() { drawerLayout.openDrawer(navigationView); diff --git a/app/src/main/java/org/bookdash/android/presentation/main/NavDrawerInterface.java b/app/src/main/java/org/bookdash/android/presentation/main/NavDrawerInterface.java index 47bedb6..0592339 100644 --- a/app/src/main/java/org/bookdash/android/presentation/main/NavDrawerInterface.java +++ b/app/src/main/java/org/bookdash/android/presentation/main/NavDrawerInterface.java @@ -1,6 +1,6 @@ package org.bookdash.android.presentation.main; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; public interface NavDrawerInterface { 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 3d60cdd..eeae7bc 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 @@ -1,12 +1,11 @@ package org.bookdash.android.presentation.readbook -import android.arch.lifecycle.Observer -import android.arch.lifecycle.ViewModelProviders import android.os.Bundle -import android.support.v4.view.ViewPager import android.view.ViewTreeObserver import android.view.WindowManager - +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.viewpager.widget.ViewPager import org.bookdash.android.R import org.bookdash.android.domain.model.gson.BookPages import org.bookdash.android.presentation.activity.BaseAppCompatActivity 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 08a1212..283e6ba 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 @@ -1,15 +1,15 @@ package org.bookdash.android.presentation.readbook; -import android.support.v4.app.FixedFragmentStatePagerAdapter; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; import org.bookdash.android.domain.model.gson.Page; import java.util.List; -public class PageAdapter extends FixedFragmentStatePagerAdapter { +public class PageAdapter extends FragmentStatePagerAdapter { private final String rootFileLocation; private List pages; diff --git a/app/src/main/java/org/bookdash/android/presentation/readbook/PageFragment.kt b/app/src/main/java/org/bookdash/android/presentation/readbook/PageFragment.kt index af915e4..fc3d369 100644 --- a/app/src/main/java/org/bookdash/android/presentation/readbook/PageFragment.kt +++ b/app/src/main/java/org/bookdash/android/presentation/readbook/PageFragment.kt @@ -1,20 +1,17 @@ package org.bookdash.android.presentation.readbook -import android.arch.lifecycle.ViewModelProviders -import android.databinding.BindingAdapter import android.os.Bundle -import android.support.v4.app.Fragment import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView - +import androidx.databinding.BindingAdapter +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProviders import com.bumptech.glide.Glide - import org.bookdash.android.databinding.FragmentPageBinding import org.bookdash.android.domain.model.gson.Page - import java.io.File diff --git a/app/src/main/java/org/bookdash/android/presentation/readbook/ReadBookViewModel.kt b/app/src/main/java/org/bookdash/android/presentation/readbook/ReadBookViewModel.kt index ea1d668..e3205a3 100644 --- a/app/src/main/java/org/bookdash/android/presentation/readbook/ReadBookViewModel.kt +++ b/app/src/main/java/org/bookdash/android/presentation/readbook/ReadBookViewModel.kt @@ -1,7 +1,6 @@ package org.bookdash.android.presentation.readbook -import android.arch.lifecycle.AndroidViewModel -import android.arch.lifecycle.ViewModel +import androidx.lifecycle.ViewModel import org.bookdash.android.presentation.utils.SingleLiveEvent diff --git a/app/src/main/java/org/bookdash/android/presentation/search/SearchActivity.java b/app/src/main/java/org/bookdash/android/presentation/search/SearchActivity.java index bf3344a..2e78e38 100644 --- a/app/src/main/java/org/bookdash/android/presentation/search/SearchActivity.java +++ b/app/src/main/java/org/bookdash/android/presentation/search/SearchActivity.java @@ -2,19 +2,23 @@ import android.app.Activity; import android.content.Intent; +import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.Window; import android.widget.Button; import android.widget.TextView; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.bookdash.android.Injection; import org.bookdash.android.R; import org.bookdash.android.domain.model.firebase.FireBookDetails; @@ -98,8 +102,19 @@ public void onClick(final View view) { searchPresenter.search(searchQuery); } }); + setStatusBarColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimaryDark)); hideLoading(); } + private void setStatusBarColor(int color) { + if (isFinishing()) { + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.setStatusBarColor(color); + } + } @Override protected String getScreenName() { diff --git a/app/src/main/java/org/bookdash/android/presentation/settings/SettingsFragment.java b/app/src/main/java/org/bookdash/android/presentation/settings/SettingsFragment.java index cd6d2af..5bfe040 100644 --- a/app/src/main/java/org/bookdash/android/presentation/settings/SettingsFragment.java +++ b/app/src/main/java/org/bookdash/android/presentation/settings/SettingsFragment.java @@ -3,15 +3,16 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceFragmentCompat; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + import org.bookdash.android.Injection; import org.bookdash.android.R; import org.bookdash.android.data.settings.SettingsApiImpl; 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 ca1e760..c3fb50c 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 @@ -3,7 +3,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import org.bookdash.android.Injection; import org.bookdash.android.R; diff --git a/app/src/main/java/org/bookdash/android/presentation/utils/GlideLoadingModule.java b/app/src/main/java/org/bookdash/android/presentation/utils/GlideLoadingModule.java deleted file mode 100644 index b2ee80b..0000000 --- a/app/src/main/java/org/bookdash/android/presentation/utils/GlideLoadingModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.bookdash.android.presentation.utils; - -import android.content.Context; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.GlideBuilder; -import com.bumptech.glide.load.DecodeFormat; -import com.bumptech.glide.module.GlideModule; - - -public class GlideLoadingModule implements GlideModule { - @Override - public void applyOptions(Context context, GlideBuilder builder) { - builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); - } - - @Override - public void registerComponents(Context context, Glide glide) { - // glide.register(Model.class, Data.class, new MyModelLoader()); - } - - -} diff --git a/app/src/main/java/org/bookdash/android/presentation/utils/SingleLiveEvent.java b/app/src/main/java/org/bookdash/android/presentation/utils/SingleLiveEvent.java index dec2b63..25dea4f 100644 --- a/app/src/main/java/org/bookdash/android/presentation/utils/SingleLiveEvent.java +++ b/app/src/main/java/org/bookdash/android/presentation/utils/SingleLiveEvent.java @@ -1,12 +1,14 @@ package org.bookdash.android.presentation.utils; -import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.Observer; -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; import android.util.Log; +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + import java.util.concurrent.atomic.AtomicBoolean; /** @@ -26,7 +28,8 @@ public class SingleLiveEvent extends MutableLiveData { private final AtomicBoolean mPending = new AtomicBoolean(false); @MainThread - public void observe(LifecycleOwner owner, final Observer observer) { + @Override + public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer observer) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); @@ -44,6 +47,7 @@ public void onChanged(@Nullable T t) { } @MainThread + @Override public void setValue(@Nullable T t) { mPending.set(true); super.setValue(t); diff --git a/app/src/main/res/layout-land/activity_book_information.xml b/app/src/main/res/layout-land/activity_book_information.xml index 59fbe95..81a5177 100644 --- a/app/src/main/res/layout-land/activity_book_information.xml +++ b/app/src/main/res/layout-land/activity_book_information.xml @@ -75,7 +75,7 @@ - - --> - --> - + - - - - + android:layout_marginBottom="4dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_marginTop="16dp" + android:visibility="gone" + app:cardCornerRadius="8dp"> + + + android:scaleType="centerCrop" + android:transitionName="@string/transition_book"/> + android:id="@+id/rel_top_title_section" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toEndOf="@+id/image_view_book_cover" + android:layout_toRightOf="@+id/image_view_book_cover" + android:minHeight="56dp"> + android:id="@+id/textView_book_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_centerVertical="true" + android:layout_marginBottom="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="8dp" + android:fontFamily="sans-serif" + android:text="@{bookInfo.bookTitle}" + android:textColor="@color/text_color_primary_dark" + android:textSize="24sp"/> + android:id="@+id/text_view_date_book_created" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_below="@id/textView_book_title" + android:layout_centerVertical="true" + android:layout_marginBottom="8dp" + android:layout_marginLeft="8dp" + android:layout_marginTop="8dp" + android:fontFamily="sans-serif" + android:gravity="center_vertical" + android:text="@{bookInfo.getCreatedDateFormatted()}" + android:textColor="@color/text_secondary_dark" + android:textSize="14sp"/> + android:id="@+id/view_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_below="@id/rel_top_title_section" + android:layout_toEndOf="@+id/image_view_book_cover" + android:layout_toRightOf="@+id/image_view_book_cover" + android:background="@color/divider_gray"/> + android:id="@+id/textView2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/view_divider" + android:layout_marginBottom="24dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_marginTop="32dp" + android:layout_toEndOf="@+id/image_view_book_cover" + android:layout_toRightOf="@+id/image_view_book_cover" + android:fontFamily="sans-serif" + android:text="@{bookInfo.bookDescription}" + android:textColor="@color/text_color_primary_dark" + android:textSize="18sp"/> - - - + - + android:layout_marginBottom="16dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_marginTop="8dp" + android:paddingTop="4dp" + android:visibility="gone" + app:cardCornerRadius="8dp"> - - - - + + + + + - + - + - + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 345ecb1..1b83823 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,4 +1,4 @@ - - - - + - + android:layout_marginTop="4dp" + android:padding="16dp" + app:cardCornerRadius="8dp"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + android:id="@+id/text_view_about" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginTop="16dp" + android:breakStrategy="high_quality" + android:hyphenationFrequency="full" + android:text="@string/heading_about" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textIsSelectable="true" + android:textSize="16sp"> - + - + android:layout_marginTop="4dp" + android:padding="16dp" + app:cardCornerRadius="8dp"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="8dp"> + android:id="@+id/imageViewLogo" + android:layout_width="wrap_content" + android:layout_height="200dp" + android:layout_gravity="center" + android:layout_marginBottom="8dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_marginTop="8dp" + app:srcCompat="@drawable/bookdash_logo"/> - - - + - + android:layout_marginBottom="4dp" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" - + + + android:orientation="vertical"> - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:breakStrategy="high_quality" - android:fontFamily="sans-serif" - android:hyphenationFrequency="normal" - android:text="@string/why_bookdash" - android:textIsSelectable="true" - android:textSize="16sp"/> +