Change text-setter to use precomputed-text-compat (androix.core)
This commit is contained in:
		
							parent
							
								
									7e12552060
								
							
						
					
					
						commit
						54335dce6e
					
				| @ -1,5 +1,10 @@ | ||||
| # Changelog | ||||
| 
 | ||||
| # 4.1.0-SNAPSHOT | ||||
| * Add `Markwon.TextSetter` interface to be able to use PrecomputedText/PrecomputedTextCompat | ||||
| * Add `PrecomputedTextSetterCompat` and `compileOnly` dependency on `androidx.core:core`  | ||||
| (clients must have this dependency in the classpath) | ||||
| 
 | ||||
| # 4.0.2 | ||||
| * Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149]) | ||||
| * Fix `JLatexMathPlugin` to update resulting formula bounds when `fitCanvas=true` and  | ||||
|  | ||||
| @ -59,6 +59,7 @@ ext { | ||||
|     deps = [ | ||||
|             'x-annotations'           : 'androidx.annotation:annotation:1.1.0', | ||||
|             'x-recycler-view'         : 'androidx.recyclerview:recyclerview:1.0.0', | ||||
|             'x-core'                  : 'androidx.core:core:1.0.2', | ||||
|             'commonmark'              : "com.atlassian.commonmark:commonmark:$commonMarkVersion", | ||||
|             'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion", | ||||
|             'commonmark-table'        : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion", | ||||
|  | ||||
| @ -18,6 +18,10 @@ dependencies { | ||||
|     deps.with { | ||||
|         api it['x-annotations'] | ||||
|         api it['commonmark'] | ||||
| 
 | ||||
|         // @since 4.1.0-SNAPSHOT to allow PrecomputedTextSetterCompat | ||||
|         // note that this dependency must be added on a client side explicitly | ||||
|         compileOnly it['x-core'] | ||||
|     } | ||||
| 
 | ||||
|     deps['test'].with { | ||||
|  | ||||
| @ -123,7 +123,7 @@ public abstract class Markwon { | ||||
|      * Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText | ||||
|      * functionality | ||||
|      * | ||||
|      * @see PrecomputedTextSetter | ||||
|      * @see PrecomputedTextSetterCompat | ||||
|      * @since 4.1.0-SNAPSHOT | ||||
|      */ | ||||
|     public interface TextSetter { | ||||
|  | ||||
| @ -1,80 +0,0 @@ | ||||
| package io.noties.markwon; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Build; | ||||
| import android.text.PrecomputedText; | ||||
| import android.text.Spanned; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.RequiresApi; | ||||
| 
 | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| /** | ||||
|  * @see io.noties.markwon.Markwon.TextSetter | ||||
|  * @since 4.1.0-SNAPSHOT | ||||
|  */ | ||||
| @RequiresApi(Build.VERSION_CODES.P) | ||||
| public class PrecomputedTextSetter implements Markwon.TextSetter { | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static PrecomputedTextSetter create() { | ||||
|         return create(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static PrecomputedTextSetter create(@NonNull Executor executor) { | ||||
|         return new PrecomputedTextSetter(executor); | ||||
|     } | ||||
| 
 | ||||
|     private final Executor executor; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     PrecomputedTextSetter(@NonNull Executor executor) { | ||||
|         this.executor = executor; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setText( | ||||
|             @NonNull TextView textView, | ||||
|             @NonNull final Spanned markdown, | ||||
|             @NonNull final TextView.BufferType bufferType, | ||||
|             @NonNull final Runnable onComplete) { | ||||
|         final WeakReference<TextView> reference = new WeakReference<>(textView); | ||||
|         executor.execute(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 final PrecomputedText precomputedText = precomputedText(reference.get(), markdown); | ||||
|                 if (precomputedText != null) { | ||||
|                     apply(reference.get(), precomputedText, bufferType, onComplete); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static PrecomputedText precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) { | ||||
|         return textView == null | ||||
|                 ? null | ||||
|                 : PrecomputedText.create(spanned, textView.getTextMetricsParams()); | ||||
|     } | ||||
| 
 | ||||
|     private static void apply( | ||||
|             @Nullable final TextView textView, | ||||
|             @NonNull final PrecomputedText precomputedText, | ||||
|             @NonNull final TextView.BufferType bufferType, | ||||
|             @NonNull final Runnable onComplete) { | ||||
|         if (textView != null) { | ||||
|             textView.post(new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     textView.setText(precomputedText, bufferType); | ||||
|                     onComplete.run(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,120 @@ | ||||
| package io.noties.markwon; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| import android.text.Spanned; | ||||
| import android.util.Log; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.text.PrecomputedTextCompat; | ||||
| 
 | ||||
| import java.lang.ref.WeakReference; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| /** | ||||
|  * Please note this class requires `androidx.core:core` artifact being explicitly added to your dependencies. | ||||
|  * Please do not use with `markwon-recycler` as it will lead to bad item rendering (due to async nature) | ||||
|  * | ||||
|  * @see io.noties.markwon.Markwon.TextSetter | ||||
|  * @since 4.1.0-SNAPSHOT | ||||
|  */ | ||||
| public class PrecomputedTextSetterCompat implements Markwon.TextSetter { | ||||
| 
 | ||||
|     /** | ||||
|      * @param executor for background execution of text pre-computation | ||||
|      */ | ||||
|     @NonNull | ||||
|     public static PrecomputedTextSetterCompat create(@NonNull Executor executor) { | ||||
|         return new PrecomputedTextSetterCompat(executor); | ||||
|     } | ||||
| 
 | ||||
|     private final Executor executor; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     PrecomputedTextSetterCompat(@NonNull Executor executor) { | ||||
|         this.executor = executor; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setText( | ||||
|             @NonNull TextView textView, | ||||
|             @NonNull final Spanned markdown, | ||||
|             @NonNull final TextView.BufferType bufferType, | ||||
|             @NonNull final Runnable onComplete) { | ||||
| 
 | ||||
|         // insert version check and do not execute on a device < 21 | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | ||||
|             // it's still no-op, so there is no need to start background execution | ||||
|             applyText(textView, markdown, bufferType, onComplete); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final WeakReference<TextView> reference = new WeakReference<>(textView); | ||||
|         executor.execute(new Runnable() { | ||||
|             @Override | ||||
|             public void run() { | ||||
|                 try { | ||||
|                     final PrecomputedTextCompat precomputedTextCompat = precomputedText(reference.get(), markdown); | ||||
|                     if (precomputedTextCompat != null) { | ||||
|                         applyText(reference.get(), precomputedTextCompat, bufferType, onComplete); | ||||
|                     } | ||||
|                 } catch (Throwable t) { | ||||
|                     Log.e("PrecomputdTxtSetterCmpt", "Exception during pre-computing text", t); | ||||
|                     // apply initial markdown | ||||
|                     applyText(reference.get(), markdown, bufferType, onComplete); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static PrecomputedTextCompat precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) { | ||||
| 
 | ||||
|         if (textView == null) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         final PrecomputedTextCompat.Params params; | ||||
| 
 | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | ||||
|             // use native parameters on P | ||||
|             params = new PrecomputedTextCompat.Params(textView.getTextMetricsParams()); | ||||
|         } else { | ||||
| 
 | ||||
|             final PrecomputedTextCompat.Params.Builder builder = | ||||
|                     new PrecomputedTextCompat.Params.Builder(textView.getPaint()); | ||||
| 
 | ||||
|             // please note that text-direction initialization is omitted | ||||
|             // by default it will be determined by the first locale-specific character | ||||
| 
 | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|                 // another miss on API surface, this can easily be done by the compat class itself | ||||
|                 builder | ||||
|                         .setBreakStrategy(textView.getBreakStrategy()) | ||||
|                         .setHyphenationFrequency(textView.getHyphenationFrequency()); | ||||
|             } | ||||
| 
 | ||||
|             params = builder.build(); | ||||
|         } | ||||
| 
 | ||||
|         return PrecomputedTextCompat.create(spanned, params); | ||||
|     } | ||||
| 
 | ||||
|     private static void applyText( | ||||
|             @Nullable final TextView textView, | ||||
|             @NonNull final Spanned text, | ||||
|             @NonNull final TextView.BufferType bufferType, | ||||
|             @NonNull final Runnable onComplete) { | ||||
|         Log.e("TXT", String.format("thread: %s, attached: %s", Thread.currentThread(), textView.isAttachedToWindow())); | ||||
|         if (textView != null) { | ||||
|             textView.post(new Runnable() { | ||||
|                 @Override | ||||
|                 public void run() { | ||||
|                     textView.setText(text, bufferType); | ||||
|                     onComplete.run(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -49,6 +49,7 @@ dependencies { | ||||
| 
 | ||||
|     deps.with { | ||||
|         implementation it['x-recycler-view'] | ||||
|         implementation it['x-core'] // for precomputedTextCompat | ||||
|         implementation it['okhttp'] | ||||
|         implementation it['prism4j'] | ||||
|         implementation it['debug'] | ||||
|  | ||||
| @ -27,6 +27,7 @@ | ||||
|         <activity android:name=".html.HtmlActivity" /> | ||||
|         <activity android:name=".simpleext.SimpleExtActivity" /> | ||||
|         <activity android:name=".customextension2.CustomExtensionActivity2" /> | ||||
|         <activity android:name=".precomputed.PrecomputedActivity" /> | ||||
| 
 | ||||
|     </application> | ||||
| 
 | ||||
|  | ||||
| @ -24,6 +24,7 @@ import io.noties.markwon.sample.customextension.CustomExtensionActivity; | ||||
| import io.noties.markwon.sample.customextension2.CustomExtensionActivity2; | ||||
| import io.noties.markwon.sample.html.HtmlActivity; | ||||
| import io.noties.markwon.sample.latex.LatexActivity; | ||||
| import io.noties.markwon.sample.precomputed.PrecomputedActivity; | ||||
| import io.noties.markwon.sample.recycler.RecyclerActivity; | ||||
| import io.noties.markwon.sample.simpleext.SimpleExtActivity; | ||||
| 
 | ||||
| @ -112,6 +113,10 @@ public class MainActivity extends Activity { | ||||
|                 activity = CustomExtensionActivity2.class; | ||||
|                 break; | ||||
| 
 | ||||
|             case PRECOMPUTED_TEXT: | ||||
|                 activity = PrecomputedActivity.class; | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 throw new IllegalStateException("No Activity is associated with sample-item: " + item); | ||||
|         } | ||||
|  | ||||
| @ -19,7 +19,9 @@ public enum Sample { | ||||
| 
 | ||||
|     SIMPLE_EXT(R.string.sample_simple_ext), | ||||
| 
 | ||||
|     CUSTOM_EXTENSION_2(R.string.sample_custom_extension_2); | ||||
|     CUSTOM_EXTENSION_2(R.string.sample_custom_extension_2), | ||||
| 
 | ||||
|     PRECOMPUTED_TEXT(R.string.sample_precomputed_text); | ||||
| 
 | ||||
|     private final int textResId; | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,38 @@ | ||||
| package io.noties.markwon.sample.precomputed; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.os.Bundle; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.concurrent.Executors; | ||||
| 
 | ||||
| import io.noties.markwon.Markwon; | ||||
| import io.noties.markwon.PrecomputedTextSetterCompat; | ||||
| import io.noties.markwon.sample.R; | ||||
| 
 | ||||
| public class PrecomputedActivity extends Activity { | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_text_view); | ||||
| 
 | ||||
|         final Markwon markwon = Markwon.builder(this) | ||||
|                 // please note that precomputedTextCompat is no-op on devices lower than L (21) | ||||
|                 .textSetter(PrecomputedTextSetterCompat.create(Executors.newCachedThreadPool())) | ||||
|                 .build(); | ||||
| 
 | ||||
|         final TextView textView = findViewById(R.id.text_view); | ||||
|         final String markdown = "# Hello!\n\n" + | ||||
|                 "This _displays_ how to implement and use `PrecomputedTextCompat` with the **Markwon**\n\n" + | ||||
|                 "> consider using PrecomputedText only if your markdown content is large enough\n> \n" + | ||||
|                 "> **please note** that it works badly with `markwon-recycler` due to asynchronous nature"; | ||||
| 
 | ||||
|         // please note that _sometimes_ (if done without `post` here) further `textView.post` | ||||
|         // (that is used in PrecomputedTextSetterCompat to deliver result to main-thread) won't be called | ||||
|         // making the result of pre-computation absent and text-view clear (no text) | ||||
|         textView.post(() -> markwon.setMarkdown(textView, markdown)); | ||||
|     } | ||||
| } | ||||
| @ -23,4 +23,6 @@ | ||||
|     <string name="sample_custom_extension_2"># \# Custom extension 2\n\nAutomatically | ||||
|         convert `#1` and `@user` to Github links</string> | ||||
| 
 | ||||
|     <string name="sample_precomputed_text"># \# PrecomputedText\n\nUsage of TextSetter and PrecomputedTextCompat</string> | ||||
| 
 | ||||
| </resources> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov