From 5a3369c4feae8ddb6c7b7143effc8346454b0a4a Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Sun, 3 Mar 2019 21:19:48 -0500 Subject: [PATCH 01/19] enable minify change app version to debug --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4db72e6..17c395e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'kotlin-kapt' apply plugin: 'io.fabric' ext.app = [ - 'version' : '2.1.1', + 'version' : '2.2.1-debug', 'version_code' : 16, 'compile_sdk_version': 28, 'min_sdk_version' : 21, @@ -44,8 +44,8 @@ android { } release { - shrinkResources false - minifyEnabled false + shrinkResources true + minifyEnabled true } } From 37bdca6545ddce130202343701676778f5a9ea1b Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Sun, 3 Mar 2019 21:20:00 -0500 Subject: [PATCH 02/19] increment dependency version --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 7439bc2..d96611e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -10,7 +10,7 @@ ext.versions = [ paging : '2.1.0', browser : '1.0.0', retrofit : '2.5.0', - fabric_gradle : '1.27.0', + fabric_gradle : '1.27.1', dagger : '2.21', lottie : '2.8.0', glide : '4.9.0', @@ -18,7 +18,7 @@ ext.versions = [ detekt_ktlint : '1.0.0-RC14', firebase_core : '16.0.7', firebase_config : '16.3.0', - firebase_firestore : '18.0.1', + firebase_firestore : '18.1.0', okHttp : '3.13.1', jSoup : '1.11.3', gson : '2.8.5', From 9296e9e3bff535502272211cfb98a330c31e9de1 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Sun, 3 Mar 2019 21:20:21 -0500 Subject: [PATCH 03/19] change snackbar animation due to glitch in fade animation --- .../com/droidfeed/ui/module/feed/FeedFragment.kt | 13 +++++++------ .../main/java/com/droidfeed/util/AppRateHelper.kt | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt index 0f8e90e..fc0ac31 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt @@ -86,6 +86,12 @@ class FeedFragment : BaseFragment("feed"), Scrollable { return binding.root } + private fun subscribePosts() { + feedViewModel.postsLiveData.observe(viewLifecycleOwner, Observer { pagedList -> + paginatedAdapter.submitList(pagedList as PagedList) + }) + } + private fun subscribePlayStoreEvent() { feedViewModel.openPlayStorePage.observe(viewLifecycleOwner, EventObserver { startActivity(rateAppIntent) @@ -139,12 +145,6 @@ class FeedFragment : BaseFragment("feed"), Scrollable { } } - private fun subscribePosts() { - feedViewModel.postsLiveData.observe(viewLifecycleOwner, Observer { pagedList -> - paginatedAdapter.submitList(pagedList as PagedList) - }) - } - private fun subscribePostOpenEvent() { feedViewModel.openPostDetail.observe(viewLifecycleOwner, EventObserver { post -> customTab.showTab(post.link) @@ -165,6 +165,7 @@ class FeedFragment : BaseFragment("feed"), Scrollable { Snackbar.LENGTH_LONG ).apply { setActionTextColor(Color.YELLOW) + animationMode = Snackbar.ANIMATION_MODE_SLIDE setAction(R.string.undo) { onUndo() } }.run { show() diff --git a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt index 06aa3fd..fa950aa 100644 --- a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt +++ b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt @@ -12,6 +12,7 @@ class AppRateHelper @Inject constructor(private val sharedPrefs: SharedPreferenc fun showRateSnackbar(view: View, onAction: () -> Unit) { Snackbar.make(view, R.string.like_to_review_df, Snackbar.LENGTH_LONG) .setAction(R.string.yes) { onAction() } + .setAnimationMode(Snackbar.ANIMATION_MODE_SLIDE) .setActionTextColor( ContextCompat.getColor( view.context, From cea4021add6c3169a5609ae15d23d291e0710ff0 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Wed, 13 Mar 2019 23:35:02 -0400 Subject: [PATCH 04/19] add NewsletterViewModel tests --- .../module/newsletter/NewsletterViewModel.kt | 10 +++-- .../com/droidfeed/util/extention/Stringx.kt | 4 ++ .../com/droidfeed/NewsletterViewModelTest.kt | 41 ++++++++++++++++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt index 4753654..d18adaa 100644 --- a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt @@ -1,7 +1,6 @@ package com.droidfeed.ui.module.newsletter import android.util.Log -import android.util.Patterns import androidx.annotation.StringRes import androidx.lifecycle.MutableLiveData import com.droidfeed.BuildConfig @@ -15,6 +14,7 @@ import com.droidfeed.data.repo.NewsletterRepo import com.droidfeed.ui.common.BaseViewModel import com.droidfeed.util.AnalyticsUtil import com.droidfeed.util.event.Event +import com.droidfeed.util.extention.isValidEmail import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.net.UnknownHostException @@ -45,18 +45,20 @@ class NewsletterViewModel @Inject constructor( fun signUp(email: String) = launch(Dispatchers.IO) { isSignButtonVisible.postValue(false) isProgressVisible.postValue(true) - errorText.postValue(R.string.empty_string) val isValid = when { email.isBlank() -> { errorText.postValue(R.string.error_empty_email) false } - !Patterns.EMAIL_ADDRESS.matcher(email).matches() -> { + !email.isValidEmail() -> { errorText.postValue(R.string.error_email_format) false } - else -> true + else -> { + errorText.postValue(R.string.empty_string) + true + } } if (isValid) { diff --git a/app/src/main/java/com/droidfeed/util/extention/Stringx.kt b/app/src/main/java/com/droidfeed/util/extention/Stringx.kt index 4f3ad4e..f543640 100644 --- a/app/src/main/java/com/droidfeed/util/extention/Stringx.kt +++ b/app/src/main/java/com/droidfeed/util/extention/Stringx.kt @@ -5,6 +5,7 @@ import android.text.SpannableString import android.text.Spanned import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan +import android.util.Patterns import android.view.View import androidx.annotation.ColorInt import com.droidfeed.util.logThrowable @@ -71,3 +72,6 @@ fun String.asTimestamp( 0L } } + +fun String.isValidEmail(): Boolean = !isNullOrBlank() && + Patterns.EMAIL_ADDRESS.matcher(this).matches() \ No newline at end of file diff --git a/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt b/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt index 9a205f6..c4e5cc7 100644 --- a/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt +++ b/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt @@ -1,35 +1,66 @@ package com.droidfeed +import android.util.Patterns import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import com.droidfeed.data.repo.NewsletterRepo import com.droidfeed.ui.module.newsletter.NewsletterViewModel import com.droidfeed.util.AnalyticsUtil +import kotlinx.coroutines.runBlocking +import org.junit.Assert +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify @Suppress("TestFunctionName") @RunWith(JUnit4::class) class NewsletterViewModelTest { - @get:Rule + @Rule + @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule() private val newsletterRepo = Mockito.mock(NewsletterRepo::class.java) private val analyticsUtil = Mockito.mock(AnalyticsUtil::class.java) - private val viewModel = NewsletterViewModel(newsletterRepo, analyticsUtil) + private lateinit var viewModel: NewsletterViewModel + + @Before + fun setup() { + viewModel = NewsletterViewModel(newsletterRepo, analyticsUtil) + } @Test fun match_default_view_state() { + Assert.assertFalse(viewModel.isProgressVisible.value!!) + Assert.assertTrue(viewModel.isSignButtonVisible.value!!) } @Test - fun GIVEN_invalid_email_input_WHEN_sign_up_clicked_THEN_show_invalid_email_error() { + fun GIVEN_empty_email_input_WHEN_sign_up_clicked_THEN_show_empty_email_error() = runBlocking { + val observer = mock>() + viewModel.errorText.observeForever(observer) + + viewModel.signUp("") + + verify(observer).onChanged(R.string.error_empty_email) } @Test - fun GIVEN_valid_email_input_WHEN_sign_up_clicked_THEN_show_progress_hide_button() { - } + fun GIVEN_valid_email_input_WHEN_sign_up_clicked_THEN_show_progress_hide_button() = + runBlocking { + val progressObserver = mock>() + val buttonObserver = mock>() + viewModel.isProgressVisible.observeForever(progressObserver) + viewModel.isSignButtonVisible.observeForever(buttonObserver) + + viewModel.signUp("valid@email.com") + + verify(progressObserver).onChanged(true) + verify(buttonObserver).onChanged(false) + } } \ No newline at end of file From 171872221650ec4d2305518862ef4c9ad94db86e Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Wed, 13 Mar 2019 23:35:22 -0400 Subject: [PATCH 05/19] add white space --- app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt b/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt index 463887e..5a161fc 100644 --- a/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt +++ b/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt @@ -54,6 +54,7 @@ class OnBoardViewModelTest { viewModel.isContinueButtonEnabled.observeForever(observer) viewModel.onAgreementChecked(true) + verify(observer).onChanged(true) } From 731af7a7d26d24166f768ff3fe6fa7ed91f9af75 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Sun, 7 Apr 2019 22:42:52 -0400 Subject: [PATCH 06/19] add custom source adding clean up some code --- app/src/main/AndroidManifest.xml | 1 - .../java/com/droidfeed/data/db/SourceDao.kt | 6 ++ .../com/droidfeed/data/model/Conference.kt | 1 - .../java/com/droidfeed/data/model/Licence.kt | 1 - .../java/com/droidfeed/data/model/Post.kt | 3 - .../java/com/droidfeed/data/model/Source.kt | 3 +- .../com/droidfeed/data/parser/FeedParser.kt | 15 +++ .../droidfeed/data/parser/NewsXmlParser.kt | 30 ++++++ .../com/droidfeed/data/parser/RssParser.kt | 27 +++++ .../com/droidfeed/data/parser/XmlParser.kt | 40 +++++++ .../com/droidfeed/data/repo/SourceRepo.kt | 54 +++++++++- .../droidfeed/ui/adapter/UIModelAdapter.kt | 29 +++-- .../ui/adapter/UIModelPaginatedAdapter.kt | 1 + .../{ => diff}/BaseUIModelDiffCallback.kt | 10 +- .../com/droidfeed/ui/adapter/diff/Diffable.kt | 1 - .../ui/adapter/diff/UIModelDiffCallback.kt | 23 ++-- .../ui/binding/CommonBindingAdapters.kt | 13 +++ .../conferences/ConferencesViewModel.kt | 3 + .../droidfeed/ui/module/feed/FeedViewModel.kt | 8 +- .../droidfeed/ui/module/main/MainActivity.kt | 31 ++++++ .../droidfeed/ui/module/main/MainViewModel.kt | 101 ++++++++++++++++-- .../module/newsletter/NewsletterViewModel.kt | 3 - .../java/com/droidfeed/util/AnalyticsUtil.kt | 30 +++++- .../java/com/droidfeed/util/AppRateHelper.kt | 2 +- .../main/res/drawable/avd_add_to_close.xml | 28 +++++ .../main/res/drawable/avd_close_to_add.xml | 27 +++++ .../drawable/avd_morph_close_to_hamburger.xml | 41 ------- .../drawable/avd_morph_hamburger_to_close.xml | 41 ------- app/src/main/res/layout/activity_main.xml | 90 ++++++++++++++-- app/src/main/res/values/strings.xml | 10 +- dependencies.gradle | 2 + 31 files changed, 526 insertions(+), 149 deletions(-) rename app/src/main/java/com/droidfeed/ui/adapter/{ => diff}/BaseUIModelDiffCallback.kt (71%) create mode 100644 app/src/main/res/drawable/avd_add_to_close.xml create mode 100644 app/src/main/res/drawable/avd_close_to_add.xml delete mode 100644 app/src/main/res/drawable/avd_morph_close_to_hamburger.xml delete mode 100644 app/src/main/res/drawable/avd_morph_hamburger_to_close.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f7e614a..6bc109c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,7 +7,6 @@ + @Query("SELECT * FROM $SOURCE_TABLE WHERE url = :sourceUrl LIMIT 1") + fun isUrlExists(sourceUrl: String): List + @Insert(onConflict = OnConflictStrategy.IGNORE) fun insertSources(source: List) + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertSource(source: Source) + @Update(onConflict = OnConflictStrategy.REPLACE) fun updateSource(source: Source) diff --git a/app/src/main/java/com/droidfeed/data/model/Conference.kt b/app/src/main/java/com/droidfeed/data/model/Conference.kt index efad997..da46220 100644 --- a/app/src/main/java/com/droidfeed/data/model/Conference.kt +++ b/app/src/main/java/com/droidfeed/data/model/Conference.kt @@ -18,5 +18,4 @@ data class Conference( override fun isSame(item: Any) = item == this - override fun hasSameContentWith(item: Any) = item == this } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/model/Licence.kt b/app/src/main/java/com/droidfeed/data/model/Licence.kt index 49d3675..638aac6 100644 --- a/app/src/main/java/com/droidfeed/data/model/Licence.kt +++ b/app/src/main/java/com/droidfeed/data/model/Licence.kt @@ -11,5 +11,4 @@ data class Licence( override fun isSame(item: Any) = equals(item) - override fun hasSameContentWith(item: Any) = equals(item) } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/model/Post.kt b/app/src/main/java/com/droidfeed/data/model/Post.kt index ace6120..fedcd2a 100644 --- a/app/src/main/java/com/droidfeed/data/model/Post.kt +++ b/app/src/main/java/com/droidfeed/data/model/Post.kt @@ -101,7 +101,4 @@ data class Post( override fun isSame(item: Any) = link == (item as Post).link - override fun hasSameContentWith(item: Any): Boolean { - return bookmarked == (item as Post).bookmarked - } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/model/Source.kt b/app/src/main/java/com/droidfeed/data/model/Source.kt index 5fafa2b..234af91 100644 --- a/app/src/main/java/com/droidfeed/data/model/Source.kt +++ b/app/src/main/java/com/droidfeed/data/model/Source.kt @@ -33,7 +33,6 @@ data class Source( @Ignore val isEnabled = ObservableBoolean() - override fun isSame(item: Any) = url == (item as Source).url + override fun isSame(item: Any) = id == (item as Source).id - override fun hasSameContentWith(item: Any) = isActive == (item as Source).isActive } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt b/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt index 8129a3a..bcdfffe 100644 --- a/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt @@ -37,6 +37,21 @@ class FeedParser @Inject constructor() : XmlParser() { return posts } + override fun getChannelTitle(parser: XmlPullParser): String? { + parser.require(XmlPullParser.START_TAG, null, "feed") + + var title: String? = null + + parseTags( + parser, + "title" to { tParser -> + title = tParser.nextText() + } + ) { title == null } + + return title + } + private fun parsePost(parser: XmlPullParser, source: Source): Post { val post = Post() diff --git a/app/src/main/java/com/droidfeed/data/parser/NewsXmlParser.kt b/app/src/main/java/com/droidfeed/data/parser/NewsXmlParser.kt index aeb949a..7cbe6d5 100644 --- a/app/src/main/java/com/droidfeed/data/parser/NewsXmlParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/NewsXmlParser.kt @@ -45,4 +45,34 @@ class NewsXmlParser @Inject constructor( } } } + + /** + * Parses and returns the channel title from given feed xml. + * + * @param xml RSS or Atom feed + */ + fun getChannelTitle(xml: String): String? { + val inputStream = StringReader(xml) + + inputStream.use { + val parser = Xml.newPullParser() + + try { + parser.run { + setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) + setInput(inputStream) + nextTag() + } + } catch (e: XmlPullParserException) { + logThrowable(e) + } + + return when (parser.name) { + "rss" -> rssParser.getChannelTitle(parser) + "feed" -> feedParser.getChannelTitle(parser) + else -> null + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/parser/RssParser.kt b/app/src/main/java/com/droidfeed/data/parser/RssParser.kt index e536425..e641a86 100644 --- a/app/src/main/java/com/droidfeed/data/parser/RssParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/RssParser.kt @@ -19,6 +19,7 @@ import javax.inject.Singleton @Singleton class RssParser @Inject constructor() : XmlParser() { + override fun parsePosts(parser: XmlPullParser, source: Source): List { val posts = mutableListOf() @@ -41,6 +42,31 @@ class RssParser @Inject constructor() : XmlParser() { return posts } + + override fun getChannelTitle(parser: XmlPullParser): String? { + parser.require(XmlPullParser.START_TAG, null, "rss") + + var title: String? = null + var channelParser: XmlPullParser? = null + + parseTags(parser, + "channel" to { cParser -> + channelParser = cParser + } + ) { channelParser == null } + + channelParser?.let { cParser -> + parseTags( + cParser, + "title" to { tParser -> + title = tParser.nextText() + } + ) { title == null } + } + + return title + } + private fun parseChannel(parser: XmlPullParser, source: Source): List { val rssChannel = Channel() val posts = mutableListOf() @@ -150,4 +176,5 @@ class RssParser @Inject constructor() : XmlParser() { else -> "" } } + } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt b/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt index 2fa8aa5..b7bbbfa 100644 --- a/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt @@ -2,15 +2,55 @@ package com.droidfeed.data.parser import com.droidfeed.data.model.Post import com.droidfeed.data.model.Source +import com.droidfeed.util.extention.skipTag +import com.droidfeed.util.logThrowable import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException abstract class XmlParser { abstract fun parsePosts(parser: XmlPullParser, source: Source): List + /** + * Parses and returns the channel title for given parser. The parser should contain valid + * formatted feed inside. + * + * @param parser + */ + abstract fun getChannelTitle(parser: XmlPullParser): String? + + /** + * Obtains and returns a link from given parser accordingly to given attribute name + * + * @param parser + * @param attributeName + */ fun parseLink(parser: XmlPullParser, attributeName: String = "href"): String { val link = parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, attributeName) parser.nextTag() return link } + + + fun parseTags( + parser: XmlPullParser, + tagMap: Pair Unit>, + isActive: () -> Boolean + ) { + try { + while (parser.next() != XmlPullParser.END_TAG && isActive()) { + if (parser.eventType != XmlPullParser.START_TAG) { + continue + } + + if (tagMap.first == parser.name) { + tagMap.second.invoke(parser) + } else { + parser.skipTag() + } + } + } catch (e: XmlPullParserException) { + logThrowable(e) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt index 0d8cdfb..7ad295e 100644 --- a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt @@ -4,8 +4,14 @@ import androidx.annotation.WorkerThread import com.droidfeed.data.DataStatus import com.droidfeed.data.db.SourceDao import com.droidfeed.data.model.Source +import com.droidfeed.data.parser.NewsXmlParser import com.droidfeed.util.extention.isOnline +import com.droidfeed.util.logThrowable import com.google.firebase.firestore.FirebaseFirestore +import kotlinx.coroutines.GlobalScope +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.IOException import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -18,7 +24,9 @@ import kotlin.coroutines.suspendCoroutine @Singleton class SourceRepo @Inject constructor( private val sourceDao: SourceDao, - private val firestore: FirebaseFirestore + private val firestore: FirebaseFirestore, + private val okHttpClient: OkHttpClient, + private val xmlParser: NewsXmlParser ) { fun getAll() = sourceDao.getSources() @@ -39,7 +47,46 @@ class SourceRepo @Inject constructor( fun insert(sources: List) = sourceDao.insertSources(sources) /** - * Pulls sources from Firebase Firestore. + * Adds news source from given url. + * + * @param sourceUrl valid RSS or Atom feed url + */ + fun addFromUrl(sourceUrl: String): DataStatus { + + return try { + val request = Request.Builder() + .url(sourceUrl) + .build() + val response = okHttpClient.newCall(request).execute() + + if (response.isSuccessful) { + val channelTitle = response.body()?.string()?.let { feedString -> + xmlParser.getChannelTitle(feedString) + } + + if (channelTitle == null) { + DataStatus.Failed() + } else { + val source = Source( + id = 0, /* will be auto-set by room */ + name = channelTitle, + url = sourceUrl + ) + + sourceDao.insertSource(source) + DataStatus.Successful(channelTitle) + } + } else { + DataStatus.HttpFailed(response.code()) + } + } catch (e: IOException) { + logThrowable(e) + DataStatus.Failed(e) + } + } + + /** + * Pulls sources from [FirebaseFirestore]. */ suspend fun pull() = suspendCoroutine>> { continuation -> firestore.collection("sources") @@ -68,4 +115,7 @@ class SourceRepo @Inject constructor( continuation.resume(DataStatus.Failed(exception)) } } + + fun isSourceExisting(sourceUrl: String) = sourceDao.isUrlExists(sourceUrl).isNotEmpty() + } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt b/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt index c8f3198..cba6c69 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt @@ -50,34 +50,33 @@ class UIModelAdapter constructor( notifyDataSetChanged() } else { launch { - val oldItems = async { ArrayList(uiModels) } val diffResult = async { - DiffUtil.calculateDiff( - UIModelDiffCallback( - oldItems.await(), - uiModels as List - ) + val diffCallback = UIModelDiffCallback( + ArrayList(uiModels), + newModels ) - } - - diffResult.await().let { result -> - withContext(Dispatchers.Main) { - dispatchUpdates(result) - } uiModels.clear() uiModels.addAll(newModels) - updateViewTypes(uiModels) + + DiffUtil.calculateDiff(diffCallback, true) } + + withContext(Dispatchers.Main) { + val dif=diffResult.await() + dispatchUpdates(dif) + } + + updateViewTypes(uiModels) } } } } - private fun dispatchUpdates(it: DiffUtil.DiffResult) { + private fun dispatchUpdates(diffResult: DiffUtil.DiffResult) { val recyclerViewState = layoutManager?.onSaveInstanceState() - it.dispatchUpdatesTo(this) + diffResult.dispatchUpdatesTo(this) layoutManager?.onRestoreInstanceState(recyclerViewState) } diff --git a/app/src/main/java/com/droidfeed/ui/adapter/UIModelPaginatedAdapter.kt b/app/src/main/java/com/droidfeed/ui/adapter/UIModelPaginatedAdapter.kt index cf4c3ba..0c61619 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/UIModelPaginatedAdapter.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/UIModelPaginatedAdapter.kt @@ -5,6 +5,7 @@ import androidx.collection.SparseArrayCompat import androidx.paging.PagedList import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.RecyclerView +import com.droidfeed.ui.adapter.diff.BaseUIModelDiffCallback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/droidfeed/ui/adapter/BaseUIModelDiffCallback.kt b/app/src/main/java/com/droidfeed/ui/adapter/diff/BaseUIModelDiffCallback.kt similarity index 71% rename from app/src/main/java/com/droidfeed/ui/adapter/BaseUIModelDiffCallback.kt rename to app/src/main/java/com/droidfeed/ui/adapter/diff/BaseUIModelDiffCallback.kt index ecfd8cf..df95ef6 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/BaseUIModelDiffCallback.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/diff/BaseUIModelDiffCallback.kt @@ -1,6 +1,8 @@ -package com.droidfeed.ui.adapter +package com.droidfeed.ui.adapter.diff import androidx.recyclerview.widget.DiffUtil +import com.droidfeed.ui.adapter.BaseUIModelAlias + class BaseUIModelDiffCallback : DiffUtil.ItemCallback() { @@ -14,9 +16,9 @@ class BaseUIModelDiffCallback : DiffUtil.ItemCallback() { } override fun areContentsTheSame( - oldConcert: BaseUIModelAlias, - newConcert: BaseUIModelAlias - ) = oldConcert == newConcert + oldContent: BaseUIModelAlias, + newContent: BaseUIModelAlias + ) = oldContent.getData() == newContent.getData() override fun getChangePayload( oldItem: BaseUIModelAlias, diff --git a/app/src/main/java/com/droidfeed/ui/adapter/diff/Diffable.kt b/app/src/main/java/com/droidfeed/ui/adapter/diff/Diffable.kt index bd3ce01..03e022a 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/diff/Diffable.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/diff/Diffable.kt @@ -7,5 +7,4 @@ interface Diffable { fun isSame(item: Any): Boolean - fun hasSameContentWith(item: Any): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/adapter/diff/UIModelDiffCallback.kt b/app/src/main/java/com/droidfeed/ui/adapter/diff/UIModelDiffCallback.kt index 502c167..86c0f3d 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/diff/UIModelDiffCallback.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/diff/UIModelDiffCallback.kt @@ -14,19 +14,16 @@ class UIModelDiffCallback( override fun areItemsTheSame( oldItemPosition: Int, newItemPosition: Int - ) = - if (oldModels[oldItemPosition].javaClass != newModels[newItemPosition].javaClass) { - false - } else { - oldModels[oldItemPosition].getData().isSame(newModels[newItemPosition].getData()) - } + ) = if (oldModels[oldItemPosition].javaClass != newModels[newItemPosition].javaClass) { + false + } else { + oldModels[oldItemPosition].getData().isSame(newModels[newItemPosition].getData()) + } override fun areContentsTheSame( - oldItemPosition: Int, - newItemPosition: Int - ) = oldModels[oldItemPosition] - .getData() - .hasSameContentWith(newModels[newItemPosition].getData()) + oldItemPos: Int, + newItemPos: Int + ) = oldModels[oldItemPos].getData() == newModels[newItemPos].getData() override fun getOldListSize(): Int = oldModels.size @@ -35,7 +32,5 @@ class UIModelDiffCallback( override fun getChangePayload( oldItemPosition: Int, newItemPosition: Int - ) = - newModels[newItemPosition] - .getData() + ) = newModels[newItemPosition].getData() } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt index 6d0910d..98e3122 100644 --- a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt +++ b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt @@ -7,6 +7,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.core.view.isVisible import androidx.databinding.BindingAdapter +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.droidfeed.R import com.droidfeed.util.extention.fadeIn import com.droidfeed.util.extention.fadeOut @@ -22,6 +23,18 @@ fun avdImageResource( imageView.setImageResource(avdImageResource) } + +@BindingAdapter("app:avdImageResource2") +fun avdImageResource2( + imageView: ImageView, + avdImageResource: Int +) { + val avd = AnimatedVectorDrawableCompat.create(imageView.context, avdImageResource) + imageView.setImageDrawable(avd) + avd?.start() +} + + @BindingAdapter("app:isVisible") fun isVisible( view: View, diff --git a/app/src/main/java/com/droidfeed/ui/module/conferences/ConferencesViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/conferences/ConferencesViewModel.kt index ff14054..3be0016 100644 --- a/app/src/main/java/com/droidfeed/ui/module/conferences/ConferencesViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/conferences/ConferencesViewModel.kt @@ -27,6 +27,7 @@ class ConferencesViewModel @Inject constructor( isProgressVisible.postValue(conferences.value?.isEmpty() == true) val dataStatus = conferenceRepo.getUpcoming() + when (dataStatus) { is DataStatus.Successful -> { val uiModels = dataStatus.data?.map { conference -> @@ -44,9 +45,11 @@ class ConferencesViewModel @Inject constructor( conference, onItemClick = { conf -> openUrl.postValue(Event(conf.url)) + analytics.logConferenceClick() }, onCFPClick = { conf -> openUrl.postValue(Event(conf.cfpUrl)) + analytics.logCFPClick() }) } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt index 7dfe054..1031901 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt @@ -180,14 +180,14 @@ class FeedViewModel @Inject constructor( launch(Dispatchers.IO) { val bookmarkCount = postRepo.getBookmarkedCount() - if (canPromptAppRate(bookmarkCount)) { - analytics.logAppRatePrompt() +// if (canPromptAppRate(bookmarkCount)) { +// analytics.logAppRatePrompt() showAppRateSnack.postValue(Event({ openPlayStorePage.postValue(Event(Unit)) - analytics.logAppRateClick() + analytics.logAppRateFromPromtClick() })) - } +// } } } } diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt index 54f7e66..8478863 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt @@ -10,6 +10,7 @@ import android.view.View import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil +import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.DefaultItemAnimator @@ -24,6 +25,7 @@ import com.droidfeed.util.AnimUtils import com.droidfeed.util.ColorPalette import com.droidfeed.util.event.EventObserver import com.droidfeed.util.extention.hideKeyboard +import kotlinx.android.synthetic.main.activity_main.* import javax.inject.Inject @Suppress("UNCHECKED_CAST") @@ -62,6 +64,8 @@ class MainActivity : BaseActivity() { subscribeSources() subscribeMenuVisibility() subscribeFilterVisibility() + subscribeCloseKeyboard() + subscribeAddSourceIcon() binding = DataBindingUtil.setContentView( this, @@ -78,6 +82,19 @@ class MainActivity : BaseActivity() { initFilterDrawer() } + private fun subscribeAddSourceIcon() { +// mainViewModel.sourceAddIcon.observe(this, EventObserver { +// +// }) + + } + + private fun subscribeCloseKeyboard() { + mainViewModel.closeKeyboardEvent.observe(this, EventObserver { + edtFeedUrl.hideKeyboard() + }) + } + private fun subscribeSources() { mainViewModel.sourceUIModelData.observe(this, Observer { sourceUIModels -> uiModelAdapter.addUIModels(sourceUIModels as List) @@ -279,6 +296,20 @@ class MainActivity : BaseActivity() { overScrollMode = View.OVER_SCROLL_NEVER layoutManager = linearLayoutManager } + binding.drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener { + override fun onDrawerStateChanged(newState: Int) { + } + + override fun onDrawerSlide(drawerView: View, slideOffset: Float) { + } + + override fun onDrawerClosed(drawerView: View) { + mainViewModel.onFilterDrawerClosed() + } + + override fun onDrawerOpened(drawerView: View) { + } + }) } override fun onBackPressed() { diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt index de6f8b9..26ebfd5 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt @@ -1,6 +1,9 @@ package com.droidfeed.ui.module.main import android.content.SharedPreferences +import android.util.Patterns +import android.webkit.URLUtil +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -16,11 +19,12 @@ import com.droidfeed.ui.common.BaseViewModel import com.droidfeed.util.event.Event import com.droidfeed.util.hasAcceptedTerms import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import javax.inject.Inject class MainViewModel @Inject constructor( - sourceRepo: SourceRepo, + private val sourceRepo: SourceRepo, postRepo: PostRepo, sharedPrefs: SharedPreferences ) : BaseViewModel() { @@ -32,19 +36,29 @@ class MainViewModel @Inject constructor( val isUserTermsAccepted = MutableLiveData().apply { value = sharedPrefs.hasAcceptedTerms } + val sourceAddIcon = MutableLiveData<@DrawableRes Int>().apply { + value = R.drawable.avd_close_to_add + } val isMenuVisible = MutableLiveData().apply { value = false } + val isSourceInputVisible = MutableLiveData().apply { value = false } val isSourceFilterVisible = MutableLiveData>().apply { value = Event(false) } + val closeKeyboardEvent = MutableLiveData>().apply { value = Event(false) } + val sourceErrText = MutableLiveData<@StringRes Int>().apply { value = R.string.empty_string } + val sourceInputText = MutableLiveData<@StringRes Int>().apply { value = R.string.empty_string } + val isSourceProgressVisible = MutableLiveData().apply { value = false } + val isSourceAddButtonEnabled = MutableLiveData().apply { value = true } val isFilterButtonVisible = MutableLiveData().apply { value = true } val isBookmarksShown = MutableLiveData().apply { value = false } val isBookmarksButtonVisible = MutableLiveData().apply { value = true } val isBookmarksButtonSelected = MutableLiveData().apply { value = false } - val sourceUIModelData: LiveData> = - map(sourceRepo.getAll()) { sourceList -> - sourceList.map { source -> - SourceUIModel(source, sourceClickListener) - } + val sourceUIModelData: LiveData> = map(sourceRepo.getAll()) { sourceList -> + sourceList.map { source -> + SourceUIModel(source, sourceClickListener) } + } + + private var addSourceJob: Job? = null init { updateSources(sourceRepo) @@ -119,6 +133,74 @@ class MainViewModel @Inject constructor( isSourceFilterVisible.postValue(Event(true)) } + fun onAddSourceClicked() { + val shouldOpenInputField = !(isSourceInputVisible.value!!) + isSourceInputVisible.postValue(shouldOpenInputField) + + if (shouldOpenInputField) { + sourceAddIcon.postValue(R.drawable.avd_add_to_close) + } else { + sourceAddIcon.postValue(R.drawable.avd_close_to_add) + } + + analytics.logAddSourceButtonClick() + } + + fun onSaveSourceClicked(url: String) { + addSourceJob = launch(Dispatchers.IO) { + if (Patterns.WEB_URL.matcher(url.toLowerCase()).matches()) { + sourceErrText.postValue(R.string.empty_string) + + val cleanUrl = URLUtil.guessUrl(url) + + val alreadyExists = sourceRepo.isSourceExisting(cleanUrl) + + if (alreadyExists) { + sourceErrText.postValue(R.string.error_source_exists) + } else { + isSourceProgressVisible.postValue(true) + isSourceAddButtonEnabled.postValue(false) + closeKeyboardEvent.postValue(Event(true)) + + val status = sourceRepo.addFromUrl(cleanUrl) + + when (status) { + is DataStatus.Successful -> { + isSourceProgressVisible.postValue(false) + isSourceAddButtonEnabled.postValue(true) + isSourceInputVisible.postValue(false) + } + is DataStatus.Failed -> { + sourceErrText.postValue(R.string.error_add_source) + isSourceProgressVisible.postValue(false) + isSourceAddButtonEnabled.postValue(true) + + } + is DataStatus.HttpFailed -> { + sourceErrText.postValue(R.string.error_internet_or_url) + isSourceProgressVisible.postValue(false) + isSourceAddButtonEnabled.postValue(true) + } + } + + } + } else if (url.isEmpty()) { + sourceErrText.postValue(R.string.error_empty_source_url) + } else { + sourceErrText.postValue(R.string.error_invalid_url) + } + + analytics.logSaveSourceButtonClick() + } + + } + + fun onSourceInputTextChanged(text: CharSequence, start: Int, before: Int, count: Int) { + if (text.isNotEmpty()) { + sourceErrText.postValue(R.string.empty_string) + } + } + fun onMenuClicked() { val isCurrentlyVisible = isMenuVisible.value ?: false isMenuVisible.postValue(!isCurrentlyVisible) @@ -149,4 +231,11 @@ class MainViewModel @Inject constructor( else -> navigateBack() } } + + fun onFilterDrawerClosed() { + isSourceInputVisible.postValue(false) + sourceInputText.postValue(R.string.empty_string) + sourceErrText.postValue(R.string.empty_string) + sourceAddIcon.postValue(R.drawable.avd_close_to_add) + } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt index d18adaa..9aa21d7 100644 --- a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt @@ -1,6 +1,5 @@ package com.droidfeed.ui.module.newsletter -import android.util.Log import androidx.annotation.StringRes import androidx.lifecycle.MutableLiveData import com.droidfeed.BuildConfig @@ -77,8 +76,6 @@ class NewsletterViewModel @Inject constructor( } private fun handleSignUpResponse(dataStatus: DataStatus) { - Log.e("JAMIRYO", "5---- $dataStatus") - when (dataStatus) { is DataStatus.Successful -> { isEmailInputVisible.postValue(false) diff --git a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt index f722957..60ffb24 100644 --- a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt +++ b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt @@ -11,9 +11,11 @@ import javax.inject.Inject class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics) { fun logBookmark(isBookmarked: Boolean) { + val text = if (isBookmarked) "bookmarked" else "unbookmarked" + analytics.logEvent( "bookmark", - bundleOf(Pair("bookmarked", isBookmarked)) + bundleOf(Pair("bookmarked", text)) ) } @@ -35,6 +37,28 @@ class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics ) } + fun logConferenceClick() { + analytics.logEvent( + FirebaseAnalytics.Event.VIEW_ITEM, + bundleOf(Pair("conference", "conference")) + ) + } + + fun logCFPClick() { + analytics.logEvent( + FirebaseAnalytics.Event.VIEW_ITEM, + bundleOf(Pair("conference", "CFP")) + ) + } + + fun logAddSourceButtonClick() { + analytics.logEvent("click_add_source", null) + } + + fun logSaveSourceButtonClick() { + analytics.logEvent("click_save_source", null) + } + fun logScreenView(activity: Activity, screenTag: String) { analytics.setCurrentScreen(activity, screenTag, null) } @@ -43,6 +67,10 @@ class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics analytics.logEvent("open_play_store", null) } + fun logAppRateFromPromtClick() { + analytics.logEvent("app_rate_prompt_rate", null) + } + fun logAppRatePrompt() { analytics.logEvent("app_rate_prompt", null) } diff --git a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt index fa950aa..c684e25 100644 --- a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt +++ b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt @@ -10,7 +10,7 @@ import javax.inject.Inject class AppRateHelper @Inject constructor(private val sharedPrefs: SharedPreferences) { fun showRateSnackbar(view: View, onAction: () -> Unit) { - Snackbar.make(view, R.string.like_to_review_df, Snackbar.LENGTH_LONG) + Snackbar.make(view, R.string.like_to_review_df, 5000) .setAction(R.string.yes) { onAction() } .setAnimationMode(Snackbar.ANIMATION_MODE_SLIDE) .setActionTextColor( diff --git a/app/src/main/res/drawable/avd_add_to_close.xml b/app/src/main/res/drawable/avd_add_to_close.xml new file mode 100644 index 0000000..6eeebff --- /dev/null +++ b/app/src/main/res/drawable/avd_add_to_close.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_close_to_add.xml b/app/src/main/res/drawable/avd_close_to_add.xml new file mode 100644 index 0000000..bab56dc --- /dev/null +++ b/app/src/main/res/drawable/avd_close_to_add.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_morph_close_to_hamburger.xml b/app/src/main/res/drawable/avd_morph_close_to_hamburger.xml deleted file mode 100644 index 1442373..0000000 --- a/app/src/main/res/drawable/avd_morph_close_to_hamburger.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/avd_morph_hamburger_to_close.xml b/app/src/main/res/drawable/avd_morph_hamburger_to_close.xml deleted file mode 100644 index b09b4a0..0000000 --- a/app/src/main/res/drawable/avd_morph_hamburger_to_close.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8d58e4c..0dc84da 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -28,20 +29,97 @@ android:layout_height="match_parent" android:layout_gravity="end" android:background="@color/colorAccent" - android:fitsSystemWindows="true"> + android:fitsSystemWindows="true" + tools:layout_gravity=""> - + android:orientation="horizontal" + android:paddingStart="@dimen/spacing_medium" + android:paddingEnd="@dimen/spacing_medium"> + + + + + + + + + + + + + + + + + + + + + Please put your email address Please enter a valid email address API call failed, please try again later + Is your device connected to the internet? + Can not obtain the news sources. Please try again later. + Failed to add. Make sure the feed format is valid. + The url is not valid. Home Feed @@ -81,9 +85,11 @@ Thanks! Please check your inbox for a confirmation. Licence Back navigation button - Can not obtain the news sources. Please try again later. - Cannot open invalid URL. CFP until %1$s Conferences + RSS/Atom feed url + add + This feed already exists. + Please enter a feed url diff --git a/dependencies.gradle b/dependencies.gradle index d96611e..de144d0 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -17,6 +17,7 @@ ext.versions = [ crashlytics : '2.9.9', detekt_ktlint : '1.0.0-RC14', firebase_core : '16.0.7', + firebase_auth : '16.2.0', firebase_config : '16.3.0', firebase_firestore : '18.1.0', okHttp : '3.13.1', @@ -93,6 +94,7 @@ deps.navigation = [ deps.firebase = [ core : "com.google.firebase:firebase-core:$versions.firebase_core", + auth : "com.google.firebase:firebase-auth:$versions.firebase_auth", config : "com.google.firebase:firebase-config:$versions.firebase_config", firestore: "com.google.firebase:firebase-firestore:$versions.firebase_firestore" ] From 1b6592539178b0bce3d9a7a643e7c8221c7d3b11 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Thu, 18 Apr 2019 07:20:33 -0400 Subject: [PATCH 07/19] change disabled state alpha to 0.3 from 0.5 --- .../main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt index 98e3122..84b0526 100644 --- a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt +++ b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt @@ -65,7 +65,7 @@ fun isEnabled( if (isEnabled) { view.fadeIn() } else { - view.fadeOut(0.5f) + view.fadeOut(0.3f) } } } From a471bae571d9d486fbeccd58bd8bed1d3694618d Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Thu, 18 Apr 2019 07:20:48 -0400 Subject: [PATCH 08/19] add assets --- .../main/res/drawable/avd_close_to_minus.xml | 28 +++++++++++++++++++ .../main/res/drawable/avd_minus_to_close.xml | 27 ++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 app/src/main/res/drawable/avd_close_to_minus.xml create mode 100644 app/src/main/res/drawable/avd_minus_to_close.xml diff --git a/app/src/main/res/drawable/avd_close_to_minus.xml b/app/src/main/res/drawable/avd_close_to_minus.xml new file mode 100644 index 0000000..5e400eb --- /dev/null +++ b/app/src/main/res/drawable/avd_close_to_minus.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_minus_to_close.xml b/app/src/main/res/drawable/avd_minus_to_close.xml new file mode 100644 index 0000000..a614ba0 --- /dev/null +++ b/app/src/main/res/drawable/avd_minus_to_close.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + From fc4848c5b83a7bafba3d173b1a8f4bdde7279e4f Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Wed, 1 May 2019 07:45:09 -0400 Subject: [PATCH 09/19] increase db version --- .../com.droidfeed.data.db.AppDatabase/5.json | 171 ++++++++++++++++++ .../com/droidfeed/db/DatabaseMigrationTest.kt | 63 +++++-- .../java/com/droidfeed/data/db/AppDatabase.kt | 2 +- .../java/com/droidfeed/di/DatabaseModule.kt | 6 +- 4 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 app/schemas/com.droidfeed.data.db.AppDatabase/5.json diff --git a/app/schemas/com.droidfeed.data.db.AppDatabase/5.json b/app/schemas/com.droidfeed.data.db.AppDatabase/5.json new file mode 100644 index 0000000..8538b2b --- /dev/null +++ b/app/schemas/com.droidfeed.data.db.AppDatabase/5.json @@ -0,0 +1,171 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "f70b83173d8568f3b154cbe5dce7c0fb", + "entities": [ + { + "tableName": "source", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`is_active` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `is_user_source` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "isActive", + "columnName": "is_active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUserSource", + "columnName": "is_user_source", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "rss", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookmarked` INTEGER NOT NULL, `link` TEXT NOT NULL, `source_id` INTEGER, `pub_date` TEXT NOT NULL, `pub_date_timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `content_raw` TEXT NOT NULL, `channel_title` TEXT NOT NULL, `channel_image_url` TEXT NOT NULL, `channel_link` TEXT NOT NULL, `content_image` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`link`), FOREIGN KEY(`source_id`) REFERENCES `source`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "link", + "columnName": "link", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourceId", + "columnName": "source_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pubDate", + "columnName": "pub_date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pubDateTimestamp", + "columnName": "pub_date_timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawContent", + "columnName": "content_raw", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channel.title", + "columnName": "channel_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channel.imageUrl", + "columnName": "channel_image_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channel.link", + "columnName": "channel_link", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content.contentImage", + "columnName": "content_image", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content.content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "link" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_rss_source_id", + "unique": false, + "columnNames": [ + "source_id" + ], + "createSql": "CREATE INDEX `index_rss_source_id` ON `${TABLE_NAME}` (`source_id`)" + } + ], + "foreignKeys": [ + { + "table": "source", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "source_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"f70b83173d8568f3b154cbe5dce7c0fb\")" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/droidfeed/db/DatabaseMigrationTest.kt b/app/src/androidTest/java/com/droidfeed/db/DatabaseMigrationTest.kt index d1e8cb7..6efb02a 100644 --- a/app/src/androidTest/java/com/droidfeed/db/DatabaseMigrationTest.kt +++ b/app/src/androidTest/java/com/droidfeed/db/DatabaseMigrationTest.kt @@ -64,17 +64,18 @@ class DatabaseMigrationTest { } @Test - fun migration_from_1_to_4_should_contain_correct_data() { + fun migration_from_1_to_5_should_contain_correct_data() { createAndPopulateDBVersion1() val migrations = arrayOf( MIGRATION_1_2, MIGRATION_1_4, MIGRATION_2_3, - MIGRATION_3_4 + MIGRATION_3_4, + MIGRATION_4_5 ) - val posts = getMigrationValidatedRoomDatabase(migrations) + val posts = getMigrationValidatedRoomDatabase(5, migrations) .postDao() .getAllAsLiveData() .blockingObserve() ?: emptyList() @@ -85,15 +86,17 @@ class DatabaseMigrationTest { } @Test - fun migration_from_2_to_4_should_contain_correct_data() { + fun migration_from_2_to_5_should_contain_correct_data() { createAndPopulateDBVersion2() val migrations = arrayOf( MIGRATION_1_4, + MIGRATION_1_5, MIGRATION_2_3, + MIGRATION_2_5, MIGRATION_3_4 ) - val migratedDb = getMigrationValidatedRoomDatabase(migrations) + val migratedDb = getMigrationValidatedRoomDatabase(5, migrations) mSqliteTestDbHelper.writableDatabase.use { database -> database.execSQL( @@ -130,10 +133,11 @@ class DatabaseMigrationTest { val migrations = arrayOf( MIGRATION_1_4, - MIGRATION_3_4 + MIGRATION_3_4, + MIGRATION_4_5 ) - val posts = getMigrationValidatedRoomDatabase(migrations) + val posts = getMigrationValidatedRoomDatabase(4, migrations) .postDao() .getAllAsLiveData().blockingObserve() ?: emptyList() @@ -142,6 +146,26 @@ class DatabaseMigrationTest { assertEquals(1, posts[0].bookmarked) } + @Test + fun migration_from_4_to_5_should_contain_correct_data() { + createAndPopulateDBVersion4() + + val migrations = arrayOf( + MIGRATION_3_5, + MIGRATION_4_5 + ) + + val sources = getMigrationValidatedRoomDatabase(5, migrations) + .sourceDao() + .getSources().blockingObserve() ?: emptyList() + + assertEquals(2, sources.size) + assertEquals(0, sources[0].id) + assertEquals(false, sources[0].isUserSource) + + } + + private fun createAndPopulateDBVersion1() { mMigrationTestHelper.createDatabase(dbName, 1).use { database -> database.execSQL( @@ -154,8 +178,8 @@ class DatabaseMigrationTest { private fun createAndPopulateDBVersion2() { mMigrationTestHelper.createDatabase(dbName, 2).use { database -> database.apply { - execSQL("INSERT INTO `source` (url, name, is_active) VALUES ('https://some.url','source name', '0') ;") - execSQL("INSERT INTO `source` (url, name, is_active) VALUES ('https://some.url2','source name2', '1') ;") + execSQL("INSERT INTO `source` (url, name, is_active) VALUES ('https://some.url', 'source name', '0') ;") + execSQL("INSERT INTO `source` (url, name, is_active) VALUES ('https://some.url2', 'source name2', '1') ;") execSQL("INSERT INTO `rss` (bookmarked,content, content_image, channel_image_url,channel_title, content_raw, author, title, pub_date_timestamp, pub_date, contentImage, link, channel_link) VALUES ('1','', '', '', '', '', 'author', 'ptitle', 'pub_date_timestamp', 'pub date', 'image', 'https://post.link.test', 'https://some.url2');") } } @@ -164,13 +188,26 @@ class DatabaseMigrationTest { private fun createAndPopulateDBVersion3() { mMigrationTestHelper.createDatabase(dbName, 3).use { database -> database.apply { - execSQL("INSERT INTO `source` (id, url, name, is_active) VALUES ('0','https://some.url','source name', '0') ;") + execSQL("INSERT INTO `source` (id, url, name, is_active) VALUES ('0', 'https://some.url', 'source name', '0') ;") execSQL("INSERT INTO `rss` (bookmarked,content, content_image, channel_image_url,channel_title, content_raw, author, title, pub_date_timestamp, pub_date, content_image, link, channel_link) VALUES ('1','', '', '', '', '', 'author', 'ptitle', 'pub_date_timestamp', 'pub date', 'image', 'https://post.link.test', 'https://channel.link.test');") } } } - private fun getMigrationValidatedRoomDatabase(migrations: Array): AppDatabase { + private fun createAndPopulateDBVersion4() { + mMigrationTestHelper.createDatabase(dbName, 3).use { database -> + database.apply { + execSQL("INSERT INTO `source` (id, url, name, is_active) VALUES ('0', 'https://some.url', 'source name', '0') ;") + execSQL("INSERT INTO `source` (id, url, name, is_active) VALUES ('1', 'https://some.url2', 'source name2', '1') ;") + + } + } + } + + private fun getMigrationValidatedRoomDatabase( + version: Int, + migrations: Array + ): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, @@ -182,10 +219,10 @@ class DatabaseMigrationTest { build() }.also { database -> mMigrationTestHelper.closeWhenFinished(database) - +// mMigrationTestHelper.runMigrationsAndValidate( dbName, - 3, + version, true, *migrations ) diff --git a/app/src/main/java/com/droidfeed/data/db/AppDatabase.kt b/app/src/main/java/com/droidfeed/data/db/AppDatabase.kt index 60c5e1b..60061c0 100644 --- a/app/src/main/java/com/droidfeed/data/db/AppDatabase.kt +++ b/app/src/main/java/com/droidfeed/data/db/AppDatabase.kt @@ -8,7 +8,7 @@ import com.droidfeed.data.model.Source @Database( entities = [(Source::class), (Post::class)], - version = 4 + version = 5 ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/droidfeed/di/DatabaseModule.kt b/app/src/main/java/com/droidfeed/di/DatabaseModule.kt index c17032e..de020d8 100644 --- a/app/src/main/java/com/droidfeed/di/DatabaseModule.kt +++ b/app/src/main/java/com/droidfeed/di/DatabaseModule.kt @@ -19,8 +19,12 @@ class DatabaseModule { ).addMigrations( MIGRATION_1_2, MIGRATION_1_4, + MIGRATION_1_5, MIGRATION_2_3, - MIGRATION_3_4 + MIGRATION_2_5, + MIGRATION_3_4, + MIGRATION_3_5, + MIGRATION_4_5 ).build() @Provides From 527a900c20f623c9ecad774c6964a65620d63f77 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Tue, 7 May 2019 23:37:20 -0400 Subject: [PATCH 10/19] wip --- .../java/com/droidfeed/data/db/Migrations.kt | 63 ++++++++++- .../java/com/droidfeed/data/db/SourceDao.kt | 5 +- .../java/com/droidfeed/data/model/Source.kt | 8 +- .../com/droidfeed/data/repo/SourceRepo.kt | 10 +- .../droidfeed/ui/adapter/UIModelAdapter.kt | 8 +- .../ui/adapter/model/SourceUIModel.kt | 6 +- .../ui/adapter/viewholder/SourceViewHolder.kt | 10 +- .../droidfeed/ui/module/feed/FeedViewModel.kt | 14 +-- .../droidfeed/ui/module/main/MainActivity.kt | 32 +++++- .../droidfeed/ui/module/main/MainViewModel.kt | 102 +++++++++++++----- .../java/com/droidfeed/util/AnalyticsUtil.kt | 4 + .../java/com/droidfeed/util/AppRateHelper.kt | 7 +- .../res/drawable/ic_trash_can_outline.xml | 9 ++ app/src/main/res/layout/activity_main.xml | 28 ++++- app/src/main/res/layout/list_item_source.xml | 34 ++++-- app/src/main/res/values/strings.xml | 1 + build.gradle | 2 +- dependencies.gradle | 4 +- 18 files changed, 278 insertions(+), 69 deletions(-) create mode 100644 app/src/main/res/drawable/ic_trash_can_outline.xml diff --git a/app/src/main/java/com/droidfeed/data/db/Migrations.kt b/app/src/main/java/com/droidfeed/data/db/Migrations.kt index 513bf96..41f5180 100644 --- a/app/src/main/java/com/droidfeed/data/db/Migrations.kt +++ b/app/src/main/java/com/droidfeed/data/db/Migrations.kt @@ -31,18 +31,77 @@ val MIGRATION_2_3: Migration = object : Migration(2, 3) { */ val MIGRATION_3_4: Migration = object : Migration(3, 4) { override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE INDEX `index_rss_source_id` ON `$POST_TABLE` (`source_id`)") + addSourceIdIndex(database) } } + val MIGRATION_1_4: Migration = object : Migration(1, 4) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("CREATE TABLE IF NOT EXISTS `$SOURCE_TABLE` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `is_active` INTEGER NOT NULL, PRIMARY KEY(`url`))") addSourceID(database) - // bindConference posts to sources + // bind posts to sources + database.execSQL("UPDATE $POST_TABLE SET source_id = (SELECT id FROM $SOURCE_TABLE WHERE url = $POST_TABLE.channel_link)") + database.execSQL("CREATE INDEX `index_rss_source_id` ON `$POST_TABLE` (`source_id`)") + } +} + +val MIGRATION_1_5: Migration = object : Migration(1, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `$SOURCE_TABLE` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `is_active` INTEGER NOT NULL, PRIMARY KEY(`url`))") + + addSourceID(database) + + // bind posts to sources database.execSQL("UPDATE $POST_TABLE SET source_id = (SELECT id FROM $SOURCE_TABLE WHERE url = $POST_TABLE.channel_link)") database.execSQL("CREATE INDEX `index_rss_source_id` ON `$POST_TABLE` (`source_id`)") + + addIsUserSource(database) + } +} + + +val MIGRATION_2_5: Migration = object : Migration(2, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + addSourceID(database) + + // bind posts to sources + database.execSQL("UPDATE $POST_TABLE SET source_id = (SELECT id FROM $SOURCE_TABLE WHERE url = $POST_TABLE.channel_link)") + database.execSQL("CREATE INDEX `index_rss_source_id` ON `$POST_TABLE` (`source_id`)") + + addIsUserSource(database) + } +} + +/** + * Adds `is_user_source` field to the `source` table. + */ +val MIGRATION_3_5: Migration = object : Migration(3, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + addSourceIdIndex(database) + addIsUserSource(database) + } +} +/** + * Adds `is_user_source` field to the `source` table. + */ +val MIGRATION_4_5: Migration = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + addIsUserSource(database) + } +} + +private fun addSourceIdIndex(database: SupportSQLiteDatabase) { + database.execSQL("CREATE INDEX `index_rss_source_id` ON `$POST_TABLE` (`source_id`)") +} + +private fun addIsUserSource(database: SupportSQLiteDatabase) { + database.run { + execSQL("CREATE TABLE IF NOT EXISTS `source_temp` (`url` TEXT NOT NULL, `name` TEXT NOT NULL, `is_active` INTEGER NOT NULL, `id` INTEGER NOT NULL,`is_user_source` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))") + execSQL("INSERT INTO source_temp (url, name, is_active, id) SELECT url, name, is_active, id FROM `$SOURCE_TABLE`;") + execSQL("DROP TABLE IF EXISTS `$SOURCE_TABLE`;") + execSQL("ALTER TABLE source_temp RENAME TO `$SOURCE_TABLE`;") } } diff --git a/app/src/main/java/com/droidfeed/data/db/SourceDao.kt b/app/src/main/java/com/droidfeed/data/db/SourceDao.kt index 3852e18..565a4cd 100644 --- a/app/src/main/java/com/droidfeed/data/db/SourceDao.kt +++ b/app/src/main/java/com/droidfeed/data/db/SourceDao.kt @@ -26,6 +26,9 @@ interface SourceDao { @Update(onConflict = OnConflictStrategy.REPLACE) fun updateSource(source: Source) - @Query("SELECT COUNT(*) FROM ${AppDatabase.SOURCE_TABLE} WHERE is_active = 1") + @Query("SELECT COUNT(*) FROM $SOURCE_TABLE WHERE is_active = 1") fun getActiveSourceCount(): Int + + @Delete + fun remove(source: Source) } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/model/Source.kt b/app/src/main/java/com/droidfeed/data/model/Source.kt index 234af91..1c4f45f 100644 --- a/app/src/main/java/com/droidfeed/data/model/Source.kt +++ b/app/src/main/java/com/droidfeed/data/model/Source.kt @@ -19,7 +19,10 @@ data class Source( val name: String, @ColumnInfo(name = "url") - val url: String + val url: String, + + @ColumnInfo(name = "is_user_source") + val isUserSource: Boolean ) : Diffable { @@ -33,6 +36,9 @@ data class Source( @Ignore val isEnabled = ObservableBoolean() + @Ignore + val isRemovable = ObservableBoolean(false) + override fun isSame(item: Any) = id == (item as Source).id } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt index 7ad295e..d51c901 100644 --- a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt @@ -8,7 +8,6 @@ import com.droidfeed.data.parser.NewsXmlParser import com.droidfeed.util.extention.isOnline import com.droidfeed.util.logThrowable import com.google.firebase.firestore.FirebaseFirestore -import kotlinx.coroutines.GlobalScope import okhttp3.OkHttpClient import okhttp3.Request import java.io.IOException @@ -52,7 +51,6 @@ class SourceRepo @Inject constructor( * @param sourceUrl valid RSS or Atom feed url */ fun addFromUrl(sourceUrl: String): DataStatus { - return try { val request = Request.Builder() .url(sourceUrl) @@ -70,7 +68,8 @@ class SourceRepo @Inject constructor( val source = Source( id = 0, /* will be auto-set by room */ name = channelTitle, - url = sourceUrl + url = sourceUrl, + isUserSource = true ) sourceDao.insertSource(source) @@ -100,7 +99,8 @@ class SourceRepo @Inject constructor( Source( (document["id"] as Long).toInt(), document["name"] as String, - document["url"] as String + document["url"] as String, + false ) } @@ -118,4 +118,6 @@ class SourceRepo @Inject constructor( fun isSourceExisting(sourceUrl: String) = sourceDao.isUrlExists(sourceUrl).isNotEmpty() + fun remove(source: Source) = sourceDao.remove(source) + } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt b/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt index cba6c69..6dae0b6 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/UIModelAdapter.kt @@ -50,7 +50,6 @@ class UIModelAdapter constructor( notifyDataSetChanged() } else { launch { - val diffResult = async { val diffCallback = UIModelDiffCallback( ArrayList(uiModels), @@ -60,11 +59,12 @@ class UIModelAdapter constructor( uiModels.clear() uiModels.addAll(newModels) +// withContext(Dispatchers.Main){notifyDataSetChanged()} DiffUtil.calculateDiff(diffCallback, true) } withContext(Dispatchers.Main) { - val dif=diffResult.await() + val dif = diffResult.await() dispatchUpdates(dif) } @@ -85,4 +85,8 @@ class UIModelAdapter constructor( viewTypes.put(baseUiModel.getViewType(), baseUiModel) } } + + fun map(block: (List) -> Unit) { + block(uiModels) + } } diff --git a/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt b/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt index 9d766b7..f915ecf 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt @@ -6,7 +6,6 @@ import com.droidfeed.data.model.Source import com.droidfeed.databinding.ListItemSourceBinding import com.droidfeed.ui.adapter.BaseUIModel import com.droidfeed.ui.adapter.UIModelType -import com.droidfeed.ui.adapter.UIModelClickListener import com.droidfeed.ui.adapter.viewholder.SourceViewHolder /** @@ -14,7 +13,8 @@ import com.droidfeed.ui.adapter.viewholder.SourceViewHolder */ class SourceUIModel( private val source: Source, - private val clickListener: UIModelClickListener + private val onClick: (Source)->Unit, + private val onRemove: (Source)->Unit ) : BaseUIModel { override fun getViewHolder(parent: ViewGroup): SourceViewHolder { @@ -28,7 +28,7 @@ class SourceUIModel( } override fun bindViewHolder(viewHolder: SourceViewHolder) { - viewHolder.bind(source, clickListener) + viewHolder.bind(source, onClick,onRemove) } override fun getViewType(): Int = UIModelType.SOURCE.ordinal diff --git a/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt b/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt index dd8550d..2dce28d 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt @@ -3,7 +3,6 @@ package com.droidfeed.ui.adapter.viewholder import androidx.recyclerview.widget.RecyclerView import com.droidfeed.data.model.Source import com.droidfeed.databinding.ListItemSourceBinding -import com.droidfeed.ui.adapter.UIModelClickListener class SourceViewHolder( private val binding: ListItemSourceBinding @@ -11,9 +10,12 @@ class SourceViewHolder( fun bind( source: Source, - clickListener: UIModelClickListener + onItemClick: (Source) -> Unit, + onRemove: (Source) -> Unit ) { binding.source = source - binding.clickListener = clickListener + binding.setItemClickListener { onItemClick(source) } + binding.setRemoveClickListener { onRemove(source) } } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt index 1031901..fb021b7 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt @@ -155,9 +155,9 @@ class FeedViewModel @Inject constructor( post.bookmarked = 0 if (feedType.value == FeedType.BOOKMARKS) { - showUndoBookmarkSnack.postValue(Event({ + showUndoBookmarkSnack.postValue(Event { togglePostBookmark(post) - })) + }) } } else { post.bookmarked = 1 @@ -180,14 +180,14 @@ class FeedViewModel @Inject constructor( launch(Dispatchers.IO) { val bookmarkCount = postRepo.getBookmarkedCount() -// if (canPromptAppRate(bookmarkCount)) { -// analytics.logAppRatePrompt() + if (canPromptAppRate(bookmarkCount)) { + analytics.logAppRatePrompt() - showAppRateSnack.postValue(Event({ + showAppRateSnack.postValue(Event { openPlayStorePage.postValue(Event(Unit)) analytics.logAppRateFromPromtClick() - })) -// } + }) + } } } } diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt index 8478863..d49d749 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt @@ -5,6 +5,7 @@ import android.animation.LayoutTransition import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Intent +import android.graphics.Color import android.os.Bundle import android.view.View import androidx.core.view.GravityCompat @@ -19,12 +20,14 @@ import com.droidfeed.R import com.droidfeed.databinding.ActivityMainBinding import com.droidfeed.ui.adapter.BaseUIModelAlias import com.droidfeed.ui.adapter.UIModelAdapter +import com.droidfeed.ui.adapter.model.SourceUIModel import com.droidfeed.ui.common.BaseActivity import com.droidfeed.ui.module.onboard.OnBoardActivity import com.droidfeed.util.AnimUtils import com.droidfeed.util.ColorPalette import com.droidfeed.util.event.EventObserver import com.droidfeed.util.extention.hideKeyboard +import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_main.* import javax.inject.Inject @@ -65,7 +68,8 @@ class MainActivity : BaseActivity() { subscribeMenuVisibility() subscribeFilterVisibility() subscribeCloseKeyboard() - subscribeAddSourceIcon() + subscribeSourceListState() + subscribeSourceRemoveUndoSnack() binding = DataBindingUtil.setContentView( this, @@ -82,11 +86,28 @@ class MainActivity : BaseActivity() { initFilterDrawer() } - private fun subscribeAddSourceIcon() { -// mainViewModel.sourceAddIcon.observe(this, EventObserver { -// -// }) + private fun subscribeSourceRemoveUndoSnack() { + mainViewModel.showUndoSourceRemoveSnack.observe(this, EventObserver { onUndo -> + Snackbar.make( + binding.root, + R.string.info_source_removed, + Snackbar.LENGTH_LONG + ).apply { + setActionTextColor(Color.YELLOW) + animationMode = Snackbar.ANIMATION_MODE_SLIDE + setAction(R.string.undo) { onUndo() } + }.run { + show() + } + }) + } + private fun subscribeSourceListState() { + mainViewModel.sourceTransformation.observe(this, Observer { transform -> + uiModelAdapter.map { items -> + transform(items as List) as List + } + }) } private fun subscribeCloseKeyboard() { @@ -296,6 +317,7 @@ class MainActivity : BaseActivity() { overScrollMode = View.OVER_SCROLL_NEVER layoutManager = linearLayoutManager } + binding.drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener { override fun onDrawerStateChanged(newState: Int) { } diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt index 26ebfd5..6dfd55f 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt @@ -13,19 +13,17 @@ import com.droidfeed.data.DataStatus import com.droidfeed.data.model.Source import com.droidfeed.data.repo.PostRepo import com.droidfeed.data.repo.SourceRepo -import com.droidfeed.ui.adapter.UIModelClickListener import com.droidfeed.ui.adapter.model.SourceUIModel import com.droidfeed.ui.common.BaseViewModel import com.droidfeed.util.event.Event import com.droidfeed.util.hasAcceptedTerms import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import javax.inject.Inject class MainViewModel @Inject constructor( private val sourceRepo: SourceRepo, - postRepo: PostRepo, + private val postRepo: PostRepo, sharedPrefs: SharedPreferences ) : BaseViewModel() { @@ -39,8 +37,14 @@ class MainViewModel @Inject constructor( val sourceAddIcon = MutableLiveData<@DrawableRes Int>().apply { value = R.drawable.avd_close_to_add } + val sourceRemoveIcon = MutableLiveData<@DrawableRes Int>().apply { + value = R.drawable.avd_close_to_minus + } val isMenuVisible = MutableLiveData().apply { value = false } val isSourceInputVisible = MutableLiveData().apply { value = false } + val isRemoveSourceVisible = MutableLiveData().apply { value = true } + val isRemoveSourceEnabled = MutableLiveData().apply { value = true } + val isAddSourceEnabled = MutableLiveData().apply { value = true } val isSourceFilterVisible = MutableLiveData>().apply { value = Event(false) } val closeKeyboardEvent = MutableLiveData>().apply { value = Event(false) } val sourceErrText = MutableLiveData<@StringRes Int>().apply { value = R.string.empty_string } @@ -51,15 +55,26 @@ class MainViewModel @Inject constructor( val isBookmarksShown = MutableLiveData().apply { value = false } val isBookmarksButtonVisible = MutableLiveData().apply { value = true } val isBookmarksButtonSelected = MutableLiveData().apply { value = false } + val sourceTransformation = MutableLiveData<(List) -> List>() + val showUndoSourceRemoveSnack = MutableLiveData Unit>>() + + + private var isUserAddedSourceExist = false val sourceUIModelData: LiveData> = map(sourceRepo.getAll()) { sourceList -> + isUserAddedSourceExist = !sourceList.none { it.isUserSource } + + isRemoveSourceVisible.postValue(isUserAddedSourceExist) + sourceList.map { source -> - SourceUIModel(source, sourceClickListener) + SourceUIModel( + source = source, + onClick = this::onSourceClicked, + onRemove = this::onSourceRemoveClicked + ) } } - private var addSourceJob: Job? = null - init { updateSources(sourceRepo) } @@ -71,21 +86,29 @@ class MainViewModel @Inject constructor( } } - private val sourceClickListener = object : UIModelClickListener { - override fun onClick(source: Source) { - source.isActive = !source.isActive + private fun onSourceRemoveClicked(source: Source) { + launch(Dispatchers.IO) { + sourceRepo.remove(source) + sourceRemoveIcon.postValue(R.drawable.avd_close_to_minus) + isSourceAddButtonEnabled.postValue(true) + isAddSourceEnabled.postValue(true) - launch(Dispatchers.IO) { - /* update source when activated */ - if (source.isActive) { - postRepo.refresh( - this, - listOf(source) - ) - } + showUndoSourceRemoveSnack.postValue(Event { + sourceRepo.insert(listOf(source)) + }) + } + } - sourceRepo.update(source) + private fun onSourceClicked(source: Source) { + source.isActive = !source.isActive + + launch(Dispatchers.IO) { + /* update source when activated */ + if (source.isActive) { + postRepo.refresh(this, listOf(source)) } + + sourceRepo.update(source) } } @@ -136,18 +159,19 @@ class MainViewModel @Inject constructor( fun onAddSourceClicked() { val shouldOpenInputField = !(isSourceInputVisible.value!!) isSourceInputVisible.postValue(shouldOpenInputField) + isRemoveSourceVisible.postValue(!shouldOpenInputField && isUserAddedSourceExist) if (shouldOpenInputField) { - sourceAddIcon.postValue(R.drawable.avd_add_to_close) + R.drawable.avd_add_to_close } else { - sourceAddIcon.postValue(R.drawable.avd_close_to_add) - } + R.drawable.avd_close_to_add + }.also(sourceAddIcon::postValue) analytics.logAddSourceButtonClick() } fun onSaveSourceClicked(url: String) { - addSourceJob = launch(Dispatchers.IO) { + launch(Dispatchers.IO) { if (Patterns.WEB_URL.matcher(url.toLowerCase()).matches()) { sourceErrText.postValue(R.string.empty_string) @@ -162,13 +186,12 @@ class MainViewModel @Inject constructor( isSourceAddButtonEnabled.postValue(false) closeKeyboardEvent.postValue(Event(true)) - val status = sourceRepo.addFromUrl(cleanUrl) - - when (status) { + when (sourceRepo.addFromUrl(cleanUrl)) { is DataStatus.Successful -> { isSourceProgressVisible.postValue(false) isSourceAddButtonEnabled.postValue(true) isSourceInputVisible.postValue(false) + sourceAddIcon.postValue(R.drawable.avd_close_to_add) } is DataStatus.Failed -> { sourceErrText.postValue(R.string.error_add_source) @@ -201,6 +224,30 @@ class MainViewModel @Inject constructor( } } + fun onRemoveSourceClicked() { + val isListRemovable = sourceRemoveIcon.value == R.drawable.avd_close_to_minus + isAddSourceEnabled.postValue(!isListRemovable) + + transformSourcesRemovable(isListRemovable) + + if (!isListRemovable) { + R.drawable.avd_close_to_minus + } else { + R.drawable.avd_minus_to_close + }.also(sourceRemoveIcon::postValue) + + analytics.logRemoveSourceButtonClick() + } + + private fun transformSourcesRemovable(isRemovable: Boolean) { + sourceTransformation.postValue { sourceList -> + sourceList + .filter { uiModel -> uiModel.getData().isUserSource } + .forEach { uiModel -> uiModel.getData().isRemovable.set(isRemovable) } + sourceList + } + } + fun onMenuClicked() { val isCurrentlyVisible = isMenuVisible.value ?: false isMenuVisible.postValue(!isCurrentlyVisible) @@ -237,5 +284,10 @@ class MainViewModel @Inject constructor( sourceInputText.postValue(R.string.empty_string) sourceErrText.postValue(R.string.empty_string) sourceAddIcon.postValue(R.drawable.avd_close_to_add) + sourceRemoveIcon.postValue(R.drawable.avd_close_to_minus) + isAddSourceEnabled.postValue(true) + isRemoveSourceVisible.postValue(isUserAddedSourceExist) + isRemoveSourceEnabled.postValue(true) + transformSourcesRemovable(false) } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt index 60ffb24..537db33 100644 --- a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt +++ b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt @@ -55,6 +55,10 @@ class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics analytics.logEvent("click_add_source", null) } + fun logRemoveSourceButtonClick() { + analytics.logEvent("click_remove_source", null) + } + fun logSaveSourceButtonClick() { analytics.logEvent("click_save_source", null) } diff --git a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt index c684e25..630cfe4 100644 --- a/app/src/main/java/com/droidfeed/util/AppRateHelper.kt +++ b/app/src/main/java/com/droidfeed/util/AppRateHelper.kt @@ -7,9 +7,14 @@ import com.droidfeed.R import com.google.android.material.snackbar.Snackbar import javax.inject.Inject -class AppRateHelper @Inject constructor(private val sharedPrefs: SharedPreferences) { +class AppRateHelper @Inject constructor( + private val sharedPrefs: SharedPreferences, + private val analyticsUtil: AnalyticsUtil +) { fun showRateSnackbar(view: View, onAction: () -> Unit) { + analyticsUtil.logAppRatePrompt() + Snackbar.make(view, R.string.like_to_review_df, 5000) .setAction(R.string.yes) { onAction() } .setAnimationMode(Snackbar.ANIMATION_MODE_SLIDE) diff --git a/app/src/main/res/drawable/ic_trash_can_outline.xml b/app/src/main/res/drawable/ic_trash_can_outline.xml new file mode 100644 index 0000000..32816ee --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_can_outline.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0dc84da..1676633 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -38,7 +38,7 @@ android:animateLayoutChanges="true" android:orientation="vertical"> - + android:text="@string/sources" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:avdImageResource2="@{viewModel.sourceAddIcon}" + app:isEnabled="@{viewModel.isAddSourceEnabled}" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + + + @@ -9,19 +10,36 @@ type="com.droidfeed.data.model.Source" /> + name="itemClickListener" + type="android.view.View.OnClickListener" /> + + - + android:gravity="center_vertical" + android:onClick="@{(v)->itemClickListener.onClick(v)}" + android:theme="@style/Base.ThemeOverlay.AppCompat.Dark" + tools:background="@color/yellow"> + + - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 589b727..5967c2c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,7 @@ Internet connection is not available Bookmark is removed + Feed source is removed Can\'t refresh. diff --git a/build.gradle b/build.gradle index f6f5e3b..8ebc21d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$versions.kotlin" classpath "io.fabric.tools:gradle:$versions.fabric_gradle" diff --git a/dependencies.gradle b/dependencies.gradle index de144d0..1657d2a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,11 +1,11 @@ ext.versions = [ - kotlin : '1.3.21', + kotlin : '1.3.31', kotlin_coroutines : '1.1.1', core_ktx : '1.0.1', collections_ktx : '1.0.0', lifecycle : '2.0.0', fragment_ktx : '1.1.0-alpha03', - room : '2.1.0-alpha04', + room : '2.1.0-alpha07', navigation : '1.0.0-alpha07', paging : '2.1.0', browser : '1.0.0', From d300384dee0dedbb5178e8d398cc14e28e3362bf Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 19:04:21 -0400 Subject: [PATCH 11/19] fix typo --- app/src/main/java/com/droidfeed/data/parser/FeedParser.kt | 4 ++-- app/src/main/java/com/droidfeed/data/parser/RssParser.kt | 4 ++-- app/src/main/java/com/droidfeed/data/parser/XmlParser.kt | 2 +- app/src/main/java/com/droidfeed/data/repo/ConferenceRepo.kt | 2 +- app/src/main/java/com/droidfeed/data/repo/NewsletterRepo.kt | 2 +- app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt | 6 +++--- .../java/com/droidfeed/ui/binding/CommonBindingAdapters.kt | 4 ++-- .../main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt | 4 ++-- .../droidfeed/ui/module/newsletter/NewsletterFragment.kt | 2 +- .../droidfeed/ui/module/newsletter/NewsletterViewModel.kt | 2 +- .../java/com/droidfeed/ui/module/onboard/OnBoardActivity.kt | 2 +- app/src/main/java/com/droidfeed/util/CustomTab.kt | 2 +- .../com/droidfeed/util/{extention => extension}/Callx.kt | 2 +- .../com/droidfeed/util/{extention => extension}/Contextx.kt | 2 +- .../droidfeed/util/{extention => extension}/DataSource.kt | 2 +- .../com/droidfeed/util/{extention => extension}/Stringx.kt | 2 +- .../com/droidfeed/util/{extention => extension}/Viewx.kt | 2 +- .../util/{extention => extension}/XmlPullParserx.kt | 2 +- 18 files changed, 24 insertions(+), 24 deletions(-) rename app/src/main/java/com/droidfeed/util/{extention => extension}/Callx.kt (96%) rename app/src/main/java/com/droidfeed/util/{extention => extension}/Contextx.kt (95%) rename app/src/main/java/com/droidfeed/util/{extention => extension}/DataSource.kt (92%) rename app/src/main/java/com/droidfeed/util/{extention => extension}/Stringx.kt (98%) rename app/src/main/java/com/droidfeed/util/{extention => extension}/Viewx.kt (95%) rename app/src/main/java/com/droidfeed/util/{extention => extension}/XmlPullParserx.kt (94%) diff --git a/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt b/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt index bcdfffe..8f6714d 100644 --- a/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/FeedParser.kt @@ -5,8 +5,8 @@ import com.droidfeed.data.model.Channel import com.droidfeed.data.model.Content import com.droidfeed.data.model.Post import com.droidfeed.data.model.Source -import com.droidfeed.util.extention.asTimestamp -import com.droidfeed.util.extention.skipTag +import com.droidfeed.util.extension.asTimestamp +import com.droidfeed.util.extension.skipTag import com.droidfeed.util.logThrowable import org.xmlpull.v1.XmlPullParser import javax.inject.Inject diff --git a/app/src/main/java/com/droidfeed/data/parser/RssParser.kt b/app/src/main/java/com/droidfeed/data/parser/RssParser.kt index e641a86..59be43e 100644 --- a/app/src/main/java/com/droidfeed/data/parser/RssParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/RssParser.kt @@ -5,8 +5,8 @@ import com.droidfeed.data.model.Channel import com.droidfeed.data.model.Content import com.droidfeed.data.model.Post import com.droidfeed.data.model.Source -import com.droidfeed.util.extention.asTimestamp -import com.droidfeed.util.extention.skipTag +import com.droidfeed.util.extension.asTimestamp +import com.droidfeed.util.extension.skipTag import com.droidfeed.util.logThrowable import org.jsoup.Jsoup import org.jsoup.nodes.Element diff --git a/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt b/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt index b7bbbfa..f66adfc 100644 --- a/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt +++ b/app/src/main/java/com/droidfeed/data/parser/XmlParser.kt @@ -2,7 +2,7 @@ package com.droidfeed.data.parser import com.droidfeed.data.model.Post import com.droidfeed.data.model.Source -import com.droidfeed.util.extention.skipTag +import com.droidfeed.util.extension.skipTag import com.droidfeed.util.logThrowable import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException diff --git a/app/src/main/java/com/droidfeed/data/repo/ConferenceRepo.kt b/app/src/main/java/com/droidfeed/data/repo/ConferenceRepo.kt index 17271e9..4f1f46c 100644 --- a/app/src/main/java/com/droidfeed/data/repo/ConferenceRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/ConferenceRepo.kt @@ -2,7 +2,7 @@ package com.droidfeed.data.repo import com.droidfeed.data.DataStatus import com.droidfeed.data.model.Conference -import com.droidfeed.util.extention.isOnline +import com.droidfeed.util.extension.isOnline import com.google.firebase.Timestamp import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query diff --git a/app/src/main/java/com/droidfeed/data/repo/NewsletterRepo.kt b/app/src/main/java/com/droidfeed/data/repo/NewsletterRepo.kt index 1e11244..7928a47 100644 --- a/app/src/main/java/com/droidfeed/data/repo/NewsletterRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/NewsletterRepo.kt @@ -5,7 +5,7 @@ import com.droidfeed.data.api.mailchimp.ErrorAdapter import com.droidfeed.data.api.mailchimp.model.MailchimpError import com.droidfeed.data.api.mailchimp.model.Subscriber import com.droidfeed.data.api.mailchimp.service.NewsletterService -import com.droidfeed.util.extention.suspendingEnqueue +import com.droidfeed.util.extension.suspendingEnqueue import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.squareup.moshi.Moshi import retrofit2.HttpException diff --git a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt index d51c901..42fae71 100644 --- a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt @@ -5,7 +5,7 @@ import com.droidfeed.data.DataStatus import com.droidfeed.data.db.SourceDao import com.droidfeed.data.model.Source import com.droidfeed.data.parser.NewsXmlParser -import com.droidfeed.util.extention.isOnline +import com.droidfeed.util.extension.isOnline import com.droidfeed.util.logThrowable import com.google.firebase.firestore.FirebaseFirestore import okhttp3.OkHttpClient @@ -39,12 +39,12 @@ class SourceRepo @Inject constructor( * * @param source */ - @WorkerThread fun update(source: Source) = sourceDao.updateSource(source) - @WorkerThread fun insert(sources: List) = sourceDao.insertSources(sources) + fun insert(sources: Source) = sourceDao.insertSource(sources) + /** * Adds news source from given url. * diff --git a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt index 84b0526..a49b104 100644 --- a/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt +++ b/app/src/main/java/com/droidfeed/ui/binding/CommonBindingAdapters.kt @@ -9,8 +9,8 @@ import androidx.core.view.isVisible import androidx.databinding.BindingAdapter import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.droidfeed.R -import com.droidfeed.util.extention.fadeIn -import com.droidfeed.util.extention.fadeOut +import com.droidfeed.util.extension.fadeIn +import com.droidfeed.util.extension.fadeOut import com.droidfeed.util.glide.GlideApp import com.google.android.material.textfield.TextInputLayout import java.util.* diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt index fb021b7..fac6653 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt @@ -19,7 +19,7 @@ import com.droidfeed.util.appOpenCount import com.droidfeed.util.appRatePrompt import com.droidfeed.util.appRatePromptIndex import com.droidfeed.util.event.Event -import com.droidfeed.util.extention.asLiveData +import com.droidfeed.util.extension.asLiveData import com.droidfeed.util.shareCount import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -95,7 +95,7 @@ class FeedViewModel @Inject constructor( val openPlayStorePage = MutableLiveData>() init { - refreshJob = refresh() /* todo: causes unnecessary fetching when rebound with the fragment*/ + refreshJob = refresh() } private val pagedListConfig = PagedList.Config.Builder() diff --git a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterFragment.kt b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterFragment.kt index cc67787..dd0796e 100644 --- a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterFragment.kt +++ b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterFragment.kt @@ -13,7 +13,7 @@ import com.droidfeed.ui.common.BaseFragment import com.droidfeed.util.AnimUtils import com.droidfeed.util.CustomTab import com.droidfeed.util.event.EventObserver -import com.droidfeed.util.extention.getClickableSpan +import com.droidfeed.util.extension.getClickableSpan import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay diff --git a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt index 9aa21d7..fc9db13 100644 --- a/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/newsletter/NewsletterViewModel.kt @@ -13,7 +13,7 @@ import com.droidfeed.data.repo.NewsletterRepo import com.droidfeed.ui.common.BaseViewModel import com.droidfeed.util.AnalyticsUtil import com.droidfeed.util.event.Event -import com.droidfeed.util.extention.isValidEmail +import com.droidfeed.util.extension.isValidEmail import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.net.UnknownHostException diff --git a/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardActivity.kt b/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardActivity.kt index 1f54d77..b8e044a 100644 --- a/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardActivity.kt @@ -12,7 +12,7 @@ import com.droidfeed.ui.common.BaseActivity import com.droidfeed.ui.module.main.MainActivity import com.droidfeed.util.CustomTab import com.droidfeed.util.event.EventObserver -import com.droidfeed.util.extention.getClickableSpan +import com.droidfeed.util.extension.getClickableSpan import com.droidfeed.util.hasAcceptedTerms import com.google.android.material.snackbar.Snackbar import javax.inject.Inject diff --git a/app/src/main/java/com/droidfeed/util/CustomTab.kt b/app/src/main/java/com/droidfeed/util/CustomTab.kt index 589efc1..6cdc1e2 100644 --- a/app/src/main/java/com/droidfeed/util/CustomTab.kt +++ b/app/src/main/java/com/droidfeed/util/CustomTab.kt @@ -14,7 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import com.droidfeed.R import com.droidfeed.ui.module.webview.WebViewActivity -import com.droidfeed.util.extention.isPackageAvailable +import com.droidfeed.util.extension.isPackageAvailable import com.google.android.material.snackbar.Snackbar import javax.inject.Inject diff --git a/app/src/main/java/com/droidfeed/util/extention/Callx.kt b/app/src/main/java/com/droidfeed/util/extension/Callx.kt similarity index 96% rename from app/src/main/java/com/droidfeed/util/extention/Callx.kt rename to app/src/main/java/com/droidfeed/util/extension/Callx.kt index 2c03c80..d86391e 100644 --- a/app/src/main/java/com/droidfeed/util/extention/Callx.kt +++ b/app/src/main/java/com/droidfeed/util/extension/Callx.kt @@ -1,6 +1,6 @@ @file:Suppress("UNCHECKED_CAST") -package com.droidfeed.util.extention +package com.droidfeed.util.extension import com.droidfeed.util.logThrowable import retrofit2.Call diff --git a/app/src/main/java/com/droidfeed/util/extention/Contextx.kt b/app/src/main/java/com/droidfeed/util/extension/Contextx.kt similarity index 95% rename from app/src/main/java/com/droidfeed/util/extention/Contextx.kt rename to app/src/main/java/com/droidfeed/util/extension/Contextx.kt index 2832fbf..747552a 100644 --- a/app/src/main/java/com/droidfeed/util/extention/Contextx.kt +++ b/app/src/main/java/com/droidfeed/util/extension/Contextx.kt @@ -1,4 +1,4 @@ -package com.droidfeed.util.extention +package com.droidfeed.util.extension import android.content.Context import android.content.pm.PackageManager diff --git a/app/src/main/java/com/droidfeed/util/extention/DataSource.kt b/app/src/main/java/com/droidfeed/util/extension/DataSource.kt similarity index 92% rename from app/src/main/java/com/droidfeed/util/extention/DataSource.kt rename to app/src/main/java/com/droidfeed/util/extension/DataSource.kt index 750c595..58f96e1 100644 --- a/app/src/main/java/com/droidfeed/util/extention/DataSource.kt +++ b/app/src/main/java/com/droidfeed/util/extension/DataSource.kt @@ -1,4 +1,4 @@ -package com.droidfeed.util.extention +package com.droidfeed.util.extension import androidx.lifecycle.LiveData import androidx.paging.DataSource diff --git a/app/src/main/java/com/droidfeed/util/extention/Stringx.kt b/app/src/main/java/com/droidfeed/util/extension/Stringx.kt similarity index 98% rename from app/src/main/java/com/droidfeed/util/extention/Stringx.kt rename to app/src/main/java/com/droidfeed/util/extension/Stringx.kt index f543640..d7c3aac 100644 --- a/app/src/main/java/com/droidfeed/util/extention/Stringx.kt +++ b/app/src/main/java/com/droidfeed/util/extension/Stringx.kt @@ -1,4 +1,4 @@ -package com.droidfeed.util.extention +package com.droidfeed.util.extension import android.text.Spannable import android.text.SpannableString diff --git a/app/src/main/java/com/droidfeed/util/extention/Viewx.kt b/app/src/main/java/com/droidfeed/util/extension/Viewx.kt similarity index 95% rename from app/src/main/java/com/droidfeed/util/extention/Viewx.kt rename to app/src/main/java/com/droidfeed/util/extension/Viewx.kt index cf59eb1..0394736 100644 --- a/app/src/main/java/com/droidfeed/util/extention/Viewx.kt +++ b/app/src/main/java/com/droidfeed/util/extension/Viewx.kt @@ -1,4 +1,4 @@ -package com.droidfeed.util.extention +package com.droidfeed.util.extension import android.content.Context import android.view.View diff --git a/app/src/main/java/com/droidfeed/util/extention/XmlPullParserx.kt b/app/src/main/java/com/droidfeed/util/extension/XmlPullParserx.kt similarity index 94% rename from app/src/main/java/com/droidfeed/util/extention/XmlPullParserx.kt rename to app/src/main/java/com/droidfeed/util/extension/XmlPullParserx.kt index a8680d2..9d6a4d6 100644 --- a/app/src/main/java/com/droidfeed/util/extention/XmlPullParserx.kt +++ b/app/src/main/java/com/droidfeed/util/extension/XmlPullParserx.kt @@ -1,4 +1,4 @@ -package com.droidfeed.util.extention +package com.droidfeed.util.extension import com.droidfeed.util.logThrowable import org.xmlpull.v1.XmlPullParser From f3b442aad3cb7ce86a95e8c5fb404e7e2c5d0ab1 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 19:08:20 -0400 Subject: [PATCH 12/19] remove empty function --- app/src/main/java/com/droidfeed/App.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/com/droidfeed/App.kt b/app/src/main/java/com/droidfeed/App.kt index a78511b..ac4e373 100644 --- a/app/src/main/java/com/droidfeed/App.kt +++ b/app/src/main/java/com/droidfeed/App.kt @@ -30,14 +30,9 @@ class App : Application(), HasActivityInjector { super.onCreate() initDagger() - initSources() - sharedPrefs.appOpenCount += 1 } - private fun initSources() { - } - private fun initDagger() { DaggerAppComponent .builder() From 087c0f09dd657f4db205cf5ef43755c72c539d4f Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 22:44:14 -0400 Subject: [PATCH 13/19] add popup menu style --- app/src/main/res/values/styles_widget.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values/styles_widget.xml b/app/src/main/res/values/styles_widget.xml index 62af370..b357a1c 100644 --- a/app/src/main/res/values/styles_widget.xml +++ b/app/src/main/res/values/styles_widget.xml @@ -142,4 +142,9 @@ true + + \ No newline at end of file From 108f0d405607823249818e72fb6c240575bef398 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 22:45:25 -0400 Subject: [PATCH 14/19] add functionality to remove sources --- .../com/droidfeed/data/repo/SourceRepo.kt | 1 - .../ui/adapter/model/SourceUIModel.kt | 7 +- .../ui/adapter/viewholder/SourceViewHolder.kt | 22 ++++- .../droidfeed/ui/module/feed/FeedFragment.kt | 13 +-- .../droidfeed/ui/module/feed/FeedViewModel.kt | 2 +- .../droidfeed/ui/module/main/MainActivity.kt | 62 ++++++++------ .../droidfeed/ui/module/main/MainViewModel.kt | 82 +++++++------------ .../ui/module/onboard/OnBoardViewModel.kt | 6 +- app/src/main/res/layout/list_item_source.xml | 25 +++--- app/src/main/res/menu/source_menu.xml | 15 ++++ app/src/main/res/values/strings.xml | 1 + 11 files changed, 134 insertions(+), 102 deletions(-) create mode 100644 app/src/main/res/menu/source_menu.xml diff --git a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt index 42fae71..6ffa682 100644 --- a/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt +++ b/app/src/main/java/com/droidfeed/data/repo/SourceRepo.kt @@ -1,6 +1,5 @@ package com.droidfeed.data.repo -import androidx.annotation.WorkerThread import com.droidfeed.data.DataStatus import com.droidfeed.data.db.SourceDao import com.droidfeed.data.model.Source diff --git a/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt b/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt index f915ecf..3303178 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/model/SourceUIModel.kt @@ -13,8 +13,9 @@ import com.droidfeed.ui.adapter.viewholder.SourceViewHolder */ class SourceUIModel( private val source: Source, - private val onClick: (Source)->Unit, - private val onRemove: (Source)->Unit + private val onClick: (Source) -> Unit, + private val onRemoveClick: (Source) -> Unit, + private val onShareClick: (Source) -> Unit ) : BaseUIModel { override fun getViewHolder(parent: ViewGroup): SourceViewHolder { @@ -28,7 +29,7 @@ class SourceUIModel( } override fun bindViewHolder(viewHolder: SourceViewHolder) { - viewHolder.bind(source, onClick,onRemove) + viewHolder.bind(source, onClick, onRemoveClick, onShareClick) } override fun getViewType(): Int = UIModelType.SOURCE.ordinal diff --git a/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt b/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt index 2dce28d..326bec5 100644 --- a/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt +++ b/app/src/main/java/com/droidfeed/ui/adapter/viewholder/SourceViewHolder.kt @@ -1,9 +1,13 @@ package com.droidfeed.ui.adapter.viewholder +import androidx.appcompat.view.ContextThemeWrapper +import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.RecyclerView +import com.droidfeed.R import com.droidfeed.data.model.Source import com.droidfeed.databinding.ListItemSourceBinding + class SourceViewHolder( private val binding: ListItemSourceBinding ) : RecyclerView.ViewHolder(binding.root) { @@ -11,11 +15,25 @@ class SourceViewHolder( fun bind( source: Source, onItemClick: (Source) -> Unit, - onRemove: (Source) -> Unit + onRemoveClick: (Source) -> Unit, + onShareClick: (Source) -> Unit ) { binding.source = source binding.setItemClickListener { onItemClick(source) } - binding.setRemoveClickListener { onRemove(source) } + binding.setRemoveClickListener { view -> + val wrapper = ContextThemeWrapper(itemView.context, R.style.PopupMenuStyle) + PopupMenu(wrapper, view).apply { + menuInflater.inflate(R.menu.source_menu, menu) + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_remove -> onRemoveClick(source) + R.id.action_share->onShareClick(source) + } + + true + } + }.also { it.show() } + } } } diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt index dfa5df2..bb0db52 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedFragment.kt @@ -10,7 +10,6 @@ import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.paging.PagedList import androidx.recyclerview.widget.DefaultItemAnimator -import com.droidfeed.R import com.droidfeed.databinding.FragmentFeedBinding import com.droidfeed.rateAppIntent import com.droidfeed.ui.adapter.BaseUIModelAlias @@ -23,11 +22,13 @@ import com.droidfeed.ui.module.main.MainViewModel import com.droidfeed.util.AppRateHelper import com.droidfeed.util.CustomTab import com.droidfeed.util.event.EventObserver -import com.droidfeed.util.extention.isOnline +import com.droidfeed.util.extension.isOnline import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_feed.* import javax.inject.Inject + + class FeedFragment : BaseFragment("feed"), Scrollable { private lateinit var feedViewModel: FeedViewModel @@ -137,7 +138,7 @@ class FeedFragment : BaseFragment("feed"), Scrollable { binding.swipeRefreshPosts.isRefreshing = false Snackbar.make( binding.root, - R.string.info_no_internet, + com.droidfeed.R.string.info_no_internet, Snackbar.LENGTH_LONG ).show() } @@ -161,12 +162,12 @@ class FeedFragment : BaseFragment("feed"), Scrollable { feedViewModel.showUndoBookmarkSnack.observe(viewLifecycleOwner, EventObserver { onUndo -> Snackbar.make( binding.root, - R.string.info_bookmark_removed, + com.droidfeed.R.string.info_bookmark_removed, Snackbar.LENGTH_LONG ).apply { setActionTextColor(Color.YELLOW) animationMode = Snackbar.ANIMATION_MODE_SLIDE - setAction(R.string.undo) { onUndo() } + setAction(com.droidfeed.R.string.undo) { onUndo() } }.run { show() } @@ -194,4 +195,6 @@ class FeedFragment : BaseFragment("feed"), Scrollable { companion object { private const val REQUEST_CODE_SHARE = 4122 } + + } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt index fac6653..50a2168 100644 --- a/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/feed/FeedViewModel.kt @@ -34,7 +34,7 @@ class FeedViewModel @Inject constructor( ) : BaseViewModel() { private val feedType = MutableLiveData() - private var refreshJob = Job() + private lateinit var refreshJob : Job val postsLiveData: LiveData> = switchMap(feedType) { type -> when (type) { diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt index d49d749..aa281f9 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt @@ -6,8 +6,11 @@ import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.content.Intent import android.graphics.Color +import android.os.Build import android.os.Bundle import android.view.View +import android.view.WindowManager +import androidx.annotation.RequiresApi import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.databinding.DataBindingUtil @@ -20,32 +23,26 @@ import com.droidfeed.R import com.droidfeed.databinding.ActivityMainBinding import com.droidfeed.ui.adapter.BaseUIModelAlias import com.droidfeed.ui.adapter.UIModelAdapter -import com.droidfeed.ui.adapter.model.SourceUIModel import com.droidfeed.ui.common.BaseActivity import com.droidfeed.ui.module.onboard.OnBoardActivity import com.droidfeed.util.AnimUtils import com.droidfeed.util.ColorPalette import com.droidfeed.util.event.EventObserver -import com.droidfeed.util.extention.hideKeyboard +import com.droidfeed.util.extension.hideKeyboard import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_main.* import javax.inject.Inject + @Suppress("UNCHECKED_CAST") class MainActivity : BaseActivity() { - @Inject - lateinit var navController: MainNavController - - @Inject - lateinit var animUtils: AnimUtils - - @Inject - lateinit var colorPalette: ColorPalette + @Inject lateinit var navController: MainNavController + @Inject lateinit var animUtils: AnimUtils + @Inject lateinit var colorPalette: ColorPalette private lateinit var mainViewModel: MainViewModel private lateinit var binding: ActivityMainBinding - private var currentMenuColor = 0 private var previousScreenColor = 0 private var previousMenuButton: View? = null @@ -68,7 +65,7 @@ class MainActivity : BaseActivity() { subscribeMenuVisibility() subscribeFilterVisibility() subscribeCloseKeyboard() - subscribeSourceListState() + subscribeSourceShareEvent() subscribeSourceRemoveUndoSnack() binding = DataBindingUtil.setContentView( @@ -77,15 +74,23 @@ class MainActivity : BaseActivity() { ).apply { viewModel = mainViewModel lifecycleOwner = this@MainActivity - } - - binding.appbar.containerView.layoutTransition.apply { - enableTransitionType(LayoutTransition.CHANGING) + appbar.containerView.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) } initFilterDrawer() } + private fun subscribeSourceShareEvent() { + mainViewModel.shareSourceEvent.observe(this, EventObserver { content -> + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, content) + addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + }.also(this::startActivity) + }) + } + private fun subscribeSourceRemoveUndoSnack() { mainViewModel.showUndoSourceRemoveSnack.observe(this, EventObserver { onUndo -> Snackbar.make( @@ -102,14 +107,6 @@ class MainActivity : BaseActivity() { }) } - private fun subscribeSourceListState() { - mainViewModel.sourceTransformation.observe(this, Observer { transform -> - uiModelAdapter.map { items -> - transform(items as List) as List - } - }) - } - private fun subscribeCloseKeyboard() { mainViewModel.closeKeyboardEvent.observe(this, EventObserver { edtFeedUrl.hideKeyboard() @@ -318,6 +315,10 @@ class MainActivity : BaseActivity() { layoutManager = linearLayoutManager } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + setFilterPaddingForCutout() + } + binding.drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener { override fun onDrawerStateChanged(newState: Int) { } @@ -327,6 +328,7 @@ class MainActivity : BaseActivity() { override fun onDrawerClosed(drawerView: View) { mainViewModel.onFilterDrawerClosed() + drawerView.hideKeyboard() } override fun onDrawerOpened(drawerView: View) { @@ -334,6 +336,18 @@ class MainActivity : BaseActivity() { }) } + @RequiresApi(Build.VERSION_CODES.P) + private fun setFilterPaddingForCutout() { + window.attributes.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + + binding.filterView.setOnApplyWindowInsetsListener { v, insets -> + val displayCutout = insets.displayCutout + binding.filterView.setPadding(0, displayCutout?.safeInsetTop ?: 0, 0, 0) + insets.consumeDisplayCutout() + } + } + override fun onBackPressed() { val isFilterDrawerOpen = binding.drawerLayout.isDrawerOpen(GravityCompat.END) diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt index 6dfd55f..e6a811e 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainViewModel.kt @@ -37,14 +37,8 @@ class MainViewModel @Inject constructor( val sourceAddIcon = MutableLiveData<@DrawableRes Int>().apply { value = R.drawable.avd_close_to_add } - val sourceRemoveIcon = MutableLiveData<@DrawableRes Int>().apply { - value = R.drawable.avd_close_to_minus - } val isMenuVisible = MutableLiveData().apply { value = false } val isSourceInputVisible = MutableLiveData().apply { value = false } - val isRemoveSourceVisible = MutableLiveData().apply { value = true } - val isRemoveSourceEnabled = MutableLiveData().apply { value = true } - val isAddSourceEnabled = MutableLiveData().apply { value = true } val isSourceFilterVisible = MutableLiveData>().apply { value = Event(false) } val closeKeyboardEvent = MutableLiveData>().apply { value = Event(false) } val sourceErrText = MutableLiveData<@StringRes Int>().apply { value = R.string.empty_string } @@ -55,52 +49,63 @@ class MainViewModel @Inject constructor( val isBookmarksShown = MutableLiveData().apply { value = false } val isBookmarksButtonVisible = MutableLiveData().apply { value = true } val isBookmarksButtonSelected = MutableLiveData().apply { value = false } - val sourceTransformation = MutableLiveData<(List) -> List>() val showUndoSourceRemoveSnack = MutableLiveData Unit>>() - + val shareSourceEvent = MutableLiveData>() private var isUserAddedSourceExist = false val sourceUIModelData: LiveData> = map(sourceRepo.getAll()) { sourceList -> isUserAddedSourceExist = !sourceList.none { it.isUserSource } - isRemoveSourceVisible.postValue(isUserAddedSourceExist) - sourceList.map { source -> SourceUIModel( source = source, onClick = this::onSourceClicked, - onRemove = this::onSourceRemoveClicked + onRemoveClick = this::onSourceRemoveClicked, + onShareClick = this::onSourceShareClicked ) } } + private fun onSourceShareClicked(source: Source) { + val content = "${source.name}\n ${source.url}" + shareSourceEvent.postValue(Event(content)) + analytics.logSourceShare() + } + init { updateSources(sourceRepo) } - private fun updateSources(sourceRepo: SourceRepo) = launch(Dispatchers.IO) { - val result = sourceRepo.pull() - if (result is DataStatus.Successful) { - sourceRepo.insert(result.data ?: emptyList()) + private fun updateSources(sourceRepo: SourceRepo) { + launch(Dispatchers.IO) { + val result = sourceRepo.pull() + if (result is DataStatus.Successful) { + sourceRepo.insert(result.data ?: emptyList()) + } } } private fun onSourceRemoveClicked(source: Source) { launch(Dispatchers.IO) { sourceRepo.remove(source) - sourceRemoveIcon.postValue(R.drawable.avd_close_to_minus) isSourceAddButtonEnabled.postValue(true) - isAddSourceEnabled.postValue(true) + analytics.logRemoveSourceButtonClick() showUndoSourceRemoveSnack.postValue(Event { - sourceRepo.insert(listOf(source)) + addSource(source) + analytics.logSourceRemoveUndo() }) } } + private fun addSource(source: Source) { + launch(Dispatchers.IO) { sourceRepo.insert(source) } + } + private fun onSourceClicked(source: Source) { source.isActive = !source.isActive + analytics.logSourceActivation(source.isActive) launch(Dispatchers.IO) { /* update source when activated */ @@ -159,7 +164,6 @@ class MainViewModel @Inject constructor( fun onAddSourceClicked() { val shouldOpenInputField = !(isSourceInputVisible.value!!) isSourceInputVisible.postValue(shouldOpenInputField) - isRemoveSourceVisible.postValue(!shouldOpenInputField && isUserAddedSourceExist) if (shouldOpenInputField) { R.drawable.avd_add_to_close @@ -172,15 +176,17 @@ class MainViewModel @Inject constructor( fun onSaveSourceClicked(url: String) { launch(Dispatchers.IO) { - if (Patterns.WEB_URL.matcher(url.toLowerCase()).matches()) { + val trimmedUrl = url.trimIndent() + if (Patterns.WEB_URL.matcher(trimmedUrl.toLowerCase()).matches()) { sourceErrText.postValue(R.string.empty_string) - val cleanUrl = URLUtil.guessUrl(url) + val cleanUrl = URLUtil.guessUrl(trimmedUrl) val alreadyExists = sourceRepo.isSourceExisting(cleanUrl) if (alreadyExists) { sourceErrText.postValue(R.string.error_source_exists) + analytics.logSourceAlreadyExists() } else { isSourceProgressVisible.postValue(true) isSourceAddButtonEnabled.postValue(false) @@ -192,12 +198,14 @@ class MainViewModel @Inject constructor( isSourceAddButtonEnabled.postValue(true) isSourceInputVisible.postValue(false) sourceAddIcon.postValue(R.drawable.avd_close_to_add) + sourceInputText.postValue(R.string.empty_string) + analytics.logSourceAddSuccess() } is DataStatus.Failed -> { sourceErrText.postValue(R.string.error_add_source) isSourceProgressVisible.postValue(false) isSourceAddButtonEnabled.postValue(true) - + analytics.logSourceAddFail() } is DataStatus.HttpFailed -> { sourceErrText.postValue(R.string.error_internet_or_url) @@ -211,6 +219,7 @@ class MainViewModel @Inject constructor( sourceErrText.postValue(R.string.error_empty_source_url) } else { sourceErrText.postValue(R.string.error_invalid_url) + analytics.logSourceAddFailInvalidUrl() } analytics.logSaveSourceButtonClick() @@ -224,30 +233,6 @@ class MainViewModel @Inject constructor( } } - fun onRemoveSourceClicked() { - val isListRemovable = sourceRemoveIcon.value == R.drawable.avd_close_to_minus - isAddSourceEnabled.postValue(!isListRemovable) - - transformSourcesRemovable(isListRemovable) - - if (!isListRemovable) { - R.drawable.avd_close_to_minus - } else { - R.drawable.avd_minus_to_close - }.also(sourceRemoveIcon::postValue) - - analytics.logRemoveSourceButtonClick() - } - - private fun transformSourcesRemovable(isRemovable: Boolean) { - sourceTransformation.postValue { sourceList -> - sourceList - .filter { uiModel -> uiModel.getData().isUserSource } - .forEach { uiModel -> uiModel.getData().isRemovable.set(isRemovable) } - sourceList - } - } - fun onMenuClicked() { val isCurrentlyVisible = isMenuVisible.value ?: false isMenuVisible.postValue(!isCurrentlyVisible) @@ -284,10 +269,5 @@ class MainViewModel @Inject constructor( sourceInputText.postValue(R.string.empty_string) sourceErrText.postValue(R.string.empty_string) sourceAddIcon.postValue(R.drawable.avd_close_to_add) - sourceRemoveIcon.postValue(R.drawable.avd_close_to_minus) - isAddSourceEnabled.postValue(true) - isRemoveSourceVisible.postValue(isUserAddedSourceExist) - isRemoveSourceEnabled.postValue(true) - transformSourcesRemovable(false) } } \ No newline at end of file diff --git a/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardViewModel.kt index f790d01..c694121 100644 --- a/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/onboard/OnBoardViewModel.kt @@ -30,7 +30,7 @@ class OnBoardViewModel @Inject constructor( private var isSourceListPulled = false private var isPendingNavigation = false - private var pullSourceJob = Job() + private var pullSourceJob: Job init { pullSourceJob = pullSources() /* news sources are pulled on first open */ @@ -59,9 +59,7 @@ class OnBoardViewModel @Inject constructor( } private fun pullSources() = launch(Dispatchers.IO) { - val result = sourceRepo.pull() - - when (result) { + when (val result = sourceRepo.pull()) { is DataStatus.Successful -> { sourceRepo.insert(result.data ?: emptyList()) isSourceListPulled = true diff --git a/app/src/main/res/layout/list_item_source.xml b/app/src/main/res/layout/list_item_source.xml index 0b2adc4..2f64539 100644 --- a/app/src/main/res/layout/list_item_source.xml +++ b/app/src/main/res/layout/list_item_source.xml @@ -28,26 +28,17 @@ android:focusable="true" android:gravity="center_vertical" android:onClick="@{(v)->itemClickListener.onClick(v)}" + android:orientation="horizontal" android:theme="@style/Base.ThemeOverlay.AppCompat.Dark" tools:background="@color/yellow"> - - + + \ No newline at end of file diff --git a/app/src/main/res/menu/source_menu.xml b/app/src/main/res/menu/source_menu.xml new file mode 100644 index 0000000..0372f96 --- /dev/null +++ b/app/src/main/res/menu/source_menu.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5967c2c..92979c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,4 +93,5 @@ add This feed already exists. Please enter a feed url + Remove From f5a0e02da32018139231984a4f5652141b83f582 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 22:45:59 -0400 Subject: [PATCH 15/19] replace mockito with mockk --- app/build.gradle | 3 +-- app/src/test/java/com/droidfeed/TestUtils.kt | 5 ----- build.gradle | 2 +- dependencies.gradle | 12 ++++-------- 4 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 app/src/test/java/com/droidfeed/TestUtils.kt diff --git a/app/build.gradle b/app/build.gradle index 075c606..4b01d7e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -107,8 +107,7 @@ dependencies { implementation deps.firebase.firestore implementation deps.jsoup implementation deps.gson // quick fix for version conflict between room and firestore - testImplementation deps.mockito.core - testImplementation deps.mockito.inline + testImplementation deps.mockk testImplementation deps.junit testImplementation deps.kotlin.coroutines_test testImplementation deps.testx.core diff --git a/app/src/test/java/com/droidfeed/TestUtils.kt b/app/src/test/java/com/droidfeed/TestUtils.kt deleted file mode 100644 index ecb9a93..0000000 --- a/app/src/test/java/com/droidfeed/TestUtils.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.droidfeed - -import org.mockito.Mockito - -inline fun mock(): T = Mockito.mock(T::class.java) \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8ebc21d..091930d 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.4.0' + classpath 'com.android.tools.build:gradle:3.5.0-beta01' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$versions.kotlin" classpath "io.fabric.tools:gradle:$versions.fabric_gradle" diff --git a/dependencies.gradle b/dependencies.gradle index 1657d2a..4a4c0ef 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,6 +1,6 @@ ext.versions = [ kotlin : '1.3.31', - kotlin_coroutines : '1.1.1', + kotlin_coroutines : '1.2.1', core_ktx : '1.0.1', collections_ktx : '1.0.0', lifecycle : '2.0.0', @@ -21,12 +21,12 @@ ext.versions = [ firebase_config : '16.3.0', firebase_firestore : '18.1.0', okHttp : '3.13.1', - jSoup : '1.11.3', + jSoup : '1.12.1', gson : '2.8.5', play_services : '4.2.0', material : '1.1.0-alpha03', constraint_layout : '2.0.0-alpha3', - mockito : '2.24.5', + mockk : '1.9.3', ktlint : '0.28.0', dep_versions : '0.20.0', android_core_testing : '1.1.1', @@ -109,11 +109,7 @@ deps.okhttp = [ logging_interceptor: "com.squareup.okhttp3:logging-interceptor:$versions.okHttp" ] -deps.mockito = [ - core : "org.mockito:mockito-core:$versions.mockito", - inline: "org.mockito:mockito-inline:$versions.mockito" -] - +deps.mockk = "io.mockk:mockk:$versions.mockk" deps.crashlytics = "com.crashlytics.sdk.android:crashlytics:$versions.crashlytics" deps.constraint_layout = "androidx.constraintlayout:constraintlayout:$versions.constraint_layout" deps.material_design = "com.google.android.material:material:$versions.material" From 3947122f526fdb74d631dd3db5d5b818b5089025 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Mon, 13 May 2019 22:47:37 -0400 Subject: [PATCH 16/19] add analytics --- .../ui/module/about/AboutViewModel.kt | 2 +- .../java/com/droidfeed/util/AnalyticsUtil.kt | 42 +++++++++++++++---- .../res/drawable/ic_more_vert_black_24dp.xml | 5 +++ app/src/main/res/layout/activity_main.xml | 15 +------ gradle/wrapper/gradle-wrapper.properties | 4 +- 5 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 app/src/main/res/drawable/ic_more_vert_black_24dp.xml diff --git a/app/src/main/java/com/droidfeed/ui/module/about/AboutViewModel.kt b/app/src/main/java/com/droidfeed/ui/module/about/AboutViewModel.kt index 33de26a..6b95a6d 100644 --- a/app/src/main/java/com/droidfeed/ui/module/about/AboutViewModel.kt +++ b/app/src/main/java/com/droidfeed/ui/module/about/AboutViewModel.kt @@ -27,7 +27,7 @@ class AboutViewModel @Inject constructor() : BaseViewModel() { fun shareApp() { startIntent.postValue(Event(shareIntent)) - analytics.logShare("app") + analytics.logShareApp() } fun openPrivacyPolicy() { diff --git a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt index 537db33..d1a933d 100644 --- a/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt +++ b/app/src/main/java/com/droidfeed/util/AnalyticsUtil.kt @@ -11,23 +11,28 @@ import javax.inject.Inject class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics) { fun logBookmark(isBookmarked: Boolean) { - val text = if (isBookmarked) "bookmarked" else "unbookmarked" - analytics.logEvent( "bookmark", - bundleOf(Pair("bookmarked", text)) + bundleOf(Pair("bookmarked", isBookmarked.toString())) ) } - fun logShare(content: String) { + private fun logShare(content: String) { analytics.logEvent( FirebaseAnalytics.Event.SHARE, bundleOf(Pair("content", content)) ) } - fun logPostShare() { - logShare("post") + fun logPostShare() = logShare("post") + fun logShareApp() = logShare("app") + fun logSourceShare() = logShare("source") + + fun logSourceActivation(isActivated: Boolean) { + analytics.logEvent( + "source", + bundleOf(Pair("activated", isActivated.toString())) + ) } fun logPostClick() { @@ -52,11 +57,15 @@ class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics } fun logAddSourceButtonClick() { - analytics.logEvent("click_add_source", null) + analytics.logEvent("click_source_add", null) } fun logRemoveSourceButtonClick() { - analytics.logEvent("click_remove_source", null) + analytics.logEvent("click_source_remove", null) + } + + fun logSourceRemoveUndo() { + analytics.logEvent("click_source_remove_undo", null) } fun logSaveSourceButtonClick() { @@ -85,4 +94,21 @@ class AnalyticsUtil @Inject constructor(private val analytics: FirebaseAnalytics bundleOf(Pair("newsletter", "")) ) } + + fun logSourceAddSuccess() { + analytics.logEvent("click_add_source_success", null) + } + + fun logSourceAddFail() { + analytics.logEvent("click_add_source_fail", null) + } + + fun logSourceAlreadyExists() { + analytics.logEvent("click_add_source_exists", null) + } + + fun logSourceAddFailInvalidUrl() { + analytics.logEvent("click_add_source_fail_invalid_url", null) + } + } diff --git a/app/src/main/res/drawable/ic_more_vert_black_24dp.xml b/app/src/main/res/drawable/ic_more_vert_black_24dp.xml new file mode 100644 index 0000000..c097d3e --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1676633..7469f1d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -61,23 +61,10 @@ android:layout_gravity="end|center_vertical" android:onClick="@{(v)->viewModel.onAddSourceClicked()}" app:avdImageResource2="@{viewModel.sourceAddIcon}" - app:isEnabled="@{viewModel.isAddSourceEnabled}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6cad4c1..2e99df9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Aug 22 08:38:12 EDT 2018 +#Thu May 09 21:00:02 EDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-rc-1-all.zip From cebb847cc773d7580c7778e2ca5c2c8664cffe24 Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Tue, 14 May 2019 20:40:50 -0400 Subject: [PATCH 17/19] add proguard rules bump app version bump fabric version --- app/build.gradle | 5 +++-- app/proguard-rules.pro | 5 ++++- dependencies.gradle | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4b01d7e..4712849 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,8 +5,8 @@ apply plugin: 'kotlin-kapt' apply plugin: 'io.fabric' ext.app = [ - 'version' : '2.1.5', - 'version_code' : 22, + 'version' : '2.2.0', + 'version_code' : 24, 'compile_sdk_version': 28, 'min_sdk_version' : 21, 'target_sdk_version' : 28 @@ -46,6 +46,7 @@ android { release { shrinkResources true minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 09d1e6f..258552b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -16,6 +16,7 @@ -keep class com.crashlytics.** { *; } -keepattributes *Annotation*,Signature,Exceptions +-keep public class * extends java.lang.Exception -keep class com.squareup.okhttp.** { *; } -keep interface com.squareup.okhttp.** { *; } @@ -24,6 +25,7 @@ @retrofit.http.* ; } +-keep class androidx.core.app.CoreComponentFactory { *; } -dontwarn com.crashlytics.** -dontwarn okhttp3.** @@ -32,4 +34,5 @@ -dontwarn com.bumptech.glide.** -dontwarn com.google.common.** -dontwarn com.google.api.client.googleapis.** --dontwarn com.google.errorprone.annotations.* \ No newline at end of file +-dontwarn com.google.errorprone.annotations.* + diff --git a/dependencies.gradle b/dependencies.gradle index 4a4c0ef..35e3bec 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -10,11 +10,11 @@ ext.versions = [ paging : '2.1.0', browser : '1.0.0', retrofit : '2.5.0', - fabric_gradle : '1.27.1', + fabric_gradle : '1.29.0', dagger : '2.21', lottie : '2.8.0', glide : '4.9.0', - crashlytics : '2.9.9', + crashlytics : '2.10.0', detekt_ktlint : '1.0.0-RC14', firebase_core : '16.0.7', firebase_auth : '16.2.0', From 68f34b775071e30198c2f9db000591f4f6b327cf Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Tue, 14 May 2019 20:46:50 -0400 Subject: [PATCH 18/19] remove unnecessary transparent status bar --- .../module/about/licence/LicencesActivity.kt | 2 -- .../droidfeed/ui/module/main/MainActivity.kt | 19 +++++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/droidfeed/ui/module/about/licence/LicencesActivity.kt b/app/src/main/java/com/droidfeed/ui/module/about/licence/LicencesActivity.kt index 037c61c..c1b0019 100644 --- a/app/src/main/java/com/droidfeed/ui/module/about/licence/LicencesActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/about/licence/LicencesActivity.kt @@ -34,8 +34,6 @@ class LicencesActivity : BaseActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - setupTransparentStatusBar() - window.apply { val pinkColor = ContextCompat.getColor( this@LicencesActivity, diff --git a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt index aa281f9..2546271 100644 --- a/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt +++ b/app/src/main/java/com/droidfeed/ui/module/main/MainActivity.kt @@ -51,13 +51,21 @@ class MainActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) - setupTransparentStatusBar() super.onCreate(savedInstanceState) mainViewModel = ViewModelProviders .of(this, viewModelFactory) .get(MainViewModel::class.java) + binding = DataBindingUtil.setContentView( + this, + R.layout.activity_main + ).apply { + viewModel = mainViewModel + lifecycleOwner = this@MainActivity + appbar.containerView.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) + } + subscribeUserTerms() subscribeNavigation() subscribeScrollTopEvent() @@ -68,15 +76,6 @@ class MainActivity : BaseActivity() { subscribeSourceShareEvent() subscribeSourceRemoveUndoSnack() - binding = DataBindingUtil.setContentView( - this, - R.layout.activity_main - ).apply { - viewModel = mainViewModel - lifecycleOwner = this@MainActivity - appbar.containerView.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) - } - initFilterDrawer() } From b26a8be7c5c91398fd1a95a3cd6be5cdb15866dc Mon Sep 17 00:00:00 2001 From: dgngulcan Date: Tue, 14 May 2019 21:17:47 -0400 Subject: [PATCH 19/19] refactor from mockito to mockk --- .../com/droidfeed/NewsletterViewModelTest.kt | 29 ++++++------- .../com/droidfeed/OnBoardViewModelTest.kt | 43 ++++++++----------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt b/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt index c4e5cc7..d7e8356 100644 --- a/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt +++ b/app/src/test/java/com/droidfeed/NewsletterViewModelTest.kt @@ -1,11 +1,14 @@ package com.droidfeed -import android.util.Patterns import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import com.droidfeed.data.repo.NewsletterRepo import com.droidfeed.ui.module.newsletter.NewsletterViewModel import com.droidfeed.util.AnalyticsUtil +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before @@ -13,24 +16,20 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.Mockito -import org.mockito.Mockito.`when` -import org.mockito.Mockito.verify @Suppress("TestFunctionName") @RunWith(JUnit4::class) class NewsletterViewModelTest { - @Rule - @JvmField - var instantTaskExecutorRule = InstantTaskExecutorRule() + @Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule() + @MockK lateinit var newsletterRepo: NewsletterRepo + @MockK lateinit var analyticsUtil: AnalyticsUtil - private val newsletterRepo = Mockito.mock(NewsletterRepo::class.java) - private val analyticsUtil = Mockito.mock(AnalyticsUtil::class.java) private lateinit var viewModel: NewsletterViewModel @Before fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) viewModel = NewsletterViewModel(newsletterRepo, analyticsUtil) } @@ -42,25 +41,25 @@ class NewsletterViewModelTest { @Test fun GIVEN_empty_email_input_WHEN_sign_up_clicked_THEN_show_empty_email_error() = runBlocking { - val observer = mock>() + val observer = mockk>(relaxed = true) viewModel.errorText.observeForever(observer) viewModel.signUp("") - verify(observer).onChanged(R.string.error_empty_email) + verify(exactly = 1) { observer.onChanged(R.string.error_empty_email) } } @Test fun GIVEN_valid_email_input_WHEN_sign_up_clicked_THEN_show_progress_hide_button() = runBlocking { - val progressObserver = mock>() - val buttonObserver = mock>() + val progressObserver = mockk>(relaxed = true) + val buttonObserver = mockk>(relaxed = true) viewModel.isProgressVisible.observeForever(progressObserver) viewModel.isSignButtonVisible.observeForever(buttonObserver) viewModel.signUp("valid@email.com") - verify(progressObserver).onChanged(true) - verify(buttonObserver).onChanged(false) + verify(exactly = 1) { progressObserver.onChanged(true) } + verify(exactly = 1) { buttonObserver.onChanged(false) } } } \ No newline at end of file diff --git a/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt b/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt index 5a161fc..72bd492 100644 --- a/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt +++ b/app/src/test/java/com/droidfeed/OnBoardViewModelTest.kt @@ -6,6 +6,8 @@ import com.droidfeed.data.DataStatus import com.droidfeed.data.repo.SourceRepo import com.droidfeed.ui.module.onboard.OnBoardViewModel import com.droidfeed.util.event.EventObserver +import io.mockk.* +import io.mockk.impl.annotations.MockK import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Before @@ -13,22 +15,18 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers -import org.mockito.Mockito.* @Suppress("TestFunctionName") @RunWith(JUnit4::class) class OnBoardViewModelTest { - @Rule - @JvmField - var instantTaskExecutorRule = InstantTaskExecutorRule() - - private val sourceRepo = mock(SourceRepo::class.java) + @Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule() + @MockK lateinit var sourceRepo: SourceRepo private lateinit var viewModel: OnBoardViewModel @Before - fun before() { + fun setup() { + MockKAnnotations.init(this, relaxed = true, relaxUnitFun = true) viewModel = OnBoardViewModel(sourceRepo) } @@ -42,56 +40,53 @@ class OnBoardViewModelTest { @Test fun WHEN_initiated_THEN_pull_sources() { - runBlocking { - verify(sourceRepo, only()).pull() - viewModel = OnBoardViewModel(sourceRepo) - } + coVerify(exactly = 1) { sourceRepo.pull() } + + viewModel = OnBoardViewModel(sourceRepo) } @Test fun WHEN_agreed_terms_THEN_enable_continue_button() { - val observer = mock>() + val observer = mockk>(relaxed = true) viewModel.isContinueButtonEnabled.observeForever(observer) viewModel.onAgreementChecked(true) - verify(observer).onChanged(true) + verify(exactly = 1) { observer.onChanged(true) } } @Test fun WHEN_disagreed_terms_THEN_disable_continue_button() { - val observer = mock>() + val observer = mockk>(relaxed = true) viewModel.onAgreementChecked(true) viewModel.isContinueButtonEnabled.observeForever(observer) viewModel.onAgreementChecked(false) - verify(observer).onChanged(false) + verify(exactly = 1) { observer.onChanged(false) } } @Test fun WHEN_continue_button_is_clicked_THEN_disable_continue_button() { - val observer = mock>() + val observer = mockk>(relaxed = true) viewModel.onAgreementChecked(true) viewModel.isContinueButtonEnabled.observeForever(observer) viewModel.onContinueClicked() - verify(observer).onChanged(false) + verify(exactly = 1) { observer.onChanged(false) } } @Test fun WHEN_continue_button_is_clicked_THEN_open_main_activity() { - runBlocking { - `when`(sourceRepo.pull()).thenReturn(DataStatus.Successful()) - } - val observer = mock>() + coEvery { runBlocking { sourceRepo.pull() } } returns DataStatus.Successful(emptyList()) + viewModel = OnBoardViewModel(sourceRepo) + val observer = mockk>(relaxed = true) viewModel.openMainActivity.observeForever(observer) viewModel.onAgreementChecked(true) - viewModel.onContinueClicked() - verify(observer, only()).onChanged(ArgumentMatchers.any()) + verify(exactly = 1) { observer.onChanged(any()) } } } \ No newline at end of file