[UI] Add text styling to manual events. (#96)

* [UI] Move text styling outside of messages module.

* [UI] Add text styling to event manual dialog.

* [UI/Events] Implement showing HTML-formatted content.

* [UI] Fix searching in styled event topic. Create HtmlMode enum.

* [UI] Add HTML Simple mode to text styling manager. Fix CharSequence replacing.

* [Events] Disable self-shared event notifications.

* [UI] Fix simple HTML mode format. Fix HTML in notifications.

* [HTML] Replace usages of Html and HtmlCompat with BetterHtml.

* [Events] Fix editing self-added events from other devices.

* [Events] Implement receiving and fix showing HTML-formatted events.

* [UI/Events] Add observing changes in event details dialog.

* [Firebase] Disable self-shared event notifications.
This commit is contained in:
Kuba Szczodrzyński 2021-10-16 19:37:41 +02:00 committed by GitHub
parent 7e0f69d95d
commit 74b766f18a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 733 additions and 414 deletions

View File

@ -1007,8 +1007,29 @@ inline fun <T> LongSparseArray<T>.filter(predicate: (T) -> Boolean): List<T> {
return destination return destination
} }
fun CharSequence.replace(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence = fun CharSequence.replaceSpanned(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence {
splitToSequence(oldValue, ignoreCase = ignoreCase).toList().concat(newValue) var seq = this
var index = seq.indexOf(oldValue, ignoreCase = ignoreCase)
while (index != -1) {
val sb = SpannableStringBuilder()
sb.appendRange(seq, 0, index)
sb.append(newValue)
sb.appendRange(seq, index + oldValue.length, seq.length)
seq = sb
index = seq.indexOf(oldValue, startIndex = index + 1, ignoreCase = ignoreCase)
}
return seq
}
fun SpannableStringBuilder.replaceSpan(spanClass: Class<*>, prefix: CharSequence, suffix: CharSequence): SpannableStringBuilder {
getSpans(0, length, spanClass).forEach {
val spanStart = getSpanStart(it)
insert(spanStart, prefix)
val spanEnd = getSpanEnd(it)
insert(spanEnd, suffix)
}
return this
}
fun Int.toColorStateList(): ColorStateList { fun Int.toColorStateList(): ColorStateList {
val states = arrayOf( val states = arrayOf(

View File

@ -1,6 +1,5 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
import android.text.Html
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.Regexes import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
@ -9,6 +8,7 @@ import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.get import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
class EdudziennikWebGetHomework( class EdudziennikWebGetHomework(
override val data: DataEdudziennik, override val data: DataEdudziennik,
@ -26,7 +26,8 @@ class EdudziennikWebGetHomework(
webGet(TAG, "Homework/$id") { text -> webGet(TAG, "Homework/$id") { text ->
val description = Regexes.EDUDZIENNIK_HOMEWORK_DESCRIPTION.find(text)?.get(1)?.trim() val description = Regexes.EDUDZIENNIK_HOMEWORK_DESCRIPTION.find(text)?.get(1)?.trim()
if (description != null) event.topic = Html.fromHtml(description).toString() if (description != null)
event.topic = BetterHtml.fromHtml(context = null, description).toString()
event.homeworkBody = "" event.homeworkBody = ""
event.isDownloaded = true event.isDownloaded = true

View File

@ -1,12 +1,12 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia
import android.text.Html
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.jsoup.Jsoup import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia
import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
class LibrusSynergiaGetHomework(override val data: DataLibrus, class LibrusSynergiaGetHomework(override val data: DataLibrus,
val event: EventFull, val event: EventFull,
@ -23,7 +23,10 @@ class LibrusSynergiaGetHomework(override val data: DataLibrus,
val table = doc.select("table.decorated tbody > tr") val table = doc.select("table.decorated tbody > tr")
event.topic = table[1].select("td")[1].text() event.topic = table[1].select("td")[1].text()
event.homeworkBody = Html.fromHtml(table[5].select("td")[1].html()).toString() event.homeworkBody = BetterHtml.fromHtml(
context = null,
html = table[5].select("td")[1].html(),
).toString()
event.isDownloaded = true event.isDownloaded = true
event.attachmentIds = mutableListOf() event.attachmentIds = mutableListOf()

View File

@ -4,12 +4,12 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api
import android.text.Html
import androidx.core.util.contains import androidx.core.util.contains
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -26,7 +26,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List<String>) {
val id = cols[0].toLong() val id = cols[0].toLong()
val teacherId = cols[7].toLong() val teacherId = cols[7].toLong()
val subjectId = cols[6].toLong() val subjectId = cols[6].toLong()
val topic = Html.fromHtml(cols[1])?.toString()?.trim() ?: "" val topic = BetterHtml.fromHtml(context = null, cols[1]).toString().trim()
val eventDate = Date.fromYmd(cols[2]) val eventDate = Date.fromYmd(cols[2])
val startTime = Time.fromYmdHm(cols[3]) val startTime = Time.fromYmdHm(cols[3])

View File

@ -245,7 +245,10 @@ class SzkolnyApi(val app: App) : CoroutineScope {
seen = profile.empty seen = profile.empty
notified = profile.empty notified = profile.empty
if (profile.userCode == event.sharedBy) sharedBy = "self" if (profile.userCode == event.sharedBy) {
sharedBy = "self"
addedManually = true
}
} }
} }
} }

View File

@ -69,80 +69,90 @@ class Notifications(val app: App, val notifications: MutableList<Notification>,
} }
private fun eventNotifications() { private fun eventNotifications() {
for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today }) { app.db.eventDao().getNotNotifiedNow().filter {
it.date >= today
}.forEach { event ->
val text = if (event.isHomework) val text = if (event.isHomework)
app.getString( app.getString(
if (event.subjectLongName.isNullOrEmpty()) if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format R.string.notification_homework_no_subject_format
else else
R.string.notification_homework_format, R.string.notification_homework_format,
event.subjectLongName, event.subjectLongName,
event.date.formattedString event.date.formattedString
) )
else else
app.getString( app.getString(
if (event.subjectLongName.isNullOrEmpty()) if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format R.string.notification_event_no_subject_format
else else
R.string.notification_event_format, R.string.notification_event_format,
event.typeName ?: "wydarzenie", event.typeName ?: "wydarzenie",
event.date.formattedString, event.date.formattedString,
event.subjectLongName event.subjectLongName
) )
val textLong = app.getString( val textLong = app.getString(
R.string.notification_event_long_format, R.string.notification_event_long_format,
event.typeName ?: "-", event.typeName ?: "-",
event.subjectLongName ?: "-", event.subjectLongName ?: "-",
event.date.formattedString, event.date.formattedString,
Week.getFullDayName(event.date.weekDay), Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day), event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200) event.topic.take(200)
) )
val type = if (event.isHomework) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.isHomework)
Notification.TYPE_NEW_HOMEWORK
else
Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong, textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }
fun sharedEventNotifications() { fun sharedEventNotifications() {
for (event in app.db.eventDao().getNotNotifiedNow().filter { it.date >= today && it.sharedBy != null }) { app.db.eventDao().getNotNotifiedNow().filter {
it.date >= today && it.sharedBy != null && it.sharedBy != "self"
}.forEach { event ->
val text = app.getString( val text = app.getString(
R.string.notification_shared_event_format, R.string.notification_shared_event_format,
event.sharedByName, event.sharedByName,
event.typeName ?: "wydarzenie", event.typeName ?: "wydarzenie",
event.date.formattedString, event.date.formattedString,
event.topic event.topicHtml
) )
val textLong = app.getString( val textLong = app.getString(
R.string.notification_shared_event_long_format, R.string.notification_shared_event_long_format,
event.sharedByName, event.sharedByName,
event.typeName ?: "-", event.typeName ?: "-",
event.subjectLongName ?: "-", event.subjectLongName ?: "-",
event.date.formattedString, event.date.formattedString,
Week.getFullDayName(event.date.weekDay), Week.getFullDayName(event.date.weekDay),
event.time?.stringHM ?: app.getString(R.string.event_all_day), event.time?.stringHM ?: app.getString(R.string.event_all_day),
event.topic.take(200) event.topicHtml.take(200)
) )
val type = if (event.isHomework) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT val type = if (event.isHomework)
Notification.TYPE_NEW_HOMEWORK
else
Notification.TYPE_NEW_EVENT
notifications += Notification( notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),
text = text, text = text,
textLong = textLong, textLong = textLong,
type = type, type = type,
profileId = event.profileId, profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name, profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA, viewId = if (event.isHomework) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong()) ).addExtra("eventId", event.id).addExtra("eventDate", event.date.value.toLong())
} }
} }

View File

@ -84,6 +84,10 @@ abstract class EventDao : BaseDao<Event, EventFull> {
fun getAllByDateNow(profileId: Int, date: Date) = fun getAllByDateNow(profileId: Int, date: Date) =
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY") getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
// GET ONE - LIVE DATA
fun getById(profileId: Int, id: Long) =
getOne("$QUERY WHERE events.profileId = $profileId AND eventId = $id")
// GET ONE - NOW // GET ONE - NOW
fun getByIdNow(profileId: Int, id: Long) = fun getByIdNow(profileId: Int, id: Long) =
getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id") getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id")

View File

@ -75,6 +75,7 @@ open class Event(
@ColumnInfo(name = "eventAddedManually") @ColumnInfo(name = "eventAddedManually")
var addedManually: Boolean = false var addedManually: Boolean = false
get() = field || sharedBy == "self"
@ColumnInfo(name = "eventSharedBy") @ColumnInfo(name = "eventSharedBy")
var sharedBy: String? = null var sharedBy: String? = null
@ColumnInfo(name = "eventSharedByName") @ColumnInfo(name = "eventSharedByName")

View File

@ -7,6 +7,7 @@ import androidx.room.Ignore
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.ui.modules.search.Searchable import pl.szczodrzynski.edziennik.ui.modules.search.Searchable
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -48,6 +49,20 @@ class EventFull(
var teamName: String? = null var teamName: String? = null
var teamCode: String? = null var teamCode: String? = null
@delegate:Ignore
@delegate:Transient
val topicHtml by lazy {
BetterHtml.fromHtml(context = null, topic, nl2br = true)
}
@delegate:Ignore
@delegate:Transient
val bodyHtml by lazy {
homeworkBody?.let {
BetterHtml.fromHtml(context = null, it, nl2br = true)
}
}
@Ignore @Ignore
@Transient @Transient
override var searchPriority = 0 override var searchPriority = 0
@ -60,7 +75,7 @@ class EventFull(
@delegate:Transient @delegate:Transient
override val searchKeywords by lazy { override val searchKeywords by lazy {
listOf( listOf(
listOf(topic, homeworkBody), listOf(topicHtml.toString(), bodyHtml?.toString()),
attachmentNames, attachmentNames,
listOf(subjectLongName), listOf(subjectLongName),
listOf(teacherName), listOf(teacherName),

View File

@ -3,12 +3,12 @@
*/ */
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.core.text.HtmlCompat
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.Relation import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.ui.modules.search.Searchable import pl.szczodrzynski.edziennik.ui.modules.search.Searchable
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
class MessageFull( class MessageFull(
profileId: Int, id: Long, type: Int, profileId: Int, id: Long, type: Int,
@ -33,11 +33,10 @@ class MessageFull(
@delegate:Transient @delegate:Transient
val bodyHtml by lazy { val bodyHtml by lazy {
body?.let { body?.let {
HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY) BetterHtml.fromHtml(context = null, it)
} }
} }
@Ignore @Ignore
@Transient @Transient
override var searchPriority = 0 override var searchPriority = 0

View File

@ -122,7 +122,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
id = json.getLong("id") ?: return, id = json.getLong("id") ?: return,
date = json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return, date = json.getInt("eventDate")?.let { Date.fromValue(it) } ?: return,
time = json.getInt("startTime")?.let { Time.fromValue(it) }, time = json.getInt("startTime")?.let { Time.fromValue(it) },
topic = json.getString("topic") ?: "", topic = json.getString("topicHtml") ?: json.getString("topic") ?: "",
color = json.getInt("color"), color = json.getInt("color"),
type = json.getLong("type") ?: 0, type = json.getLong("type") ?: 0,
teacherId = json.getLong("teacherId") ?: -1, teacherId = json.getLong("teacherId") ?: -1,
@ -135,7 +135,10 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
event.sharedBy = json.getString("sharedBy") event.sharedBy = json.getString("sharedBy")
event.sharedByName = json.getString("sharedByName") event.sharedByName = json.getString("sharedByName")
if (profile.userCode == event.sharedBy) event.sharedBy = "self" if (profile.userCode == event.sharedBy) {
event.sharedBy = "self"
event.addedManually = true
}
val metadata = Metadata( val metadata = Metadata(
event.profileId, event.profileId,
@ -148,7 +151,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
val type = if (event.isHomework) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT val type = if (event.isHomework) Notification.TYPE_NEW_SHARED_HOMEWORK else Notification.TYPE_NEW_SHARED_EVENT
val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter val notificationFilter = app.config.getFor(event.profileId).sync.notificationFilter
if (!notificationFilter.contains(type)) { if (!notificationFilter.contains(type) && event.sharedBy != "self") {
val notification = Notification( val notification = Notification(
id = Notification.buildId(event.profileId, type, event.id), id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type), title = app.getNotificationTitle(type),

View File

@ -9,7 +9,6 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.Html
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.work.* import androidx.work.*
@ -20,6 +19,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -115,7 +115,7 @@ class UpdateWorker(val context: Context, val params: WorkerParameters) : Worker(
.setStyle(NotificationCompat.BigTextStyle() .setStyle(NotificationCompat.BigTextStyle()
.bigText(listOf( .bigText(listOf(
app.getString(R.string.notification_updates_text, update.versionName), app.getString(R.string.notification_updates_text, update.versionName),
update.releaseNotes?.let { Html.fromHtml(it) } update.releaseNotes?.let { BetterHtml.fromHtml(context = null, it) }
).concat("\n"))) ).concat("\n")))
.setColor(0xff2196f3.toInt()) .setColor(0xff2196f3.toInt())
.setLights(0xFF00FFFF.toInt(), 2000, 2000) .setLights(0xFF00FFFF.toInt(), 2000, 2000)

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-11.
*/
package pl.szczodrzynski.edziennik.ui.dialogs
import android.content.res.ColorStateList
import android.text.Editable
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.StyledTextDialogBinding
import pl.szczodrzynski.edziennik.utils.DefaultTextStyles
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.SIMPLE
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig
class StyledTextDialog(
val activity: AppCompatActivity,
val initialText: Editable?,
val onSuccess: (text: Editable) -> Unit,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) {
companion object {
private const val TAG = "StyledTextDialog"
}
private lateinit var app: App
private lateinit var b: StyledTextDialogBinding
private lateinit var dialog: AlertDialog
private lateinit var config: StylingConfig
private val manager
get() = app.textStylingManager
init {
show()
}
fun show() {
if (activity.isFinishing)
return
onShowListener?.invoke(TAG)
app = activity.applicationContext as App
b = StyledTextDialogBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.styled_text_dialog_title)
.setView(b.root)
.setPositiveButton(R.string.save) { _, _ ->
onSuccess(b.editText.text ?: SpannableStringBuilder(""))
}
.setNeutralButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
config = StylingConfig(
editText = b.editText,
fontStyleGroup = b.fontStyle.styles,
fontStyleClear = b.fontStyle.clear,
styles = DefaultTextStyles.getAsList(b.fontStyle),
textHtml = if (App.devMode) b.htmlText else null,
htmlMode = SIMPLE,
)
manager.attach(config)
b.editText.text = initialText
// this is awful
if (Themes.isDark) {
val colorStateList = ColorStateList.valueOf(0x40ffffff)
b.fontStyle.bold.strokeColor = colorStateList
b.fontStyle.italic.strokeColor = colorStateList
b.fontStyle.underline.strokeColor = colorStateList
b.fontStyle.strike.strokeColor = colorStateList
b.fontStyle.subscript.strokeColor = colorStateList
b.fontStyle.superscript.strokeColor = colorStateList
b.fontStyle.clear.strokeColor = colorStateList
}
}
}

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs package pl.szczodrzynski.edziennik.ui.dialogs
import android.text.Html
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -14,6 +13,7 @@ import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.sync.UpdateDownloaderService import pl.szczodrzynski.edziennik.sync.UpdateDownloaderService
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class UpdateAvailableDialog( class UpdateAvailableDialog(
@ -48,7 +48,7 @@ class UpdateAvailableDialog(
R.string.update_available_format, R.string.update_available_format,
BuildConfig.VERSION_NAME, BuildConfig.VERSION_NAME,
update.versionName, update.versionName,
update.releaseNotes?.let { Html.fromHtml(it) } ?: "---" update.releaseNotes?.let { BetterHtml.fromHtml(activity, it) } ?: "---"
) )
.setPositiveButton(R.string.update_available_button) { dialog, _ -> .setPositiveButton(R.string.update_available_button) { dialog, _ ->
activity.startService(Intent(app, UpdateDownloaderService::class.java)) activity.startService(Intent(app, UpdateDownloaderService::class.java))

View File

@ -5,7 +5,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs.changelog package pl.szczodrzynski.edziennik.ui.dialogs.changelog
import android.os.Build import android.os.Build
import android.text.Html
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -18,6 +17,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.dp import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class ChangelogDialog( class ChangelogDialog(
@ -52,12 +52,11 @@ class ChangelogDialog(
text = text.replace("""\[(.+?)]\(@([A-z0-9-]+)\)""".toRegex(), "<a href=\"$commitsUrlPrefix$2\">$1</a>") text = text.replace("""\[(.+?)]\(@([A-z0-9-]+)\)""".toRegex(), "<a href=\"$commitsUrlPrefix$2\">$1</a>")
text = text.replace("""\s@([A-z0-9-]+)""".toRegex(), " <a href=\"$commitsUrlPrefix$1\">@$1</a>") text = text.replace("""\s@([A-z0-9-]+)""".toRegex(), " <a href=\"$commitsUrlPrefix$1\">@$1</a>")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
textView.text = Html.fromHtml(text) text
} else
else { text.replace("<li>", "<br><li> - ")
textView.text = Html.fromHtml(text.replace("<li>", "<br><li> - ")) textView.text = BetterHtml.fromHtml(activity, html)
}
textView.movementMethod = BetterLinkMovementMethod.getInstance() textView.movementMethod = BetterLinkMovementMethod.getInstance()

View File

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.ui.dialogs.sync package pl.szczodrzynski.edziennik.ui.dialogs.sync
import android.text.Html
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -14,6 +13,7 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.task.AppSync import pl.szczodrzynski.edziennik.data.api.task.AppSync
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class RegistrationConfigDialog( class RegistrationConfigDialog(
@ -61,7 +61,7 @@ class RegistrationConfigDialog(
onShowListener?.invoke(TAG + "Enable") onShowListener?.invoke(TAG + "Enable")
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.registration_config_title) .setTitle(R.string.registration_config_title)
.setMessage(Html.fromHtml(app.getString(R.string.registration_config_enable_text))) .setMessage(BetterHtml.fromHtml(activity, R.string.registration_config_enable_text))
.setPositiveButton(R.string.i_agree) { _, _ -> .setPositiveButton(R.string.i_agree) { _, _ ->
enableRegistration() enableRegistration()
} }
@ -76,7 +76,7 @@ class RegistrationConfigDialog(
onShowListener?.invoke(TAG + "Disable") onShowListener?.invoke(TAG + "Disable")
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.registration_config_title) .setTitle(R.string.registration_config_title)
.setMessage(Html.fromHtml(app.getString(R.string.registration_config_disable_text))) .setMessage(R.string.registration_config_disable_text)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
disableRegistration() disableRegistration()
} }

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
@ -22,6 +21,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.Themes.appTheme import pl.szczodrzynski.edziennik.utils.Themes.appTheme
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/* /*
@ -90,7 +90,7 @@ class CrashActivity : AppCompatActivity(), CoroutineScope {
moreInfoButton.setOnClickListener { moreInfoButton.setOnClickListener {
MaterialAlertDialogBuilder(this, R.style.AppTheme_MaterialAlertDialogMonospace) MaterialAlertDialogBuilder(this, R.style.AppTheme_MaterialAlertDialogMonospace)
.setTitle(R.string.crash_details) .setTitle(R.string.crash_details)
.setMessage(Html.fromHtml(getErrorString(intent, false))) .setMessage(BetterHtml.fromHtml(context = null, getErrorString(intent, false)))
.setPositiveButton(R.string.close, null) .setPositiveButton(R.string.close, null)
.setNeutralButton(R.string.copy_to_clipboard) { _, _ -> copyErrorToClipboard() } .setNeutralButton(R.string.copy_to_clipboard) { _, _ -> copyErrorToClipboard() }
.show() .show()

View File

@ -31,6 +31,7 @@ import kotlin.coroutines.CoroutineContext
class EventDetailsDialog( class EventDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
// this event is observed for changes
var event: EventFull, var event: EventFull,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
@ -85,7 +86,11 @@ class EventDetailsDialog(
showRemoveEventDialog() showRemoveEventDialog()
} }
update() // watch the event for changes
app.db.eventDao().getById(event.profileId, event.id).observe(activity) {
event = it ?: return@observe
update()
}
}} }}
private fun update() { private fun update() {
@ -93,6 +98,9 @@ class EventDetailsDialog(
b.eventShared = eventShared b.eventShared = eventShared
b.eventOwn = eventOwn b.eventOwn = eventOwn
b.topic.text = event.topicHtml
b.body.text = event.bodyHtml
if (!event.seen) { if (!event.seen) {
manager.markAsSeen(event) manager.markAsSeen(event)
} }
@ -170,8 +178,9 @@ class EventDetailsDialog(
dialog.dismiss() dialog.dismiss()
return@EventManualDialog return@EventManualDialog
} }
event = it // this should not be needed as the event is observed by the ID
update() // event = it
// update()
}, },
onShowListener = onShowListener, onShowListener = onShowListener,
onDismissListener = onDismissListener onDismissListener = onDismissListener
@ -350,7 +359,7 @@ class EventDetailsDialog(
val intent = Intent(Intent.ACTION_EDIT).apply { val intent = Intent(Intent.ACTION_EDIT).apply {
data = Events.CONTENT_URI data = Events.CONTENT_URI
putExtra(Events.TITLE, title) putExtra(Events.TITLE, title)
putExtra(Events.DESCRIPTION, event.topic) putExtra(Events.DESCRIPTION, event.topicHtml.toString())
if (event.time == null) { if (event.time == null) {
putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true) putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true)

View File

@ -13,6 +13,9 @@ import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -27,9 +30,13 @@ import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.StyledTextDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.SIMPLE
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfigBase
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -59,6 +66,10 @@ class EventManualDialog(
private lateinit var b: DialogEventManualV2Binding private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private lateinit var profile: Profile private lateinit var profile: Profile
private lateinit var stylingConfig: StylingConfigBase
private val textStylingManager
get() = app.textStylingManager
private var customColor: Int? = null private var customColor: Int? = null
private val editingShared = editingEvent?.sharedBy != null private val editingShared = editingEvent?.sharedBy != null
@ -128,6 +139,23 @@ class EventManualDialog(
} }
} }
b.topicLayout.endIconDrawable = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_open_in_new).apply {
sizeDp = 24
}
b.topicLayout.setEndIconOnClickListener {
StyledTextDialog(
activity,
initialText = b.topic.text,
onSuccess = {
b.topic.text = it
},
onShowListener,
onDismissListener
)
}
stylingConfig = StylingConfigBase(editText = b.topic, htmlMode = SIMPLE)
updateShareText() updateShareText()
b.shareSwitch.onChange { _, isChecked -> b.shareSwitch.onChange { _, isChecked ->
updateShareText(isChecked) updateShareText(isChecked)
@ -332,7 +360,7 @@ class EventManualDialog(
// copy data from event being edited // copy data from event being edited
editingEvent?.let { editingEvent?.let {
b.topic.setText(it.topic) b.topic.setText(BetterHtml.fromHtml(activity, it.topic, nl2br = true))
if (it.color != -1) if (it.color != -1)
customColor = it.color customColor = it.color
} }
@ -458,12 +486,13 @@ class EventManualDialog(
val id = System.currentTimeMillis() val id = System.currentTimeMillis()
val topicHtml = textStylingManager.getHtmlText(stylingConfig)
val eventObject = Event( val eventObject = Event(
profileId = profileId, profileId = profileId,
id = editingEvent?.id ?: id, id = editingEvent?.id ?: id,
date = date, date = date,
time = startTime, time = startTime,
topic = topic, topic = topicHtml,
color = customColor, color = customColor,
type = type?.id ?: Event.TYPE_DEFAULT, type = type?.id ?: Event.TYPE_DEFAULT,
teacherId = teacher?.id ?: -1, teacherId = teacher?.id ?: -1,

View File

@ -9,7 +9,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.iconics.utils.buildIconics
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.EventListItemBinding import pl.szczodrzynski.edziennik.databinding.EventListItemBinding
@ -99,15 +98,13 @@ class EventViewHolder(
/* 3$ */ /* 3$ */
item.teamName?.let { bullet + it } ?: "", item.teamName?.let { bullet + it } ?: "",
) )
// workaround for the span data lost during setText above
val addedBySpanned = adapter.highlightSearchText( val addedBySpanned = adapter.highlightSearchText(
item = item, item = item,
text = addedBy, text = addedBy,
color = colorHighlight color = colorHighlight
) )
b.addedBy.text = b.addedBy.text.replace(addedBy, addedBySpanned) b.addedBy.text = b.addedBy.text.replaceSpanned(addedBy, addedBySpanned)
// for now, as CharSequence.replace() converts the original sequence to string,
// so the Iconics span data is lost and the share icon set above does not display
b.addedBy.buildIconics()
b.attachmentIcon.isVisible = item.hasAttachments b.attachmentIcon.isVisible = item.hasAttachments

View File

@ -8,7 +8,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.plusAssign import androidx.core.view.plusAssign
import androidx.core.view.setMargins import androidx.core.view.setMargins
@ -25,6 +24,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.UpdateAvailableDialog
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class HomeAvailabilityCard( class HomeAvailabilityCard(
@ -61,8 +61,8 @@ class HomeAvailabilityCard(
// show "register unavailable" only when disabled // show "register unavailable" only when disabled
if (status?.userMessage != null) { if (status?.userMessage != null) {
b.homeAvailabilityTitle.text = HtmlCompat.fromHtml(status.userMessage.title, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityTitle.text = BetterHtml.fromHtml(activity, status.userMessage.title)
b.homeAvailabilityText.text = HtmlCompat.fromHtml(status.userMessage.contentShort, HtmlCompat.FROM_HTML_MODE_LEGACY) b.homeAvailabilityText.text = BetterHtml.fromHtml(activity, status.userMessage.contentShort)
b.homeAvailabilityUpdate.isVisible = false b.homeAvailabilityUpdate.isVisible = false
b.homeAvailabilityIcon.setImageResource(R.drawable.ic_sync) b.homeAvailabilityIcon.setImageResource(R.drawable.ic_sync)
if (status.userMessage.icon != null) if (status.userMessage.icon != null)

View File

@ -10,7 +10,6 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -31,6 +30,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.RegisterUnavailableDialog
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackActivity
import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod import pl.szczodrzynski.edziennik.utils.BetterLinkMovementMethod
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type import pl.szczodrzynski.edziennik.utils.managers.AvailabilityManager.Error.Type
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -218,7 +218,7 @@ class LoginChooserFragment : Fragment(), CoroutineScope {
if (!app.config.privacyPolicyAccepted) { if (!app.config.privacyPolicyAccepted) {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setTitle(R.string.privacy_policy) .setTitle(R.string.privacy_policy)
.setMessage(Html.fromHtml(activity.getString(R.string.privacy_policy_dialog_html))) .setMessage(BetterHtml.fromHtml(activity, R.string.privacy_policy_dialog_html))
.setPositiveButton(R.string.i_agree) { _, _ -> .setPositiveButton(R.string.i_agree) { _, _ ->
app.config.privacyPolicyAccepted = true app.config.privacyPolicyAccepted = true
onLoginModeClicked(loginType, loginMode) onLoginModeClicked(loginType, loginMode)

View File

@ -38,15 +38,17 @@ import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding import pl.szczodrzynski.edziennik.databinding.MessagesComposeFragmentBinding
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.list.MessagesFragment
import pl.szczodrzynski.edziennik.utils.DefaultTextStyles
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.managers.MessageManager.UIConfig import pl.szczodrzynski.edziennik.utils.managers.MessageManager.UIConfig
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.COMPATIBLE
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.ORIGINAL
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig
import pl.szczodrzynski.edziennik.utils.span.* import pl.szczodrzynski.edziennik.utils.span.*
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class MessagesComposeFragment : Fragment(), CoroutineScope { class MessagesComposeFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "MessagesComposeFragment" private const val TAG = "MessagesComposeFragment"
@ -103,6 +105,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.breakpoints.visibility = if (App.devMode) View.VISIBLE else View.GONE b.breakpoints.visibility = if (App.devMode) View.VISIBLE else View.GONE
b.breakpoints.setOnClickListener { b.breakpoints.setOnClickListener {
b.breakpoints.isEnabled = true b.breakpoints.isEnabled = true
@SuppressLint("SetTextI18n")
b.breakpoints.text = "Breakpoints!" b.breakpoints.text = "Breakpoints!"
// do your job // do your job
} }
@ -232,45 +235,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
b.subjectLayout.isEnabled = false b.subjectLayout.isEnabled = false
b.textLayout.isEnabled = false b.textLayout.isEnabled = false
val styles = listOf( val styles = DefaultTextStyles.getAsList(b.fontStyle)
StylingConfig.Style(
button = b.fontStyleBold,
spanClass = BoldSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_bold,
hint = R.string.hint_style_bold,
),
StylingConfig.Style(
button = b.fontStyleItalic,
spanClass = ItalicSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_italic,
hint = R.string.hint_style_italic,
),
StylingConfig.Style(
button = b.fontStyleUnderline,
// a custom span is used to prevent issues with keyboards which underline words
spanClass = UnderlineCustomSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_underline,
hint = R.string.hint_style_underline,
),
StylingConfig.Style(
button = b.fontStyleStrike,
spanClass = StrikethroughSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_strikethrough,
hint = R.string.hint_style_strike,
),
StylingConfig.Style(
button = b.fontStyleSubscript,
spanClass = SubscriptSizeSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_subscript,
hint = R.string.hint_style_subscript,
),
StylingConfig.Style(
button = b.fontStyleSuperscript,
spanClass = SuperscriptSizeSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_superscript,
hint = R.string.hint_style_superscript,
),
)
uiConfig = UIConfig( uiConfig = UIConfig(
context = activity, context = activity,
@ -285,28 +250,24 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
) )
stylingConfig = StylingConfig( stylingConfig = StylingConfig(
editText = b.text, editText = b.text,
fontStyleGroup = b.fontStyle, fontStyleGroup = b.fontStyle.styles,
fontStyleClear = b.fontStyleClear, fontStyleClear = b.fontStyle.clear,
styles = styles, styles = styles,
textHtml = if (App.devMode) b.textHtml else null, textHtml = if (App.devMode) b.textHtml else null,
htmlCompatibleMode = app.profile.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK, htmlMode = when (app.profile.loginStoreType) {
LOGIN_TYPE_MOBIDZIENNIK -> COMPATIBLE
else -> ORIGINAL
},
) )
b.fontStyleLayout.isVisible = enableTextStyling b.fontStyle.root.isVisible = enableTextStyling
if (enableTextStyling) { if (enableTextStyling) {
textStylingManager.attach(stylingConfig) textStylingManager.attach(stylingConfig)
b.fontStyle.addOnButtonCheckedListener { _, _, _ -> b.fontStyle.styles.addOnButtonCheckedListener { _, _, _ ->
changedBody = true changedBody = true
} }
} }
if (App.devMode) {
b.textHtml.isVisible = true
b.text.addTextChangedListener {
b.textHtml.text = getMessageBody()
}
}
activity.navView.bottomBar.apply { activity.navView.bottomBar.apply {
fabEnable = true fabEnable = true
fabExtendedText = getString(R.string.messages_compose_send) fabExtendedText = getString(R.string.messages_compose_send)

View File

@ -5,7 +5,6 @@
package pl.szczodrzynski.edziennik.ui.modules.messages.single package pl.szczodrzynski.edziennik.ui.modules.messages.single
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -32,6 +31,7 @@ import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
import pl.szczodrzynski.edziennik.ui.modules.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.list.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -255,7 +255,7 @@ class MessageFragment : Fragment(), CoroutineScope {
} }
} }
messageRecipients.append("</ul>") messageRecipients.append("</ul>")
b.recipients.text = Html.fromHtml(messageRecipients.toString()) b.recipients.text = BetterHtml.fromHtml(activity, messageRecipients)
showAttachments() showAttachments()

View File

@ -4,6 +4,8 @@
package pl.szczodrzynski.edziennik.ui.widgets.timetable; package pl.szczodrzynski.edziennik.ui.widgets.timetable;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -16,7 +18,6 @@ import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Html;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
@ -32,14 +33,13 @@ import com.mikepenz.iconics.utils.IconicsDrawableExtensionsKt;
import java.util.List; import java.util.List;
import kotlin.Unit; import kotlin.Unit;
import pl.szczodrzynski.edziennik.ExtensionsKt;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.utils.models.Date; import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel; import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
import pl.szczodrzynski.edziennik.utils.models.Time; import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.models.Week; import pl.szczodrzynski.edziennik.utils.models.Week;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
public class WidgetTimetableFactory implements RemoteViewsService.RemoteViewsFactory { public class WidgetTimetableFactory implements RemoteViewsService.RemoteViewsFactory {
private static final String TAG = "WidgetTimetableProvider"; private static final String TAG = "WidgetTimetableProvider";
@ -309,17 +309,17 @@ public class WidgetTimetableFactory implements RemoteViewsService.RemoteViewsFac
views.setViewVisibility(R.id.widgetTimetableOldSubjectName, View.GONE); views.setViewVisibility(R.id.widgetTimetableOldSubjectName, View.GONE);
if (lesson.lessonChange) { if (lesson.lessonChange) {
views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<i>"+lesson.subjectName+"</i>")); views.setTextViewText(R.id.widgetTimetableSubjectName, ExtensionsKt.asItalicSpannable(lesson.subjectName));
if (lesson.lessonChangeNoClassroom) { if (lesson.lessonChangeNoClassroom) {
views.setTextViewText(R.id.widgetTimetableClassroomName, Html.fromHtml("<del>"+lesson.classroomName+"</del>")); views.setTextViewText(R.id.widgetTimetableClassroomName, ExtensionsKt.asStrikethroughSpannable(lesson.classroomName));
} }
else { else {
views.setTextViewText(R.id.widgetTimetableClassroomName, Html.fromHtml("<i>" + lesson.classroomName + "</i>")); views.setTextViewText(R.id.widgetTimetableClassroomName, ExtensionsKt.asItalicSpannable(lesson.classroomName));
} }
} }
else if (lesson.lessonCancelled) { else if (lesson.lessonCancelled) {
views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<del>"+lesson.subjectName+"</del>")); views.setTextViewText(R.id.widgetTimetableSubjectName, ExtensionsKt.asStrikethroughSpannable(lesson.subjectName));
views.setTextViewText(R.id.widgetTimetableClassroomName, Html.fromHtml("<del>"+lesson.classroomName+"</del>")); views.setTextViewText(R.id.widgetTimetableClassroomName, ExtensionsKt.asStrikethroughSpannable(lesson.classroomName));
} }
else { else {
views.setTextViewText(R.id.widgetTimetableSubjectName, lesson.subjectName); views.setTextViewText(R.id.widgetTimetableSubjectName, lesson.subjectName);

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-11.
*/
package pl.szczodrzynski.edziennik.utils
import android.text.style.StrikethroughSpan
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.StyledTextButtonsBinding
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig
import pl.szczodrzynski.edziennik.utils.span.*
object DefaultTextStyles {
fun getAsList(b: StyledTextButtonsBinding) = listOf(
StylingConfig.Style(
button = b.bold,
spanClass = BoldSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_bold,
hint = R.string.hint_style_bold,
),
StylingConfig.Style(
button = b.italic,
spanClass = ItalicSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_italic,
hint = R.string.hint_style_italic,
),
StylingConfig.Style(
button = b.underline,
// a custom span is used to prevent issues with keyboards which underline words
spanClass = UnderlineCustomSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_underline,
hint = R.string.hint_style_underline,
),
StylingConfig.Style(
button = b.strike,
spanClass = StrikethroughSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_strikethrough,
hint = R.string.hint_style_strike,
),
StylingConfig.Style(
button = b.subscript,
spanClass = SubscriptSizeSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_subscript,
hint = R.string.hint_style_subscript,
),
StylingConfig.Style(
button = b.superscript,
spanClass = SuperscriptSizeSpan::class.java,
icon = CommunityMaterial.Icon2.cmd_format_superscript,
hint = R.string.hint_style_superscript,
),
)
}

View File

@ -32,8 +32,14 @@ object BetterHtml {
SuperscriptSizeSpan::class.java, SuperscriptSizeSpan::class.java,
) )
fun fromHtml(context: Context, stringRes: Int) = fromHtml(
context,
context.getString(stringRes),
nl2br = true,
)
@JvmStatic @JvmStatic
fun fromHtml(context: Context, html: String): Spanned { fun fromHtml(context: Context?, html: CharSequence, nl2br: Boolean = false): Spanned {
val hexPattern = "(#[a-fA-F0-9]{6})" val hexPattern = "(#[a-fA-F0-9]{6})"
val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})" val colorRegex = "(?:color=\"$hexPattern\")|(?:style=\"color: ?${hexPattern})"
.toRegex(RegexOption.IGNORE_CASE) .toRegex(RegexOption.IGNORE_CASE)
@ -42,29 +48,35 @@ object BetterHtml {
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "") .replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
.replace("background-color: ?$hexPattern;".toRegex(), "") .replace("background-color: ?$hexPattern;".toRegex(), "")
val colorBackground = android.R.attr.colorBackground.resolveAttr(context) if (nl2br) {
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff text = text.replace("\n", "<br>")
}
colorRegex.findAll(text).forEach { result -> if (context != null) {
val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach val colorBackground = android.R.attr.colorBackground.resolveAttr(context)
val textColorPrimary = android.R.attr.textColorPrimary.resolveAttr(context) and 0xffffff
val color = Color.parseColor(group.value) colorRegex.findAll(text).forEach { result ->
var newColor = 0xff000000.toInt() or color val group = result.groups.drop(1).firstOrNull { it != null } ?: return@forEach
var blendAmount = 1 val color = Color.parseColor(group.value)
var numIterations = 0 var newColor = 0xff000000.toInt() or color
while (numIterations < 100 && ColorUtils.calculateContrast( var blendAmount = 1
colorBackground, var numIterations = 0
newColor
) < 4.5f while (numIterations < 100 && ColorUtils.calculateContrast(
) { colorBackground,
blendAmount += 2 newColor
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary) ) < 4.5f
numIterations++ ) {
blendAmount += 2
newColor = blendColors(color, blendAmount shl 24 or textColorPrimary)
numIterations++
}
text = text.replaceRange(group.range, "#" + (newColor and 0xffffff).toString(16))
} }
text = text.replaceRange(group.range, "#" + (newColor and 0xffffff).toString(16))
} }
/*val olRegex = """<ol>(.+?)</\s*?ol>""" /*val olRegex = """<ol>(.+?)</\s*?ol>"""

View File

@ -45,16 +45,13 @@ class EventManager(val app: App) : CoroutineScope {
showType: Boolean = true, showType: Boolean = true,
doneIconColor: Int? = null doneIconColor: Int? = null
) { ) {
var eventTopic = if (showType) val topicSpan = event.topicHtml
"${event.typeName ?: "wydarzenie"} - ${event.topic}"
else
event.topic
if (event.addedManually) { title.text = listOfNotNull(
eventTopic = "{cmd-clipboard-edit-outline} $eventTopic" if (event.addedManually) "{cmd-clipboard-edit-outline} " else null,
} if (showType) "${event.typeName ?: "wydarzenie"} - " else null,
topicSpan,
title.text = eventTopic ).concat()
title.setCompoundDrawables( title.setCompoundDrawables(
null, null,

View File

@ -25,6 +25,7 @@ import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.ORIGINAL
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfig
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -148,7 +149,7 @@ class MessageManager(private val app: App) {
suspend fun saveAsDraft(config: UIConfig, stylingConfig: StylingConfig, profileId: Int, messageId: Long?) { suspend fun saveAsDraft(config: UIConfig, stylingConfig: StylingConfig, profileId: Int, messageId: Long?) {
val teachers = config.recipients.allChips.mapNotNull { it.data as? Teacher } val teachers = config.recipients.allChips.mapNotNull { it.data as? Teacher }
val subject = config.subject.text?.toString() ?: "" val subject = config.subject.text?.toString() ?: ""
val body = textStylingManager.getHtmlText(stylingConfig, enableHtmlCompatible = false) val body = textStylingManager.getHtmlText(stylingConfig, htmlMode = ORIGINAL)
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
if (messageId != null) { if (messageId != null) {

View File

@ -6,18 +6,25 @@ package pl.szczodrzynski.edziennik.utils.managers
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.style.StrikethroughSpan
import android.text.style.SubscriptSpan
import android.text.style.SuperscriptSpan
import android.text.style.UnderlineSpan
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.button.MaterialButtonToggleGroup
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.attachToastHint
import pl.szczodrzynski.edziennik.hasSet
import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.*
import pl.szczodrzynski.edziennik.utils.span.BoldSpan
import pl.szczodrzynski.edziennik.utils.span.ItalicSpan
class TextStylingManager(private val app: App) { class TextStylingManager(private val app: App) {
companion object { companion object {
@ -28,14 +35,45 @@ class TextStylingManager(private val app: App) {
"((?:<br>)+)</p>".toRegex() "((?:<br>)+)</p>".toRegex()
} }
data class StylingConfig( enum class HtmlMode {
/**
* The default mode, suitable for fromHtml conversion.
*/
ORIGINAL,
/**
* A more browser-compatible mode.
*/
COMPATIBLE,
/**
* A simple, paragraph-stripped mode with \n instead of <br>.
* The converted text has no HTML tags when no spans in source.
*/
SIMPLE,
/**
* Markdown-compatible text mode.
*/
MARKDOWN,
}
open class StylingConfigBase(
val editText: TextInputKeyboardEdit, val editText: TextInputKeyboardEdit,
val htmlMode: HtmlMode = ORIGINAL,
) {
var watchStyleChecked = true
var watchSelectionChanged = true
}
class StylingConfig(
editText: TextInputKeyboardEdit,
val fontStyleGroup: MaterialButtonToggleGroup, val fontStyleGroup: MaterialButtonToggleGroup,
val fontStyleClear: Button, val fontStyleClear: Button,
val styles: List<Style>, val styles: List<Style>,
val textHtml: TextView? = null, val textHtml: TextView? = null,
val htmlCompatibleMode: Boolean = false, htmlMode: HtmlMode = ORIGINAL,
) { ) : StylingConfigBase(editText, htmlMode) {
data class Style( data class Style(
val button: MaterialButton, val button: MaterialButton,
val spanClass: Class<*>, val spanClass: Class<*>,
@ -45,9 +83,6 @@ class TextStylingManager(private val app: App) {
) { ) {
fun newInstance(): Any = spanClass.newInstance() fun newInstance(): Any = spanClass.newInstance()
} }
var watchStyleChecked = true
var watchSelectionChanged = true
} }
fun attach(config: StylingConfig) { fun attach(config: StylingConfig) {
@ -76,6 +111,14 @@ class TextStylingManager(private val app: App) {
onSelectionChanged(config, selectionStart, selectionEnd) onSelectionChanged(config, selectionStart, selectionEnd)
} }
if (config.textHtml != null) {
config.editText.addTextChangedListener {
config.textHtml.text = getHtmlText(config)
}
config.textHtml.isVisible = true
config.textHtml.text = getHtmlText(config)
}
/*b.fontStyleBold.shapeAppearanceModel = b.fontStyleBold.shapeAppearanceModel /*b.fontStyleBold.shapeAppearanceModel = b.fontStyleBold.shapeAppearanceModel
.toBuilder() .toBuilder()
.setBottomLeftCornerSize(0f) .setBottomLeftCornerSize(0f)
@ -91,15 +134,14 @@ class TextStylingManager(private val app: App) {
.build()*/ .build()*/
} }
fun getHtmlText(config: StylingConfig, enableHtmlCompatible: Boolean = true): String { fun getHtmlText(config: StylingConfigBase, htmlMode: HtmlMode = config.htmlMode): String {
val text = config.editText.text?.trimEnd() ?: return "" val text = config.editText.text?.trimEnd() ?: return ""
val spanned = SpannableStringBuilder(text) val spanned = SpannableStringBuilder(text)
val htmlCompatibleMode = config.htmlCompatibleMode && enableHtmlCompatible val toHtmlFlag = when (htmlMode) {
val toHtmlFlag = if (htmlCompatibleMode) COMPATIBLE -> HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL
HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL else -> HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE
else }
HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE
// apparently setting the spans to a different Spannable calls the original EditText's // apparently setting the spans to a different Spannable calls the original EditText's
// onSelectionChanged with selectionStart=-1, which in effect unchecks the format toggles // onSelectionChanged with selectionStart=-1, which in effect unchecks the format toggles
@ -113,24 +155,41 @@ class TextStylingManager(private val app: App) {
if (spanStart == spanEnd && it::class.java in BetterHtml.customSpanClasses) if (spanStart == spanEnd && it::class.java in BetterHtml.customSpanClasses)
spanned.removeSpan(it) spanned.removeSpan(it)
} }
var textHtml = HtmlCompat.toHtml(spanned, toHtmlFlag)
.replace("\n", "") var textHtml = when (htmlMode) {
.replace(" dir=\"ltr\"", "") SIMPLE -> spanned
.replace("</b><b>", "") .replaceSpan(BoldSpan::class.java, "<b>", "</b>")
.replace("</i><i>", "") .replaceSpan(ItalicSpan::class.java, "<i>", "</i>")
.replace("</u><u>", "") .replaceSpan(UnderlineSpan::class.java, "<u>", "</u>")
.replace("</sub><sub>", "") .replaceSpan(StrikethroughSpan::class.java, "<s>", "</s>")
.replace("</sup><sup>", "") .replaceSpan(SubscriptSpan::class.java, "<sub>", "</sub>")
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "p") .replaceSpan(SuperscriptSpan::class.java, "<sup>", "</sup>")
.replace("<br></p>", "</p><br>") .toString()
// replace multiple newlines so they convert fromHtml correctly MARKDOWN -> spanned
// this should not be breaking with htmlCompatibleMode == true, .replaceSpan(BoldSpan::class.java, "**", "**")
// as line breaks cannot occur inside paragraphs with these flags .replaceSpan(ItalicSpan::class.java, "_", "_")
.replace(paragraphBrRegex, "</p>$1") .replaceSpan(UnderlineSpan::class.java, "__", "__")
.replaceSpan(StrikethroughSpan::class.java, "~~", "~~")
.toString()
else -> HtmlCompat.toHtml(spanned, toHtmlFlag)
.replace("\n", "")
.replace(" dir=\"ltr\"", "")
.replace("</b><b>", "")
.replace("</i><i>", "")
.replace("</u><u>", "")
.replace("</sub><sub>", "")
.replace("</sup><sup>", "")
.replace("p style=\"margin-top:0; margin-bottom:0;\"", "p")
.replace("<br></p>", "</p><br>")
// replace multiple newlines so they convert fromHtml correctly
// this should not be breaking with htmlCompatibleMode == true,
// as line breaks cannot occur inside paragraphs with these flags
.replace(paragraphBrRegex, "</p>$1")
}
config.watchSelectionChanged = true config.watchSelectionChanged = true
if (htmlCompatibleMode) { if (htmlMode == COMPATIBLE) {
textHtml = textHtml textHtml = textHtml
.replace("<br>", "<p>&nbsp;</p>") .replace("<br>", "<p>&nbsp;</p>")
.replace("<b>", "<strong>") .replace("<b>", "<strong>")
@ -147,7 +206,7 @@ class TextStylingManager(private val app: App) {
private fun onStyleChecked( private fun onStyleChecked(
config: StylingConfig, config: StylingConfig,
style: StylingConfig.Style, style: StylingConfig.Style,
isChecked: Boolean isChecked: Boolean,
) { ) {
if (!config.watchStyleChecked) if (!config.watchStyleChecked)
return return

View File

@ -164,7 +164,6 @@
android:id="@+id/topic" android:id="@+id/topic"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{event.topic}"
android:textAppearance="@style/NavView.TextView.Medium" android:textAppearance="@style/NavView.TextView.Medium"
android:textIsSelectable="true" android:textIsSelectable="true"
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." /> tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />
@ -190,7 +189,6 @@
android:id="@+id/body" android:id="@+id/body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{event.homeworkBody}"
android:textAppearance="@style/NavView.TextView.Medium" android:textAppearance="@style/NavView.TextView.Medium"
android:textIsSelectable="true" android:textIsSelectable="true"
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." /> tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />

View File

@ -118,11 +118,14 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/topicLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_topic"> android:hint="@string/dialog_event_manual_topic"
app:endIconMode="custom"
tools:endIconDrawable="@android:drawable/ic_menu_crop">
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit <pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/topic" android:id="@+id/topic"

View File

@ -8,7 +8,7 @@
<data> <data>
<import type="android.view.View" /> <import type="android.view.View" />
<import type="androidx.core.text.HtmlCompat" /> <import type="pl.szczodrzynski.edziennik.utils.html.BetterHtml" />
<variable <variable
name="message" name="message"
type="pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus.Message" /> type="pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus.Message" />
@ -43,7 +43,7 @@
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{HtmlCompat.fromHtml(message.title, HtmlCompat.FROM_HTML_MODE_LEGACY)}" android:text="@{BetterHtml.fromHtml(context, message.title, false)}"
android:textAppearance="@style/NavView.TextView.Title" android:textAppearance="@style/NavView.TextView.Title"
tools:text="Dziennik nie działa" /> tools:text="Dziennik nie działa" />
@ -52,7 +52,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="@{HtmlCompat.fromHtml(message.contentLong, HtmlCompat.FROM_HTML_MODE_LEGACY)}" android:text="@{BetterHtml.fromHtml(context, message.contentLong, false)}"
tools:text="Dziennik się zepsuł i nie działa, szkoda\n\n\nwiele linijek ma ten tekst" /> tools:text="Dziennik się zepsuł i nie działa, szkoda\n\n\nwiele linijek ma ten tekst" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@ -95,7 +95,7 @@
tools:visibility="visible" /> tools:visibility="visible" />
</LinearLayout> </LinearLayout>
<TextView <com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/addedBy" android:id="@+id/addedBy"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -2,202 +2,106 @@
<!-- <!--
~ Copyright (c) Kuba Szczodrzyński 2019-12-22. ~ Copyright (c) Kuba Szczodrzyński 2019-12-22.
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<layout 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:layout_width="match_parent"
android:layout_height="match_parent">
<data> <LinearLayout
<import type="com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial" />
</data>
<ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:orientation="vertical"
<LinearLayout android:paddingBottom="40dp"><!-- half of the FAB's size -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/recipientsLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" app:boxBackgroundColor="@android:color/transparent"
android:paddingBottom="40dp"><!-- half of the FAB's size --> app:boxBackgroundMode="filled"
<com.google.android.material.textfield.TextInputLayout app:endIconDrawable="@drawable/dropdown_arrow"
android:id="@+id/recipientsLayout" app:endIconMode="custom">
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
<com.hootsuite.nachos.NachoTextView
android:id="@+id/recipients"
style="@style/Widget.MaterialComponents.TextInputEditText.FilledBox.Dense"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:boxBackgroundColor="@android:color/transparent" android:focusable="true"
app:boxBackgroundMode="filled" android:focusableInTouchMode="true"
app:endIconDrawable="@drawable/dropdown_arrow" android:focusedByDefault="true"
app:endIconMode="custom"> android:hint="@string/messages_compose_to_hint" />
</com.google.android.material.textfield.TextInputLayout>
<com.hootsuite.nachos.NachoTextView <com.google.android.material.textfield.TextInputLayout
android:id="@+id/recipients" android:id="@+id/subjectLayout"
style="@style/Widget.MaterialComponents.TextInputEditText.FilledBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusable="true" app:boxBackgroundColor="@android:color/transparent"
android:focusableInTouchMode="true" app:boxBackgroundMode="filled"
android:focusedByDefault="true" app:counterEnabled="true"
android:hint="@string/messages_compose_to_hint" /> tools:counterMaxLength="180">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/subjectLayout" android:id="@+id/subject"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox" style="@style/Widget.MaterialComponents.TextInputEditText.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:boxBackgroundColor="@android:color/transparent" android:focusable="true"
app:boxBackgroundMode="filled" android:focusableInTouchMode="true"
app:counterEnabled="true" android:hint="@string/messages_compose_subject_hint"
tools:counterMaxLength="180"> android:inputType="textCapSentences|textAutoCorrect|textShortMessage|textAutoComplete|textEmailSubject"
android:singleLine="true"
tools:text="kachoomba" />
</com.google.android.material.textfield.TextInputLayout>
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit <com.google.android.material.textfield.TextInputLayout
android:id="@+id/subject" android:id="@+id/textLayout"
style="@style/Widget.MaterialComponents.TextInputEditText.FilledBox" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" app:boxBackgroundColor="@android:color/transparent"
android:focusable="true" app:boxBackgroundMode="filled"
android:focusableInTouchMode="true" app:counterEnabled="true"
android:hint="@string/messages_compose_subject_hint" tools:counterMaxLength="1983">
android:inputType="textCapSentences|textAutoCorrect|textShortMessage|textAutoComplete|textEmailSubject"
android:singleLine="true"
tools:text="kachoomba" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/textLayout" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:boxBackgroundColor="@android:color/transparent" android:ems="10"
app:boxBackgroundMode="filled" android:focusable="true"
app:counterEnabled="true" android:focusableInTouchMode="true"
tools:counterMaxLength="1983"> android:gravity="start|top"
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&quot;To jest tak, ale nie. Pamiętaj żeby oczywiście&quot;\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." />
</com.google.android.material.textfield.TextInputLayout>
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit <include
android:id="@+id/text" android:id="@+id/fontStyle"
android:layout_width="match_parent" layout="@layout/styled_text_buttons" />
android:layout_height="wrap_content"
android:ems="10"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="start|top"
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&quot;To jest tak, ale nie. Pamiętaj żeby oczywiście&quot;\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." />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout <Button
android:id="@+id/fontStyleLayout" android:id="@+id/breakpoints"
android:layout_width="wrap_content" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_gravity="center_horizontal" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginHorizontal="8dp"
android:text="Breakpoints!"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButtonToggleGroup <TextView
android:id="@+id/fontStyle" android:id="@+id/textHtml"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"> android:layout_margin="8dp"
android:fontFamily="monospace"
<com.google.android.material.button.MaterialButton android:visibility="gone"
android:id="@+id/fontStyleBold" tools:text="&lt;p&gt;Witam,&lt;/p&gt;To jest bardzo długi tekst żeby sprawdzić ok działa"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" tools:visibility="visible" />
android:layout_width="wrap_content" </LinearLayout>
android:layout_height="wrap_content" </ScrollView>
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf674" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleItalic"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf691" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleUnderline"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6c5" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleStrike"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleSubscript"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleSuperscript"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b3" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<!-- TODO 2021-10-05: stack the group and button on top -->
<!-- android:layout_marginTop="-13dp" -->
<com.google.android.material.button.MaterialButton
android:id="@+id/fontStyleClear"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="@string/messages_compose_style_clear" />
</LinearLayout>
<Button
android:id="@+id/breakpoints"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="Breakpoints!"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/textHtml"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="monospace"
android:visibility="gone"
tools:text="&lt;p&gt;Witam,&lt;/p&gt;To jest bardzo długi tekst żeby sprawdzić ok działa"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>
</layout>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-10-11.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/styles"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/bold"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf674" />
<com.google.android.material.button.MaterialButton
android:id="@+id/italic"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf691" />
<com.google.android.material.button.MaterialButton
android:id="@+id/underline"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6c5" />
<com.google.android.material.button.MaterialButton
android:id="@+id/strike"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/subscript"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/superscript"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/community_material_font_v5_8_55"
android:minWidth="48dp"
android:textSize="20sp"
app:iconPadding="0dp"
tools:text="\uf6b3" />
</com.google.android.material.button.MaterialButtonToggleGroup>
</HorizontalScrollView>
<!-- TODO 2021-10-05: stack the group and button on top -->
<!-- android:layout_marginTop="-13dp" -->
<com.google.android.material.button.MaterialButton
android:id="@+id/clear"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="@string/styled_text_clear_button" />
</LinearLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-10-11.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="250dp"
app:counterEnabled="true">
<pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:inputType="textMultiLine|textAutoCorrect|textLongMessage|textAutoComplete|textCapSentences"
android:minLines="10"
tools:text="Witam,\n\nchciałem przekazać bardzo ważną wiadomość.\nJest to cytat znanej osoby.\n\n&quot;To jest tak, ale nie. Pamiętaj żeby oczywiście&quot;\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." />
</com.google.android.material.textfield.TextInputLayout>
<include
android:id="@+id/fontStyle"
layout="@layout/styled_text_buttons" />
<TextView
android:id="@+id/htmlText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:visibility="gone" />
</LinearLayout>
</ScrollView>

View File

@ -1470,7 +1470,7 @@
<string name="hint_style_strike">Przekreślenie</string> <string name="hint_style_strike">Przekreślenie</string>
<string name="hint_style_subscript">Indeks dolny</string> <string name="hint_style_subscript">Indeks dolny</string>
<string name="hint_style_superscript">Indeks górny</string> <string name="hint_style_superscript">Indeks górny</string>
<string name="messages_compose_style_clear">Wyczyść format</string> <string name="styled_text_clear_button">Wyczyść format</string>
<string name="hint_message_star">Oznacz gwiazdką</string> <string name="hint_message_star">Oznacz gwiazdką</string>
<string name="message_forward">Przekaż dalej</string> <string name="message_forward">Przekaż dalej</string>
<string name="message_delete">Usuń</string> <string name="message_delete">Usuń</string>
@ -1491,4 +1491,5 @@
<string name="messages_compose_discard_draft_text">Czy chcesz odrzucić zapisaną wersję wiadomości? Spowoduje to również anulowanie wprowadzonych zmian i usunięcie wiadomości.</string> <string name="messages_compose_discard_draft_text">Czy chcesz odrzucić zapisaną wersję wiadomości? Spowoduje to również anulowanie wprowadzonych zmian i usunięcie wiadomości.</string>
<string name="login_mobidziennik_server_prefix">https://</string> <string name="login_mobidziennik_server_prefix">https://</string>
<string name="login_mobidziennik_server_suffix">.mobidziennik.pl/</string> <string name="login_mobidziennik_server_suffix">.mobidziennik.pl/</string>
<string name="styled_text_dialog_title">Edytuj tekst</string>
</resources> </resources>