Sample app deeplinking
This commit is contained in:
parent
9dce1c8533
commit
78ec885294
@ -18,6 +18,12 @@ android {
|
|||||||
resConfig 'en'
|
resConfig 'en'
|
||||||
|
|
||||||
setProperty("archivesBaseName", "markwon-$versionName")
|
setProperty("archivesBaseName", "markwon-$versionName")
|
||||||
|
|
||||||
|
final def scheme = 'markwon'
|
||||||
|
buildConfigField 'String', 'DEEPLINK_SCHEME', "\"$scheme\""
|
||||||
|
manifestPlaceholders += [
|
||||||
|
'deeplink_scheme': scheme
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
|
@ -16,10 +16,20 @@
|
|||||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||||
|
|
||||||
<activity android:name=".sample.MainActivity">
|
<activity android:name=".sample.MainActivity">
|
||||||
|
<!-- launcher intent -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,10 @@ package io.noties.markwon.app.sample
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
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
|
import io.noties.markwon.app.sample.ui.SampleListFragment
|
||||||
|
|
||||||
class MainActivity : FragmentActivity() {
|
class MainActivity : FragmentActivity() {
|
||||||
@ -11,9 +14,27 @@ class MainActivity : FragmentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
if (supportFragmentManager.findFragmentById(Window.ID_ANDROID_CONTENT) == null) {
|
if (supportFragmentManager.findFragmentById(Window.ID_ANDROID_CONTENT) == null) {
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(Window.ID_ANDROID_CONTENT, SampleListFragment.init())
|
.add(Window.ID_ANDROID_CONTENT, SampleListFragment.init())
|
||||||
.commitNowAllowingStateLoss()
|
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,6 +15,10 @@ class SampleManager(
|
|||||||
SampleUtils.readSamples(context)
|
SampleUtils.readSamples(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sample(id: String): Sample? {
|
||||||
|
return samples.firstOrNull { id == it.id }
|
||||||
|
}
|
||||||
|
|
||||||
fun samples(search: SampleSearch?, callback: (List<Sample>) -> Unit): Cancellable {
|
fun samples(search: SampleSearch?, callback: (List<Sample>) -> Unit): Cancellable {
|
||||||
|
|
||||||
var action: ((List<Sample>) -> Unit)? = callback
|
var action: ((List<Sample>) -> Unit)? = callback
|
||||||
|
@ -98,11 +98,20 @@ class SampleListFragment : Fragment() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
val state: State? = arguments?.getParcelable(STATE)
|
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
|
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 {
|
} else {
|
||||||
fetch()
|
fetch()
|
||||||
}
|
}
|
||||||
@ -144,35 +153,38 @@ class SampleListFragment : Fragment() {
|
|||||||
val appBarTitle: TextView = appBar.findViewById(R.id.app_bar_title)
|
val appBarTitle: TextView = appBar.findViewById(R.id.app_bar_title)
|
||||||
val appBarIconReadme: ImageView = appBar.findViewById(R.id.app_bar_icon_readme)
|
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
|
appBarIcon.hidden = isInitialScreen
|
||||||
appBarIconReadme.hidden = !isInitialScreen
|
appBarIconReadme.hidden = !isInitialScreen
|
||||||
|
|
||||||
val type = this.type
|
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 {
|
appBarIconReadme.setOnClickListener {
|
||||||
context?.let {
|
context?.let {
|
||||||
val intent = ReadMeActivity.makeIntent(it)
|
val intent = ReadMeActivity.makeIntent(it)
|
||||||
it.startActivity(intent)
|
it.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
appBarIcon.setImageResource(R.drawable.ic_arrow_back_white_24dp)
|
appBarIcon.setImageResource(R.drawable.ic_arrow_back_white_24dp)
|
||||||
appBarIcon.setOnClickListener {
|
appBarIcon.setOnClickListener {
|
||||||
requireActivity().onBackPressed()
|
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>) {
|
private fun bindSamples(samples: List<Sample>) {
|
||||||
@ -192,7 +204,6 @@ class SampleListFragment : Fragment() {
|
|||||||
val scrollPosition = pendingRecyclerScrollPosition
|
val scrollPosition = pendingRecyclerScrollPosition
|
||||||
|
|
||||||
Debug.i(scrollPosition)
|
Debug.i(scrollPosition)
|
||||||
Debug.trace()
|
|
||||||
|
|
||||||
if (scrollPosition != null) {
|
if (scrollPosition != null) {
|
||||||
pendingRecyclerScrollPosition = null
|
pendingRecyclerScrollPosition = null
|
||||||
@ -242,7 +253,6 @@ class SampleListFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Debug.i(sampleSearch)
|
Debug.i(sampleSearch)
|
||||||
Debug.trace()
|
|
||||||
|
|
||||||
// clear current
|
// clear current
|
||||||
cancellable?.let {
|
cancellable?.let {
|
||||||
@ -259,6 +269,7 @@ class SampleListFragment : Fragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val ARG_ARTIFACT = "arg.Artifact"
|
private const val ARG_ARTIFACT = "arg.Artifact"
|
||||||
private const val ARG_TAG = "arg.Tag"
|
private const val ARG_TAG = "arg.Tag"
|
||||||
|
private const val ARG_SEARCH = "arg.Search"
|
||||||
private const val STATE = "key.State"
|
private const val STATE = "key.State"
|
||||||
|
|
||||||
fun init(): SampleListFragment {
|
fun init(): SampleListFragment {
|
||||||
@ -283,6 +294,23 @@ class SampleListFragment : Fragment() {
|
|||||||
return 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 {
|
fun markwon(context: Context): Markwon {
|
||||||
return Markwon.builder(context)
|
return Markwon.builder(context)
|
||||||
.usePlugin(MovementMethodPlugin.none())
|
.usePlugin(MovementMethodPlugin.none())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user