From 0994e38dfcc19938e48a537ed927537d200cab18 Mon Sep 17 00:00:00 2001 From: Yun Date: Wed, 26 Jun 2024 16:45:48 -0400 Subject: [PATCH 1/8] MBL-1475 Add backing details navigation --- app/src/main/AndroidManifest.xml | 3 ++ .../ui/BackingDetailsActivity.kt | 48 +++++++++++++++++ .../pledgedprojectsoverview/ui/PPOCardView.kt | 13 ++++- .../ui/PledgedProjectsOverviewActivity.kt | 10 ++++ .../ui/PledgedProjectsOverviewScreen.kt | 8 ++- .../PledgedProjectsOverviewViewModel.kt | 2 +- .../viewmodels/BackingDetailsViewModel.kt | 53 +++++++++++++++++++ .../res/layout/activity_backing_details.xml | 45 ++++++++++++++++ app/src/main/res/values/strings.xml | 3 +- .../PledgedProjectsOverviewViewModelTest.kt | 2 +- 10 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt create mode 100644 app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt create mode 100644 app/src/main/res/layout/activity_backing_details.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 985e6dd4b3..7a187ceefa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -166,6 +166,9 @@ + diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt new file mode 100644 index 0000000000..1cac1abc69 --- /dev/null +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt @@ -0,0 +1,48 @@ +package com.kickstarter.features.pledgedprojectsoverview.ui + +import android.os.Bundle +import androidx.activity.addCallback +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import com.kickstarter.databinding.ActivityBackingDetailsBinding +import com.kickstarter.libs.utils.extensions.addToDisposable +import com.kickstarter.libs.utils.extensions.getEnvironment +import com.kickstarter.ui.extensions.finishWithAnimation +import com.kickstarter.viewmodels.BackingDetailsViewModel +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable + +class BackingDetailsActivity : AppCompatActivity() { + + private lateinit var binding: ActivityBackingDetailsBinding + private lateinit var viewModelFactory: BackingDetailsViewModel.BackingDetailsViewModel.Factory + private val viewModel: BackingDetailsViewModel.BackingDetailsViewModel by viewModels { viewModelFactory } + + private val disposables = CompositeDisposable() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityBackingDetailsBinding.inflate(layoutInflater) + + this.getEnvironment()?.let { env -> + viewModelFactory = BackingDetailsViewModel.BackingDetailsViewModel.Factory(env, intent = intent) + env + } + + setContentView(binding.root) + + this.viewModel.outputs.url() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { binding.webView.loadUrl(it) } + .addToDisposable(disposables) + + this.onBackPressedDispatcher.addCallback { + finishWithAnimation() + } + } + + override fun onDestroy() { + disposables.clear() + super.onDestroy() + } +} diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PPOCardView.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PPOCardView.kt index c0f72a6571..af855b2b3a 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PPOCardView.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PPOCardView.kt @@ -68,6 +68,7 @@ fun PPOCardPreview() { showBadge = true, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 5 ) @@ -84,6 +85,7 @@ fun PPOCardPreview() { showBadge = true, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 6 ) @@ -101,6 +103,7 @@ fun PPOCardPreview() { showBadge = false, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 6 ) @@ -118,6 +121,7 @@ fun PPOCardPreview() { showBadge = true, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 7 ) @@ -135,6 +139,7 @@ fun PPOCardPreview() { showBadge = false, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 7 ) @@ -152,6 +157,7 @@ fun PPOCardPreview() { showBadge = true, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 8 ) @@ -169,6 +175,7 @@ fun PPOCardPreview() { showBadge = false, onActionButtonClicked = {}, onSecondaryActionButtonClicked = {}, + onProjectPledgeSummaryClick = {}, timeNumberForAction = 8 ) @@ -198,6 +205,7 @@ enum class PPOCardViewTestTag { fun PPOCardView( viewType: PPOCardViewType, onCardClick: () -> Unit, + onProjectPledgeSummaryClick: () -> Unit, projectName: String? = null, pledgeAmount: String? = null, imageUrl: String? = null, @@ -240,7 +248,8 @@ fun PPOCardView( projectName = projectName, pledgeAmount = pledgeAmount, imageUrl = imageUrl, - imageContentDescription = imageContentDescription + imageContentDescription = imageContentDescription, + onProjectPledgeSummaryClick = onProjectPledgeSummaryClick ) CreatorNameSendMessageView( @@ -280,9 +289,11 @@ fun ProjectPledgeSummaryView( pledgeAmount: String? = null, imageUrl: String? = null, imageContentDescription: String? = null, + onProjectPledgeSummaryClick: () -> Unit ) { Row( modifier = Modifier + .clickable { onProjectPledgeSummaryClick.invoke() } .fillMaxWidth() .padding(all = dimensions.paddingMediumSmall) ) { diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt index 4b8da00908..a7608ff25d 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt @@ -1,5 +1,6 @@ package com.kickstarter.features.pledgedprojectsoverview.ui +import android.content.Intent import android.os.Build import android.os.Bundle import androidx.activity.OnBackPressedCallback @@ -20,6 +21,7 @@ import com.kickstarter.libs.MessagePreviousScreenType import com.kickstarter.libs.utils.TransitionUtils import com.kickstarter.libs.utils.extensions.getEnvironment import com.kickstarter.libs.utils.extensions.isDarkModeEnabled +import com.kickstarter.ui.IntentKey import com.kickstarter.ui.SharedPreferenceKey import com.kickstarter.ui.activities.AppThemes import com.kickstarter.ui.compose.designsystem.KickstarterApp @@ -72,6 +74,8 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() { ppoCards = ppoCardPagingSource, totalAlerts = totalAlerts.value, onAddressConfirmed = { viewModel.showSnackbarAndRefreshCardsList() }, + onCardClick = { }, + onProjectPledgeSummaryClick = { url -> openBackingDetailsWebView(url) }, onSendMessageClick = { projectName -> viewModel.onMessageCreatorClicked(projectName) } ) } @@ -98,4 +102,10 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() { } } } + + private fun openBackingDetailsWebView(url: String) { + val intent = Intent(this, BackingDetailsActivity::class.java) + .putExtra(IntentKey.URL, url) + startActivity(intent) + } } diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt index 2e19f11107..0b70c26c8d 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt @@ -56,6 +56,8 @@ private fun PledgedProjectsOverviewScreenPreview() { totalAlerts = 10, onBackPressed = {}, onAddressConfirmed = {}, + onCardClick = {}, + onProjectPledgeSummaryClick = {}, onSendMessageClick = {}, errorSnackBarHostState = SnackbarHostState() ) @@ -72,6 +74,8 @@ fun PledgedProjectsOverviewScreen( errorSnackBarHostState: SnackbarHostState, ppoCards: LazyPagingItems, totalAlerts: Int = 0, + onCardClick: () -> Unit, + onProjectPledgeSummaryClick: (backingDetailsUrl: String) -> Unit, onSendMessageClick: (projectName: String) -> Unit ) { val openConfirmAddressAlertDialog = remember { mutableStateOf(false) } @@ -129,6 +133,7 @@ fun PledgedProjectsOverviewScreen( PPOCardView( viewType = it.viewType, onCardClick = { }, + onProjectPledgeSummaryClick = { onProjectPledgeSummaryClick(it.backingDetailsUrl) }, projectName = it.projectName, pledgeAmount = it.pledgeAmount, imageUrl = it.imageUrl, @@ -196,5 +201,6 @@ data class PPOCardDataMock( val showBadge: Boolean = true, val onActionButtonClicked: () -> Unit = {}, val onSecondaryActionButtonClicked: () -> Unit = {}, - val timeNumberForAction: Int = 25 + val timeNumberForAction: Int = 25, + val backingDetailsUrl: String = "https://www.kickstarter.com/projects/thehoneycouple/the-honey-couples-building-expansion" ) diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt index 7d67218019..8ea447c635 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt @@ -35,7 +35,7 @@ class PledgedProjectsOverviewViewModel(environment: Environment) : ViewModel() { val totalAlertsState: StateFlow = totalAlerts.asStateFlow() fun showSnackbarAndRefreshCardsList() { - snackbarMessage.invoke(R.string.address_confirmed_snackbar_text) + snackbarMessage.invoke(R.string.address_confirmed_snackbar_text_fpo) // TODO: MBL-1556 refresh the PPO list (i.e. requery the PPO list). } diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt new file mode 100644 index 0000000000..7564013228 --- /dev/null +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -0,0 +1,53 @@ +package com.kickstarter.viewmodels + +import android.content.Intent +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.kickstarter.libs.Environment +import com.kickstarter.libs.utils.extensions.addToDisposable +import com.kickstarter.ui.IntentKey +import io.reactivex.Observable +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.subjects.BehaviorSubject + +interface BackingDetailsViewModel { + + interface Inputs + + interface Outputs { + /** Emits the URL of the backing details to load in the web view. */ + fun url(): Observable + } + + class BackingDetailsViewModel(environment: Environment, private val intent: Intent? = null) : ViewModel(), Inputs, Outputs { + + private val url = BehaviorSubject.create() + + val inputs: Inputs = this + val outputs: Outputs = this + + private val disposables = CompositeDisposable() + + private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() + + init { + intent() + .map { it.getStringExtra(IntentKey.URL) } + .ofType(String::class.java) + .subscribe { this.url.onNext(it) } + .addToDisposable(disposables) + } + override fun url(): Observable = this.url + + override fun onCleared() { + disposables.clear() + super.onCleared() + } + + class Factory(private val environment: Environment, private val intent: Intent? = null) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return BackingDetailsViewModel(environment, intent) as T + } + } + } +} diff --git a/app/src/main/res/layout/activity_backing_details.xml b/app/src/main/res/layout/activity_backing_details.xml new file mode 100644 index 0000000000..ffe4f438f9 --- /dev/null +++ b/app/src/main/res/layout/activity_backing_details.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27ef5fe6c0..2da0c6ea77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -91,6 +91,7 @@ Project Alerts Alerts (%1$s) - Address confirmed! Need to change your address before it locks? Visit your backing details on our website. + Address confirmed! Need to change your address before it locks? Visit your backing details on our website. + Backing details diff --git a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt index 029f294b8d..a282aa1429 100644 --- a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt @@ -82,7 +82,7 @@ class PledgedProjectsOverviewViewModelTest : KSRobolectricTestCase() { // Should equal address confirmed string id assertEquals( snackbarAction, - R.string.address_confirmed_snackbar_text + R.string.address_confirmed_snackbar_text_fpo ) } } From d4bc9085ef2d51ba5fa5241e3a08b5dab42d18ce Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 27 Jun 2024 11:54:32 -0400 Subject: [PATCH 2/8] Fix tests --- .../ui/PledgedProjectsOverviewScreenTest.kt | 4 +++- .../ui/activities/compose/PPOCardViewKtTest.kt | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt index 4475473018..ef23c5e534 100644 --- a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt +++ b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt @@ -33,7 +33,9 @@ class PledgedProjectsOverviewScreenTest : KSRobolectricTestCase() { ppoCards = ppoCardList, errorSnackBarHostState = SnackbarHostState(), onSendMessageClick = {}, - onAddressConfirmed = {} + onAddressConfirmed = {}, + onCardClick = {}, + onProjectPledgeSummaryClick = {} ) } } diff --git a/app/src/test/java/com/kickstarter/ui/activities/compose/PPOCardViewKtTest.kt b/app/src/test/java/com/kickstarter/ui/activities/compose/PPOCardViewKtTest.kt index 32679aa3da..abedaa38c9 100644 --- a/app/src/test/java/com/kickstarter/ui/activities/compose/PPOCardViewKtTest.kt +++ b/app/src/test/java/com/kickstarter/ui/activities/compose/PPOCardViewKtTest.kt @@ -24,6 +24,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.CONFIRM_ADDRESS, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$50.00", creatorName = "Some really really really really really really really long name", @@ -53,6 +54,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.ADDRESS_CONFIRMED, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$50.00", creatorName = "Some really really really really really really really long name", @@ -83,6 +85,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.FIX_PAYMENT, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$50.00", creatorName = "Some really really really really really really really long name", @@ -113,6 +116,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.PAYMENT_FIXED, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$50.00", creatorName = "Some really really really really really really really long name", @@ -141,6 +145,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.AUTHENTICATE_CARD, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$60.00", creatorName = "Some really really really really really really really long name", @@ -171,6 +176,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.CARD_AUTHENTICATED, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$60.00", creatorName = "Some really really really really really really really long name", @@ -199,6 +205,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.TAKE_SURVEY, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$70.00", creatorName = "Some really really really really really really really long name", @@ -228,6 +235,7 @@ class PPOCardViewKtTest : KSRobolectricTestCase() { PPOCardView( viewType = PPOCardViewType.SURVEY_SUBMITTED, onCardClick = {}, + onProjectPledgeSummaryClick = {}, projectName = "Sugardew Island - Your cozy farm shop let’s pretend this is a longer title let’s pretend this is a longer title", pledgeAmount = "$70.00", creatorName = "Some really really really really really really really long name", From 7131121c8c1bab07d08f9b09b2443aa4e4c05ebe Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 27 Jun 2024 14:01:39 -0400 Subject: [PATCH 3/8] Use coroutines --- .../ui/BackingDetailsActivity.kt | 31 +++++---- .../viewmodels/BackingDetailsViewModel.kt | 63 ++++++++----------- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt index 1cac1abc69..9ac592448c 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/BackingDetailsActivity.kt @@ -4,45 +4,42 @@ import android.os.Bundle import androidx.activity.addCallback import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.kickstarter.databinding.ActivityBackingDetailsBinding -import com.kickstarter.libs.utils.extensions.addToDisposable import com.kickstarter.libs.utils.extensions.getEnvironment import com.kickstarter.ui.extensions.finishWithAnimation import com.kickstarter.viewmodels.BackingDetailsViewModel -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.launch class BackingDetailsActivity : AppCompatActivity() { private lateinit var binding: ActivityBackingDetailsBinding - private lateinit var viewModelFactory: BackingDetailsViewModel.BackingDetailsViewModel.Factory - private val viewModel: BackingDetailsViewModel.BackingDetailsViewModel by viewModels { viewModelFactory } - - private val disposables = CompositeDisposable() + private lateinit var viewModelFactory: BackingDetailsViewModel.Factory + private val viewModel: BackingDetailsViewModel by viewModels { viewModelFactory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityBackingDetailsBinding.inflate(layoutInflater) this.getEnvironment()?.let { env -> - viewModelFactory = BackingDetailsViewModel.BackingDetailsViewModel.Factory(env, intent = intent) + viewModelFactory = BackingDetailsViewModel.Factory(env, intent = intent) env } setContentView(binding.root) - this.viewModel.outputs.url() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { binding.webView.loadUrl(it) } - .addToDisposable(disposables) + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.url.collect { url -> + binding.webView.loadUrl(url) + } + } + } this.onBackPressedDispatcher.addCallback { finishWithAnimation() } } - - override fun onDestroy() { - disposables.clear() - super.onDestroy() - } } diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt index 7564013228..ccd9c62088 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -3,51 +3,40 @@ package com.kickstarter.viewmodels import android.content.Intent import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import com.kickstarter.libs.Environment -import com.kickstarter.libs.utils.extensions.addToDisposable import com.kickstarter.ui.IntentKey import io.reactivex.Observable -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.subjects.BehaviorSubject - -interface BackingDetailsViewModel { - - interface Inputs - - interface Outputs { - /** Emits the URL of the backing details to load in the web view. */ - fun url(): Observable - } - - class BackingDetailsViewModel(environment: Environment, private val intent: Intent? = null) : ViewModel(), Inputs, Outputs { - - private val url = BehaviorSubject.create() - - val inputs: Inputs = this - val outputs: Outputs = this - - private val disposables = CompositeDisposable() - - private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() - - init { +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.rx2.asFlow + +class BackingDetailsViewModel(environment: Environment, private val intent: Intent? = null) : ViewModel() { + + private var mutableUrl = MutableSharedFlow() + val url: SharedFlow + get() = mutableUrl + .asSharedFlow() + private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() + + init { + viewModelScope.launch { intent() .map { it.getStringExtra(IntentKey.URL) } .ofType(String::class.java) - .subscribe { this.url.onNext(it) } - .addToDisposable(disposables) - } - override fun url(): Observable = this.url - - override fun onCleared() { - disposables.clear() - super.onCleared() + .asFlow().map { url -> + mutableUrl.emit(url) + }.collect() } + } - class Factory(private val environment: Environment, private val intent: Intent? = null) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return BackingDetailsViewModel(environment, intent) as T - } + class Factory(private val environment: Environment, private val intent: Intent? = null) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return BackingDetailsViewModel(environment, intent) as T } } } From 9b438e8433a19cc7224d85e61090fbc380eaab4d Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 27 Jun 2024 14:19:07 -0400 Subject: [PATCH 4/8] Don't need to be a stream --- .../kickstarter/viewmodels/BackingDetailsViewModel.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt index ccd9c62088..c3803f552b 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -7,9 +7,8 @@ import androidx.lifecycle.viewModelScope import com.kickstarter.libs.Environment import com.kickstarter.ui.IntentKey import io.reactivex.Observable -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -17,10 +16,9 @@ import kotlinx.coroutines.rx2.asFlow class BackingDetailsViewModel(environment: Environment, private val intent: Intent? = null) : ViewModel() { - private var mutableUrl = MutableSharedFlow() - val url: SharedFlow + private var mutableUrl = MutableStateFlow("") + val url: StateFlow get() = mutableUrl - .asSharedFlow() private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() init { From ab2e491ff44de4d5e9b43b0009bc60b9730c6890 Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 27 Jun 2024 15:33:15 -0400 Subject: [PATCH 5/8] Add vm test --- .../viewmodels/BackingDetailsViewModel.kt | 10 ++++- .../viewmodels/BackingDetailsViewModelTest.kt | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt index c3803f552b..010e636afd 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -8,9 +8,12 @@ import com.kickstarter.libs.Environment import com.kickstarter.ui.IntentKey import io.reactivex.Observable import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asFlow @@ -18,7 +21,12 @@ class BackingDetailsViewModel(environment: Environment, private val intent: Inte private var mutableUrl = MutableStateFlow("") val url: StateFlow - get() = mutableUrl + get() = mutableUrl.asStateFlow() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = "" + ) private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() init { diff --git a/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt new file mode 100644 index 0000000000..bd374b046f --- /dev/null +++ b/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt @@ -0,0 +1,38 @@ +package com.kickstarter.viewmodels + +import android.content.Intent +import com.kickstarter.KSRobolectricTestCase +import com.kickstarter.libs.Environment +import com.kickstarter.ui.IntentKey +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class BackingDetailsViewModelTest : KSRobolectricTestCase() { + private lateinit var vm: BackingDetailsViewModel + + private fun setUpEnvironment(environment: Environment, intent: Intent) { + this.vm = BackingDetailsViewModel.Factory(environment, intent).create(BackingDetailsViewModel::class.java) + } + + @Test + fun `test VM init state that the backing details url is emitted from intent`() = runTest { + val testUrl = "https://www.kickstarter.com/" + val intent = Intent().apply { + putExtra(IntentKey.URL, testUrl) + } + setUpEnvironment(environment(), intent = intent) + + val emittedUrls = mutableListOf() + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + vm.url.toList(emittedUrls) + } + + assertEquals(emittedUrls.size, 2) + assertEquals(emittedUrls.last(), testUrl) + } +} From 615f4ed0f01e52f6870cb3cb0b299ea3eae9f890 Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 27 Jun 2024 15:54:01 -0400 Subject: [PATCH 6/8] lint --- .../com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt index bd374b046f..414c9e30c4 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/BackingDetailsViewModelTest.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class BackingDetailsViewModelTest : KSRobolectricTestCase() { +class BackingDetailsViewModelTest : KSRobolectricTestCase() { private lateinit var vm: BackingDetailsViewModel private fun setUpEnvironment(environment: Environment, intent: Intent) { From 8aeb9e9cc7f74ecea25723fe054065ad225e2f20 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 1 Jul 2024 09:59:55 -0400 Subject: [PATCH 7/8] Oops more rx to remove --- .../kickstarter/viewmodels/BackingDetailsViewModel.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt index 010e636afd..2852fae3b0 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -27,16 +27,11 @@ class BackingDetailsViewModel(environment: Environment, private val intent: Inte started = SharingStarted.WhileSubscribed(), initialValue = "" ) - private fun intent() = this.intent?.let { Observable.just(it) } ?: Observable.empty() - init { viewModelScope.launch { - intent() - .map { it.getStringExtra(IntentKey.URL) } - .ofType(String::class.java) - .asFlow().map { url -> - mutableUrl.emit(url) - }.collect() + intent?.getStringExtra(IntentKey.URL)?.let { url -> + mutableUrl.emit(url) + } } } From fbebe9daf50fa3f5c32cc8b49641530c5a13b5a2 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 1 Jul 2024 10:40:43 -0400 Subject: [PATCH 8/8] lint --- .../com/kickstarter/viewmodels/BackingDetailsViewModel.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt index 2852fae3b0..8108c513f3 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/BackingDetailsViewModel.kt @@ -6,16 +6,12 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.kickstarter.libs.Environment import com.kickstarter.ui.IntentKey -import io.reactivex.Observable import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.rx2.asFlow class BackingDetailsViewModel(environment: Environment, private val intent: Intent? = null) : ViewModel() {