Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ fun Uri.isDiscoverPlacesPath(): Boolean {
return DISCOVER_PLACES_PATTERN.matcher(path()).matches()
}

fun Uri.isDiscoverUri(webEndpoint: String): Boolean {
return isKickstarterUri(webEndpoint) && DISCOVER_PATTERN.matcher(path()).matches()
}

fun Uri.isHivequeenUri(webEndpoint: String): Boolean {
return isKickstarterUri(webEndpoint) && Secrets.RegExpPattern.HIVEQUEEN.matcher(host()).matches()
}
Expand Down Expand Up @@ -244,6 +248,10 @@ private val CHECKOUT_THANKS_PATTERN = Pattern.compile(
"\\A\\/projects(\\/[a-zA-Z0-9_-]+)?\\/[a-zA-Z0-9_-]+\\/checkouts\\/\\d+\\/thanks\\z"
)

// /discover
// /discover/.*
private val DISCOVER_PATTERN = Pattern.compile("\\A\\/discover(?:\\/.*)?\\z")

// /discover/categories/param
private val DISCOVER_CATEGORIES_PATTERN = Pattern.compile("\\A\\/discover\\/categories\\/.*")

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/kickstarter/services/DiscoveryParams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ class DiscoveryParams private constructor(
put("backed", backed().toString())
}
category()?.let {
// TODO:
// `queryParams()` is used for `DiscoveryParams.toString()` so this overlap with
// `categoryParam` becomes unclear.
put("category_id", it.id().toString())
}
categoryParam()?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.res.Configuration
import android.graphics.drawable.Animatable
import android.os.Build
import android.os.Bundle
import android.view.ViewTreeObserver
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
Expand Down Expand Up @@ -69,6 +70,7 @@ class DiscoveryActivity : AppCompatActivity(), SharedPreferences.OnSharedPrefere
binding.root
)
setContentView(binding.root)

getEnvironment()?.let { env ->
viewModelFactory = DiscoveryViewModel.Factory(env)

Expand All @@ -78,7 +80,17 @@ class DiscoveryActivity : AppCompatActivity(), SharedPreferences.OnSharedPrefere
statsigClient = requireNotNull(env.statsigClient())
}

viewModel.provideIntent(intent)
/*
* Provide the Intent _after_ the view tree (most importantly the ViewPager)
* has completed a first-pass render, ensuring that the Intent workflows begin
* after those associated with the `pagerSetPrimaryPage` Subject et al. in the VM.
*/
binding.root.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
viewModel.provideIntent(intent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’–

}
})

// TODO: Replace with compose implementation
val nightModeFlags =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ class SplashScreenActivity : AppCompatActivity() {

viewModel.outputs.startDiscoveryActivity()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { startDiscoveryActivity() }
.subscribe {
if (it.isPresent) {
startNewDiscoveryActivityWithData(it.get())
} else {
startDiscoveryActivity()
}
}
.addToDisposable(disposables)

viewModel.outputs.startProjectActivity()
Expand Down Expand Up @@ -194,6 +200,19 @@ class SplashScreenActivity : AppCompatActivity() {
finish()
}

/*
* We will monitor how this Splash Screen evolves before moving this to either
* `ActivityExt.kt` or `ApplicationUtils.kt` for reusability.
*/
private fun startNewDiscoveryActivityWithData(data: Uri) {
val intent = Intent(this@SplashScreenActivity, DiscoveryActivity::class.java)
.setAction(Intent.ACTION_VIEW)
.setData(data)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
}

private fun startProjectActivity(uri: Uri) {
startActivity(projectIntent(uri))
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.kickstarter.libs.utils.UrlUtils.refTag
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.canUpdateFulfillment
import com.kickstarter.libs.utils.extensions.isCheckoutUri
import com.kickstarter.libs.utils.extensions.isDiscoverUri
import com.kickstarter.libs.utils.extensions.isEmailDomain
import com.kickstarter.libs.utils.extensions.isKSDomain
import com.kickstarter.libs.utils.extensions.isMainPage
Expand Down Expand Up @@ -63,6 +64,7 @@ import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.util.Optional

enum class InitializationState {
NOT_STARTED,
Expand All @@ -84,6 +86,7 @@ interface CustomNetworkClient {
fun obtainUriFromRedirection(uri: Uri): Observable<Response>
}

/* For Crashlytics logging only */
private class ProcessIntentException(message: String) : Exception(message)

interface SplashScreenViewModel {
Expand All @@ -92,7 +95,7 @@ interface SplashScreenViewModel {
fun startBrowser(): Observable<String>

/** Emits when we should start the [com.kickstarter.ui.activities.DiscoveryActivity]. */
fun startDiscoveryActivity(): Observable<Unit>
fun startDiscoveryActivity(): Observable<Optional<Uri>>

/** Emits when we should start the [com.kickstarter.ui.activities.ProjectActivity]. */
fun startProjectActivity(): Observable<Uri>
Expand Down Expand Up @@ -127,7 +130,7 @@ interface SplashScreenViewModel {
ViewModel(), Outputs {

private val startBrowser = BehaviorSubject.create<String>()
private val startDiscoveryActivity = BehaviorSubject.create<Unit>()
private val startDiscoveryActivity = BehaviorSubject.create<Optional<Uri>>()
private val startProjectActivity = BehaviorSubject.create<Uri>()
private val startProjectActivityForComment = BehaviorSubject.create<Uri>()
private val startProjectActivityForUpdate = BehaviorSubject.create<Uri>()
Expand Down Expand Up @@ -214,7 +217,7 @@ interface SplashScreenViewModel {
FirebaseCrashlytics.getInstance().recordException(ProcessIntentException(strIntentUri))
}
}
startDiscoveryActivity.onNext(Unit)
startDiscoveryActivity.onNext(Optional.empty<Uri>())
}
.addToDisposable(disposables)

Expand Down Expand Up @@ -252,7 +255,7 @@ interface SplashScreenViewModel {

mainPageUri
.subscribe {
startDiscoveryActivity.onNext(Unit)
startDiscoveryActivity.onNext(Optional.empty<Uri>())
}.addToDisposable(disposables)

projectFromEmail
Expand All @@ -262,15 +265,23 @@ interface SplashScreenViewModel {

isKSDomainUriFromEmail
.subscribe {
startDiscoveryActivity.onNext(Unit)
startDiscoveryActivity.onNext(Optional.empty<Uri>())
}.addToDisposable(disposables)

uriFromIntent
.filter { it.isDiscoverUri(webEndpoint) }
.subscribe {
startDiscoveryActivity.onNext(Optional.of(it))
}
.addToDisposable(disposables)

uriFromIntent
.filter { it.isNotNull() }
.filter { !it.isDiscoverUri(webEndpoint) }
.filter { lastPathSegmentIsProjects(it) }
.compose(Transformers.ignoreValuesV2())
.subscribe {
startDiscoveryActivity.onNext(it)
startDiscoveryActivity.onNext(Optional.empty<Uri>())
}.addToDisposable(disposables)

val projectObservable: Observable<Project> = uriFromIntent
Expand Down Expand Up @@ -443,6 +454,7 @@ interface SplashScreenViewModel {
val unsupportedDeepLink = uriFromIntent
.filter { it.isNotNull() }
.filter { !it.isMainPage() }
.filter { !it.isDiscoverUri(webEndpoint) }
.filter { !lastPathSegmentIsProjects(it) }
.filter { !it.isSettingsUrl() }
.filter { !it.isProjectSaveUri(webEndpoint) }
Expand Down Expand Up @@ -526,7 +538,7 @@ interface SplashScreenViewModel {

override fun startBrowser(): Observable<String> = startBrowser

override fun startDiscoveryActivity(): Observable<Unit> = startDiscoveryActivity
override fun startDiscoveryActivity(): Observable<Optional<Uri>> = startDiscoveryActivity

override fun startProjectActivityForComment(): Observable<Uri> = startProjectActivityForComment

Expand Down
18 changes: 18 additions & 0 deletions app/src/test/java/com/kickstarter/services/UriExtTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.kickstarter.libs.utils.extensions.isDiscoverCategoriesPath
import com.kickstarter.libs.utils.extensions.isDiscoverPlacesPath
import com.kickstarter.libs.utils.extensions.isDiscoverScopePath
import com.kickstarter.libs.utils.extensions.isDiscoverSortParam
import com.kickstarter.libs.utils.extensions.isDiscoverUri
import com.kickstarter.libs.utils.extensions.isEmailDomain
import com.kickstarter.libs.utils.extensions.isKSFavIcon
import com.kickstarter.libs.utils.extensions.isKickstarterUri
Expand Down Expand Up @@ -118,6 +119,23 @@ class UriExtTest : KSRobolectricTestCase() {
assertFalse(uri.isKickstarterUri(webEndpoint))
}

@Test
fun testUri_isDiscoverUri() {
val discoverBaseUri1 = Uri.parse("https://www.ksr.com/discover")
val discoverBaseUri2 = Uri.parse("https://www.ksr.com/discover/")
val discoverScopeUri = Uri.parse("https://www.ksr.com/discover/ending-soon")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question about the url of these tests πŸ€” , in which scenario do we see something like https://www.ksr.com/discover were the domain is www.ksr.com?
ksr is used as schema instead of https but never saw it as domain

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. For flexibility, isDiscoverUri() follows a pattern in similar parts of UriExt by calling isKickstarterUri(webEndpoint) in its implementation. So in the test case we also follow the pattern of using in the top-level webEndpoint variable defined in the test suite (L72), which is set to "https://www.ksr.com".

val discoverSortUri = Uri.parse("https://www.ksr.com/discover/advanced?sort=ending-soon")
val discoverMalformedUri = Uri.parse("https://www.ksr.com/discovery/advanced?sort=popularity")
assertTrue(discoverBaseUri1.isDiscoverUri(webEndpoint))
assertTrue(discoverBaseUri2.isDiscoverUri(webEndpoint))
assertTrue(discoverScopeUri.isDiscoverUri(webEndpoint))
assertTrue(discoverSortUri.isDiscoverUri(webEndpoint))
assertTrue(discoverCategoriesUri.isDiscoverUri(webEndpoint))
assertTrue(discoverPlacesUri.isDiscoverUri(webEndpoint))
assertFalse(discoverMalformedUri.isDiscoverUri(webEndpoint))
assertFalse(projectUri.isDiscoverUri(webEndpoint))
}

@Test
fun testUri_isWebViewUri() {
val ksrUri = Uri.parse("https://www.ksr.com/project")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ import org.junit.Assert.assertThrows
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import java.util.Optional

class DeepLinkViewModelTest : KSRobolectricTestCase() {
lateinit var vm: SplashScreenViewModel.DeepLinkViewModel
private val startBrowser = TestSubscriber<String>()
private val startDiscoveryActivity = TestSubscriber<Unit>()
private val startDiscoveryActivity = TestSubscriber<Optional<Uri>>()
private val startProjectActivity = TestSubscriber<Uri>()
private val startProjectActivityForCheckout = TestSubscriber<Uri>()
private val startProjectActivityForComment = TestSubscriber<Uri>()
Expand Down Expand Up @@ -137,7 +138,7 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
)

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue(Unit)
startDiscoveryActivity.assertValue { it.isEmpty }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
Expand Down Expand Up @@ -179,7 +180,7 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
)

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue(Unit)
startDiscoveryActivity.assertValue { it.isEmpty }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
Expand Down Expand Up @@ -215,7 +216,7 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
)

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue(Unit)
startDiscoveryActivity.assertValue { it.isEmpty }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
Expand Down Expand Up @@ -292,7 +293,7 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
vm.runInitializations()

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue(Unit)
startDiscoveryActivity.assertValue { it.isEmpty }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
Expand All @@ -302,6 +303,77 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
startPMOrderEditWebview.assertNoValues()
}

@Test
fun testBaseDiscoverDeeplink_OpensDiscovery() = runTest {
val url = "https://www.kickstarter.com/discover"

var environment = environment().toBuilder().featureFlagClient(MockFeatureFlagClient()).build()
setUpEnvironment(intent = intentWithData(url), environment = environment)

vm.runInitializations()

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue { it.isPresent && it.get() == Uri.parse(url) }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
startProjectActivityForUpdate.assertNoValues()
startProjectActivityForCommentToUpdate.assertNoValues()
startProjectActivityToSave.assertNoValues()
startPreLaunchProjectActivity.assertNoValues()
finishDeeplinkActivity.assertNoValues()
startProjectSurveyActivity.assertNoValues()
startPMOrderEditWebview.assertNoValues()
}

@Test
fun testDiscoverDeeplink_OpensDiscovery() = runTest {
// This can be any URL whose path begins with `/discover/`
val url =
"https://www.kickstarter.com/discover/advanced?sort=newest&staff_picks=1&raised=1"

var environment = environment().toBuilder().featureFlagClient(MockFeatureFlagClient()).build()
setUpEnvironment(intent = intentWithData(url), environment = environment)

vm.runInitializations()

startBrowser.assertNoValues()
startDiscoveryActivity.assertValue { it.isPresent && it.get() == Uri.parse(url) }
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
startProjectActivityForUpdate.assertNoValues()
startProjectActivityForCommentToUpdate.assertNoValues()
startProjectActivityToSave.assertNoValues()
startPreLaunchProjectActivity.assertNoValues()
finishDeeplinkActivity.assertNoValues()
startProjectSurveyActivity.assertNoValues()
startPMOrderEditWebview.assertNoValues()
}

@Test
fun testMalformedDiscoverDeeplink_OpensBrowser() = runTest {
val url = "https://www.kickstarter.com/discovery/categories/games"

var environment = environment().toBuilder().featureFlagClient(MockFeatureFlagClient()).build()
setUpEnvironment(intent = intentWithData(url), environment = environment)

vm.runInitializations()

startBrowser.assertValue(url)
startDiscoveryActivity.assertNoValues()
startProjectActivity.assertNoValues()
startProjectActivityForCheckout.assertNoValues()
startProjectActivityForComment.assertNoValues()
startProjectActivityForUpdate.assertNoValues()
startProjectActivityForCommentToUpdate.assertNoValues()
startProjectActivityToSave.assertNoValues()
startPreLaunchProjectActivity.assertNoValues()
finishDeeplinkActivity.assertNoValues()
startProjectSurveyActivity.assertNoValues()
startPMOrderEditWebview.assertNoValues()
}

@Test
fun testProjectPreviewLink_startsBrowser() {
val url =
Expand Down Expand Up @@ -658,6 +730,9 @@ class DeepLinkViewModelTest : KSRobolectricTestCase() {
}

@Test
/* 2025-12-08 Not changing this test, but noting that this corresponds to the
* `lastPathSegmentIsProjects()` method in the VM. Since this now overlaps with support for
* other Discover deep links, we can target this case more precisely. */
fun testDiscoveryDeepLink_startsDiscoveryActivity() {
val url = "https://www.kickstarter.com/projects"
var environment = environment().toBuilder().featureFlagClient(MockFeatureFlagClient()).build()
Expand Down