Skip to content

Commit fe1b5fe

Browse files
authored
Merge pull request #18 from amardeshbd/develop
Tagging for release 1.1
2 parents 071bfbc + bb35a21 commit fe1b5fe

27 files changed

+975
-75
lines changed

README.md

Lines changed: 183 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
yet another android syntax highlighter (YAASH)
33

44
### Objective
5-
Explore well established web based syntax highlighter like [PrismJS](https://prismjs.com/) and [highlight.js](https://highlightjs.org/), and showcase how anybody can quickly incorporate these into their project by following some examples provided here.
5+
Explore well established web based syntax highlighter like [PrismJS](https://prismjs.com/) and [highlight.js](https://highlightjs.org/),
6+
and showcase how anybody can quickly incorporate these into their project by following some examples provided here.
67

7-
The objective is not exactly to provide a _library_ that you can use with Gradle.
8+
9+
> The intention is **NOT** to create another library project that gets abandoned over time.
10+
Feel free to copy parts of code that is necessary for you to add syntax highlighting support to your app.
811

912

1013
## Existing Syntax Highlighting Libraries
@@ -56,7 +59,8 @@ For example:
5659
```
5760

5861
> NOTE: For most cases, hard coding sample code for each sample-code is not ideal.
59-
> Soon, we will explore how to make the HTML file as template and inject source code from Activity or Fragment.
62+
> Soon, we will explore how to make the HTML file as template and inject source code from Activity or Fragment.
63+
> See [Custom View](#building-your-own-fragment-or-custom-view) section below for detailed instructions.
6064
6165
### 3. Load the static HTML on `WebView`
6266
Finally on your Activity or Fragment, once view is loaded initialize `WebView` with local html file from `assets`.
@@ -73,4 +77,179 @@ webView.apply {
7377
#### Screenshot
7478
Here is a screenshot taken from a demo static html page that has syntax highlighting using Prism JS.
7579

76-
<img width="200" src="https://user-images.githubusercontent.com/99822/87729492-809b4200-c793-11ea-8bfd-810359a11663.png">
80+
| ![device-2020-07-18-092715](https://user-images.githubusercontent.com/99822/87853541-fc52d700-c8d8-11ea-9dc6-2d4c624f3b74.png) | ![device-2020-07-18-092727](https://user-images.githubusercontent.com/99822/87853542-fceb6d80-c8d8-11ea-9641-4ecf927b5a01.png) | ![device-2020-07-18-092736](https://user-images.githubusercontent.com/99822/87853543-fe1c9a80-c8d8-11ea-9e11-c9779202368e.png) |
81+
| --- | --- | --- |
82+
83+
## Building your own Fragment or Custom View
84+
Ideally, there should be a modular component or custom-view that you **re-use** syntax highlighting with dynamic content.
85+
For that having a `Fragment` or custom `View` is ideal.
86+
87+
We can taken the learning from [above](#under-the-hood) to wrap the JavaScript based syntax highlighting library
88+
in fragment or custom view using `WebView`. Both comes with advantage of it's own.
89+
Regardless if which option is chosen, the underlying code is _almost_ identical.
90+
91+
### Custom View
92+
The advantage of custom view is that, it can be used anywhere, `Activity` or `Fragment`.
93+
Let's take a look how we can templatize the HTML to load source code dynamically.
94+
95+
In this case, all we need to do is move the [html content defined above](#2-use-htmlcssjs-asset) to a `String` variable with options you need.
96+
97+
#### PrismJS Template Function
98+
```kotlin
99+
fun prismJsHtmlContent(
100+
formattedSourceCode: String,
101+
language: String,
102+
showLineNumbers: Boolean = true
103+
): String {
104+
return """<!DOCTYPE html>
105+
<html>
106+
<head>
107+
<!-- https://developer.chrome.com/multidevice/webview/pixelperfect -->
108+
<meta name="viewport" content="width=device-width, initial-scale=1">
109+
<link href="www/main.css" rel="stylesheet"/>
110+
111+
<!-- https://prismjs.com/ -->
112+
<link href="www/prism.css" rel="stylesheet"/>
113+
<script src="www/prism.js"></script>
114+
</head>
115+
<body>
116+
<pre class="${if (showLineNumbers) "line-numbers" else ""}">
117+
<code class="language-${language}">${formattedSourceCode}</code>
118+
</pre>
119+
</body>
120+
</html>
121+
"""
122+
}
123+
```
124+
125+
In this example, we have `showLineNumbers` as optional parameter, likewise we could have line number parameter to highlight a line or section.
126+
PrismJS has [dozens of plugins](https://prismjs.com/download.html) that you can use and expose those options though this function.
127+
128+
#### Creating custom syntax highlighter WebView
129+
Here you just need to extend the `WebView` and expose a function `bindSyntaxHighlighter()` to send source code and configurations.
130+
```kotlin
131+
class SyntaxHighlighterWebView @JvmOverloads constructor(
132+
context: Context,
133+
attrs: AttributeSet? = null,
134+
defStyleAttr: Int = 0
135+
) : WebView(context, attrs, defStyleAttr) {
136+
companion object {
137+
private const val ANDROID_ASSETS_PATH = "file:///android_asset/"
138+
}
139+
140+
@SuppressLint("SetJavaScriptEnabled")
141+
fun bindSyntaxHighlighter(
142+
formattedSourceCode: String,
143+
language: String,
144+
showLineNumbers: Boolean = false
145+
) {
146+
settings.javaScriptEnabled = true
147+
webChromeClient = WebViewChromeClient()
148+
webViewClient = AppWebViewClient()
149+
150+
loadDataWithBaseURL(
151+
ANDROID_ASSETS_PATH /* baseUrl */,
152+
prismJsHtmlContent(formattedSourceCode, language, showLineNumbers) /* html-data */,
153+
"text/html" /* mimeType */,
154+
"utf-8" /* encoding */,
155+
"" /* failUrl */
156+
)
157+
}
158+
}
159+
```
160+
161+
#### Use custom view from Fragment or Activity
162+
163+
Using the newly defined `SyntaxHighlighterWebView` in `Fragment` or `Activity` is business as usual that you are used to.
164+
165+
In your Layout XML file, add the view with proper layout parameters.
166+
167+
```xml
168+
<your.prismjs.SyntaxHighlighterWebView
169+
android:id="@+id/syntax_highlighter_webview"
170+
android:layout_width="match_parent"
171+
android:layout_height="match_parent" />
172+
```
173+
174+
When `Fragment` or `Activity` is loaded, get reference to `SyntaxHighlighterWebView` and bind it with source code.
175+
```kotlin
176+
val syntaxHighlighter = findViewById(R.id.syntax_highlighter_webview)
177+
178+
syntaxHighlighter.bindSyntaxHighlighter(
179+
formattedSourceCode = "data class Student(val name: String)",
180+
language = "kotlin"
181+
)
182+
```
183+
184+
That's it, you have re-usable custom view that you can use anywhere in the layout.
185+
186+
## Fragment
187+
`Fragment` is a modular UI component that can be use in a `Activity`. It comes with it's own flexibility like:
188+
* Being able to replace whole screen content
189+
* Replace only part of the content
190+
* Use with navigation library as destination
191+
* and so on
192+
193+
In this case `Fragment` is just a shell that loads `WebView` and configures it such that it
194+
can show syntax highlighted source code.
195+
196+
### Create custom Syntax Highlighter Fragment
197+
198+
Based on Android [official guide](https://developer.android.com/guide/components/fragments#Example)
199+
we need to pass all the required data needed for `prismJsHtmlContent` function [defined above](#prismjs-template-function)
200+
```kotlin
201+
fun newInstance(
202+
formattedSourceCode: String,
203+
language: String,
204+
showLineNumbers: Boolean = false
205+
) = SyntaxHighlighterFragment().apply {
206+
arguments = Bundle().apply {
207+
putString(ARG_KEY_SOURCE_CODE_CONTENT, formattedSourceCode)
208+
putString(ARG_KEY_CODE_LANGUAGE, language)
209+
putBoolean(ARG_KEY_SHOW_LINE_NUMBERS, showLineNumbers)
210+
}
211+
}
212+
```
213+
214+
> See [SyntaxHighlighterFragment.kt](https://github.com/amardeshbd/android-syntax-highlighter/blob/develop/highlighter/src/main/java/dev/hossain/yaash/prismjs/SyntaxHighlighterFragment.kt)
215+
> source code for full example.
216+
217+
And finally when `Fragment#onViewCreated()` is called, we use the extracted the bundle parameters
218+
to initialize and load the syntax highlighting.
219+
220+
```kotlin
221+
@SuppressLint("SetJavaScriptEnabled")
222+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
223+
super.onViewCreated(view, savedInstanceState)
224+
// Loads the plain `WebView` defined in `fragment_highlighter.xml`
225+
val webView: WebView = view.findViewById(R.id.web_view)
226+
227+
webView.apply {
228+
settings.javaScriptEnabled = true
229+
webChromeClient = WebViewChromeClient()
230+
webViewClient = AppWebViewClient()
231+
232+
loadDataWithBaseURL(
233+
ANDROID_ASSETS_PATH /* baseUrl */,
234+
prismJsHtmlContent(sourceCode, language, showLineNumbers) /* html-data */,
235+
"text/html" /* mimeType */,
236+
"utf-8" /* encoding */,
237+
"" /* failUrl */
238+
)
239+
}
240+
}
241+
```
242+
243+
### Using the Syntax Highlighter Fragment
244+
From your `Activity` or `Fragment`, create an instance of `SyntaxHighlighterFragment` and add that
245+
to fragment container on the screen.
246+
```kotlin
247+
val fragment = SyntaxHighlighterFragment.newInstance(
248+
formattedSourceCode = "data class Student(val name: String)",
249+
language = "kotlin",
250+
showLineNumbers = true
251+
)
252+
```
253+
254+
> See [PrismJsDemoActivity.kt](https://github.com/amardeshbd/android-syntax-highlighter/blob/develop/example/src/main/java/dev/hossain/yaash/example/ui/demoprismjs/PrismJsDemoActivity.kt)
255+
> source code for full example.

example/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ android {
1010
applicationId "dev.hossain.yaash.example"
1111
minSdkVersion rootProject.ext.androidMinSdkVersion
1212
targetSdkVersion rootProject.ext.androidTargetSdkVersion
13-
versionCode 1
14-
versionName "1.0"
13+
versionCode 2
14+
versionName "1.1"
1515

1616
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1717
}

example/src/main/AndroidManifest.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
android:roundIcon="@mipmap/ic_launcher_round"
1111
android:supportsRtl="true"
1212
android:theme="@style/AppTheme">
13-
<activity android:name=".MainActivity">
13+
<activity android:name=".ui.MainActivity">
1414
<intent-filter>
1515
<action android:name="android.intent.action.MAIN" />
1616

1717
<category android:name="android.intent.category.LAUNCHER" />
1818
</intent-filter>
1919
</activity>
20+
<activity
21+
android:name=".ui.demohighlightjs.HighlightJsDemoActivity"
22+
android:parentActivityName=".ui.MainActivity" />
23+
<activity
24+
android:name=".ui.demoprismjs.PrismJsDemoActivity"
25+
android:parentActivityName=".ui.MainActivity" />
2026
</application>
2127

2228
</manifest>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.hossain.yaash.example.ui
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import android.widget.Button
6+
import androidx.appcompat.app.AppCompatActivity
7+
import dev.hossain.yaash.example.R
8+
import dev.hossain.yaash.example.ui.demohighlightjs.HighlightJsDemoActivity
9+
import dev.hossain.yaash.example.ui.demoprismjs.PrismJsDemoActivity
10+
11+
/**
12+
* Main activity to showcase both fragment based and custom view based syntax highlighting.
13+
*/
14+
class MainActivity : AppCompatActivity() {
15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(savedInstanceState)
17+
setContentView(R.layout.activity_main)
18+
19+
findViewById<Button>(R.id.highlightjs_demo_button).setOnClickListener {
20+
startActivity(Intent(this, HighlightJsDemoActivity::class.java))
21+
}
22+
23+
findViewById<Button>(R.id.prismjs_demo_button).setOnClickListener {
24+
startActivity(Intent(this, PrismJsDemoActivity::class.java))
25+
}
26+
}
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dev.hossain.yaash.example.ui.common
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.net.Uri
6+
import android.widget.Toast
7+
import timber.log.Timber
8+
9+
object IntentAction {
10+
/**
11+
* Loads an external web URL.
12+
* https://developer.android.com/guide/components/intents-common#ViewUrl
13+
*/
14+
fun openWebPage(context: Context, url: String) {
15+
val webPage: Uri = Uri.parse(url)
16+
val intent = Intent(Intent.ACTION_VIEW, webPage)
17+
if (intent.resolveActivity(context.packageManager) != null) {
18+
context.startActivity(intent)
19+
} else {
20+
Timber.w("Unable to find any app that can open web URL")
21+
22+
// Instead of keeping user in dark, show them feedback about the issue.
23+
Toast.makeText(
24+
context,
25+
"Ops! We're unable to open the URL as we can't find default browser app :-(",
26+
Toast.LENGTH_SHORT
27+
).show()
28+
}
29+
}
30+
}

example/src/main/java/dev/hossain/yaash/example/MainActivity.kt renamed to example/src/main/java/dev/hossain/yaash/example/ui/common/SampleSourceCode.kt

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,12 @@
1-
package dev.hossain.yaash.example
1+
package dev.hossain.yaash.example.ui.common
22

3-
import android.os.Bundle
4-
import androidx.appcompat.app.AppCompatActivity
5-
import dev.hossain.yaash.prismjs.SyntaxHighlighterFragment
6-
import dev.hossain.yaash.prismjs.SyntaxHighlighterWebView
7-
8-
/**
9-
* Main activity to showcase both fragment based and custom view based syntax highlighting.
10-
*
11-
* @see SyntaxHighlighterFragment
12-
* @see SyntaxHighlighterWebView
13-
*/
14-
class MainActivity : AppCompatActivity() {
15-
override fun onCreate(savedInstanceState: Bundle?) {
16-
super.onCreate(savedInstanceState)
17-
setContentView(R.layout.activity_main)
18-
19-
loadSourceCodeFragment()
20-
loadSourceCodeCustomView()
21-
}
22-
23-
private fun loadSourceCodeFragment() {
24-
val fragment = SyntaxHighlighterFragment.newInstance(
25-
formattedSourceCode = fragmentSourceCode,
26-
language = "kotlin",
27-
showLineNumbers = true
28-
)
29-
30-
val fragmentManager = supportFragmentManager
31-
val fragmentTransaction = fragmentManager.beginTransaction()
32-
fragmentTransaction.add(R.id.fragment_container, fragment)
33-
fragmentTransaction.commit()
34-
}
35-
36-
private fun loadSourceCodeCustomView() {
37-
val syntaxHighlighter: SyntaxHighlighterWebView = findViewById(R.id.syntax_highlighter_webview)
38-
39-
syntaxHighlighter.bindSyntaxHighlighter(
40-
formattedSourceCode = customViewSourceCode,
41-
language = "kotlin",
42-
showLineNumbers = true
43-
)
44-
}
3+
object SampleSourceCode {
454

465
//
476
// Sample codes for syntax-highlight preview
487
//
498

50-
private val fragmentSourceCode = """
9+
val fragmentOnCreated = """
5110
|@SuppressLint("SetJavaScriptEnabled")
5211
|override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
5312
| super.onViewCreated(view, savedInstanceState)
@@ -74,7 +33,7 @@ class MainActivity : AppCompatActivity() {
7433
|}
7534
""".trimMargin()
7635

77-
private val customViewSourceCode = """
36+
val customViewBind = """
7837
|@SuppressLint("SetJavaScriptEnabled")
7938
|fun bindSyntaxHighlighter(
8039
| formattedSourceCode: String,

0 commit comments

Comments
 (0)