Sample app, update check
This commit is contained in:
		
							parent
							
								
									086494bd97
								
							
						
					
					
						commit
						c96ea690f6
					
				| @ -3,6 +3,15 @@ apply plugin: 'kotlin-android' | ||||
| apply plugin: 'kotlin-kapt' | ||||
| apply plugin: 'kotlin-android-extensions' | ||||
| 
 | ||||
| def gitSha = { -> | ||||
|     def output = new ByteArrayOutputStream() | ||||
|     exec { | ||||
|         commandLine 'git', 'rev-parse', '--short', 'HEAD' | ||||
|         standardOutput = output | ||||
|     } | ||||
|     return output.toString().trim() | ||||
| }.memoize() | ||||
| 
 | ||||
| android { | ||||
| 
 | ||||
|     compileSdkVersion config['compile-sdk'] | ||||
| @ -17,7 +26,10 @@ android { | ||||
| 
 | ||||
|         resConfig 'en' | ||||
| 
 | ||||
|         setProperty("archivesBaseName", "markwon-$versionName") | ||||
|         setProperty("archivesBaseName", "markwon") | ||||
| 
 | ||||
|         buildConfigField 'String', 'GIT_SHA', "\"${gitSha()}\"" | ||||
|         buildConfigField 'String', 'GIT_REPOSITORY', '"https://github.com/noties/Markwon"' | ||||
| 
 | ||||
|         final def scheme = 'markwon' | ||||
|         buildConfigField 'String', 'DEEPLINK_SCHEME', "\"$scheme\"" | ||||
| @ -41,6 +53,37 @@ android { | ||||
|             java.srcDirs += '../sample-utils/annotations' | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     signingConfigs { | ||||
|         config { | ||||
| 
 | ||||
|             final def keystoreFile = project.file('keystore.jks') | ||||
|             final def keystoreFilePassword = 'MARKWON_KEYSTORE_FILE_PASSWORD' | ||||
|             final def keystoreAlias = 'MARKWON_KEY_ALIAS' | ||||
|             final def keystoreAliasPassword = 'MARKWON_KEY_ALIAS_PASSWORD' | ||||
| 
 | ||||
|             final def properties = [ | ||||
|                     keystoreFilePassword, | ||||
|                     keystoreAlias, | ||||
|                     keystoreAliasPassword | ||||
|             ] | ||||
| 
 | ||||
|             if (!keystoreFile.exists()) { | ||||
|                 throw new IllegalStateException("No '${keystoreFile.name}' file is found.") | ||||
|             } | ||||
| 
 | ||||
|             final def missingProperties = properties.findAll { !project.hasProperty(it) } | ||||
|             if (!missingProperties.isEmpty()) { | ||||
|                 throw new IllegalStateException("Missing required signing properties: $missingProperties") | ||||
|             } | ||||
| 
 | ||||
|             storeFile keystoreFile | ||||
|             storePassword project[keystoreFilePassword] | ||||
| 
 | ||||
|             keyAlias project[keystoreAlias] | ||||
|             keyPassword project[keystoreAliasPassword] | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| kapt { | ||||
|  | ||||
							
								
								
									
										24
									
								
								app-sample/deploy.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								app-sample/deploy.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,24 @@ | ||||
| #!/usr/bin/env sh | ||||
| 
 | ||||
| # abort on errors | ||||
| set -e | ||||
| 
 | ||||
| # build | ||||
| ../gradlew :app-sample:clean | ||||
| ../gradlew :app-sample:assembleDebug | ||||
| 
 | ||||
| # navigate into the build output directory | ||||
| cd ./build/outputs/apk/debug/ | ||||
| 
 | ||||
| revision=$(git rev-parse --short HEAD) | ||||
| 
 | ||||
| echo "output.json" > ./.gitignore | ||||
| echo "$revision" > ./version | ||||
| 
 | ||||
| git init | ||||
| git add -A | ||||
| git commit -m "sample $revision" | ||||
| 
 | ||||
| git push -f git@github.com:noties/Markwon.git master:sample-store | ||||
| 
 | ||||
| cd - | ||||
							
								
								
									
										
											BIN
										
									
								
								app-sample/keystore.jks
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app-sample/keystore.jks
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app-sample/release/markwon-4.4.1-SNAPSHOT-release.apk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app-sample/release/markwon-4.4.1-SNAPSHOT-release.apk
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										20
									
								
								app-sample/release/output.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app-sample/release/output.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| { | ||||
|   "version": 1, | ||||
|   "artifactType": { | ||||
|     "type": "APK", | ||||
|     "kind": "Directory" | ||||
|   }, | ||||
|   "applicationId": "io.noties.markwon.app", | ||||
|   "variantName": "release", | ||||
|   "elements": [ | ||||
|     { | ||||
|       "type": "SINGLE", | ||||
|       "filters": [], | ||||
|       "properties": [], | ||||
|       "versionCode": 1, | ||||
|       "versionName": "1", | ||||
|       "enabled": true, | ||||
|       "outputFile": "markwon-4.4.1-SNAPSHOT-release.apk" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @ -1,8 +1,12 @@ | ||||
| package io.noties.markwon.app.sample.ui | ||||
| 
 | ||||
| import android.app.AlertDialog | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.os.Parcelable | ||||
| import android.text.TextUtils | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| @ -15,20 +19,26 @@ import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import io.noties.adapt.Adapt | ||||
| import io.noties.adapt.DiffUtilDataSetChanged | ||||
| import io.noties.adapt.Item | ||||
| import io.noties.debug.Debug | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.app.App | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import io.noties.markwon.app.R | ||||
| import io.noties.markwon.app.readme.ReadMeActivity | ||||
| import io.noties.markwon.app.sample.Sample | ||||
| import io.noties.markwon.app.sample.SampleItem | ||||
| import io.noties.markwon.app.sample.SampleManager | ||||
| import io.noties.markwon.app.sample.SampleSearch | ||||
| import io.noties.markwon.app.sample.ui.adapt.CheckForUpdateItem | ||||
| import io.noties.markwon.app.sample.ui.adapt.SampleItem | ||||
| import io.noties.markwon.app.sample.ui.adapt.VersionItem | ||||
| import io.noties.markwon.app.utils.Cancellable | ||||
| import io.noties.markwon.app.utils.UpdateUtils | ||||
| import io.noties.markwon.app.utils.displayName | ||||
| import io.noties.markwon.app.utils.hidden | ||||
| import io.noties.markwon.app.utils.onPreDraw | ||||
| import io.noties.markwon.app.utils.recyclerView | ||||
| import io.noties.markwon.app.utils.stackTraceString | ||||
| import io.noties.markwon.app.utils.tagDisplayName | ||||
| import io.noties.markwon.app.widget.SearchBar | ||||
| import io.noties.markwon.movement.MovementMethodPlugin | ||||
| @ -50,6 +60,13 @@ class SampleListFragment : Fragment() { | ||||
|     private var pendingRecyclerScrollPosition: RecyclerScrollPosition? = null | ||||
| 
 | ||||
|     private var cancellable: Cancellable? = null | ||||
|     private var checkForUpdateCancellable: Cancellable? = null | ||||
| 
 | ||||
|     private lateinit var progressBar: View | ||||
| 
 | ||||
|     private val versionItem: VersionItem by lazy(LazyThreadSafetyMode.NONE) { | ||||
|         VersionItem() | ||||
|     } | ||||
| 
 | ||||
|     private val sampleManager: SampleManager | ||||
|         get() = App.sampleManager | ||||
| @ -73,6 +90,8 @@ class SampleListFragment : Fragment() { | ||||
| 
 | ||||
|         val context = requireContext() | ||||
| 
 | ||||
|         progressBar = view.findViewById(R.id.progress_bar) | ||||
| 
 | ||||
|         val searchBar: SearchBar = view.findViewById(R.id.search_bar) | ||||
|         searchBar.onSearchListener = { | ||||
|             search = it | ||||
| @ -187,16 +206,30 @@ class SampleListFragment : Fragment() { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun bindSamples(samples: List<Sample>) { | ||||
|         val items = samples.map { | ||||
|             SampleItem( | ||||
|                     markwon, | ||||
|                     it, | ||||
|                     { artifact -> openArtifact(artifact) }, | ||||
|                     { tag -> openTag(tag) }, | ||||
|                     { sample -> openSample(sample) } | ||||
|             ) | ||||
|         } | ||||
|     private fun bindSamples(samples: List<Sample>, addVersion: Boolean) { | ||||
| 
 | ||||
|         val items: List<Item<*>> = samples | ||||
|                 .map { | ||||
|                     SampleItem( | ||||
|                             markwon, | ||||
|                             it, | ||||
|                             { artifact -> openArtifact(artifact) }, | ||||
|                             { tag -> openTag(tag) }, | ||||
|                             { sample -> openSample(sample) } | ||||
|                     ) | ||||
|                 } | ||||
|                 .let { | ||||
|                     if (addVersion) { | ||||
|                         val list: List<Item<*>> = it | ||||
|                         list.toMutableList().apply { | ||||
|                             add(0, CheckForUpdateItem(this@SampleListFragment::checkForUpdate)) | ||||
|                             add(0, versionItem) | ||||
|                         } | ||||
|                     } else { | ||||
|                         it | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|         adapt.setItems(items) | ||||
| 
 | ||||
|         val recyclerView = adapt.recyclerView ?: return | ||||
| @ -218,6 +251,66 @@ class SampleListFragment : Fragment() { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun checkForUpdate() { | ||||
|         val current = checkForUpdateCancellable | ||||
|         if (current != null && !current.isCancelled) { | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         progressBar.hidden = false | ||||
|         checkForUpdateCancellable = UpdateUtils.checkForUpdate { result -> | ||||
|             progressBar.post { | ||||
|                 processUpdateResult(result) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun processUpdateResult(result: UpdateUtils.Result) { | ||||
|         val context = context ?: return | ||||
| 
 | ||||
|         progressBar.hidden = true | ||||
| 
 | ||||
|         val builder = AlertDialog.Builder(context) | ||||
| 
 | ||||
|         when (result) { | ||||
|             is UpdateUtils.Result.UpdateAvailable -> { | ||||
|                 val md = """ | ||||
|                     ## Update available | ||||
|                     Would you like to download it? | ||||
|                 """.trimIndent() | ||||
|                 builder.setMessage(markwon.toMarkdown(md)) | ||||
|                 builder.setNegativeButton(android.R.string.cancel, null) | ||||
|                 builder.setPositiveButton("Download") { _, _ -> | ||||
|                     val intent = Intent(Intent.ACTION_VIEW, Uri.parse(result.url)) | ||||
|                     startActivity(Intent.createChooser(intent, null)) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             is UpdateUtils.Result.NoUpdate -> { | ||||
|                 val md = """ | ||||
|                     ## No update | ||||
|                     You are using latest version (${BuildConfig.GIT_SHA}) | ||||
|                 """.trimIndent() | ||||
|                 builder.setMessage(markwon.toMarkdown(md)) | ||||
|                 builder.setPositiveButton(android.R.string.ok, null) | ||||
|             } | ||||
| 
 | ||||
|             is UpdateUtils.Result.Error -> { | ||||
|                 // trimIndent is confused by tabs in stack trace | ||||
|                 val md = """ | ||||
| ## Error | ||||
| ``` | ||||
| ${result.throwable.stackTraceString()} | ||||
| ``` | ||||
| """ | ||||
|                 builder.setMessage(markwon.toMarkdown(md)) | ||||
|                 builder.setPositiveButton(android.R.string.ok, null) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         builder.show() | ||||
|     } | ||||
| 
 | ||||
|     private fun openArtifact(artifact: MarkwonArtifact) { | ||||
|         Debug.i(artifact) | ||||
|         openResultFragment(init(artifact)) | ||||
| @ -262,7 +355,8 @@ class SampleListFragment : Fragment() { | ||||
|         } | ||||
| 
 | ||||
|         cancellable = sampleManager.samples(sampleSearch) { | ||||
|             bindSamples(it) | ||||
|             val addVersion = sampleSearch is SampleSearch.All && TextUtils.isEmpty(sampleSearch.text) | ||||
|             bindSamples(it, addVersion) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,22 @@ | ||||
| package io.noties.markwon.app.sample.ui.adapt | ||||
| 
 | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import io.noties.adapt.Item | ||||
| import io.noties.markwon.app.R | ||||
| 
 | ||||
| class CheckForUpdateItem(private val action: () -> Unit) : Item<CheckForUpdateItem.Holder>(42L) { | ||||
| 
 | ||||
|     override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder { | ||||
|         return Holder(inflater.inflate(R.layout.adapt_check_for_update, parent, false)) | ||||
|     } | ||||
| 
 | ||||
|     override fun render(holder: Holder) { | ||||
|         holder.button.setOnClickListener { action() } | ||||
|     } | ||||
| 
 | ||||
|     class Holder(view: View) : Item.Holder(view) { | ||||
|         val button: View = requireView(R.id.button) | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package io.noties.markwon.app.sample | ||||
| package io.noties.markwon.app.sample.ui.adapt | ||||
| 
 | ||||
| import android.text.Spanned | ||||
| import android.view.LayoutInflater | ||||
| @ -8,6 +8,7 @@ import android.widget.TextView | ||||
| import io.noties.adapt.Item | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.app.R | ||||
| import io.noties.markwon.app.sample.Sample | ||||
| import io.noties.markwon.app.utils.displayName | ||||
| import io.noties.markwon.app.utils.hidden | ||||
| import io.noties.markwon.app.utils.tagDisplayName | ||||
| @ -0,0 +1,84 @@ | ||||
| package io.noties.markwon.app.sample.ui.adapt | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.text.Spanned | ||||
| import android.text.TextPaint | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.TextView | ||||
| import io.noties.adapt.Item | ||||
| import io.noties.markwon.AbstractMarkwonPlugin | ||||
| import io.noties.markwon.LinkResolver | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.MarkwonSpansFactory | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import io.noties.markwon.app.R | ||||
| import io.noties.markwon.core.CoreProps | ||||
| import io.noties.markwon.core.MarkwonTheme | ||||
| import io.noties.markwon.core.spans.LinkSpan | ||||
| import io.noties.markwon.html.HtmlPlugin | ||||
| import io.noties.markwon.image.ImagesPlugin | ||||
| import io.noties.markwon.movement.MovementMethodPlugin | ||||
| import org.commonmark.node.Link | ||||
| 
 | ||||
| class VersionItem : Item<VersionItem.Holder>(42L) { | ||||
| 
 | ||||
|     private lateinit var context: Context | ||||
| 
 | ||||
|     private val markwon: Markwon by lazy(LazyThreadSafetyMode.NONE) { | ||||
|         Markwon.builder(context) | ||||
|                 .usePlugin(ImagesPlugin.create()) | ||||
|                 .usePlugin(MovementMethodPlugin.link()) | ||||
|                 .usePlugin(HtmlPlugin.create()) | ||||
|                 .usePlugin(object : AbstractMarkwonPlugin() { | ||||
|                     override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { | ||||
|                         builder.setFactory(Link::class.java) { configuration, props -> | ||||
|                             LinkSpanNoUnderline( | ||||
|                                     configuration.theme(), | ||||
|                                     CoreProps.LINK_DESTINATION.require(props), | ||||
|                                     configuration.linkResolver() | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 }) | ||||
|                 .build() | ||||
|     } | ||||
| 
 | ||||
|     private val text: Spanned by lazy(LazyThreadSafetyMode.NONE) { | ||||
|         val md = """ | ||||
|             <a href="${BuildConfig.GIT_REPOSITORY}/blob/master/CHANGELOG.md"> | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|             </a> | ||||
|         """.trimIndent() | ||||
|         markwon.toMarkdown(md) | ||||
|     } | ||||
| 
 | ||||
|     override fun createHolder(inflater: LayoutInflater, parent: ViewGroup): Holder { | ||||
|         context = parent.context | ||||
|         return Holder(inflater.inflate(R.layout.adapt_version, parent, false)) | ||||
|     } | ||||
| 
 | ||||
|     override fun render(holder: Holder) { | ||||
|         markwon.setParsedMarkdown(holder.textView, text) | ||||
|     } | ||||
| 
 | ||||
|     class Holder(view: View) : Item.Holder(view) { | ||||
|         val textView: TextView = requireView(R.id.text_view) | ||||
|     } | ||||
| 
 | ||||
|     class LinkSpanNoUnderline( | ||||
|             theme: MarkwonTheme, | ||||
|             destination: String, | ||||
|             resolver: LinkResolver | ||||
|     ) : LinkSpan(theme, destination, resolver) { | ||||
|         override fun updateDrawState(ds: TextPaint) { | ||||
|             super.updateDrawState(ds) | ||||
|             ds.isUnderlineText = false | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -11,6 +11,7 @@ import java.util.regex.Pattern; | ||||
| 
 | ||||
| import io.noties.markwon.AbstractMarkwonPlugin; | ||||
| import io.noties.markwon.Markwon; | ||||
| import io.noties.markwon.app.BuildConfig; | ||||
| import io.noties.markwon.app.sample.Tags; | ||||
| import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; | ||||
| import io.noties.markwon.inlineparser.InlineProcessor; | ||||
| @ -78,7 +79,7 @@ class IssueInlineProcessor extends InlineProcessor { | ||||
| 
 | ||||
|   @NonNull | ||||
|   private static String createIssueOrPullRequestLinkDestination(@NonNull String id) { | ||||
|     return "https://github.com/noties/Markwon/issues/" + id; | ||||
|     return BuildConfig.GIT_REPOSITORY + "/issues/" + id; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,7 @@ import io.noties.markwon.MarkwonConfiguration; | ||||
| import io.noties.markwon.MarkwonVisitor; | ||||
| import io.noties.markwon.RenderProps; | ||||
| import io.noties.markwon.SpannableBuilder; | ||||
| import io.noties.markwon.app.BuildConfig; | ||||
| import io.noties.markwon.app.sample.Tags; | ||||
| import io.noties.markwon.app.sample.ui.MarkwonTextViewSample; | ||||
| import io.noties.markwon.core.CorePlugin; | ||||
| @ -87,7 +88,7 @@ class GithubLinkifyRegexTextAddedListener implements CorePlugin.OnTextAddedListe | ||||
|     // issues and pull-requests on github follow the same pattern and we | ||||
|     // cannot know for sure which one it is, but if we use issues for all types, | ||||
|     // github will automatically redirect to pull-request if it's the one which is opened | ||||
|     return "https://github.com/noties/Markwon/issues/" + number; | ||||
|     return BuildConfig.GIT_REPOSITORY + "/issues/" + number; | ||||
|   } | ||||
| 
 | ||||
|   @NonNull | ||||
|  | ||||
| @ -5,6 +5,7 @@ import android.view.ViewGroup | ||||
| import android.widget.TextView | ||||
| import android.widget.Toast | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import io.noties.markwon.app.sample.Tags | ||||
| import io.noties.markwon.app.sample.ui.MarkwonTextViewSample | ||||
| import io.noties.markwon.image.ImagesPlugin | ||||
| @ -23,7 +24,7 @@ class ToastDynamicContentSample : MarkwonTextViewSample() { | ||||
|     val md = """ | ||||
|       # Head! | ||||
|        | ||||
|        | ||||
|        | ||||
|        | ||||
|       Do you see an image? ☝️ | ||||
|     """.trimIndent() | ||||
|  | ||||
| @ -28,6 +28,7 @@ import java.util.List; | ||||
| import io.noties.markwon.Markwon; | ||||
| import io.noties.markwon.MarkwonVisitor; | ||||
| import io.noties.markwon.SpannableBuilder; | ||||
| import io.noties.markwon.app.BuildConfig; | ||||
| import io.noties.markwon.app.R; | ||||
| import io.noties.markwon.app.sample.Tags; | ||||
| import io.noties.markwon.app.sample.ui.MarkwonSample; | ||||
| @ -56,7 +57,7 @@ public class HtmlDetailsSample extends MarkwonSample { | ||||
| 
 | ||||
|   @Override | ||||
|   protected int getLayoutResId() { | ||||
|     return R.layout.activity_html_details; | ||||
|     return R.layout.sample_html_details; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
| @ -83,7 +84,8 @@ public class HtmlDetailsSample extends MarkwonSample { | ||||
|       "* list\n" + | ||||
|       "* with\n" + | ||||
|       "\n\n" + | ||||
|       "\n\n" + | ||||
|       "\n\n" + | ||||
|       "" + | ||||
|       " 1. nested\n" + | ||||
|       " 1. items\n" + | ||||
|       "\n" + | ||||
|  | ||||
| @ -2,6 +2,7 @@ package io.noties.markwon.app.samples.movementmethod | ||||
| 
 | ||||
| import android.text.method.ScrollingMovementMethod | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import io.noties.markwon.app.sample.Tags | ||||
| import io.noties.markwon.app.sample.ui.MarkwonTextViewSample | ||||
| import io.noties.markwon.sample.annotations.MarkwonArtifact | ||||
| @ -22,7 +23,7 @@ class ExplicitMovementMethodSample : MarkwonTextViewSample() { | ||||
|       If `TextView` already has a movement method specified, then `Markwon` | ||||
|       won't be applying a default one. You can specify movement  | ||||
|       method via call to `setMovementMethod`. If your movement method can | ||||
|       handle [links](https://github.com/noties/Markwon) then link would be | ||||
|       handle [links](${BuildConfig.GIT_REPOSITORY}) then link would be | ||||
|       _clickable_ | ||||
|     """.trimIndent() | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| package io.noties.markwon.app.samples.movementmethod | ||||
| 
 | ||||
| import io.noties.markwon.Markwon | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import io.noties.markwon.app.sample.Tags | ||||
| import io.noties.markwon.app.sample.ui.MarkwonTextViewSample | ||||
| import io.noties.markwon.sample.annotations.MarkwonArtifact | ||||
| @ -18,7 +19,7 @@ class ImplicitMovementMethodSample : MarkwonTextViewSample() { | ||||
|     val md = """ | ||||
|       # Implicit movement method | ||||
|       By default `Markwon` applies `LinkMovementMethod` if it is missing, | ||||
|       so in order for [links](https://github.com/noties/Markwon) to be clickable | ||||
|       so in order for [links](${BuildConfig.GIT_REPOSITORY}) to be clickable | ||||
|       nothing special should be done | ||||
|     """.trimIndent() | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,13 @@ | ||||
| package io.noties.markwon.app.utils | ||||
| 
 | ||||
| import java.io.PrintWriter | ||||
| import java.io.StringWriter | ||||
| 
 | ||||
| object ThrowableUtils | ||||
| 
 | ||||
| fun Throwable.stackTraceString(): String { | ||||
|     val stringWriter = StringWriter() | ||||
|     val printWriter = PrintWriter(stringWriter) | ||||
|     this.printStackTrace(printWriter) | ||||
|     return stringWriter.toString() | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package io.noties.markwon.app.utils | ||||
| 
 | ||||
| @Suppress("unused") | ||||
| class UncaughtExceptionHandler(private val origin: Thread.UncaughtExceptionHandler?) | ||||
|     : Thread.UncaughtExceptionHandler { | ||||
| 
 | ||||
|     override fun uncaughtException(t: Thread?, e: Throwable?) { | ||||
|          | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package io.noties.markwon.app.utils | ||||
| 
 | ||||
| import io.noties.markwon.app.App | ||||
| import io.noties.markwon.app.BuildConfig | ||||
| import okhttp3.Call | ||||
| import okhttp3.Callback | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import java.io.IOException | ||||
| 
 | ||||
| object UpdateUtils { | ||||
| 
 | ||||
|     sealed class Result { | ||||
|         class UpdateAvailable(val url: String) : Result() | ||||
|         object NoUpdate : Result() | ||||
|         class Error(val throwable: Throwable) : Result() | ||||
|     } | ||||
| 
 | ||||
|     fun checkForUpdate(updateAction: (Result) -> Unit): Cancellable { | ||||
|         var action: ((Result) -> Unit)? = updateAction | ||||
| 
 | ||||
|         val future = App.executorService | ||||
|                 .submit { | ||||
|                     val url = "${BuildConfig.GIT_REPOSITORY}/raw/sample-store/version" | ||||
|                     val request = Request.Builder() | ||||
|                             .get() | ||||
|                             .url(url) | ||||
|                             .build() | ||||
|                     OkHttpClient().newCall(request).enqueue(object : Callback { | ||||
|                         override fun onFailure(call: Call, e: IOException) { | ||||
|                             action?.invoke(Result.Error(e)) | ||||
|                         } | ||||
| 
 | ||||
|                         override fun onResponse(call: Call, response: Response) { | ||||
|                             try { | ||||
|                                 val revision = response.body()?.string() | ||||
|                                 val hasUpdate = revision != null && BuildConfig.GIT_SHA != revision | ||||
|                                 if (hasUpdate) { | ||||
|                                     action?.invoke(Result.UpdateAvailable(apkUrl)) | ||||
|                                 } else { | ||||
|                                     action?.invoke(Result.NoUpdate) | ||||
|                                 } | ||||
|                             } catch (e: IOException) { | ||||
|                                 action?.invoke(Result.Error(e)) | ||||
|                             } | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
| 
 | ||||
|         return object : Cancellable { | ||||
|             override val isCancelled: Boolean | ||||
|                 get() = future.isDone | ||||
| 
 | ||||
|             override fun cancel() { | ||||
|                 action = null | ||||
|                 future.cancel(true) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private const val apkUrl = "${BuildConfig.GIT_REPOSITORY}/raw/sample-store/markwon-debug.apk" | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="24dp" | ||||
|     android:height="24dp" | ||||
|     android:tint="#FFFFFF" | ||||
|     android:viewportWidth="24" | ||||
|     android:viewportHeight="24"> | ||||
|     <path | ||||
|         android:fillColor="@android:color/white" | ||||
|         android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z" /> | ||||
| </vector> | ||||
							
								
								
									
										15
									
								
								app-sample/src/main/res/layout/adapt_check_for_update.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app-sample/src/main/res/layout/adapt_check_for_update.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:paddingLeft="@dimen/content_padding" | ||||
|     android:paddingRight="@dimen/content_padding"> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/button" | ||||
|         style="@android:style/Widget.Material.Button.Borderless.Colored" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="@string/check_for_update" /> | ||||
| 
 | ||||
| </FrameLayout> | ||||
							
								
								
									
										12
									
								
								app-sample/src/main/res/layout/adapt_version.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app-sample/src/main/res/layout/adapt_version.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <TextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/text_view" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:paddingLeft="@dimen/content_padding_double" | ||||
|     android:paddingTop="@dimen/content_padding" | ||||
|     android:paddingRight="@dimen/content_padding_double" | ||||
|     android:paddingBottom="@dimen/content_padding" | ||||
|     android:textAppearance="?android:attr/textAppearanceMedium" | ||||
|     tools:text="Badges here" /> | ||||
| @ -48,6 +48,17 @@ | ||||
| 
 | ||||
|     </LinearLayout> | ||||
| 
 | ||||
|     <ProgressBar | ||||
|         android:id="@+id/progress_bar" | ||||
|         style="@android:style/Widget.Material.ProgressBar.Horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="16dip" | ||||
|         android:layout_marginTop="-8dip" | ||||
|         android:layout_marginBottom="-8dip" | ||||
|         android:indeterminate="true" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
| 
 | ||||
|     <LinearLayout | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|  | ||||
| @ -5,6 +5,8 @@ | ||||
|     <string name="tab_bar_preview">Preview</string> | ||||
|     <string name="tab_bar_code">Code</string> | ||||
| 
 | ||||
|     <string name="check_for_update">Check for update</string> | ||||
| 
 | ||||
|     <string name="lorem"><![CDATA[ | ||||
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis rutrum orci at aliquet dapibus. Quisque laoreet fermentum bibendum. Suspendisse euismod nisl vel sapien viverra faucibus. Nulla vel neque volutpat, egestas dui ac, consequat elit. Donec et interdum massa. Quisque porta ornare posuere. Nam at ante a felis facilisis tempus eu et erat. Curabitur auctor mauris eget purus iaculis vulputate. | ||||
| 
 | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| @ -29,7 +29,6 @@ dependencies { | ||||
|     testImplementation deps['commons-io'] | ||||
| 
 | ||||
|     deps['test'].with { | ||||
| 
 | ||||
|         testImplementation it['junit'] | ||||
|         testImplementation it['robolectric'] | ||||
|         testImplementation it['mockito'] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov