diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
index 42eed408..8c03ded0 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt
@@ -1308,3 +1308,41 @@ fun Profile.getSchoolYearConstrains(): CalendarConstraints {
.setEnd(dateYearEnd.inMillisUtc)
.build()
}
+
+fun CharSequence.getWordBounds(position: Int, onlyInWord: Boolean = false): Pair? {
+ if (length == 0)
+ return null
+
+ // only if cursor between letters
+ if (onlyInWord) {
+ if (position < 1)
+ return null
+ if (position == length)
+ return null
+
+ val charBefore = this[position - 1]
+ if (!charBefore.isLetterOrDigit())
+ return null
+ val charAfter = this[position]
+ if (!charAfter.isLetterOrDigit())
+ return null
+ }
+
+ var rangeStart = substring(0 until position).indexOfLast { !it.isLetterOrDigit() }
+ if (rangeStart == -1) // no whitespace, set to first index
+ rangeStart = 0
+ else // cut the leading whitespace
+ rangeStart += 1
+
+ var rangeEnd = substring(position).indexOfFirst { !it.isLetterOrDigit() }
+ if (rangeEnd == -1) // no whitespace, set to last index
+ rangeEnd = length
+ else // append the substring offset
+ rangeEnd += position
+
+ if (!onlyInWord && rangeStart == rangeEnd)
+ return null
+ return rangeStart to rangeEnd
+}
+
+infix fun Int.hasSet(what: Int) = this and what == what
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipCreator.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipCreator.kt
new file mode 100644
index 00000000..649fd9ed
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipCreator.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-4.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.messages.compose
+
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.ForegroundColorSpan
+import android.widget.Toast
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.hootsuite.nachos.ChipConfiguration
+import com.hootsuite.nachos.NachoTextView
+import com.hootsuite.nachos.chip.ChipInfo
+import com.hootsuite.nachos.chip.ChipSpan
+import com.hootsuite.nachos.chip.ChipSpanChipCreator
+import pl.szczodrzynski.edziennik.*
+import pl.szczodrzynski.edziennik.data.db.entity.Teacher
+import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
+import pl.szczodrzynski.edziennik.utils.Colors
+import pl.szczodrzynski.edziennik.utils.Themes
+import pl.szczodrzynski.navlib.elevateSurface
+
+class MessagesComposeChipCreator(
+ private val context: Context,
+ private val nacho: NachoTextView,
+ private val teacherList: List,
+) : ChipSpanChipCreator() {
+
+ override fun createChip(context: Context, text: CharSequence, data: Any?): ChipSpan? {
+ if (data == null || data !is Teacher)
+ return null
+ if (data.id !in -24L..0L) {
+ nacho.allChips.forEach {
+ if (it.data == data) {
+ Toast.makeText(
+ context,
+ R.string.messages_compose_recipient_exists,
+ Toast.LENGTH_SHORT
+ ).show()
+ return null
+ }
+ }
+ val chipSpan = ChipSpan(
+ context,
+ data.fullName,
+ BitmapDrawable(context.resources, data.image),
+ data
+ )
+ chipSpan.setIconBackgroundColor(Colors.stringToMaterialColor(data.fullName))
+ return chipSpan
+ }
+
+ val type = (data.id * -1).toInt()
+
+ val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context)
+ val textColorSecondary = android.R.attr.textColorSecondary.resolveAttr(context)
+
+ val sortByCategory = type in listOf(
+ Teacher.TYPE_PARENTS_COUNCIL,
+ Teacher.TYPE_EDUCATOR,
+ Teacher.TYPE_STUDENT
+ )
+ val teachers = if (sortByCategory)
+ teacherList.sortedBy { it.typeDescription }
+ else
+ teacherList
+
+ val category = mutableListOf()
+ val categoryNames = mutableListOf()
+ val categoryCheckedItems = mutableListOf()
+ teachers.forEach { teacher ->
+ if (!teacher.isType(type))
+ return@forEach
+
+ category += teacher
+ val name = teacher.fullName
+ val description = when (type) {
+ Teacher.TYPE_TEACHER -> null
+ Teacher.TYPE_PARENTS_COUNCIL -> teacher.typeDescription
+ Teacher.TYPE_SCHOOL_PARENTS_COUNCIL -> null
+ Teacher.TYPE_PEDAGOGUE -> null
+ Teacher.TYPE_LIBRARIAN -> null
+ Teacher.TYPE_SCHOOL_ADMIN -> null
+ Teacher.TYPE_SUPER_ADMIN -> null
+ Teacher.TYPE_SECRETARIAT -> null
+ Teacher.TYPE_PRINCIPAL -> null
+ Teacher.TYPE_EDUCATOR -> teacher.typeDescription
+ Teacher.TYPE_PARENT -> teacher.typeDescription
+ Teacher.TYPE_STUDENT -> teacher.typeDescription
+ Teacher.TYPE_SPECIALIST -> null
+ else -> teacher.typeDescription
+ }
+ categoryNames += listOfNotNull(
+ name.asSpannable(
+ ForegroundColorSpan(textColorPrimary)
+ ),
+ description?.asSpannable(
+ ForegroundColorSpan(textColorSecondary),
+ AbsoluteSizeSpan(14.dp)
+ )
+ ).concat("\n")
+
+ // check the teacher if already added as a recipient
+ categoryCheckedItems += nacho.allChips.firstOrNull { it.data == teacher } != null
+ }
+
+ MaterialAlertDialogBuilder(context)
+ .setTitle("Dodaj odbiorców - " + Teacher.typeName(context, type))
+ //.setMessage(getString(R.string.messages_compose_recipients_text_format, Teacher.typeName(activity, type)))
+ .setPositiveButton("OK", null)
+ .setNeutralButton("Anuluj", null)
+ .setMultiChoiceItems(
+ categoryNames.toTypedArray(),
+ categoryCheckedItems.toBooleanArray()
+ ) { _, which, isChecked ->
+ val teacher = category[which]
+ if (isChecked) {
+ val chipInfoList = mutableListOf()
+ teacher.image =
+ MessagesUtils.getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
+ chipInfoList.add(ChipInfo(teacher.fullName, teacher))
+ nacho.addTextWithChips(chipInfoList)
+ } else {
+ nacho.allChips.forEach {
+ if (it.data == teacher)
+ nacho.chipTokenizer?.deleteChipAndPadding(it, nacho.text)
+ }
+ }
+ }
+ .show()
+ return null
+ }
+
+ override fun configureChip(chip: ChipSpan, chipConfiguration: ChipConfiguration) {
+ super.configureChip(chip, chipConfiguration)
+ chip.setBackgroundColor(elevateSurface(context, 8).toColorStateList())
+ chip.setTextColor(Themes.getPrimaryTextColor(context))
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipTokenizer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipTokenizer.kt
new file mode 100644
index 00000000..560b0a06
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeChipTokenizer.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-4.
+ */
+
+package pl.szczodrzynski.edziennik.ui.modules.messages.compose
+
+import android.content.Context
+import com.hootsuite.nachos.NachoTextView
+import com.hootsuite.nachos.chip.ChipSpan
+import com.hootsuite.nachos.tokenizer.SpanChipTokenizer
+import pl.szczodrzynski.edziennik.data.db.entity.Teacher
+
+class MessagesComposeChipTokenizer(
+ context: Context,
+ nacho: NachoTextView,
+ teacherList: List,
+) : SpanChipTokenizer(
+ context,
+ MessagesComposeChipCreator(
+ context = context,
+ nacho = nacho,
+ teacherList = teacherList
+ ),
+ ChipSpan::class.java
+)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt
index 33744a98..25599bb5 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt
@@ -5,30 +5,21 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.compose
import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.Typeface
-import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
-import android.text.Spannable
-import android.text.SpannableString
-import android.text.SpannableStringBuilder
-import android.text.style.AbsoluteSizeSpan
-import android.text.style.ForegroundColorSpan
-import android.text.style.StyleSpan
+import android.text.*
+import android.text.Spanned.*
+import android.text.style.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AutoCompleteTextView
-import android.widget.Toast
import androidx.core.text.HtmlCompat
+import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
+import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.hootsuite.nachos.ChipConfiguration
import com.hootsuite.nachos.chip.ChipInfo
-import com.hootsuite.nachos.chip.ChipSpan
-import com.hootsuite.nachos.chip.ChipSpanChipCreator
-import com.hootsuite.nachos.tokenizer.SpanChipTokenizer
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
@@ -47,13 +38,15 @@ import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
-import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes
-import pl.szczodrzynski.edziennik.utils.Themes.getPrimaryTextColor
+import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
+import pl.szczodrzynski.edziennik.utils.span.BoldSpan
+import pl.szczodrzynski.edziennik.utils.span.ItalicSpan
+import pl.szczodrzynski.edziennik.utils.span.SubscriptSizeSpan
+import pl.szczodrzynski.edziennik.utils.span.SuperscriptSizeSpan
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
-import pl.szczodrzynski.navlib.elevateSurface
import kotlin.coroutines.CoroutineContext
import kotlin.text.replace
@@ -76,11 +69,16 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
private var teachers = mutableListOf()
+ private var watchFormatChecked = true
+ private var watchSelectionChanged = true
+ private val enableTextStyling
+ get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
context ?: return null
app = activity.application as App
- context!!.theme.applyStyle(Themes.appTheme, true)
+ requireContext().theme.applyStyle(Themes.appTheme, true)
// activity, context and profile is valid
b = MessagesComposeFragmentBinding.inflate(inflater)
return b.root
@@ -103,40 +101,12 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
// do your job
}
- /*b.fontStyleBold.icon = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_format_bold)
- .sizeDp(24)
- .colorAttr(activity, R.attr.colorOnPrimary)
- b.fontStyleItalic.icon = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_format_italic)
- .sizeDp(24)
- .colorAttr(activity, R.attr.colorOnPrimary)
- b.fontStyleUnderline.icon = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_format_underline)
- .sizeDp(24)
- .colorAttr(activity, R.attr.colorOnPrimary)
-
- b.fontStyle.addOnButtonCheckedListener { _, checkedId, isChecked ->
- val span: Any = when (checkedId) {
- R.id.fontStyleBold -> StyleSpan(Typeface.BOLD)
- R.id.fontStyleItalic -> StyleSpan(Typeface.ITALIC)
- R.id.fontStyleUnderline -> UnderlineSpan()
- else -> StyleSpan(Typeface.NORMAL)
+ if (App.devMode) {
+ b.textHtml.isVisible = true
+ b.text.addTextChangedListener {
+ b.textHtml.text = getHtmlText()
}
-
- if (isChecked) {
- val flags = if (b.text.selectionStart == b.text.selectionEnd)
- SpannableString.SPAN_INCLUSIVE_INCLUSIVE
- else
- SpannableString.SPAN_EXCLUSIVE_INCLUSIVE
- b.text.text?.setSpan(span, b.text.selectionStart, b.text.selectionEnd, flags)
- }
- else {
- b.text.text?.getSpans(b.text.selectionStart, b.text.selectionEnd, span.javaClass)?.forEach {
- if (it is StyleSpan && span is StyleSpan && it.style == span.style)
- b.text.text?.removeSpan(it)
- else if (it.javaClass == span.javaClass)
- b.text.text?.removeSpan(it)
- }
- }
- }*/
+ }
activity.bottomSheet.prependItem(
BottomSheetPrimaryItem(true)
@@ -156,6 +126,50 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
}
}
+ private fun getHtmlText(): String {
+ val text = b.text.text ?: return ""
+
+ // apparently setting the spans to a different Spannable calls the original EditText's
+ // onSelectionChanged with selectionStart=-1, which in effect unchecks the format toggles
+ watchSelectionChanged = false
+ var textHtml = if (enableTextStyling) {
+ val spanned = SpannableString(text)
+ // remove zero-length spans, as they seem to affect
+ // the whole line when converted to HTML
+ spanned.getSpans(0, spanned.length, Any::class.java).forEach {
+ val spanStart = spanned.getSpanStart(it)
+ val spanEnd = spanned.getSpanEnd(it)
+ if (spanStart == spanEnd && it::class.java in BetterHtml.customSpanClasses)
+ spanned.removeSpan(it)
+ }
+ HtmlCompat.toHtml(spanned, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
+ .replace("\n", "")
+ .replace(" dir=\"ltr\"", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("p style=\"margin-top:0; margin-bottom:0;\"", "p")
+ } else {
+ text.toString()
+ }
+ watchSelectionChanged = true
+
+ if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) {
+ textHtml = textHtml
+ .replace("
", "
")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ }
+
+ return textHtml
+ }
+
private fun getRecipientList() {
if (System.currentTimeMillis() - app.profile.lastReceiversSync > 1 * DAY * 1000 && app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN) {
activity.snackbar("Pobieranie listy odbiorców...")
@@ -171,6 +185,80 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
}
}
+ @Suppress("UNUSED_PARAMETER")
+ private fun onFormatChecked(
+ group: MaterialButtonToggleGroup,
+ checkedId: Int,
+ isChecked: Boolean
+ ) {
+ if (!watchFormatChecked)
+ return
+ val span = when (checkedId) {
+ R.id.fontStyleBold -> BoldSpan()
+ R.id.fontStyleItalic -> ItalicSpan()
+ R.id.fontStyleUnderline -> UnderlineSpan()
+ R.id.fontStyleStrike -> StrikethroughSpan()
+ R.id.fontStyleSubscript -> SubscriptSizeSpan(10, dip = true)
+ R.id.fontStyleSuperscript -> SuperscriptSizeSpan(10, dip = true)
+ else -> return
+ }
+
+ // see comments in getHtmlText()
+ watchSelectionChanged = false
+ if (isChecked)
+ BetterHtml.applyFormat(span = span, editText = b.text)
+ else
+ BetterHtml.removeFormat(span = span, editText = b.text)
+ watchSelectionChanged = true
+
+ if (App.devMode)
+ b.textHtml.text = getHtmlText()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ private fun onFormatClear(view: View) {
+ // shortened version on onFormatChecked(), removing all spans
+ watchSelectionChanged = false
+ BetterHtml.removeFormat(span = null, editText = b.text)
+ watchSelectionChanged = true
+ if (App.devMode)
+ b.textHtml.text = getHtmlText()
+ // force update of text style toggle states
+ onSelectionChanged(b.text.selectionStart, b.text.selectionEnd)
+ }
+
+ private fun onSelectionChanged(selectionStart: Int, selectionEnd: Int) {
+ if (!watchSelectionChanged)
+ return
+ val spanned = b.text.text ?: return
+ val spans = spanned.getSpans(selectionStart, selectionEnd, Any::class.java).mapNotNull {
+ if (it::class.java !in BetterHtml.customSpanClasses)
+ return@mapNotNull null
+ val spanStart = spanned.getSpanStart(it)
+ val spanEnd = spanned.getSpanEnd(it)
+ // remove 0-length spans after navigating out of them
+ if (spanStart == spanEnd)
+ spanned.removeSpan(it)
+ else if (spanned.getSpanFlags(it) hasSet SPAN_EXCLUSIVE_EXCLUSIVE)
+ spanned.setSpan(it, spanStart, spanEnd, SPAN_EXCLUSIVE_INCLUSIVE)
+
+ // names are helpful here
+ val isNotAfterWord = selectionEnd <= spanEnd
+ val isSelectionInWord = selectionStart != selectionEnd && selectionStart >= spanStart
+ val isCursorInWord = selectionStart == selectionEnd && selectionStart > spanStart
+ val isChecked = (isCursorInWord || isSelectionInWord) && isNotAfterWord
+ if (isChecked) it::class.java else null
+ }
+ watchFormatChecked = false
+ b.fontStyleBold.isChecked = BoldSpan::class.java in spans
+ b.fontStyleItalic.isChecked = ItalicSpan::class.java in spans
+ b.fontStyleUnderline.isChecked = UnderlineSpan::class.java in spans
+ b.fontStyleStrike.isChecked = StrikethroughSpan::class.java in spans
+ b.fontStyleSubscript.isChecked = SubscriptSizeSpan::class.java in spans
+ b.fontStyleSuperscript.isChecked = SuperscriptSizeSpan::class.java in spans
+ watchFormatChecked = true
+ }
+
private fun createView() {
b.recipientsLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
b.subjectLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
@@ -203,107 +291,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
else -> -1
}
- b.recipients.chipTokenizer = SpanChipTokenizer(activity, object : ChipSpanChipCreator() {
- override fun createChip(context: Context, text: CharSequence, data: Any?): ChipSpan? {
- if (data == null || data !is Teacher)
- return null
- if (data.id !in -24L..0L) {
- b.recipients.allChips.forEach {
- if (it.data == data) {
- Toast.makeText(activity, R.string.messages_compose_recipient_exists, Toast.LENGTH_SHORT).show()
- return null
- }
- }
- val chipSpan = ChipSpan(context, data.fullName, BitmapDrawable(context.resources, data.image), data)
- chipSpan.setIconBackgroundColor(Colors.stringToMaterialColor(data.fullName))
- return chipSpan
- }
-
- val type = (data.id * -1).toInt()
-
- val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(activity)
- val textColorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
-
- val sortByCategory = type in listOf(
- Teacher.TYPE_PARENTS_COUNCIL,
- Teacher.TYPE_EDUCATOR,
- Teacher.TYPE_STUDENT
- )
- val teachers = if (sortByCategory)
- teachers.sortedBy { it.typeDescription }
- else
- teachers
-
- val category = mutableListOf()
- val categoryNames = mutableListOf()
- val categoryCheckedItems = mutableListOf()
- teachers.forEach { teacher ->
- if (!teacher.isType(type))
- return@forEach
-
- category += teacher
- val name = teacher.fullName
- val description = when (type) {
- Teacher.TYPE_TEACHER -> null
- Teacher.TYPE_PARENTS_COUNCIL -> teacher.typeDescription
- Teacher.TYPE_SCHOOL_PARENTS_COUNCIL -> null
- Teacher.TYPE_PEDAGOGUE -> null
- Teacher.TYPE_LIBRARIAN -> null
- Teacher.TYPE_SCHOOL_ADMIN -> null
- Teacher.TYPE_SUPER_ADMIN -> null
- Teacher.TYPE_SECRETARIAT -> null
- Teacher.TYPE_PRINCIPAL -> null
- Teacher.TYPE_EDUCATOR -> teacher.typeDescription
- Teacher.TYPE_PARENT -> teacher.typeDescription
- Teacher.TYPE_STUDENT -> teacher.typeDescription
- Teacher.TYPE_SPECIALIST -> null
- else -> teacher.typeDescription
- }
- categoryNames += listOfNotNull(
- name.asSpannable(
- ForegroundColorSpan(textColorPrimary)
- ),
- description?.asSpannable(
- ForegroundColorSpan(textColorSecondary),
- AbsoluteSizeSpan(14.dp)
- )
- ).concat("\n")
-
- // check the teacher if already added as a recipient
- categoryCheckedItems += b.recipients.allChips.firstOrNull { it.data == teacher } != null
- }
-
- MaterialAlertDialogBuilder(activity)
- .setTitle("Dodaj odbiorców - "+ Teacher.typeName(activity, type))
- //.setMessage(getString(R.string.messages_compose_recipients_text_format, Teacher.typeName(activity, type)))
- .setPositiveButton("OK", null)
- .setNeutralButton("Anuluj", null)
- .setMultiChoiceItems(categoryNames.toTypedArray(), categoryCheckedItems.toBooleanArray()) { _, which, isChecked ->
- val teacher = category[which]
- if (isChecked) {
- val chipInfoList = mutableListOf()
- teacher.image = getProfileImage(48, 24, 16, 12, 1, teacher.fullName)
- chipInfoList.add(ChipInfo(teacher.fullName, teacher))
- b.recipients.addTextWithChips(chipInfoList)
- }
- else {
- b.recipients.allChips.forEach {
- if (it.data == teacher)
- b.recipients.chipTokenizer?.deleteChipAndPadding(it, b.recipients.text)
- }
- }
- }
- .show()
- return null
- }
-
- override fun configureChip(chip: ChipSpan, chipConfiguration: ChipConfiguration) {
- super.configureChip(chip, chipConfiguration)
- chip.setBackgroundColor(elevateSurface(activity, 8).toColorStateList())
- chip.setTextColor(getPrimaryTextColor(activity))
- }
- }, ChipSpan::class.java)
-
+ b.recipients.chipTokenizer = MessagesComposeChipTokenizer(activity, b.recipients, teachers)
b.recipients.setIllegalCharacterIdentifier { c ->
c.toString().matches("[\\n;:_ ]".toRegex())
}
@@ -330,6 +318,55 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.subjectLayout.isEnabled = false
b.textLayout.isEnabled = false
+ b.fontStyleLayout.isVisible = enableTextStyling
+ b.fontStyleBold.isEnabled = false
+ b.fontStyleItalic.isEnabled = false
+ b.fontStyleUnderline.isEnabled = false
+ b.fontStyleStrike.isEnabled = false
+ b.fontStyleSubscript.isEnabled = false
+ b.fontStyleSuperscript.isEnabled = false
+ b.fontStyleClear.isEnabled = false
+ b.text.setOnFocusChangeListener { _, hasFocus ->
+ b.fontStyleBold.isEnabled = hasFocus
+ b.fontStyleItalic.isEnabled = hasFocus
+ b.fontStyleUnderline.isEnabled = hasFocus
+ b.fontStyleStrike.isEnabled = hasFocus
+ b.fontStyleSubscript.isEnabled = hasFocus
+ b.fontStyleSuperscript.isEnabled = hasFocus
+ b.fontStyleClear.isEnabled = hasFocus
+ }
+
+ b.fontStyleBold.text = CommunityMaterial.Icon2.cmd_format_bold.character.toString()
+ b.fontStyleItalic.text = CommunityMaterial.Icon2.cmd_format_italic.character.toString()
+ b.fontStyleUnderline.text = CommunityMaterial.Icon2.cmd_format_underline.character.toString()
+ b.fontStyleStrike.text = CommunityMaterial.Icon2.cmd_format_strikethrough.character.toString()
+ b.fontStyleSubscript.text = CommunityMaterial.Icon2.cmd_format_subscript.character.toString()
+ b.fontStyleSuperscript.text = CommunityMaterial.Icon2.cmd_format_superscript.character.toString()
+ b.fontStyleBold.attachToastHint(R.string.hint_style_bold)
+ b.fontStyleItalic.attachToastHint(R.string.hint_style_italic)
+ b.fontStyleUnderline.attachToastHint(R.string.hint_style_underline)
+ b.fontStyleStrike.attachToastHint(R.string.hint_style_strike)
+ b.fontStyleSubscript.attachToastHint(R.string.hint_style_subscript)
+ b.fontStyleSuperscript.attachToastHint(R.string.hint_style_superscript)
+
+ /*b.fontStyleBold.shapeAppearanceModel = b.fontStyleBold.shapeAppearanceModel
+ .toBuilder()
+ .setBottomLeftCornerSize(0f)
+ .build()
+ b.fontStyleSuperscript.shapeAppearanceModel = b.fontStyleBold.shapeAppearanceModel
+ .toBuilder()
+ .setBottomRightCornerSize(0f)
+ .build()
+ b.fontStyleClear.shapeAppearanceModel = b.fontStyleClear.shapeAppearanceModel
+ .toBuilder()
+ .setTopLeftCornerSize(0f)
+ .setTopRightCornerSize(0f)
+ .build()*/
+
+ b.fontStyle.addOnButtonCheckedListener(this::onFormatChecked)
+ b.fontStyleClear.setOnClickListener(this::onFormatClear)
+ b.text.setSelectionChangedListener(this::onSelectionChanged)
+
activity.navView.bottomBar.apply {
fabEnable = true
fabExtendedText = getString(R.string.messages_compose_send)
@@ -382,11 +419,11 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
val dateString = getString(R.string.messages_date_time_format, Date.fromMillis(msg.addedDate).formattedStringShort, Time.fromMillis(msg.addedDate).stringHM)
// add original message info
span.appendText("W dniu ")
- span.appendSpan(dateString, StyleSpan(Typeface.ITALIC), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ span.appendSpan(dateString, ItalicSpan(), SPAN_EXCLUSIVE_EXCLUSIVE)
span.appendText(", ")
- span.appendSpan(msg.senderName.fixName(), StyleSpan(Typeface.ITALIC), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ span.appendSpan(msg.senderName.fixName(), ItalicSpan(), SPAN_EXCLUSIVE_EXCLUSIVE)
span.appendText(" napisał(a):")
- span.setSpan(StyleSpan(Typeface.BOLD), 0, span.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ span.setSpan(BoldSpan(), 0, span.length, SPAN_EXCLUSIVE_EXCLUSIVE)
span.appendText("\n\n")
if (arguments?.getString("type") == "reply") {
@@ -422,6 +459,8 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.subject.setText(subject)
b.text.apply {
text = span.appendText(body)
+ if (!enableTextStyling)
+ setText(text?.toString())
setSelection(0)
}
b.root.scrollTo(0, 0)
@@ -494,30 +533,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
return
- var textHtml = if (app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN) {
- HtmlCompat.toHtml(SpannableString(text), HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
- .replace("\n", "")
- .replace(" dir=\"ltr\"", "")
- }
- else {
- text.toString()
- }
-
- textHtml = textHtml
- .replace("", "")
- .replace("", "")
- .replace("p style=\"margin-top:0; margin-bottom:0;\"", "p")
-
- if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) {
- textHtml = textHtml
- .replace("
", "")
- .replace("", "")
- .replace("", "")
- .replace("", "")
- .replace("", "")
- .replace("", "")
- .replace("", "")
- }
+ val textHtml = getHtmlText()
activity.bottomSheet.hideKeyboard()
@@ -525,7 +541,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
.setTitle(R.string.messages_compose_confirm_title)
.setMessage(R.string.messages_compose_confirm_text)
.setPositiveButton(R.string.send) { _, _ ->
- EdziennikTask.messageSend(App.profileId, recipients, subject.trim(), textHtml.trim()).enqueue(activity)
+ EdziennikTask.messageSend(App.profileId, recipients, subject.trim(), textHtml).enqueue(activity)
}
.setNegativeButton(R.string.cancel, null)
.show()
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputKeyboardEdit.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputKeyboardEdit.kt
index cbb4f5a0..0b5e1c67 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputKeyboardEdit.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/TextInputKeyboardEdit.kt
@@ -18,6 +18,7 @@ class TextInputKeyboardEdit : AppCompatEditText {
* Keyboard Listener
*/
internal var listener: KeyboardListener? = null
+ private var selectionListener: ((Int, Int) -> Unit)? = null
constructor(context: Context) : super(context)
@@ -27,14 +28,12 @@ class TextInputKeyboardEdit : AppCompatEditText {
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
- if (listener != null)
- listener!!.onStateChanged(this, true)
+ listener?.onStateChanged(this, true)
}
override fun onKeyPreIme(keyCode: Int, @NonNull event: KeyEvent): Boolean {
if (event.keyCode == KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
- if (listener != null)
- listener!!.onStateChanged(this, false)
+ listener?.onStateChanged(this, false)
// Hide cursor
isFocusable = false
@@ -50,6 +49,15 @@ class TextInputKeyboardEdit : AppCompatEditText {
this.listener = listener
}
+ fun setSelectionChangedListener(listener: ((selectionStart: Int, selectionEnd: Int) -> Unit)?) {
+ this.selectionListener = listener
+ }
+
+ override fun onSelectionChanged(selStart: Int, selEnd: Int) {
+ super.onSelectionChanged(selStart, selEnd)
+ selectionListener?.invoke(selStart, selEnd)
+ }
+
interface KeyboardListener {
fun onStateChanged(keyboardEditText: TextInputKeyboardEdit, showing: Boolean)
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/BetterHtml.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/BetterHtml.kt
index 4119369e..90119e55 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/BetterHtml.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/BetterHtml.kt
@@ -6,26 +6,41 @@ package pl.szczodrzynski.edziennik.utils.html
import android.content.Context
import android.graphics.Color
+import android.graphics.Typeface
+import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.Spanned
-import android.text.style.BulletSpan
+import android.text.Spanned.*
+import android.text.style.*
+import androidx.appcompat.widget.AppCompatEditText
import androidx.core.graphics.ColorUtils
import androidx.core.text.HtmlCompat
import pl.szczodrzynski.edziennik.dp
+import pl.szczodrzynski.edziennik.getWordBounds
import pl.szczodrzynski.edziennik.resolveAttr
+import pl.szczodrzynski.edziennik.utils.span.*
import pl.szczodrzynski.navlib.blendColors
object BetterHtml {
+ val customSpanClasses = listOf(
+ BoldSpan::class.java,
+ ItalicSpan::class.java,
+ UnderlineSpan::class.java,
+ StrikethroughSpan::class.java,
+ SubscriptSizeSpan::class.java,
+ SuperscriptSizeSpan::class.java,
+ )
+
@JvmStatic
fun fromHtml(context: Context, html: String): Spanned {
val hexPattern = "(#[a-fA-F0-9]{6})"
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
- .toRegex(RegexOption.IGNORE_CASE)
+ .toRegex(RegexOption.IGNORE_CASE)
var text = html
- .replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
- .replace("background-color: ?$hexPattern;".toRegex(), "")
+ .replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
+ .replace("background-color: ?$hexPattern;".toRegex(), "")
val colorBackground = android.R.attr.colorBackground.resolveAttr(context)
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff
@@ -39,7 +54,11 @@ object BetterHtml {
var blendAmount = 1
var numIterations = 0
- while (numIterations < 100 && ColorUtils.calculateContrast(colorBackground, newColor) < 4.5f) {
+ while (numIterations < 100 && ColorUtils.calculateContrast(
+ colorBackground,
+ newColor
+ ) < 4.5f
+ ) {
blendAmount += 2
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary)
numIterations++
@@ -59,26 +78,109 @@ object BetterHtml {
@Suppress("DEPRECATION")
val htmlSpannable = HtmlCompat.fromHtml(
- text,
- HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
- null,
- LiTagHandler()
+ text,
+ HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST or HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_DIV,
+ null,
+ LiTagHandler()
)
- val spannableBuilder = SpannableStringBuilder(htmlSpannable)
- val bulletSpans = spannableBuilder.getSpans(0, spannableBuilder.length, BulletSpan::class.java)
- bulletSpans.forEach {
- val start = spannableBuilder.getSpanStart(it)
- val end = spannableBuilder.getSpanEnd(it)
- spannableBuilder.removeSpan(it)
- spannableBuilder.setSpan(
- ImprovedBulletSpan(bulletRadius = 3.dp, startWidth = 24.dp, gapWidth = 8.dp),
- start,
- end,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE
- )
+ val spanned = SpannableStringBuilder(htmlSpannable)
+ spanned.getSpans(0, spanned.length, Any::class.java).forEach {
+ val spanStart = spanned.getSpanStart(it)
+ val spanEnd = spanned.getSpanEnd(it)
+ val spanFlags = spanned.getSpanFlags(it)
+
+ val newSpan: Any? = when (it) {
+ is BulletSpan -> ImprovedBulletSpan(
+ bulletRadius = 3.dp,
+ startWidth = 24.dp,
+ gapWidth = 8.dp
+ )
+ is StyleSpan -> when (it.style) {
+ Typeface.BOLD -> BoldSpan()
+ Typeface.ITALIC -> ItalicSpan()
+ else -> null
+ }
+ is SubscriptSpan -> SubscriptSizeSpan(size = 10, dip = true)
+ is SuperscriptSpan -> SuperscriptSizeSpan(size = 10, dip = true)
+ else -> null
+ }
+
+ if (newSpan != null) {
+ spanned.removeSpan(it)
+ spanned.setSpan(newSpan, spanStart, spanEnd, spanFlags)
+ }
}
- return spannableBuilder
+ return spanned
+ }
+
+ fun applyFormat(span: Any, editText: AppCompatEditText) {
+ applyFormat(span, editText.text ?: return, editText.selectionStart, editText.selectionEnd)
+ }
+
+ fun removeFormat(span: Any?, editText: AppCompatEditText) {
+ removeFormat(span, editText.text ?: return, editText.selectionStart, editText.selectionEnd)
+ }
+
+ fun applyFormat(span: Any, spanned: Editable, selectionStart: Int, selectionEnd: Int) {
+ if (selectionStart == -1 || selectionEnd == -1) return
+ val cursorOnly = selectionStart == selectionEnd
+
+ val wordBounds = spanned.getWordBounds(selectionStart, onlyInWord = true)
+ if (cursorOnly && wordBounds != null) {
+ // use the detected word bounds instead of cursor/selection
+ val (start, end) = wordBounds
+ spanned.setSpan(span, start, end, SPAN_EXCLUSIVE_INCLUSIVE)
+ } else {
+ val spanFlags = if (cursorOnly)
+ SPAN_INCLUSIVE_INCLUSIVE
+ else
+ SPAN_EXCLUSIVE_INCLUSIVE
+ spanned.setSpan(span, selectionStart, selectionEnd, spanFlags)
+ }
+ }
+
+ fun removeFormat(span: Any?, spanned: Editable, selectionStart: Int, selectionEnd: Int) {
+ if (selectionStart == -1 || selectionEnd == -1) return
+ val cursorOnly = selectionStart == selectionEnd
+
+ val spanClass = span?.javaClass ?: Any::class.java
+ spanned.getSpans(selectionStart, selectionEnd, spanClass).forEach {
+ if (span == null && it::class.java !in customSpanClasses)
+ return@forEach
+ val spanStart = spanned.getSpanStart(it)
+ val spanEnd = spanned.getSpanEnd(it)
+ val wordBounds = spanned.getWordBounds(selectionStart, onlyInWord = true)
+
+ val (newSpanStart, newSpanEnd, newSpanFlags) = when {
+ !cursorOnly -> {
+ // cut the selected range out of the span
+ Triple(selectionStart, selectionEnd, SPAN_EXCLUSIVE_INCLUSIVE)
+ }
+ wordBounds == null -> {
+ // this allows to change spans mid-word - EXCLUSIVE so the style does
+ // not apply to characters typed later
+ // it's set back to INCLUSIVE when the cursor enters the word again
+ // (onSelectionChanged)
+ Triple(selectionStart, selectionEnd, SPAN_EXCLUSIVE_EXCLUSIVE)
+ }
+ else /* wordBounds != null */ -> {
+ // a word is selected, slice the span in two
+ Triple(wordBounds.first, wordBounds.second, SPAN_EXCLUSIVE_INCLUSIVE)
+ }
+ }
+
+ // remove the existing span
+ spanned.removeSpan(it)
+ // "clone" the span so it can be applied twice, if needed
+ // (can't use 'span' from the parameters as it's nullable)
+ val itClone = it::class.java.newInstance()
+ // reapply the span wherever needed
+ if (spanStart < newSpanStart)
+ spanned.setSpan(it, spanStart, newSpanStart, newSpanFlags)
+ if (spanEnd > newSpanEnd)
+ spanned.setSpan(itClone, newSpanEnd, spanEnd, newSpanFlags)
+ }
}
}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/BoldSpan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/BoldSpan.kt
new file mode 100644
index 00000000..1dd7fb7a
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/BoldSpan.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-3.
+ */
+
+package pl.szczodrzynski.edziennik.utils.span
+
+import android.graphics.Typeface
+import android.text.style.StyleSpan
+
+class BoldSpan : StyleSpan(Typeface.BOLD)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/ImprovedBulletSpan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ImprovedBulletSpan.kt
similarity index 90%
rename from app/src/main/java/pl/szczodrzynski/edziennik/utils/html/ImprovedBulletSpan.kt
rename to app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ImprovedBulletSpan.kt
index 96dcbdfa..6bdfbe42 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/html/ImprovedBulletSpan.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ImprovedBulletSpan.kt
@@ -6,7 +6,7 @@
* https://github.com/davidbilik/bullet-span-sample/blob/master/app/src/main/java/cz/davidbilik/bulletsample/ImprovedBulletSpan.kt
*/
-package pl.szczodrzynski.edziennik.utils.html
+package pl.szczodrzynski.edziennik.utils.span
import android.graphics.Canvas
import android.graphics.Paint
@@ -20,10 +20,10 @@ import android.text.style.LeadingMarginSpan
* Copy of [android.text.style.BulletSpan] from android SDK 28 with removed internal code
*/
class ImprovedBulletSpan(
- val bulletRadius: Int = STANDARD_BULLET_RADIUS,
- val startWidth: Int = STANDARD_GAP_WIDTH,
- val gapWidth: Int = STANDARD_GAP_WIDTH,
- val color: Int = STANDARD_COLOR
+ val bulletRadius: Int = STANDARD_BULLET_RADIUS,
+ val startWidth: Int = STANDARD_GAP_WIDTH,
+ val gapWidth: Int = STANDARD_GAP_WIDTH,
+ val color: Int = STANDARD_COLOR
) : LeadingMarginSpan {
companion object {
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ItalicSpan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ItalicSpan.kt
new file mode 100644
index 00000000..4ae7650d
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/ItalicSpan.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-3.
+ */
+
+package pl.szczodrzynski.edziennik.utils.span
+
+import android.graphics.Typeface
+import android.text.style.StyleSpan
+
+class ItalicSpan : StyleSpan(Typeface.ITALIC)
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SubscriptSizeSpan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SubscriptSizeSpan.kt
new file mode 100644
index 00000000..97fd9f2a
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SubscriptSizeSpan.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-3.
+ */
+
+package pl.szczodrzynski.edziennik.utils.span
+
+import android.text.TextPaint
+import android.text.style.SubscriptSpan
+
+class SubscriptSizeSpan(
+ private val size: Int,
+ private val dip: Boolean,
+) : SubscriptSpan() {
+
+ override fun updateDrawState(textPaint: TextPaint) {
+ super.updateDrawState(textPaint)
+ if (dip) {
+ textPaint.textSize = size * textPaint.density
+ } else {
+ textPaint.textSize = size.toFloat()
+ }
+ }
+
+ override fun updateMeasureState(textPaint: TextPaint) {
+ super.updateMeasureState(textPaint)
+ if (dip) {
+ textPaint.textSize = size * textPaint.density
+ } else {
+ textPaint.textSize = size.toFloat()
+ }
+ }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SuperscriptSizeSpan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SuperscriptSizeSpan.kt
new file mode 100644
index 00000000..7b82ed51
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/span/SuperscriptSizeSpan.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Kuba Szczodrzyński 2021-10-3.
+ */
+
+package pl.szczodrzynski.edziennik.utils.span
+
+import android.text.TextPaint
+import android.text.style.SuperscriptSpan
+
+class SuperscriptSizeSpan(
+ private val size: Int,
+ private val dip: Boolean,
+) : SuperscriptSpan() {
+
+ override fun updateDrawState(textPaint: TextPaint) {
+ super.updateDrawState(textPaint)
+ if (dip) {
+ textPaint.textSize = size * textPaint.density
+ } else {
+ textPaint.textSize = size.toFloat()
+ }
+ }
+
+ override fun updateMeasureState(textPaint: TextPaint) {
+ super.updateMeasureState(textPaint)
+ if (dip) {
+ textPaint.textSize = size * textPaint.density
+ } else {
+ textPaint.textSize = size.toFloat()
+ }
+ }
+}
diff --git a/app/src/main/res/layout/messages_compose_fragment.xml b/app/src/main/res/layout/messages_compose_fragment.xml
index 9363a92f..d9d24613 100644
--- a/app/src/main/res/layout/messages_compose_fragment.xml
+++ b/app/src/main/res/layout/messages_compose_fragment.xml
@@ -6,15 +6,21 @@
+
+
+
+
+
+
+
-
+ android:orientation="vertical"
+ android:paddingBottom="40dp">
+ app:endIconDrawable="@drawable/dropdown_arrow"
+ app:endIconMode="custom">
-
-
+ tools:text="kachoomba" />
+
+ android:hint="@string/messages_compose_text_hint"
+ android:inputType="textMultiLine|textAutoCorrect|textLongMessage|textAutoComplete|textCapSentences"
+ android:minLines="3"
+ tools:text="Witam,\n\nchciałem przekazać bardzo ważną wiadomość.\nJest to cytat znanej osoby.\n\n"To jest tak, ale nie. Pamiętaj żeby oczywiście"\n\nCytat ma bardzo duże przesłanie i ogromny wpływ na dzisiejszą kulturę i rozwój współczesnej cywilizacji.\n\nJako zadanie domowe, należy wypisać 5 pierwszych liczb pierwszych. Uzasadnij decyzję, odwołując się do cytatu i 3 innych przykładów z literatury lub filmu.\n\nPozdrawiam,\nJa." />
-
+
-
-
-
- -->
+ android:layout_marginHorizontal="8dp"
+ android:text="@string/messages_compose_style_clear" />
+
-
+ android:visibility="gone"
+ tools:visibility="visible" />
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fd9a6935..6a1f3e86 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1464,4 +1464,11 @@
\@%s - %s
Najłatwiejszy sposób na korzystanie z e-dziennika.
Szczegóły
+ Pogrubienie
+ Pochylenie
+ Podkreślenie
+ Przekreślenie
+ Indeks dolny
+ Indeks górny
+ Wyczyść format