Sample app deeplinking

This commit is contained in:
Dimitry Ivanov 2020-07-03 16:16:25 +03:00
parent 9dce1c8533
commit 78ec885294
6 changed files with 166 additions and 24 deletions

View File

@ -18,6 +18,12 @@ android {
resConfig 'en'
setProperty("archivesBaseName", "markwon-$versionName")
final def scheme = 'markwon'
buildConfigField 'String', 'DEEPLINK_SCHEME', "\"$scheme\""
manifestPlaceholders += [
'deeplink_scheme': scheme
]
}
dexOptions {

View File

@ -16,10 +16,20 @@
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".sample.MainActivity">
<!-- launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="${deeplink_scheme}" />
</intent-filter>
</activity>
<activity
@ -39,8 +49,8 @@
<data android:pathPattern=".*\\.md" />
<data android:pathPattern=".*\\..*\\.md" />
<data android:pathPattern=".*\\..*\\..*\\.md"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.md"/>
<data android:pathPattern=".*\\..*\\..*\\.md" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.md" />
</intent-filter>
</activity>

View File

@ -0,0 +1,73 @@
package io.noties.markwon.app.sample
import android.net.Uri
import io.noties.debug.Debug
import io.noties.markwon.app.BuildConfig
import io.noties.markwon.sample.annotations.MarkwonArtifact
sealed class Deeplink {
data class Sample(val id: String) : Deeplink()
data class Search(val search: SampleSearch) : Deeplink()
companion object {
fun parse(data: Uri?): Deeplink? {
Debug.i(data)
@Suppress("NAME_SHADOWING")
val data = data ?: return null
if (BuildConfig.DEEPLINK_SCHEME != data.scheme) return null
return when (data.host) {
// markwon://sample/20202827
"sample" -> parseSample(data.lastPathSegment)
// markwon://search?a=ext-latex&q=text
"search" -> parseSearch(data.query)
else -> null
}
}
private fun parseSample(id: String?): Sample? {
if (id == null) return null
return Sample(id)
}
private fun parseSearch(query: String?): Search? {
Debug.i("query: '$query'")
val params = query
?.split("&")
?.map {
val (k, v) = it.split("=")
Pair(k, v)
}
?.toMap()
?: return null
val artifact = params["a"]
val tag = params["t"]
val search = params["q"]
Debug.i("artifact: '$artifact', tag: '$tag', search: '$search'")
val sampleSearch: SampleSearch? = if (artifact != null) {
val encodedArtifact = MarkwonArtifact.values()
.firstOrNull { it.artifactName() == artifact }
if (encodedArtifact != null) {
SampleSearch.Artifact(search, encodedArtifact)
} else {
null
}
} else if (tag != null) {
SampleSearch.Tag(search, tag)
} else if (search != null) {
SampleSearch.All(search)
} else {
null
}
if (sampleSearch == null) {
return null
}
return Search(sampleSearch)
}
}
}

View File

@ -2,7 +2,10 @@ package io.noties.markwon.app.sample
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import io.noties.markwon.app.App
import io.noties.markwon.app.sample.ui.SampleFragment
import io.noties.markwon.app.sample.ui.SampleListFragment
class MainActivity : FragmentActivity() {
@ -11,9 +14,27 @@ class MainActivity : FragmentActivity() {
super.onCreate(savedInstanceState)
if (supportFragmentManager.findFragmentById(Window.ID_ANDROID_CONTENT) == null) {
supportFragmentManager.beginTransaction()
.add(Window.ID_ANDROID_CONTENT, SampleListFragment.init())
.commitNowAllowingStateLoss()
// process deeplink if we are not restored
val deeplink = Deeplink.parse(intent.data)
val deepLinkFragment: Fragment? = if (deeplink != null) {
when (deeplink) {
is Deeplink.Sample -> App.sampleManager.sample(deeplink.id)
?.let { SampleFragment.init(it) }
is Deeplink.Search -> SampleListFragment.init(deeplink.search)
}
} else null
if (deepLinkFragment != null) {
supportFragmentManager.beginTransaction()
.replace(Window.ID_ANDROID_CONTENT, deepLinkFragment)
.addToBackStack(null)
.commit()
}
}
}
}

View File

@ -15,6 +15,10 @@ class SampleManager(
SampleUtils.readSamples(context)
}
fun sample(id: String): Sample? {
return samples.firstOrNull { id == it.id }
}
fun samples(search: SampleSearch?, callback: (List<Sample>) -> Unit): Cancellable {
var action: ((List<Sample>) -> Unit)? = callback

View File

@ -98,11 +98,20 @@ class SampleListFragment : Fragment() {
// }
val state: State? = arguments?.getParcelable(STATE)
Debug.i(state)
val initialSearch = arguments?.getString(ARG_SEARCH)
// clear it anyway
arguments?.remove(ARG_SEARCH)
Debug.i(state, initialSearch)
pendingRecyclerScrollPosition = state?.recyclerScrollPosition
if (state?.search != null) {
searchBar.search(state.search)
val search = listOf(state?.search, initialSearch)
.firstOrNull { it != null }
if (search != null) {
searchBar.search(search)
} else {
fetch()
}
@ -144,35 +153,38 @@ class SampleListFragment : Fragment() {
val appBarTitle: TextView = appBar.findViewById(R.id.app_bar_title)
val appBarIconReadme: ImageView = appBar.findViewById(R.id.app_bar_icon_readme)
val isInitialScreen = type is Type.All
val isInitialScreen = fragmentManager?.backStackEntryCount == 0
appBarIcon.hidden = isInitialScreen
appBarIconReadme.hidden = !isInitialScreen
val type = this.type
if (type is Type.All) {
val (text, background) = when (type) {
is Type.Artifact -> Pair(type.artifact.displayName, R.drawable.bg_artifact)
is Type.Tag -> Pair(type.tag.tagDisplayName, R.drawable.bg_tag)
is Type.All -> Pair(resources.getString(R.string.app_name), 0)
}
appBarTitle.text = text
if (background != 0) {
appBarTitle.setBackgroundResource(background)
}
if (isInitialScreen) {
appBarIconReadme.setOnClickListener {
context?.let {
val intent = ReadMeActivity.makeIntent(it)
it.startActivity(intent)
}
}
return
}
} else {
appBarIcon.setImageResource(R.drawable.ic_arrow_back_white_24dp)
appBarIcon.setOnClickListener {
requireActivity().onBackPressed()
}
val (text, background) = when (type) {
is Type.Artifact -> Pair(type.artifact.displayName, R.drawable.bg_artifact)
is Type.Tag -> Pair(type.tag.tagDisplayName, R.drawable.bg_tag)
else -> error("Unexpected type: $type")
}
appBarTitle.text = text
appBarTitle.setBackgroundResource(background)
}
private fun bindSamples(samples: List<Sample>) {
@ -192,7 +204,6 @@ class SampleListFragment : Fragment() {
val scrollPosition = pendingRecyclerScrollPosition
Debug.i(scrollPosition)
Debug.trace()
if (scrollPosition != null) {
pendingRecyclerScrollPosition = null
@ -242,7 +253,6 @@ class SampleListFragment : Fragment() {
}
Debug.i(sampleSearch)
Debug.trace()
// clear current
cancellable?.let {
@ -259,6 +269,7 @@ class SampleListFragment : Fragment() {
companion object {
private const val ARG_ARTIFACT = "arg.Artifact"
private const val ARG_TAG = "arg.Tag"
private const val ARG_SEARCH = "arg.Search"
private const val STATE = "key.State"
fun init(): SampleListFragment {
@ -283,6 +294,23 @@ class SampleListFragment : Fragment() {
return fragment
}
fun init(search: SampleSearch): SampleListFragment {
val fragment = SampleListFragment()
fragment.arguments = Bundle().apply {
when (search) {
is SampleSearch.Artifact -> putString(ARG_ARTIFACT, search.artifact.name)
is SampleSearch.Tag -> putString(ARG_TAG, search.tag)
}
val query = search.text
if (query != null) {
putString(ARG_SEARCH, query)
}
}
return fragment
}
fun markwon(context: Context): Markwon {
return Markwon.builder(context)
.usePlugin(MovementMethodPlugin.none())