From 493444aaaba8d6e1d587df3b699b491f8230fb68 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Mon, 11 Sep 2023 21:58:19 -0600 Subject: [PATCH] Implement Readability Highlighting in compose This could still use some performance tweaks to make it run a bit better but at least it works --- .../simplemarkdown/model/Readability.kt | 2 +- .../wbrawner/simplemarkdown/ui/MainScreen.kt | 49 ++- .../view/fragment/EditFragment.kt | 358 +++++++++--------- .../view/fragment/PreviewFragment.kt | 192 +++++----- 4 files changed, 322 insertions(+), 279 deletions(-) diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/model/Readability.kt b/app/src/main/java/com/wbrawner/simplemarkdown/model/Readability.kt index aad2bee..d0d0e1c 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/model/Readability.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/model/Readability.kt @@ -8,7 +8,7 @@ class Readability(private val content: String) { val list = ArrayList() var startOfSentance = 0 var lineBuilder = StringBuilder() - for (i in 0 until content.length) { + for (i in content.indices) { val c = content[i] + "" if (DELIMS.contains(c)) { list.add(Sentence(content, startOfSentance, i)) diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt index 231ecf6..7a0aa39 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt @@ -1,6 +1,8 @@ package com.wbrawner.simplemarkdown.ui import android.content.Intent +import android.text.SpannableString +import android.text.style.BackgroundColorSpan import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -48,16 +50,22 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavController +import androidx.preference.PreferenceManager import com.wbrawner.simplemarkdown.R +import com.wbrawner.simplemarkdown.model.Readability import com.wbrawner.simplemarkdown.view.activity.Route import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel import kotlinx.coroutines.launch +import timber.log.Timber @OptIn(ExperimentalFoundationApi::class) @Composable @@ -121,6 +129,11 @@ fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) { }) { paddingValues -> val coroutineScope = rememberCoroutineScope() val pagerState = rememberPagerState { 2 } + val context = LocalContext.current + val enableReadability = remember { + PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(PREF_KEY_READABILITY, false) + } Column( modifier = Modifier .fillMaxSize() @@ -139,14 +152,32 @@ fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) { userScrollEnabled = !lockSwiping ) { page -> val markdown by viewModel.markdownUpdates.collectAsState() + var textFieldValue by remember { + val annotatedMarkdown = if (enableReadability) { + markdown.annotateReadability() + } else { + AnnotatedString(markdown) + } + mutableStateOf(TextFieldValue(annotatedMarkdown)) + } if (page == 0) { BasicTextField( modifier = Modifier .fillMaxSize() .padding(8.dp), - value = markdown, - onValueChange = { viewModel.updateMarkdown(it) }, - textStyle = TextStyle.Default.copy(fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface), + value = textFieldValue, + onValueChange = { + textFieldValue = if (enableReadability) { + it.copy(annotatedString = it.text.annotateReadability()) + } else { + it + } + viewModel.updateMarkdown(it.text) + }, + textStyle = TextStyle.Default.copy( + fontFamily = FontFamily.Monospace, + color = MaterialTheme.colorScheme.onSurface + ), keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences), cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface) ) @@ -159,6 +190,18 @@ fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) { } } +private fun String.annotateReadability(): AnnotatedString { + val readability = Readability(this) + val annotated = AnnotatedString.Builder(this) + for (sentence in readability.sentences()) { + var color = Color.Transparent + if (sentence.syllableCount() > 25) color = Color(229, 232, 42, 100) + if (sentence.syllableCount() > 35) color = Color(193, 66, 66, 100) + annotated.addStyle(SpanStyle(background = color), sentence.start(), sentence.end()) + } + return annotated.toAnnotatedString() +} + @Composable fun MarkdownNavigationDrawer( navigate: (Route) -> Unit, content: @Composable (drawerState: DrawerState) -> Unit diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt index 2f82e9b..ee93280 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/EditFragment.kt @@ -1,179 +1,179 @@ -package com.wbrawner.simplemarkdown.view.fragment - -import android.annotation.SuppressLint -import android.graphics.Color -import android.os.Bundle -import android.text.Editable -import android.text.SpannableString -import android.text.TextWatcher -import android.text.style.BackgroundColorSpan -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.widget.EditText -import android.widget.TextView -import androidx.core.widget.NestedScrollView -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope -import androidx.preference.PreferenceManager -import com.wbrawner.simplemarkdown.R -import com.wbrawner.simplemarkdown.model.Readability -import com.wbrawner.simplemarkdown.utility.hideKeyboard -import com.wbrawner.simplemarkdown.utility.showKeyboard -import com.wbrawner.simplemarkdown.view.ViewPagerPage -import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel -import kotlinx.coroutines.* -import timber.log.Timber -import kotlin.math.abs - -class EditFragment : Fragment(), ViewPagerPage { - private var markdownEditor: EditText? = null - private var markdownEditorScroller: NestedScrollView? = null - private val viewModel: MarkdownViewModel by viewModels({ requireParentFragment() }) - private var readabilityWatcher: TextWatcher? = null - private val markdownWatcher = object : TextWatcher { - private var searchFor = "" - - override fun afterTextChanged(s: Editable?) { - val searchText = s.toString().trim() - if (searchText == searchFor) - return - - searchFor = searchText - - lifecycleScope.launch { - delay(50) - if (searchText != searchFor) - return@launch - viewModel.updateMarkdown(searchText) - } - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } - } - - @SuppressLint("ClickableViewAccessibility") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? = - inflater.inflate(R.layout.fragment_edit, container, false) - - @SuppressLint("ClickableViewAccessibility") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - markdownEditor = view.findViewById(R.id.markdown_edit) - markdownEditorScroller = view.findViewById(R.id.markdown_edit_container) - markdownEditor?.addTextChangedListener(markdownWatcher) - - var touchDown = 0L - var oldX = 0f - var oldY = 0f - markdownEditorScroller!!.setOnTouchListener { _, event -> - // The ScrollView's onClickListener doesn't seem to be called, so I've had to - // implement a sort of custom click listener that checks that the tap was both quick - // and didn't drag. - when (event?.action) { - MotionEvent.ACTION_DOWN -> { - touchDown = System.currentTimeMillis() - oldX = event.rawX - oldY = event.rawY - } - MotionEvent.ACTION_UP -> { - if (System.currentTimeMillis() - touchDown < 150 - && abs(event.rawX - oldX) < 25 - && abs(event.rawY - oldY) < 25) - markdownEditor?.showKeyboard() - } - } - false - } - markdownEditor?.setText(viewModel.markdownUpdates.value) - viewModel.editorActions.observe(viewLifecycleOwner, Observer { - if (it.consumed.getAndSet(true)) return@Observer - if (it is MarkdownViewModel.EditorAction.Load) { - markdownEditor?.apply { - removeTextChangedListener(markdownWatcher) - setText(it.markdown) - addTextChangedListener(markdownWatcher) - } - } - }) - lifecycleScope.launch { - val enableReadability = withContext(Dispatchers.IO) { - context?.let { - PreferenceManager.getDefaultSharedPreferences(it) - .getBoolean(getString(R.string.readability_enabled), false) - } ?: false - } - if (enableReadability) { - if (readabilityWatcher == null) { - readabilityWatcher = ReadabilityTextWatcher() - } - markdownEditor?.addTextChangedListener(readabilityWatcher) - } else { - readabilityWatcher?.let { - markdownEditor?.removeTextChangedListener(it) - } - readabilityWatcher = null - } - } - } - - override fun onSelected() { - markdownEditor?.showKeyboard() - } - - override fun onDeselected() { - markdownEditor?.hideKeyboard() - } - - inner class ReadabilityTextWatcher : TextWatcher { - private var previousValue = "" - private var searchFor = "" - - override fun afterTextChanged(s: Editable?) { - val searchText = s.toString().trim() - if (searchText == searchFor) - return - - searchFor = searchText - - lifecycleScope.launch { - delay(250) - if (searchText != searchFor) - return@launch - val start = System.currentTimeMillis() - if (searchFor.isEmpty()) return@launch - if (previousValue == searchFor) return@launch - val readability = Readability(searchFor) - val span = SpannableString(searchFor) - for (sentence in readability.sentences()) { - var color = Color.TRANSPARENT - if (sentence.syllableCount() > 25) color = Color.argb(100, 229, 232, 42) - if (sentence.syllableCount() > 35) color = Color.argb(100, 193, 66, 66) - Timber.d("Sentence start: ${sentence.start()} end: ${ - sentence - .end() - }") - span.setSpan(BackgroundColorSpan(color), sentence.start(), sentence.end(), 0) - } - markdownEditor?.setTextKeepState(span, TextView.BufferType.SPANNABLE) - previousValue = searchFor - val timeTakenMs = System.currentTimeMillis() - start - Timber.d("Handled markdown in $timeTakenMs ms") - } - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } - } -} +//package com.wbrawner.simplemarkdown.view.fragment +// +//import android.annotation.SuppressLint +//import android.graphics.Color +//import android.os.Bundle +//import android.text.Editable +//import android.text.SpannableString +//import android.text.TextWatcher +//import android.text.style.BackgroundColorSpan +//import android.view.LayoutInflater +//import android.view.MotionEvent +//import android.view.View +//import android.view.ViewGroup +//import android.widget.EditText +//import android.widget.TextView +//import androidx.core.widget.NestedScrollView +//import androidx.fragment.app.Fragment +//import androidx.fragment.app.viewModels +//import androidx.lifecycle.Observer +//import androidx.lifecycle.lifecycleScope +//import androidx.preference.PreferenceManager +//import com.wbrawner.simplemarkdown.R +//import com.wbrawner.simplemarkdown.model.Readability +//import com.wbrawner.simplemarkdown.utility.hideKeyboard +//import com.wbrawner.simplemarkdown.utility.showKeyboard +//import com.wbrawner.simplemarkdown.view.ViewPagerPage +//import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel +//import kotlinx.coroutines.* +//import timber.log.Timber +//import kotlin.math.abs +// +//class EditFragment : Fragment(), ViewPagerPage { +// private var markdownEditor: EditText? = null +// private var markdownEditorScroller: NestedScrollView? = null +// private val viewModel: MarkdownViewModel by viewModels({ requireParentFragment() }) +// private var readabilityWatcher: TextWatcher? = null +// private val markdownWatcher = object : TextWatcher { +// private var searchFor = "" +// +// override fun afterTextChanged(s: Editable?) { +// val searchText = s.toString().trim() +// if (searchText == searchFor) +// return +// +// searchFor = searchText +// +// lifecycleScope.launch { +// delay(50) +// if (searchText != searchFor) +// return@launch +// viewModel.updateMarkdown(searchText) +// } +// } +// +// override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { +// } +// +// override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { +// } +// } +// +// @SuppressLint("ClickableViewAccessibility") +// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, +// savedInstanceState: Bundle?): View? = +// inflater.inflate(R.layout.fragment_edit, container, false) +// +// @SuppressLint("ClickableViewAccessibility") +// override fun onViewCreated(view: View, savedInstanceState: Bundle?) { +// super.onViewCreated(view, savedInstanceState) +// markdownEditor = view.findViewById(R.id.markdown_edit) +// markdownEditorScroller = view.findViewById(R.id.markdown_edit_container) +// markdownEditor?.addTextChangedListener(markdownWatcher) +// +// var touchDown = 0L +// var oldX = 0f +// var oldY = 0f +// markdownEditorScroller!!.setOnTouchListener { _, event -> +// // The ScrollView's onClickListener doesn't seem to be called, so I've had to +// // implement a sort of custom click listener that checks that the tap was both quick +// // and didn't drag. +// when (event?.action) { +// MotionEvent.ACTION_DOWN -> { +// touchDown = System.currentTimeMillis() +// oldX = event.rawX +// oldY = event.rawY +// } +// MotionEvent.ACTION_UP -> { +// if (System.currentTimeMillis() - touchDown < 150 +// && abs(event.rawX - oldX) < 25 +// && abs(event.rawY - oldY) < 25) +// markdownEditor?.showKeyboard() +// } +// } +// false +// } +// markdownEditor?.setText(viewModel.markdownUpdates.value) +// viewModel.editorActions.observe(viewLifecycleOwner, Observer { +// if (it.consumed.getAndSet(true)) return@Observer +// if (it is MarkdownViewModel.EditorAction.Load) { +// markdownEditor?.apply { +// removeTextChangedListener(markdownWatcher) +// setText(it.markdown) +// addTextChangedListener(markdownWatcher) +// } +// } +// }) +// lifecycleScope.launch { +// val enableReadability = withContext(Dispatchers.IO) { +// context?.let { +// PreferenceManager.getDefaultSharedPreferences(it) +// .getBoolean(getString(R.string.readability_enabled), false) +// } ?: false +// } +// if (enableReadability) { +// if (readabilityWatcher == null) { +// readabilityWatcher = ReadabilityTextWatcher() +// } +// markdownEditor?.addTextChangedListener(readabilityWatcher) +// } else { +// readabilityWatcher?.let { +// markdownEditor?.removeTextChangedListener(it) +// } +// readabilityWatcher = null +// } +// } +// } +// +// override fun onSelected() { +// markdownEditor?.showKeyboard() +// } +// +// override fun onDeselected() { +// markdownEditor?.hideKeyboard() +// } +// +// inner class ReadabilityTextWatcher : TextWatcher { +// private var previousValue = "" +// private var searchFor = "" +// +// override fun afterTextChanged(s: Editable?) { +// val searchText = s.toString().trim() +// if (searchText == searchFor) +// return +// +// searchFor = searchText +// +// lifecycleScope.launch { +// delay(250) +// if (searchText != searchFor) +// return@launch +// val start = System.currentTimeMillis() +// if (searchFor.isEmpty()) return@launch +// if (previousValue == searchFor) return@launch +// val readability = Readability(searchFor) +// val span = SpannableString(searchFor) +// for (sentence in readability.sentences()) { +// var color = Color.TRANSPARENT +// if (sentence.syllableCount() > 25) color = Color.argb(100, 229, 232, 42) +// if (sentence.syllableCount() > 35) color = Color.argb(100, 193, 66, 66) +// Timber.d("Sentence start: ${sentence.start()} end: ${ +// sentence +// .end() +// }") +// span.setSpan(BackgroundColorSpan(color), sentence.start(), sentence.end(), 0) +// } +// markdownEditor?.setTextKeepState(span, TextView.BufferType.SPANNABLE) +// previousValue = searchFor +// val timeTakenMs = System.currentTimeMillis() - start +// Timber.d("Handled markdown in $timeTakenMs ms") +// } +// } +// +// override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { +// } +// +// override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { +// } +// } +//} diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt index 804c8ee..090129d 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/PreviewFragment.kt @@ -1,96 +1,96 @@ -package com.wbrawner.simplemarkdown.view.fragment - -import android.content.Context -import android.content.res.Configuration.UI_MODE_NIGHT_MASK -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.WebView -import androidx.appcompat.app.AppCompatDelegate -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.preference.PreferenceManager -import com.wbrawner.simplemarkdown.BuildConfig -import com.wbrawner.simplemarkdown.R -import com.wbrawner.simplemarkdown.utility.toHtml -import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel -import kotlinx.coroutines.* - -class PreviewFragment : Fragment() { - private val viewModel: MarkdownViewModel by viewModels({ requireParentFragment() }) - private var markdownPreview: WebView? = null - private var style: String = "" - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = inflater.inflate(R.layout.fragment_preview, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - markdownPreview = view.findViewById(R.id.markdown_view) - WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) - lifecycleScope.launch { - val isNightMode = AppCompatDelegate.getDefaultNightMode() == - AppCompatDelegate.MODE_NIGHT_YES - || requireContext().resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES - val defaultCssId = if (isNightMode) { - R.string.pref_custom_css_default_dark - } else { - R.string.pref_custom_css_default - } - val css = withContext(Dispatchers.IO) { - val context = context ?: return@withContext null - @Suppress("ConstantConditionIf") - if (!BuildConfig.ENABLE_CUSTOM_CSS) { - context.getString(defaultCssId) - } else { - PreferenceManager.getDefaultSharedPreferences(context) - .getString( - getString(R.string.pref_custom_css), - getString(defaultCssId) - ) - } - } - style = String.format(FORMAT_CSS, css ?: "") - } - } - - override fun onAttach(context: Context) { - super.onAttach(context) - updateWebContent(viewModel.markdownUpdates.value ?: "") -// viewModel.markdownUpdates.observe(this, { -// updateWebContent(it) -// }) - } - - private fun updateWebContent(markdown: String) { - markdownPreview?.post { - lifecycleScope.launch { - markdownPreview?.loadDataWithBaseURL(null, - style + markdown.toHtml(), - "text/html", - "UTF-8", null - ) - } - } - } - - override fun onDestroyView() { - markdownPreview?.let { - (it.parent as ViewGroup).removeView(it) - it.destroy() - markdownPreview = null - } - super.onDestroyView() - } - - companion object { - var FORMAT_CSS = "" - } -} +//package com.wbrawner.simplemarkdown.view.fragment +// +//import android.content.Context +//import android.content.res.Configuration.UI_MODE_NIGHT_MASK +//import android.content.res.Configuration.UI_MODE_NIGHT_YES +//import android.os.Bundle +//import android.view.LayoutInflater +//import android.view.View +//import android.view.ViewGroup +//import android.webkit.WebView +//import androidx.appcompat.app.AppCompatDelegate +//import androidx.fragment.app.Fragment +//import androidx.fragment.app.viewModels +//import androidx.lifecycle.lifecycleScope +//import androidx.preference.PreferenceManager +//import com.wbrawner.simplemarkdown.BuildConfig +//import com.wbrawner.simplemarkdown.R +//import com.wbrawner.simplemarkdown.utility.toHtml +//import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel +//import kotlinx.coroutines.* +// +//class PreviewFragment : Fragment() { +// private val viewModel: MarkdownViewModel by viewModels({ requireParentFragment() }) +// private var markdownPreview: WebView? = null +// private var style: String = "" +// +// override fun onCreateView( +// inflater: LayoutInflater, +// container: ViewGroup?, +// savedInstanceState: Bundle? +// ): View? = inflater.inflate(R.layout.fragment_preview, container, false) +// +// override fun onViewCreated(view: View, savedInstanceState: Bundle?) { +// markdownPreview = view.findViewById(R.id.markdown_view) +// WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) +// lifecycleScope.launch { +// val isNightMode = AppCompatDelegate.getDefaultNightMode() == +// AppCompatDelegate.MODE_NIGHT_YES +// || requireContext().resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES +// val defaultCssId = if (isNightMode) { +// R.string.pref_custom_css_default_dark +// } else { +// R.string.pref_custom_css_default +// } +// val css = withContext(Dispatchers.IO) { +// val context = context ?: return@withContext null +// @Suppress("ConstantConditionIf") +// if (!BuildConfig.ENABLE_CUSTOM_CSS) { +// context.getString(defaultCssId) +// } else { +// PreferenceManager.getDefaultSharedPreferences(context) +// .getString( +// getString(R.string.pref_custom_css), +// getString(defaultCssId) +// ) +// } +// } +// style = String.format(FORMAT_CSS, css ?: "") +// } +// } +// +// override fun onAttach(context: Context) { +// super.onAttach(context) +// updateWebContent(viewModel.markdownUpdates.value ?: "") +//// viewModel.markdownUpdates.observe(this, { +//// updateWebContent(it) +//// }) +// } +// +// private fun updateWebContent(markdown: String) { +// markdownPreview?.post { +// lifecycleScope.launch { +// markdownPreview?.loadDataWithBaseURL(null, +// style + markdown.toHtml(), +// "text/html", +// "UTF-8", null +// ) +// } +// } +// } +// +// override fun onDestroyView() { +// markdownPreview?.let { +// (it.parent as ViewGroup).removeView(it) +// it.destroy() +// markdownPreview = null +// } +// super.onDestroyView() +// } +// +// companion object { +// var FORMAT_CSS = "" +// } +//}