Sample app deeplinking
This commit is contained in:
		
							parent
							
								
									9dce1c8533
								
							
						
					
					
						commit
						78ec885294
					
				| @ -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 { | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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.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() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|  | ||||
| @ -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() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         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()) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov