From d46365240376aa6c01f6d266a931b15958031224 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Fri, 30 Mar 2018 01:46:22 +0200 Subject: [PATCH] Implement new and improved formatting input --- .../quasseldroid/ui/chat/ChatActivity.kt | 7 +- .../quasseldroid/ui/chat/input/Editor.kt | 116 ++++++++++++-- .../ui/chat/input/FormatHandler.kt | 148 +++++++----------- .../util/helper/IntRangeHelper.kt | 13 ++ .../util/helper/SelectionHelper.kt | 7 +- .../util/ui/EditTextSelectionChange.kt | 27 ++++ .../main/res/drawable-de/ic_format_bold.xml | 9 -- .../main/res/drawable-de/ic_format_italic.xml | 9 -- .../res/drawable/ic_format_foreground.xml | 2 +- app/src/main/res/layout/layout_editor.xml | 16 +- app/src/main/res/layout/widget_formatting.xml | 119 ++++++++++++++ app/src/main/res/menu/formatting.xml | 44 ------ app/src/main/res/values/styles_widgets.xml | 7 + .../libquassel/util/helpers/MathHelper.kt | 6 +- 14 files changed, 347 insertions(+), 183 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/EditTextSelectionChange.kt delete mode 100644 app/src/main/res/drawable-de/ic_format_bold.xml delete mode 100644 app/src/main/res/drawable-de/ic_format_italic.xml create mode 100644 app/src/main/res/layout/widget_formatting.xml delete mode 100644 app/src/main/res/menu/formatting.xml diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index 19cf45c49..42b7c8edd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -112,7 +112,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc findViewById(R.id.autocomplete_list), findViewById(R.id.autocomplete_list_expanded) ), - findViewById(R.id.formatting_menu), findViewById(R.id.formatting_toolbar), appearanceSettings, autoCompleteSettings, @@ -279,6 +278,12 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc recreate() } super.onStart() + editor.onStart() + } + + override fun onStop() { + editor.onStop() + super.onStop() } data class AutoCompletionState( diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt index 94dba8dc1..b02c6fb18 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt @@ -7,19 +7,16 @@ import android.support.v7.widget.* import android.text.Editable import android.text.InputType import android.text.TextWatcher -import android.view.GestureDetector -import android.view.KeyEvent -import android.view.MenuItem -import android.view.MotionEvent +import android.view.* import android.view.inputmethod.EditorInfo +import butterknife.BindView +import butterknife.ButterKnife import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AutoCompleteSettings import de.kuschku.quasseldroid.ui.chat.ChatActivity -import de.kuschku.quasseldroid.util.helper.lastWordIndices -import de.kuschku.quasseldroid.util.helper.lineSequence -import de.kuschku.quasseldroid.util.helper.retint -import de.kuschku.quasseldroid.util.helper.visibleIf +import de.kuschku.quasseldroid.util.helper.* +import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject @@ -31,11 +28,10 @@ class Editor( private val autoCompleteData: LiveData<Pair<String, List<AutoCompleteItem>>>, lastWordContainer: BehaviorSubject<Observable<Pair<String, IntRange>>>, // Views - val chatline: AppCompatEditText, + val chatline: EditTextSelectionChange, send: AppCompatImageButton, tabComplete: AppCompatImageButton, autoCompleteLists: List<RecyclerView>, - formattingMenu: ActionMenuView, formattingToolbar: Toolbar, // Settings private val appearanceSettings: AppearanceSettings, @@ -49,7 +45,7 @@ class Editor( panelStateCallback(true) true } - else -> formatHandler.onMenuItemClick(item) + else -> false } private val lastWord = BehaviorSubject.createDefault(Pair("", IntRange.EMPTY)) @@ -78,6 +74,8 @@ class Editor( } lastWord.onNext(next ?: Pair("", IntRange.EMPTY)) + + updateButtons(chatline.selection) } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit @@ -88,6 +86,30 @@ class Editor( private var autocompletionState: ChatActivity.AutoCompletionState? = null + @BindView(R.id.action_format_bold) + lateinit var boldButton: View + + @BindView(R.id.action_format_italic) + lateinit var italicButton: View + + @BindView(R.id.action_format_underline) + lateinit var underlineButton: View + + @BindView(R.id.action_format_strikethrough) + lateinit var strikethroughButton: View + + @BindView(R.id.action_format_monospace) + lateinit var monospaceButton: View + + @BindView(R.id.action_format_foreground) + lateinit var foregroundButton: View + + @BindView(R.id.action_format_background) + lateinit var backgroundButton: View + + @BindView(R.id.action_format_clear) + lateinit var clearButton: View + init { send.setOnClickListener { send() @@ -121,8 +143,6 @@ class Editor( ) }.fold(0, Int::or) - chatline.addTextChangedListener(textWatcher) - val autocompleteAdapter = AutoCompleteAdapter( // This is still broken when mixing tab complete and UI auto complete formatHandler::autoComplete @@ -168,13 +188,75 @@ class Editor( lastWordContainer.onNext(lastWord) - activity.menuInflater.inflate(formatHandler.menu, formattingMenu.menu) - formattingMenu.menu.retint(activity) - formattingMenu.setOnMenuItemClickListener(this) - activity.menuInflater.inflate(R.menu.editor, formattingToolbar.menu) formattingToolbar.menu.retint(activity) formattingToolbar.setOnMenuItemClickListener(this) + + ButterKnife.bind(this, formattingToolbar) + + boldButton.setOnClickListener { + formatHandler.toggleBold(chatline.selection) + updateButtons(chatline.selection) + } + italicButton.setOnClickListener { + formatHandler.toggleItalic(chatline.selection) + updateButtons(chatline.selection) + } + underlineButton.setOnClickListener { + formatHandler.toggleUnderline(chatline.selection) + updateButtons(chatline.selection) + } + strikethroughButton.setOnClickListener { + formatHandler.toggleStrikethrough(chatline.selection) + updateButtons(chatline.selection) + } + monospaceButton.setOnClickListener { + formatHandler.toggleMonospace(chatline.selection) + updateButtons(chatline.selection) + } + clearButton.setOnClickListener { + formatHandler.clearFormatting(chatline.selection) + updateButtons(chatline.selection) + } + + chatline.setOnKeyListener { _, keyCode, event -> + if (event.isCtrlPressed && !event.isAltPressed) when (keyCode) { + KeyEvent.KEYCODE_B -> { + formatHandler.toggleBold(chatline.selection) + updateButtons(chatline.selection) + true + } + KeyEvent.KEYCODE_I -> { + formatHandler.toggleItalic(chatline.selection) + updateButtons(chatline.selection) + true + } + KeyEvent.KEYCODE_U -> { + formatHandler.toggleUnderline(chatline.selection) + updateButtons(chatline.selection) + true + } + else -> false + } else false + } + } + + fun updateButtons(selection: IntRange) { + boldButton.isSelected = formatHandler.isBold(selection) + italicButton.isSelected = formatHandler.isItalic(selection) + underlineButton.isSelected = formatHandler.isUnderline(selection) + strikethroughButton.isSelected = formatHandler.isStrikethrough(selection) + monospaceButton.isSelected = formatHandler.isMonospace(selection) + } + + fun onStart() { + chatline.addTextChangedListener(textWatcher) + chatline.setSelectionChangeListener(::updateButtons) + } + + fun onStop() { + chatline.removeTextChangedListener(textWatcher) + chatline.removeSelectionChangeListener() } private fun send() { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt index 770e01905..f7c0d8547 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt @@ -1,7 +1,6 @@ package de.kuschku.quasseldroid.ui.chat.input import android.graphics.Typeface -import android.support.annotation.MenuRes import android.text.Editable import android.text.SpannableString import android.text.Spanned @@ -9,13 +8,12 @@ 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.R import de.kuschku.quasseldroid.ui.chat.ChatActivity import de.kuschku.quasseldroid.util.helper.lastWordIndices import de.kuschku.quasseldroid.util.helper.lineSequence import de.kuschku.quasseldroid.util.helper.selection +import de.kuschku.quasseldroid.util.helper.without import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer import de.kuschku.quasseldroid.util.irc.format.spans.* @@ -42,14 +40,13 @@ class FormatHandler( text } - @MenuRes - val menu: Int = R.menu.formatting + fun isBold(range: IntRange) = editText.text.hasSpans<StyleSpan>(range) { + it.style == Typeface.BOLD || it.style == Typeface.BOLD_ITALIC + } fun toggleBold(range: IntRange, createNew: Boolean = true) { - if (range.isEmpty()) - return - - val exists = editText.text.removeSpans<StyleSpan, IrcBoldSpan>(range) { span -> + val bold = isBold(range) + editText.text.removeSpans<StyleSpan, IrcBoldSpan>(range) { span -> when { span is IrcBoldSpan -> span span.style == Typeface.BOLD -> IrcBoldSpan() @@ -57,18 +54,21 @@ class FormatHandler( } } - if (!exists && createNew) { + if (!bold && createNew) { editText.text.setSpan( - IrcBoldSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE + IrcBoldSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) } } + fun isItalic(range: IntRange) = editText.text.hasSpans<StyleSpan>(range) { + it.style == Typeface.ITALIC || it.style == Typeface.BOLD_ITALIC + } + fun toggleItalic(range: IntRange, createNew: Boolean = true) { - if (range.isEmpty()) - return + val italic = isItalic(range) - val exists = editText.text.removeSpans<StyleSpan, IrcItalicSpan>(range) { span -> + editText.text.removeSpans<StyleSpan, IrcItalicSpan>(range) { span -> when { span is IrcItalicSpan -> span span.style == Typeface.ITALIC -> IrcItalicSpan() @@ -76,55 +76,60 @@ class FormatHandler( } } - if (!exists && createNew) { + if (!italic && createNew) { editText.text.setSpan( - IrcItalicSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE + IrcItalicSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) } } + fun isUnderline(range: IntRange) = editText.text.hasSpans<UnderlineSpan>(range) + fun toggleUnderline(range: IntRange, createNew: Boolean = true) { - if (range.isEmpty()) - return + val underline = isUnderline(range) - val exists = editText.text.removeSpans<UnderlineSpan, IrcUnderlineSpan>(range) { span -> + editText.text.removeSpans<UnderlineSpan, IrcUnderlineSpan>(range) { span -> when (span) { is IrcUnderlineSpan -> span else -> IrcUnderlineSpan() } } - if (!exists && createNew) { + if (!underline && createNew) { editText.text.setSpan( - IrcUnderlineSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE + IrcUnderlineSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) } } + fun isStrikethrough(range: IntRange) = editText.text.hasSpans<StrikethroughSpan>(range) + fun toggleStrikethrough(range: IntRange, createNew: Boolean = true) { - if (range.isEmpty()) - return + val strikethrough = isStrikethrough(range) - val exists = editText.text.removeSpans<StrikethroughSpan, IrcStrikethroughSpan>(range) { span -> + editText.text.removeSpans<StrikethroughSpan, IrcStrikethroughSpan>(range) { span -> when (span) { is IrcStrikethroughSpan -> span else -> IrcStrikethroughSpan() } } - if (!exists && createNew) { + if (!strikethrough && createNew) { editText.text.setSpan( IrcStrikethroughSpan(), range.start, range.endInclusive + 1, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE + Spanned.SPAN_INCLUSIVE_INCLUSIVE ) } } + fun isMonospace(range: IntRange) = editText.text.hasSpans<TypefaceSpan>(range) { + it.family == "monospace" + } + fun toggleMonospace(range: IntRange, createNew: Boolean = true) { - if (range.isEmpty()) - return + val monospace = isMonospace(range) - val exists = editText.text.removeSpans<TypefaceSpan, IrcMonospaceSpan>(range) { span -> + editText.text.removeSpans<TypefaceSpan, IrcMonospaceSpan>(range) { span -> when { span is IrcMonospaceSpan -> span span.family == "monospace" -> IrcMonospaceSpan() @@ -132,17 +137,14 @@ class FormatHandler( } } - if (!exists && createNew) { + if (!monospace && createNew) { editText.text.setSpan( - IrcMonospaceSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE + IrcMonospaceSpan(), range.start, range.endInclusive + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) } } fun clearFormatting(range: IntRange) { - if (range.isEmpty()) - return - toggleBold(range, false) toggleItalic(range, false) toggleUnderline(range, false) @@ -150,40 +152,24 @@ class FormatHandler( toggleMonospace(range, false) } - fun onMenuItemClick(item: MenuItem?) = when (item?.itemId) { - R.id.format_bold -> { - toggleBold(editText.selection) - true + private inline fun <reified U> Spanned.hasSpans(range: IntRange) = + getSpans(range.start, range.endInclusive + 1, U::class.java).any { + getSpanFlags(it) and Spanned.SPAN_COMPOSING == 0 && + (getSpanEnd(it) != range.start || + getSpanFlags(it) and 0x02 != 0) } - 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 + + private inline fun <reified U> Spanned.hasSpans(range: IntRange, f: (U) -> Boolean) = + getSpans(range.start, range.last + 1, U::class.java).any { + f(it) && + getSpanFlags(it) and Spanned.SPAN_COMPOSING == 0 && + (getSpanEnd(it) != range.start || + getSpanFlags(it) and 0x02 != 0) } - 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 + range: IntRange, removeInvalid: Boolean = false, f: (U) -> T? + ) where T : Copyable<T> { for (raw in getSpans<U>(range.start, range.endInclusive + 1, U::class.java)) { val spanFlags = getSpanFlags(raw) @@ -199,36 +185,20 @@ class FormatHandler( } 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 + for (spanRange in spanStart until spanEnd without range) { + setSpan( + span.copy(), + spanRange.start, + spanRange.endInclusive + 1, + (spanFlags and 0x03.inv()) or 0x01 + ) } } } - - return removedAny } fun autoComplete(text: CharSequence) { - val range = editText.text.lastWordIndices(editText.selectionStart, true) + val range = editText.text.lastWordIndices(editText.selection.start, true) val replacement = if (range?.start == 0) { "$text: " } else { @@ -268,4 +238,4 @@ class FormatHandler( editText.setText(text) editText.setSelection(editText.text.length) } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt new file mode 100644 index 000000000..fc62e431e --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt @@ -0,0 +1,13 @@ +package de.kuschku.quasseldroid.util.helper + +import clamp + +infix fun IntRange.without(other: IntRange): Iterable<IntRange> { + val otherStart = minOf(other.start, other.last + 1).clamp(this.start, this.last + 1) + val otherLast = maxOf(other.start, other.last + 1).clamp(this.start, this.last + 1) + + val startingFragment: IntRange = this.start until otherStart + val endingFragment: IntRange = otherLast + 1 until this.last + 1 + + return listOf(startingFragment, endingFragment).filter { it.last >= it.start } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/SelectionHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/SelectionHelper.kt index 67924afdc..6d107d6f5 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/helper/SelectionHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/SelectionHelper.kt @@ -4,7 +4,12 @@ import android.text.Selection import android.widget.EditText val CharSequence.selection: IntRange - get() = Selection.getSelectionStart(this) until Selection.getSelectionEnd(this) + get() { + val start = Selection.getSelectionStart(this) + val end = Selection.getSelectionEnd(this) + + return minOf(start, end) until maxOf(start, end) + } val EditText.selection: IntRange get() = text.selection \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/EditTextSelectionChange.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/EditTextSelectionChange.kt new file mode 100644 index 000000000..ad316e498 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/EditTextSelectionChange.kt @@ -0,0 +1,27 @@ +package de.kuschku.quasseldroid.util.ui + +import android.content.Context +import android.support.v7.widget.AppCompatEditText +import android.util.AttributeSet + +class EditTextSelectionChange : AppCompatEditText { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : + super(context, attrs, defStyleAttr) + + private var selectionChangeListener: ((IntRange) -> Unit)? = null + + fun setSelectionChangeListener(f: (IntRange) -> Unit) { + selectionChangeListener = f + } + + fun removeSelectionChangeListener() { + selectionChangeListener = null + } + + override fun onSelectionChanged(selStart: Int, selEnd: Int) { + super.onSelectionChanged(selStart, selEnd) + selectionChangeListener?.invoke(selStart until selEnd) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-de/ic_format_bold.xml b/app/src/main/res/drawable-de/ic_format_bold.xml deleted file mode 100644 index f14946cf3..000000000 --- a/app/src/main/res/drawable-de/ic_format_bold.xml +++ /dev/null @@ -1,9 +0,0 @@ -<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="M 16.509807,12.423077 H 11.0675 V 18 H 7.6924995 V 4.0000001 H 17.057884 V 6.605769 H 11.0675 v 3.2211544 h 5.442307 z" /> -</vector> diff --git a/app/src/main/res/drawable-de/ic_format_italic.xml b/app/src/main/res/drawable-de/ic_format_italic.xml deleted file mode 100644 index c6232c77a..000000000 --- a/app/src/main/res/drawable-de/ic_format_italic.xml +++ /dev/null @@ -1,9 +0,0 @@ -<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="m10.3 12-2.41 1.7-1.87 4.3h-2.37l6.16-14h2.39l-2.86 6.4 7.96-6.4h3.1l-7.9 6.3 1.8 7.7h-2.64z" /> -</vector> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_format_foreground.xml b/app/src/main/res/drawable/ic_format_foreground.xml index d0f11e7e5..f529befb6 100644 --- a/app/src/main/res/drawable/ic_format_foreground.xml +++ b/app/src/main/res/drawable/ic_format_foreground.xml @@ -5,5 +5,5 @@ android:viewportWidth="24"> <path android:fillColor="#000" - android:pathData="M18,4V3A1,1 0 0,0 17,2H5A1,1 0 0,0 4,3V7A1,1 0 0,0 5,8H17A1,1 0 0,0 18,7V6H19V10H9V21A1,1 0 0,0 10,22H12A1,1 0 0,0 13,21V12H21V4H18Z" /> + android:pathData="M9.62,12L12,5.67L14.37,12M11,3L5.5,17H7.75L8.87,14H15.12L16.25,17H18.5L13,3H11Z" /> </vector> diff --git a/app/src/main/res/layout/layout_editor.xml b/app/src/main/res/layout/layout_editor.xml index 898de974f..b74c91337 100644 --- a/app/src/main/res/layout/layout_editor.xml +++ b/app/src/main/res/layout/layout_editor.xml @@ -13,9 +13,9 @@ android:background="?attr/selectableItemBackgroundBorderless" android:padding="12dp" android:scaleType="fitXY" - app:tint="?attr/colorTextSecondary" app:layout_constraintStart_toStartOf="parent" - app:srcCompat="@drawable/ic_tab" /> + app:srcCompat="@drawable/ic_tab" + app:tint="?attr/colorTextSecondary" /> <ScrollView android:id="@+id/chatline_scroller" @@ -27,7 +27,7 @@ app:layout_constraintStart_toEndOf="@+id/tab_complete" app:layout_constraintTop_toTopOf="parent"> - <android.support.v7.widget.AppCompatEditText + <de.kuschku.quasseldroid.util.ui.EditTextSelectionChange android:id="@+id/chatline" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -54,9 +54,9 @@ android:background="?attr/selectableItemBackgroundBorderless" android:padding="12dp" android:scaleType="fitXY" - app:tint="?attr/colorAccent" app:layout_constraintEnd_toEndOf="parent" - app:srcCompat="@drawable/ic_send" /> + app:srcCompat="@drawable/ic_send" + app:tint="?attr/colorAccent" /> <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView android:id="@+id/autocomplete_list_expanded" @@ -82,11 +82,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <android.support.v7.widget.ActionMenuView - android:id="@+id/formatting_menu" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:theme="?attr/formatBarTheme" /> + <include layout="@layout/widget_formatting" /> </HorizontalScrollView> </android.support.v7.widget.Toolbar> </android.support.design.widget.AppBarLayout> diff --git a/app/src/main/res/layout/widget_formatting.xml b/app/src/main/res/layout/widget_formatting.xml new file mode 100644 index 000000000..00c1af122 --- /dev/null +++ b/app/src/main/res/layout/widget_formatting.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="2dp"> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_bold" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_bold" + app:tint="?colorTextPrimary" /> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_italic" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_italic" + app:tint="?colorTextPrimary" /> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_underline" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_underline" + app:tint="?colorTextPrimary" /> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_strikethrough" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_strikethrough" + app:tint="?colorTextPrimary" /> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_monospace" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_monospace" + app:tint="?colorTextPrimary" /> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <FrameLayout + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center"> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_foreground" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?backgroundMenuItem" + android:paddingBottom="4dp" + app:srcCompat="@drawable/ic_format_foreground" + app:tint="?colorTextPrimary" /> + + <View + android:id="@+id/ic_format_foreground_preview" + android:layout_width="match_parent" + android:layout_height="4dp" + android:layout_gravity="center_horizontal|bottom" + android:layout_margin="8dp" + android:background="?colorTextPrimary" /> + + </FrameLayout> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <FrameLayout + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center"> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?backgroundMenuItem" + android:paddingBottom="4dp" + app:srcCompat="@drawable/ic_format_background" + app:tint="?colorTextPrimary" /> + + <View + android:id="@+id/ic_format_background_preview" + android:layout_width="match_parent" + android:layout_height="4dp" + android:layout_gravity="center_horizontal|bottom" + android:layout_margin="8dp" + android:background="?colorTextPrimary" /> + + </FrameLayout> + + <Space + android:layout_width="2dp" + android:layout_height="match_parent" /> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/action_format_clear" + style="@style/Widget.Button.Format" + app:srcCompat="@drawable/ic_format_clear" + app:tint="?colorTextPrimary" /> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/menu/formatting.xml b/app/src/main/res/menu/formatting.xml deleted file mode 100644 index 3add5e54e..000000000 --- a/app/src/main/res/menu/formatting.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?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 diff --git a/app/src/main/res/values/styles_widgets.xml b/app/src/main/res/values/styles_widgets.xml index 7bcd0a570..3d7def74e 100644 --- a/app/src/main/res/values/styles_widgets.xml +++ b/app/src/main/res/values/styles_widgets.xml @@ -63,6 +63,13 @@ <style name="Widget.DialogTheme.Light" parent="Theme.AppCompat.Light.Dialog.Alert" /> + <style name="Widget.Button.Format" parent=""> + <item name="android:layout_width">48dp</item> + <item name="android:layout_height">48dp</item> + <item name="android:layout_gravity">center</item> + <item name="android:background">?backgroundMenuItem</item> + </style> + <!-- NavigationDrawerLayout --> <declare-styleable name="NavigationDrawerLayout"> <attr name="insetBackground" /> diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt index bedb3827a..2335af048 100644 --- a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt +++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt @@ -1,2 +1,4 @@ -inline fun Int.clamp(lowerBound: Int, upperBound: Int): Int = maxOf(lowerBound, - minOf(this, upperBound)) +inline fun Int.clamp(lowerBound: Int, upperBound: Int): Int = + maxOf(lowerBound, minOf(this, upperBound)) + +inline fun Int.clamp(range: IntRange): Int = clamp(range.start, range.last) \ No newline at end of file -- GitLab