Skip to content
Snippets Groups Projects
Commit 37ade3a7 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement formatting editor

parent 0e668ac3
No related branches found
No related tags found
No related merge requests found
Showing
with 387 additions and 78 deletions
...@@ -11,11 +11,12 @@ import android.os.Build ...@@ -11,11 +11,12 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle import android.os.PersistableBundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.DrawableCompat
import android.support.v4.widget.DrawerLayout import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.ActionMenuView
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.text.InputType import android.text.InputType
import android.text.Spanned
import android.view.* import android.view.*
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.EditText import android.widget.EditText
...@@ -40,23 +41,29 @@ import de.kuschku.quasseldroid_ng.ui.settings.data.Settings ...@@ -40,23 +41,29 @@ import de.kuschku.quasseldroid_ng.ui.settings.data.Settings
import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
import de.kuschku.quasseldroid_ng.util.helper.* import de.kuschku.quasseldroid_ng.util.helper.*
import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatSerializer
import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity
import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenceChangeListener { class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
@BindView(R.id.drawerLayout) ActionMenuView.OnMenuItemClickListener {
@BindView(R.id.drawer_layout)
lateinit var drawerLayout: DrawerLayout lateinit var drawerLayout: DrawerLayout
@BindView(R.id.toolbar) @BindView(R.id.toolbar)
lateinit var toolbar: Toolbar lateinit var toolbar: Toolbar
@BindView(R.id.progressBar) @BindView(R.id.formatting_menu)
lateinit var formattingMenu: ActionMenuView
@BindView(R.id.progress_bar)
lateinit var progressBar: MaterialContentLoadingProgressBar lateinit var progressBar: MaterialContentLoadingProgressBar
@BindView(R.id.editor_panel) @BindView(R.id.editor_panel)
lateinit var editorPanel: SlidingUpPanelLayout lateinit var editorPanel: SlidingUpPanelLayout
@BindView(R.id.history_panel)
lateinit var historyPanel: SlidingUpPanelLayout
@BindView(R.id.send) @BindView(R.id.send)
lateinit var send: ImageButton lateinit var send: ImageButton
...@@ -75,7 +82,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc ...@@ -75,7 +82,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
private lateinit var backlogSettings: BacklogSettings private lateinit var backlogSettings: BacklogSettings
private lateinit var ircFormatSerializer: IrcFormatSerializer private lateinit var inputEditor: InputEditor
private val panelSlideListener: SlidingUpPanelLayout.PanelSlideListener = object : private val panelSlideListener: SlidingUpPanelLayout.PanelSlideListener = object :
SlidingUpPanelLayout.PanelSlideListener { SlidingUpPanelLayout.PanelSlideListener {
...@@ -107,7 +114,21 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc ...@@ -107,7 +114,21 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java] viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java]
viewModel.setBackend(this.backend) viewModel.setBackend(this.backend)
backlogSettings = Settings.backlog(this) backlogSettings = Settings.backlog(this)
ircFormatSerializer = IrcFormatSerializer(this)
inputEditor = InputEditor(chatline)
menuInflater.inflate(inputEditor.menu, formattingMenu.menu)
menuInflater.inflate(R.menu.input_panel, formattingMenu.menu)
formattingMenu.setOnMenuItemClickListener(this)
formattingMenu.context.theme.styledAttributes(R.attr.colorControlNormal) {
val color = getColor(0, 0)
for (item in (0 until formattingMenu.menu.size()).map { formattingMenu.menu.getItem(it) }) {
val drawable = item.icon.mutate()
DrawableCompat.setTint(drawable, color)
item.icon = drawable
}
}
database = QuasselDatabase.Creator.init(application) database = QuasselDatabase.Creator.init(application)
...@@ -185,7 +206,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc ...@@ -185,7 +206,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
for (line in text.lineSequence()) { for (line in text.lineSequence()) {
session.aliasManager?.processInput( session.aliasManager?.processInput(
bufferInfo, bufferInfo,
if (line is Spanned) ircFormatSerializer.toEscapeCodes(line) else line.toString(), inputEditor.formattedString,
output output
) )
} }
...@@ -320,6 +341,14 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc ...@@ -320,6 +341,14 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) {
R.id.input_history -> {
historyPanel.panelState = SlidingUpPanelLayout.PanelState.EXPANDED
true
}
else -> inputEditor.onMenuItemClick(item)
}
override fun onDestroy() { override fun onDestroy() {
handler.onDestroy() handler.onDestroy()
super.onDestroy() super.onDestroy()
......
package de.kuschku.quasseldroid_ng.ui.chat
import android.graphics.Typeface
import android.support.annotation.MenuRes
import android.text.Editable
import android.text.Spanned
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.TypefaceSpan
import android.text.style.UnderlineSpan
import android.view.MenuItem
import android.widget.EditText
import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.util.helper.selection
import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatSerializer
import de.kuschku.quasseldroid_ng.util.irc.format.spans.*
class InputEditor(private val editText: EditText) {
private val serializer = IrcFormatSerializer(editText.context)
val formattedString: String
get() = serializer.toEscapeCodes(editText.text)
@MenuRes
val menu: Int = R.menu.editor
fun toggleBold(range: IntRange, createNew: Boolean = true) {
if (range.isEmpty())
return
val exists = editText.text.removeSpans<StyleSpan, IrcBoldSpan>(range) { span ->
when {
span is IrcBoldSpan -> span
span.style == Typeface.BOLD -> IrcBoldSpan()
else -> null
}
}
if (!exists && createNew) {
editText.text.setSpan(
IrcBoldSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
fun toggleItalic(range: IntRange, createNew: Boolean = true) {
if (range.isEmpty())
return
val exists = editText.text.removeSpans<StyleSpan, IrcItalicSpan>(range) { span ->
when {
span is IrcItalicSpan -> span
span.style == Typeface.ITALIC -> IrcItalicSpan()
else -> null
}
}
if (!exists && createNew) {
editText.text.setSpan(
IrcItalicSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
fun toggleUnderline(range: IntRange, createNew: Boolean = true) {
if (range.isEmpty())
return
val exists = editText.text.removeSpans<UnderlineSpan, IrcUnderlineSpan>(range) { span ->
when {
span is IrcUnderlineSpan -> span
else -> IrcUnderlineSpan()
}
}
if (!exists && createNew) {
editText.text.setSpan(
IrcUnderlineSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
fun toggleStrikethrough(range: IntRange, createNew: Boolean = true) {
if (range.isEmpty())
return
val exists = editText.text.removeSpans<StrikethroughSpan, IrcStrikethroughSpan>(range) { span ->
when {
span is IrcStrikethroughSpan -> span
else -> IrcStrikethroughSpan()
}
}
if (!exists && createNew) {
editText.text.setSpan(
IrcStrikethroughSpan(), range.start, range.endInclusive + 1,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
fun toggleMonospace(range: IntRange, createNew: Boolean = true) {
if (range.isEmpty())
return
val exists = editText.text.removeSpans<TypefaceSpan, IrcMonospaceSpan>(range) { span ->
when {
span is IrcMonospaceSpan -> span
span.family == "monospace" -> IrcMonospaceSpan()
else -> null
}
}
if (!exists && createNew) {
editText.text.setSpan(
IrcMonospaceSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
fun clearFormatting(range: IntRange) {
if (range.isEmpty())
return
toggleBold(range, false)
toggleItalic(range, false)
toggleUnderline(range, false)
toggleStrikethrough(range, false)
toggleMonospace(range, false)
}
fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) {
R.id.format_bold -> {
toggleBold(editText.selection)
true
}
R.id.format_italic -> {
toggleItalic(editText.selection)
true
}
R.id.format_underline -> {
toggleUnderline(editText.selection)
true
}
R.id.format_strikethrough -> {
toggleStrikethrough(editText.selection)
true
}
R.id.format_monospace -> {
toggleMonospace(editText.selection)
true
}
R.id.format_clear -> {
clearFormatting(editText.selection)
true
}
else -> false
}
private inline fun <reified U, T> Editable.removeSpans(
range: IntRange, removeInvalid: Boolean = false, f: (U) -> T?): Boolean where T : Copyable<T> {
if (range.isEmpty())
return false
var removedAny = false
for (raw in getSpans<U>(range.start, range.endInclusive + 1, U::class.java)) {
val spanFlags = getSpanFlags(raw)
if (spanFlags and Spanned.SPAN_COMPOSING != 0) continue
val spanEnd = getSpanEnd(raw)
val spanStart = getSpanStart(raw)
val span = f(raw)
if (span == null) {
if (removeInvalid)
removeSpan(raw)
} else {
removeSpan(raw)
val endIsIn = spanEnd in range
val endIsAfter = spanEnd > range.endInclusive + 1
val startIsIn = spanStart in range
val startIsBefore = spanStart < range.start
if (endIsIn && startIsIn) {
removedAny = true
} else if (endIsIn) {
setSpan(span, spanStart, range.start, spanFlags)
removedAny = true
} else if (startIsIn) {
setSpan(span, range.endInclusive + 1, spanEnd, spanFlags)
removedAny = true
} else if (startIsBefore && endIsAfter) {
setSpan(span, spanStart, range.start, spanFlags)
setSpan(span.copy(), range.endInclusive + 1, spanEnd, spanFlags)
removedAny = true
} else if (startIsBefore) {
setSpan(span, spanStart, range.start, spanFlags)
removedAny = true
}
}
}
return removedAny
}
}
\ No newline at end of file
package de.kuschku.quasseldroid_ng.util.helper
import android.text.Selection
import android.widget.EditText
val CharSequence.selection: IntRange
get() = Selection.getSelectionStart(this) until Selection.getSelectionEnd(this)
val EditText.selection: IntRange
get() = text.selection
\ No newline at end of file
...@@ -76,13 +76,13 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -76,13 +76,13 @@ class IrcFormatDeserializer(private val context: Context) {
if (str == null) return "" if (str == null) return ""
val plainText = SpannableStringBuilder() val plainText = SpannableStringBuilder()
var bold: FormatDescription? = null var bold: FormatDescription<BoldIrcFormat>? = null
var italic: FormatDescription? = null var italic: FormatDescription<ItalicIrcFormat>? = null
var underline: FormatDescription? = null var underline: FormatDescription<UnderlineIrcFormat>? = null
var strikethrough: FormatDescription? = null var strikethrough: FormatDescription<StrikethroughIrcFormat>? = null
var monospace: FormatDescription? = null var monospace: FormatDescription<MonospaceIrcFormat>? = null
var color: FormatDescription? = null var color: FormatDescription<ColorIrcFormat>? = null
var hexColor: FormatDescription? = null var hexColor: FormatDescription<HexIrcFormat>? = null
// Iterating over every character // Iterating over every character
var normalCount = 0 var normalCount = 0
...@@ -100,8 +100,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -100,8 +100,7 @@ class IrcFormatDeserializer(private val context: Context) {
bold = null bold = null
// Otherwise create a new one // Otherwise create a new one
} else { } else {
val format = fromId(character) bold = FormatDescription(plainText.length, BoldIrcFormat())
bold = FormatDescription(plainText.length, format!!)
} }
} }
CODE_ITALIC -> { CODE_ITALIC -> {
...@@ -114,8 +113,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -114,8 +113,7 @@ class IrcFormatDeserializer(private val context: Context) {
italic = null italic = null
// Otherwise create a new one // Otherwise create a new one
} else { } else {
val format = fromId(character) italic = FormatDescription(plainText.length, ItalicIrcFormat())
italic = FormatDescription(plainText.length, format!!)
} }
} }
CODE_UNDERLINE -> { CODE_UNDERLINE -> {
...@@ -128,8 +126,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -128,8 +126,7 @@ class IrcFormatDeserializer(private val context: Context) {
underline = null underline = null
// Otherwise create a new one // Otherwise create a new one
} else { } else {
val format = fromId(character) underline = FormatDescription(plainText.length, UnderlineIrcFormat())
underline = FormatDescription(plainText.length, format!!)
} }
} }
CODE_STRIKETHROUGH -> { CODE_STRIKETHROUGH -> {
...@@ -142,8 +139,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -142,8 +139,7 @@ class IrcFormatDeserializer(private val context: Context) {
strikethrough = null strikethrough = null
// Otherwise create a new one // Otherwise create a new one
} else { } else {
val format = fromId(character) strikethrough = FormatDescription(plainText.length, StrikethroughIrcFormat())
strikethrough = FormatDescription(plainText.length, format!!)
} }
} }
CODE_MONOSPACE -> { CODE_MONOSPACE -> {
...@@ -156,8 +152,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -156,8 +152,7 @@ class IrcFormatDeserializer(private val context: Context) {
monospace = null monospace = null
// Otherwise create a new one // Otherwise create a new one
} else { } else {
val format = fromId(character) monospace = FormatDescription(plainText.length, MonospaceIrcFormat())
monospace = FormatDescription(plainText.length, format!!)
} }
} }
CODE_COLOR -> { CODE_COLOR -> {
...@@ -183,7 +178,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -183,7 +178,7 @@ class IrcFormatDeserializer(private val context: Context) {
if (colorize) color.apply(plainText, plainText.length) if (colorize) color.apply(plainText, plainText.length)
// Reuse old background, if possible // Reuse old background, if possible
if (background.toInt() == -1) if (background.toInt() == -1)
background = (color.format as ColorIrcFormat).background background = color.format.background
} }
// Add new format // Add new format
color = FormatDescription(plainText.length, ColorIrcFormat(foreground, background)) color = FormatDescription(plainText.length, ColorIrcFormat(foreground, background))
...@@ -207,7 +202,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -207,7 +202,7 @@ class IrcFormatDeserializer(private val context: Context) {
if (colorEnd > colorStart) { if (colorEnd > colorStart) {
val foreground = readHexNumber(str, colorStart, colorEnd) val foreground = readHexNumber(str, colorStart, colorEnd)
// Add new format // Add new format
hexColor = FormatDescription(plainText.length, ColorHexFormat(foreground)) hexColor = FormatDescription(plainText.length, HexIrcFormat(foreground))
// i points in front of the next character // i points in front of the next character
i = colorEnd - 1 i = colorEnd - 1
...@@ -226,7 +221,7 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -226,7 +221,7 @@ class IrcFormatDeserializer(private val context: Context) {
if (color != null) { if (color != null) {
if (colorize) color.apply(plainText, plainText.length) if (colorize) color.apply(plainText, plainText.length)
color = FormatDescription( color = FormatDescription(
plainText.length, (color.format as ColorIrcFormat).copySwapped() plainText.length, color.format.copySwapped()
) )
} }
} }
...@@ -274,20 +269,27 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -274,20 +269,27 @@ class IrcFormatDeserializer(private val context: Context) {
if (underline != null) { if (underline != null) {
if (colorize) underline.apply(plainText, plainText.length) if (colorize) underline.apply(plainText, plainText.length)
} }
if (strikethrough != null) {
if (colorize) strikethrough.apply(plainText, plainText.length)
}
if (monospace != null) {
if (colorize) monospace.apply(plainText, plainText.length)
}
if (color != null) { if (color != null) {
if (colorize) color.apply(plainText, plainText.length) if (colorize) color.apply(plainText, plainText.length)
} }
if (hexColor != null) {
if (colorize) hexColor.apply(plainText, plainText.length)
}
plainText.append(str.substring(str.length - normalCount, str.length)) plainText.append(str.substring(str.length - normalCount, str.length))
return plainText return plainText
} }
private interface IrcFormat { private interface IrcFormat {
fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int)
fun id(): Byte
} }
private class FormatDescription(val start: Int, val format: IrcFormat) { private class FormatDescription<U : IrcFormat>(val start: Int, val format: U) {
fun apply(editable: SpannableStringBuilder, end: Int) { fun apply(editable: SpannableStringBuilder, end: Int) {
format.applyTo(editable, start, end) format.applyTo(editable, start, end)
...@@ -298,53 +300,33 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -298,53 +300,33 @@ class IrcFormatDeserializer(private val context: Context) {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan(IrcItalicSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) editable.setSpan(IrcItalicSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
override fun id(): Byte {
return CODE_ITALIC.toByte()
}
} }
private class UnderlineIrcFormat : IrcFormat { private class UnderlineIrcFormat : IrcFormat {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan(IrcUnderlineSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) editable.setSpan(IrcUnderlineSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
override fun id(): Byte {
return CODE_UNDERLINE.toByte()
}
} }
private class StrikethroughIrcFormat : IrcFormat { private class StrikethroughIrcFormat : IrcFormat {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan(IrcStrikethroughSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) editable.setSpan(IrcStrikethroughSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
override fun id(): Byte {
return CODE_STRIKETHROUGH.toByte()
}
} }
private class MonospaceIrcFormat : IrcFormat { private class MonospaceIrcFormat : IrcFormat {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan(IrcMonospaceSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) editable.setSpan(IrcMonospaceSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
override fun id(): Byte {
return CODE_MONOSPACE.toByte()
}
} }
private class BoldIrcFormat : IrcFormat { private class BoldIrcFormat : IrcFormat {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan(IrcBoldSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) editable.setSpan(IrcBoldSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
override fun id(): Byte {
return CODE_BOLD.toByte()
}
} }
private inner class ColorHexFormat(val color: Int) : IrcFormat { private inner class HexIrcFormat(val color: Int) : IrcFormat {
override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) { override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
editable.setSpan( editable.setSpan(
...@@ -352,10 +334,6 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -352,10 +334,6 @@ class IrcFormatDeserializer(private val context: Context) {
Spanned.SPAN_INCLUSIVE_EXCLUSIVE Spanned.SPAN_INCLUSIVE_EXCLUSIVE
) )
} }
override fun id(): Byte {
return CODE_HEXCOLOR.toByte()
}
} }
private inner class ColorIrcFormat(val foreground: Byte, val background: Byte) : IrcFormat { private inner class ColorIrcFormat(val foreground: Byte, val background: Byte) : IrcFormat {
...@@ -378,10 +356,6 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -378,10 +356,6 @@ class IrcFormatDeserializer(private val context: Context) {
fun copySwapped(): ColorIrcFormat { fun copySwapped(): ColorIrcFormat {
return ColorIrcFormat(background, foreground) return ColorIrcFormat(background, foreground)
} }
override fun id(): Byte {
return CODE_COLOR.toByte()
}
} }
companion object { companion object {
...@@ -465,14 +439,5 @@ class IrcFormatDeserializer(private val context: Context) { ...@@ -465,14 +439,5 @@ class IrcFormatDeserializer(private val context: Context) {
} }
return start + i return start + i
} }
private fun fromId(id: Char) = when (id) {
CODE_BOLD -> BoldIrcFormat()
CODE_ITALIC -> ItalicIrcFormat()
CODE_UNDERLINE -> UnderlineIrcFormat()
CODE_STRIKETHROUGH -> StrikethroughIrcFormat()
CODE_MONOSPACE -> MonospaceIrcFormat()
else -> null
}
} }
} }
...@@ -105,6 +105,7 @@ class IrcFormatSerializer internal constructor(private val context: Context) { ...@@ -105,6 +105,7 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
} }
fun writeReset() { fun writeReset() {
println("reset")
out.append(CODE_RESET) out.append(CODE_RESET)
} }
...@@ -139,8 +140,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) { ...@@ -139,8 +140,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
when (aStyle) { when (aStyle) {
is StyleSpan -> { is StyleSpan -> {
afterBold = (aStyle.style and Typeface.BOLD != 0) afterBold = afterBold || aStyle.style and Typeface.BOLD != 0
afterItalic = (aStyle.style and Typeface.ITALIC != 0) afterItalic = afterItalic || aStyle.style and Typeface.ITALIC != 0
} }
is UnderlineSpan -> afterUnderline = true is UnderlineSpan -> afterUnderline = true
is StrikethroughSpan -> afterStrikethrough = true is StrikethroughSpan -> afterStrikethrough = true
...@@ -199,7 +200,7 @@ class IrcFormatSerializer internal constructor(private val context: Context) { ...@@ -199,7 +200,7 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
i = next i = next
} }
if (bold || italic || underline || background != null || foreground != null) if (bold || italic || underline || strikethrough || monospace || background != null || foreground != null)
writeReset() writeReset()
} }
......
<!-- drawable/format_clear.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#000"
android:pathData="M6,5V5.18L8.82,8H11.22L10.5,9.68L12.6,11.78L14.21,8H20V5H6M3.27,5L2,6.27L8.97,13.24L6.5,19H9.5L11.07,15.34L16.73,21L18,19.73L3.55,5.27L3.27,5Z" />
</vector>
\ No newline at end of file
<!-- drawable/code_tags.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#000"
android:pathData="M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z" />
</vector>
\ No newline at end of file
<!-- drawable/format_strikethrough_variant.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#000"
android:pathData="M23,12V14H18.61C19.61,16.14 19.56,22 12.38,22C4.05,22.05 4.37,15.5 4.37,15.5L8.34,15.55C8.37,18.92 11.5,18.92 12.12,18.88C12.76,18.83 15.15,18.84 15.34,16.5C15.42,15.41 14.32,14.58 13.12,14H1V12H23M19.41,7.89L15.43,7.86C15.43,7.86 15.6,5.09 12.15,5.08C8.7,5.06 9,7.28 9,7.56C9.04,7.84 9.34,9.22 12,9.88H5.71C5.71,9.88 2.22,3.15 10.74,2C19.45,0.8 19.43,7.91 19.41,7.89Z" />
</vector>
\ No newline at end of file
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
......
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
android:hint="@string/label_placeholder" android:hint="@string/label_placeholder"
android:imeOptions="flagNoExtractUi" android:imeOptions="flagNoExtractUi"
android:inputType="textCapSentences|textAutoCorrect|textShortMessage" android:inputType="textCapSentences|textAutoCorrect|textShortMessage"
android:minHeight="?actionBarSize" android:minHeight="?attr/actionBarSize"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingLeft="20dp" android:paddingLeft="20dp"
android:paddingRight="20dp" android:paddingRight="20dp"
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorBackgroundCard" android:background="?attr/colorBackgroundCard"
android:theme="?attr/formatBarTheme"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
...@@ -62,7 +63,7 @@ ...@@ -62,7 +63,7 @@
<android.support.v7.widget.ActionMenuView <android.support.v7.widget.ActionMenuView
android:id="@+id/formatting_menu" android:id="@+id/formatting_menu"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize" />
</android.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.sothree.slidinguppanel.SlidingUpPanelLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.sothree.slidinguppanel.SlidingUpPanelLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/history_panel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="bottom" android:gravity="bottom"
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</android.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
<de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar <de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
android:id="@+id/progressBar" android:id="@+id/progress_bar"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/format_bold"
android:icon="@drawable/ic_format_bold"
android:title="@string/label_bold"
app:showAsAction="always" />
<item
android:id="@+id/format_italic"
android:icon="@drawable/ic_format_italic"
android:title="@string/label_italic"
app:showAsAction="always" />
<item
android:id="@+id/format_underline"
android:icon="@drawable/ic_format_underline"
android:title="@string/label_underline"
app:showAsAction="always" />
<item
android:id="@+id/format_strikethrough"
android:icon="@drawable/ic_format_strikethrough"
android:title="@string/label_strikethrough"
app:showAsAction="always" />
<item
android:id="@+id/format_monospace"
android:icon="@drawable/ic_format_monospace"
android:title="@string/label_monospace"
app:showAsAction="always" />
<!--
<item
android:id="@+id/format_foreground"
android:icon="@drawable/ic_format_foreground"
android:title="@string/label_foreground"
app:showAsAction="always" />
<item
android:id="@+id/format_background"
android:icon="@drawable/ic_format_background"
android:title="@string/label_background"
app:showAsAction="always" />
-->
<item
android:id="@+id/format_clear"
android:icon="@drawable/ic_format_clear"
android:title="@string/label_clear_formatting"
app:showAsAction="always" />
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/input_history"
android:icon="@drawable/ic_history"
android:title="@string/label_input_history"
app:showAsAction="always" />
</menu>
\ No newline at end of file
...@@ -40,4 +40,12 @@ ...@@ -40,4 +40,12 @@
<string name="notification_channel_highlight_title">Highlight</string> <string name="notification_channel_highlight_title">Highlight</string>
<string name="buffer_delete_confirmation">Do you want to delete this buffer permanently?</string> <string name="buffer_delete_confirmation">Do you want to delete this buffer permanently?</string>
<string name="label_bold">Bold</string>
<string name="label_italic">Italic</string>
<string name="label_strikethrough">Strikethrough</string>
<string name="label_underline">Underline</string>
<string name="label_monospace">Monospace</string>
<string name="label_foreground">Foreground</string>
<string name="label_background">Background</string>
<string name="label_clear_formatting">Clear Formatting</string>
</resources> </resources>
...@@ -10,7 +10,7 @@ import java.nio.charset.CharsetDecoder ...@@ -10,7 +10,7 @@ import java.nio.charset.CharsetDecoder
import java.nio.charset.CharsetEncoder import java.nio.charset.CharsetEncoder
abstract class StringSerializer( abstract class StringSerializer(
private val encoder: CharsetEncoder, private var encoder: CharsetEncoder,
private val decoder: CharsetDecoder, private val decoder: CharsetDecoder,
private val trailingNullBytes: Int private val trailingNullBytes: Int
) : Serializer<String?> { ) : Serializer<String?> {
...@@ -51,6 +51,7 @@ abstract class StringSerializer( ...@@ -51,6 +51,7 @@ abstract class StringSerializer(
val charBuffer = charBuffer(data.length) val charBuffer = charBuffer(data.length)
charBuffer.put(data) charBuffer.put(data)
charBuffer.flip() charBuffer.flip()
encoder = encoder.charset().newEncoder()
val byteBuffer = encoder.encode(charBuffer) val byteBuffer = encoder.encode(charBuffer)
IntSerializer.serialize(buffer, byteBuffer.remaining() + trailingNullBytes, features) IntSerializer.serialize(buffer, byteBuffer.remaining() + trailingNullBytes, features)
buffer.put(byteBuffer) buffer.put(byteBuffer)
...@@ -70,6 +71,7 @@ abstract class StringSerializer( ...@@ -70,6 +71,7 @@ abstract class StringSerializer(
val charBuffer = charBuffer(data.length) val charBuffer = charBuffer(data.length)
charBuffer.put(data) charBuffer.put(data)
charBuffer.flip() charBuffer.flip()
encoder = encoder.charset().newEncoder()
return encoder.encode(charBuffer) return encoder.encode(charBuffer)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment