mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-01-31 05:48:19 +01:00
[Messages] Add support for starring messages. (#86)
* [UI/Messages] Add stars to message list layout. * [Messages] Move text styling-related code to a manager. * [Messages] Implement starring messages. Move more code to the manager. Update UI. * [UI] Add padding to the no data text. * [Messages] Fix checking sent message recipient read state. * [Messages] Add star icon padding.
This commit is contained in:
parent
a6aca42c8c
commit
44263ac95f
@ -72,6 +72,8 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
|
|||||||
val attendanceManager by lazy { AttendanceManager(this) }
|
val attendanceManager by lazy { AttendanceManager(this) }
|
||||||
val buildManager by lazy { BuildManager(this) }
|
val buildManager by lazy { BuildManager(this) }
|
||||||
val availabilityManager by lazy { AvailabilityManager(this) }
|
val availabilityManager by lazy { AvailabilityManager(this) }
|
||||||
|
val textStylingManager by lazy { TextStylingManager(this) }
|
||||||
|
val messageManager by lazy { MessageManager(this) }
|
||||||
|
|
||||||
val db
|
val db
|
||||||
get() = App.db
|
get() = App.db
|
||||||
|
@ -814,6 +814,8 @@ fun View.attachToastHint(stringRes: Int) = onLongClick {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.detachToastHint() = setOnLongClickListener(null)
|
||||||
|
|
||||||
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
|
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
|
||||||
observe(lifecycleOwner, object : Observer<T> {
|
observe(lifecycleOwner, object : Observer<T> {
|
||||||
override fun onChanged(t: T?) {
|
override fun onChanged(t: T?) {
|
||||||
|
@ -29,7 +29,7 @@ abstract class MessageDao : BaseDao<Message, MessageFull> {
|
|||||||
LEFT JOIN metadata ON messageId = thingId AND thingType = ${Metadata.TYPE_MESSAGE} AND metadata.profileId = messages.profileId
|
LEFT JOIN metadata ON messageId = thingId AND thingType = ${Metadata.TYPE_MESSAGE} AND metadata.profileId = messages.profileId
|
||||||
"""
|
"""
|
||||||
|
|
||||||
private const val ORDER_BY = """ORDER BY messageIsPinned, addedDate DESC"""
|
private const val ORDER_BY = """ORDER BY messageIsPinned DESC, addedDate DESC"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private val selective by lazy { MessageDaoSelective(App.db) }
|
private val selective by lazy { MessageDaoSelective(App.db) }
|
||||||
|
@ -41,7 +41,7 @@ open class Message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ColumnInfo(name = "messageIsPinned")
|
@ColumnInfo(name = "messageIsPinned")
|
||||||
var isPinned: Boolean = false
|
var isStarred: Boolean = false
|
||||||
|
|
||||||
var hasAttachments = false // if the attachments are not yet downloaded but we already know there are some
|
var hasAttachments = false // if the attachments are not yet downloaded but we already know there are some
|
||||||
get() = field || attachmentIds.isNotNullNorEmpty()
|
get() = field || attachmentIds.isNotNullNorEmpty()
|
||||||
|
@ -31,6 +31,8 @@ class MessageFull(
|
|||||||
var filterWeight = 0
|
var filterWeight = 0
|
||||||
@Ignore
|
@Ignore
|
||||||
var searchHighlightText: CharSequence? = null
|
var searchHighlightText: CharSequence? = null
|
||||||
|
@Ignore
|
||||||
|
var readByEveryone = true
|
||||||
|
|
||||||
// metadata
|
// metadata
|
||||||
var seen = false
|
var seen = false
|
||||||
|
@ -27,7 +27,6 @@ import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
|
|||||||
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore.Companion.LOGIN_TYPE_IDZIENNIK
|
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore.Companion.LOGIN_TYPE_IDZIENNIK
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED
|
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
|
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
|
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessageFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
||||||
@ -53,6 +52,8 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
private val manager
|
||||||
|
get() = app.messageManager
|
||||||
private lateinit var message: MessageFull
|
private lateinit var message: MessageFull
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
@ -92,6 +93,35 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
it.maxLines = if (it.maxLines == 30) 2 else 30
|
it.maxLines = if (it.maxLines == 30) 2 else 30
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val replyDrawable = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_reply_outline).apply {
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||||
|
}
|
||||||
|
val forwardDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_arrow_right).apply {
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||||
|
}
|
||||||
|
val deleteDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_delete_outline).apply {
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||||
|
}
|
||||||
|
val downloadDrawable = IconicsDrawable(activity, CommunityMaterial.Icon.cmd_download_outline).apply {
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, android.R.attr.textColorPrimary)
|
||||||
|
}
|
||||||
|
b.replyButton.setCompoundDrawables(null, replyDrawable, null, null)
|
||||||
|
b.forwardButton.setCompoundDrawables(null, forwardDrawable, null, null)
|
||||||
|
b.deleteButton.setCompoundDrawables(null, deleteDrawable, null, null)
|
||||||
|
b.downloadButton.setCompoundDrawables(null, downloadDrawable, null, null)
|
||||||
|
|
||||||
|
b.messageStar.onClick {
|
||||||
|
launch {
|
||||||
|
manager.starMessage(message, !message.isStarred)
|
||||||
|
manager.setStarIcon(b.messageStar, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.messageStar.attachToastHint(R.string.hint_message_star)
|
||||||
|
|
||||||
b.replyButton.onClick {
|
b.replyButton.onClick {
|
||||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE, Bundle(
|
activity.loadTarget(MainActivity.TARGET_MESSAGES_COMPOSE, Bundle(
|
||||||
"message" to app.gson.toJson(message),
|
"message" to app.gson.toJson(message),
|
||||||
@ -110,10 +140,7 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
.setMessage(R.string.messages_delete_confirmation_text)
|
.setMessage(R.string.messages_delete_confirmation_text)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
launch {
|
launch {
|
||||||
message.type = TYPE_DELETED
|
manager.markAsDeleted(message)
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
app.db.messageDao().replace(message)
|
|
||||||
}
|
|
||||||
Toast.makeText(activity, "Wiadomość przeniesiona do usuniętych", Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, "Wiadomość przeniesiona do usuniętych", Toast.LENGTH_SHORT).show()
|
||||||
activity.navigateUp()
|
activity.navigateUp()
|
||||||
}
|
}
|
||||||
@ -127,59 +154,10 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
|
message = manager.getMessage(App.profileId, arguments) ?: run {
|
||||||
val messageString = arguments?.getString("message")
|
|
||||||
val messageId = arguments?.getLong("messageId")
|
|
||||||
if (messageId == null) {
|
|
||||||
activity.navigateUp()
|
activity.navigateUp()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val msg = withContext(Dispatchers.Default) {
|
|
||||||
|
|
||||||
val msg =
|
|
||||||
if (messageString != null)
|
|
||||||
app.gson.fromJson(messageString, MessageFull::class.java)?.also {
|
|
||||||
if (arguments?.getLong("sentDate") ?: 0L > 0L)
|
|
||||||
it.addedDate = arguments?.getLong("sentDate") ?: 0L
|
|
||||||
}
|
|
||||||
else
|
|
||||||
app.db.messageDao().getByIdNow(App.profileId, messageId)
|
|
||||||
|
|
||||||
if (msg == null)
|
|
||||||
return@withContext null
|
|
||||||
|
|
||||||
// make recipients ID-unique
|
|
||||||
// this helps when multiple profiles receive the same message
|
|
||||||
// (there are multiple -1 recipients for the same message ID)
|
|
||||||
val recipientsDistinct =
|
|
||||||
msg.recipients?.distinctBy { it.id } ?: return@withContext null
|
|
||||||
msg.recipients?.clear()
|
|
||||||
msg.recipients?.addAll(recipientsDistinct)
|
|
||||||
|
|
||||||
// load recipients in sent messages
|
|
||||||
val teachers by lazy { app.db.teacherDao().getAllNow(App.profileId) }
|
|
||||||
msg.recipients?.forEach { recipient ->
|
|
||||||
if (recipient.fullName == null) {
|
|
||||||
recipient.fullName = teachers.firstOrNull { it.id == recipient.id }?.fullName ?: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.type == TYPE_SENT && msg.senderName == null) {
|
|
||||||
msg.senderName = app.profile.accountName ?: app.profile.studentNameLong
|
|
||||||
}
|
|
||||||
|
|
||||||
//msg.recipients = app.db.messageRecipientDao().getAllByMessageId(msg.profileId, msg.id)
|
|
||||||
if (msg.body != null && !msg.seen) {
|
|
||||||
app.db.metadataDao().setSeen(msg.profileId, msg, true)
|
|
||||||
}
|
|
||||||
return@withContext msg
|
|
||||||
} ?: run {
|
|
||||||
activity.navigateUp()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
message = msg
|
|
||||||
b.subject.text = message.subject
|
b.subject.text = message.subject
|
||||||
checkMessage()
|
checkMessage()
|
||||||
}
|
}
|
||||||
@ -208,7 +186,6 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val readByAll = checkRecipients()
|
|
||||||
if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_VULCAN) {
|
if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_VULCAN) {
|
||||||
// vulcan: change message status or download attachments
|
// vulcan: change message status or download attachments
|
||||||
if (message.type == TYPE_RECEIVED && !message.seen || message.attachmentIds == null) {
|
if (message.type == TYPE_RECEIVED && !message.seen || message.attachmentIds == null) {
|
||||||
@ -216,7 +193,7 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!readByAll) {
|
else if (!message.readByEveryone) {
|
||||||
// if a sent msg is not read by everyone, download it again to check the read status
|
// if a sent msg is not read by everyone, download it again to check the read status
|
||||||
EdziennikTask.messageGet(App.profileId, message).enqueue(activity)
|
EdziennikTask.messageGet(App.profileId, message).enqueue(activity)
|
||||||
return
|
return
|
||||||
@ -225,16 +202,6 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
showMessage()
|
showMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkRecipients(): Boolean {
|
|
||||||
message.recipients?.forEach { recipient ->
|
|
||||||
if (recipient.id == -1L)
|
|
||||||
recipient.fullName = app.profile.accountName ?: app.profile.studentNameLong ?: ""
|
|
||||||
if (message.type == TYPE_SENT && recipient.readDate < 1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showMessage() {
|
private fun showMessage() {
|
||||||
b.body.text = MessagesUtils.htmlToSpannable(activity, message.body.toString())
|
b.body.text = MessagesUtils.htmlToSpannable(activity, message.body.toString())
|
||||||
b.date.text = getString(R.string.messages_date_time_format, Date.fromMillis(message.addedDate).formattedStringShort, Time.fromMillis(message.addedDate).stringHM)
|
b.date.text = getString(R.string.messages_date_time_format, Date.fromMillis(message.addedDate).formattedStringShort, Time.fromMillis(message.addedDate).stringHM)
|
||||||
@ -245,6 +212,8 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
|
|
||||||
b.subject.text = message.subject
|
b.subject.text = message.subject
|
||||||
|
|
||||||
|
manager.setStarIcon(b.messageStar, message)
|
||||||
|
|
||||||
b.replyButton.isVisible = message.type == TYPE_RECEIVED || message.type == TYPE_DELETED
|
b.replyButton.isVisible = message.type == TYPE_RECEIVED || message.type == TYPE_DELETED
|
||||||
b.deleteButton.isVisible = message.type == TYPE_RECEIVED
|
b.deleteButton.isVisible = message.type == TYPE_RECEIVED
|
||||||
if (message.type == TYPE_RECEIVED || message.type == TYPE_DELETED) {
|
if (message.type == TYPE_RECEIVED || message.type == TYPE_DELETED) {
|
||||||
@ -255,9 +224,9 @@ class MessageFragment : Fragment(), CoroutineScope {
|
|||||||
fabIcon = CommunityMaterial.Icon3.cmd_reply_outline
|
fabIcon = CommunityMaterial.Icon3.cmd_reply_outline
|
||||||
}
|
}
|
||||||
|
|
||||||
setFabOnClickListener(View.OnClickListener {
|
setFabOnClickListener {
|
||||||
b.replyButton.performClick()
|
b.replyButton.performClick()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
activity.gainAttentionFAB()
|
activity.gainAttentionFAB()
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
class MessagesAdapter(
|
class MessagesAdapter(
|
||||||
val activity: AppCompatActivity,
|
val activity: AppCompatActivity,
|
||||||
val teachers: List<Teacher>,
|
val teachers: List<Teacher>,
|
||||||
val onItemClick: ((item: MessageFull) -> Unit)? = null
|
val onItemClick: ((item: MessageFull) -> Unit)? = null,
|
||||||
|
val onStarClick: ((item: MessageFull) -> Unit)? = null,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope, Filterable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MessagesAdapter"
|
private const val TAG = "MessagesAdapter"
|
||||||
@ -32,13 +33,17 @@ class MessagesAdapter(
|
|||||||
|
|
||||||
private val app = activity.applicationContext as App
|
private val app = activity.applicationContext as App
|
||||||
// optional: place the manager here
|
// optional: place the manager here
|
||||||
|
internal val manager
|
||||||
|
get() = app.messageManager
|
||||||
|
|
||||||
private val job = Job()
|
private val job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
// mutable var changed by the filter
|
||||||
var items = listOf<Any>()
|
var items = listOf<Any>()
|
||||||
var allItems = listOf<Any>()
|
// mutable list managed by the fragment
|
||||||
|
val allItems = mutableListOf<Any>()
|
||||||
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
val typefaceNormal: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) }
|
||||||
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
val typefaceBold: Typeface by lazy { Typeface.create(Typeface.DEFAULT, Typeface.BOLD) }
|
||||||
|
|
||||||
|
@ -12,13 +12,11 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
import pl.szczodrzynski.edziennik.databinding.MessagesListFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.MessagesListFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
import pl.szczodrzynski.edziennik.ui.modules.messages.models.MessagesSearch
|
||||||
@ -33,13 +31,15 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
private lateinit var activity: MainActivity
|
private lateinit var activity: MainActivity
|
||||||
private lateinit var b: MessagesListFragmentBinding
|
private lateinit var b: MessagesListFragmentBinding
|
||||||
private var adapter: MessagesAdapter? = null
|
private lateinit var adapter: MessagesAdapter
|
||||||
|
|
||||||
private val job: Job = Job()
|
private val job: Job = Job()
|
||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
// local/private variables go here
|
// local/private variables go here
|
||||||
|
private val manager
|
||||||
|
get() = app.messageManager
|
||||||
var teachers = listOf<Teacher>()
|
var teachers = listOf<Teacher>()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
@ -54,22 +54,28 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
|
val messageType = arguments.getInt("messageType", Message.TYPE_RECEIVED)
|
||||||
var topPosition = arguments.getInt("topPosition", NO_POSITION)
|
var topPosition = arguments.getInt("topPosition", NO_POSITION)
|
||||||
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
|
var bottomPosition = arguments.getInt("bottomPosition", NO_POSITION)
|
||||||
val searchText = arguments.getString("searchText", "")
|
val searchText = arguments?.getString("searchText")
|
||||||
|
|
||||||
teachers = withContext(Dispatchers.Default) {
|
teachers = withContext(Dispatchers.Default) {
|
||||||
app.db.teacherDao().getAllNow(App.profileId)
|
app.db.teacherDao().getAllNow(App.profileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = MessagesAdapter(activity, teachers) {
|
adapter = MessagesAdapter(activity, teachers, onItemClick = {
|
||||||
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
|
activity.loadTarget(MainActivity.TARGET_MESSAGES_DETAILS, Bundle(
|
||||||
"messageId" to it.id
|
"messageId" to it.id
|
||||||
))
|
))
|
||||||
}
|
}, onStarClick = {
|
||||||
|
this@MessagesListFragment.launch {
|
||||||
|
manager.starMessage(it, !it.isStarred)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
|
app.db.messageDao().getAllByType(App.profileId, messageType).observe(this@MessagesListFragment, Observer { messages ->
|
||||||
if (!isAdded) return@Observer
|
if (!isAdded || !this@MessagesListFragment::adapter.isInitialized)
|
||||||
|
return@Observer
|
||||||
|
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
|
// uh oh, so these are the workarounds ??
|
||||||
message.recipients?.removeAll { it.profileId != message.profileId }
|
message.recipients?.removeAll { it.profileId != message.profileId }
|
||||||
message.recipients?.forEach { recipient ->
|
message.recipients?.forEach { recipient ->
|
||||||
if (recipient.fullName == null) {
|
if (recipient.fullName == null) {
|
||||||
@ -78,62 +84,62 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load & configure the adapter
|
// show/hide relevant views
|
||||||
val items = messages.toMutableList<Any>()
|
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT)
|
||||||
items.add(0, MessagesSearch().also {
|
b.progressBar.isVisible = false
|
||||||
it.searchText = searchText
|
b.list.isVisible = messages.isNotEmpty()
|
||||||
})
|
b.noData.isVisible = messages.isEmpty()
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
return@Observer
|
||||||
|
}
|
||||||
|
|
||||||
adapter?.items = items
|
if (adapter.allItems.isEmpty()) {
|
||||||
adapter?.allItems = items
|
// items empty - add the search field
|
||||||
|
adapter.allItems += MessagesSearch().also {
|
||||||
if (items.isNotNullNorEmpty() && b.list.adapter == null) {
|
it.searchText = searchText ?: ""
|
||||||
if (searchText.isNotBlank())
|
}
|
||||||
adapter?.filter?.filter(searchText) {
|
} else {
|
||||||
b.list.adapter = adapter
|
// items not empty - remove all messages
|
||||||
}
|
adapter.allItems.removeAll { it is MessageFull }
|
||||||
else
|
}
|
||||||
b.list.adapter = adapter
|
// add all messages
|
||||||
|
adapter.allItems.addAll(messages)
|
||||||
|
|
||||||
|
// configure the adapter & recycler view
|
||||||
|
if (b.list.adapter == null) {
|
||||||
b.list.apply {
|
b.list.apply {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
addItemDecoration(SimpleDividerItemDecoration(context))
|
addItemDecoration(SimpleDividerItemDecoration(context))
|
||||||
if (messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT)
|
if (messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT)
|
||||||
addOnScrollListener(onScrollListener)
|
addOnScrollListener(onScrollListener)
|
||||||
|
this.adapter = this@MessagesListFragment.adapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSwipeToRefresh(messageType in Message.TYPE_RECEIVED..Message.TYPE_SENT && items.isNullOrEmpty())
|
val layoutManager = (b.list.layoutManager as? LinearLayoutManager) ?: return@Observer
|
||||||
|
|
||||||
(b.list.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
|
// reapply the filter
|
||||||
if (topPosition != NO_POSITION && topPosition > layoutManager.findLastCompletelyVisibleItemPosition()) {
|
val searchItem = adapter.items.firstOrNull { it is MessagesSearch } as? MessagesSearch
|
||||||
b.list.scrollToPosition(topPosition)
|
adapter.filter.filter(searchText ?: searchItem?.searchText, null)
|
||||||
} else if (bottomPosition != NO_POSITION && bottomPosition < layoutManager.findFirstVisibleItemPosition()) {
|
|
||||||
b.list.scrollToPosition(bottomPosition)
|
|
||||||
}
|
|
||||||
topPosition = NO_POSITION
|
|
||||||
bottomPosition = NO_POSITION
|
|
||||||
}
|
|
||||||
|
|
||||||
// show/hide relevant views
|
// restore the previously saved scroll position
|
||||||
b.progressBar.isVisible = false
|
if (topPosition != NO_POSITION && topPosition > layoutManager.findLastCompletelyVisibleItemPosition()) {
|
||||||
if (items.isNullOrEmpty()) {
|
b.list.scrollToPosition(topPosition)
|
||||||
b.list.isVisible = false
|
} else if (bottomPosition != NO_POSITION && bottomPosition < layoutManager.findFirstVisibleItemPosition()) {
|
||||||
b.noData.isVisible = true
|
b.list.scrollToPosition(bottomPosition)
|
||||||
} else {
|
|
||||||
b.list.isVisible = true
|
|
||||||
b.noData.isVisible = false
|
|
||||||
}
|
}
|
||||||
|
topPosition = NO_POSITION
|
||||||
|
bottomPosition = NO_POSITION
|
||||||
})
|
})
|
||||||
}; return true }
|
}; return true }
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (!isAdded)
|
if (!isAdded || !this::adapter.isInitialized)
|
||||||
return
|
return
|
||||||
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
|
val layoutManager = (b.list.layoutManager as? LinearLayoutManager)
|
||||||
val searchItem = adapter?.items?.firstOrNull { it is MessagesSearch } as? MessagesSearch
|
val searchItem = adapter.items.firstOrNull { it is MessagesSearch } as? MessagesSearch
|
||||||
|
|
||||||
onPageDestroy?.invoke(position, Bundle(
|
onPageDestroy?.invoke(position, Bundle(
|
||||||
"topPosition" to layoutManager?.findFirstVisibleItemPosition(),
|
"topPosition" to layoutManager?.findFirstVisibleItemPosition(),
|
||||||
|
@ -13,11 +13,9 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import androidx.core.text.HtmlCompat
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.hootsuite.nachos.chip.ChipInfo
|
import com.hootsuite.nachos.chip.ChipInfo
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
@ -27,6 +25,7 @@ import org.greenrobot.eventbus.Subscribe
|
|||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.*
|
||||||
import pl.szczodrzynski.edziennik.data.api.ERROR_MESSAGE_NOT_SENT
|
import pl.szczodrzynski.edziennik.data.api.ERROR_MESSAGE_NOT_SENT
|
||||||
|
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
|
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
|
||||||
import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent
|
import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent
|
||||||
@ -39,16 +38,12 @@ import pl.szczodrzynski.edziennik.ui.dialogs.MessagesConfigDialog
|
|||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils
|
||||||
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
|
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils.getProfileImage
|
||||||
import pl.szczodrzynski.edziennik.utils.Themes
|
import pl.szczodrzynski.edziennik.utils.Themes
|
||||||
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
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
|
||||||
import pl.szczodrzynski.edziennik.utils.span.BoldSpan
|
import pl.szczodrzynski.edziennik.utils.span.*
|
||||||
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.bottomsheet.items.BottomSheetPrimaryItem
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.text.replace
|
|
||||||
|
|
||||||
class MessagesComposeFragment : Fragment(), CoroutineScope {
|
class MessagesComposeFragment : Fragment(), CoroutineScope {
|
||||||
companion object {
|
companion object {
|
||||||
@ -63,14 +58,17 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
|
// private val manager
|
||||||
|
// get() = app.messageManager
|
||||||
|
private val textStylingManager
|
||||||
|
get() = app.textStylingManager
|
||||||
private val profileConfig by lazy { app.config.forProfile().ui }
|
private val profileConfig by lazy { app.config.forProfile().ui }
|
||||||
private val greetingText
|
private val greetingText
|
||||||
get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
|
get() = profileConfig.messagesGreetingText ?: "\n\nZ poważaniem\n${app.profile.accountOwnerName}"
|
||||||
|
|
||||||
private var teachers = mutableListOf<Teacher>()
|
private var teachers = mutableListOf<Teacher>()
|
||||||
|
|
||||||
private var watchFormatChecked = true
|
private lateinit var stylingConfig: StylingConfig
|
||||||
private var watchSelectionChanged = true
|
|
||||||
private val enableTextStyling
|
private val enableTextStyling
|
||||||
get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN
|
get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN
|
||||||
|
|
||||||
@ -101,13 +99,6 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
// do your job
|
// do your job
|
||||||
}
|
}
|
||||||
|
|
||||||
if (App.devMode) {
|
|
||||||
b.textHtml.isVisible = true
|
|
||||||
b.text.addTextChangedListener {
|
|
||||||
b.textHtml.text = getHtmlText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.bottomSheet.prependItem(
|
activity.bottomSheet.prependItem(
|
||||||
BottomSheetPrimaryItem(true)
|
BottomSheetPrimaryItem(true)
|
||||||
.withTitle(R.string.menu_messages_config)
|
.withTitle(R.string.menu_messages_config)
|
||||||
@ -126,48 +117,11 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getHtmlText(): String {
|
private fun getMessageBody(): String {
|
||||||
val text = b.text.text ?: return ""
|
return if (enableTextStyling)
|
||||||
|
textStylingManager.getHtmlText(stylingConfig)
|
||||||
// apparently setting the spans to a different Spannable calls the original EditText's
|
else
|
||||||
// onSelectionChanged with selectionStart=-1, which in effect unchecks the format toggles
|
b.text.text?.toString() ?: ""
|
||||||
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("</b><b>", "")
|
|
||||||
.replace("</i><i>", "")
|
|
||||||
.replace("</u><u>", "")
|
|
||||||
.replace("</sub><sub>", "")
|
|
||||||
.replace("</sup><sup>", "")
|
|
||||||
.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("<br>", "<p> </p>")
|
|
||||||
.replace("<b>", "<strong>")
|
|
||||||
.replace("</b>", "</strong>")
|
|
||||||
.replace("<i>", "<em>")
|
|
||||||
.replace("</i>", "</em>")
|
|
||||||
.replace("<u>", "<span style=\"text-decoration: underline;\">")
|
|
||||||
.replace("</u>", "</span>")
|
|
||||||
}
|
|
||||||
|
|
||||||
return textHtml
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRecipientList() {
|
private fun getRecipientList() {
|
||||||
@ -185,80 +139,6 @@ 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() {
|
private fun createView() {
|
||||||
b.recipientsLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
|
b.recipientsLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
|
||||||
b.subjectLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
|
b.subjectLayout.setBoxCornerRadii(0f, 0f, 0f, 0f)
|
||||||
@ -318,54 +198,66 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
b.subjectLayout.isEnabled = false
|
b.subjectLayout.isEnabled = false
|
||||||
b.textLayout.isEnabled = false
|
b.textLayout.isEnabled = false
|
||||||
|
|
||||||
|
val styles = listOf(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
stylingConfig = StylingConfig(
|
||||||
|
editText = b.text,
|
||||||
|
fontStyleGroup = b.fontStyle,
|
||||||
|
fontStyleClear = b.fontStyleClear,
|
||||||
|
styles = styles,
|
||||||
|
textHtml = if (App.devMode) b.textHtml else null,
|
||||||
|
htmlCompatibleMode = app.profile.loginStoreType == LOGIN_TYPE_MOBIDZIENNIK,
|
||||||
|
)
|
||||||
|
|
||||||
b.fontStyleLayout.isVisible = enableTextStyling
|
b.fontStyleLayout.isVisible = enableTextStyling
|
||||||
b.fontStyleBold.isEnabled = false
|
if (enableTextStyling) {
|
||||||
b.fontStyleItalic.isEnabled = false
|
textStylingManager.attach(stylingConfig)
|
||||||
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()
|
if (App.devMode) {
|
||||||
b.fontStyleItalic.text = CommunityMaterial.Icon2.cmd_format_italic.character.toString()
|
b.textHtml.isVisible = true
|
||||||
b.fontStyleUnderline.text = CommunityMaterial.Icon2.cmd_format_underline.character.toString()
|
b.text.addTextChangedListener {
|
||||||
b.fontStyleStrike.text = CommunityMaterial.Icon2.cmd_format_strikethrough.character.toString()
|
b.textHtml.text = getMessageBody()
|
||||||
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 {
|
activity.navView.bottomBar.apply {
|
||||||
fabEnable = true
|
fabEnable = true
|
||||||
@ -533,7 +425,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
|
if (b.textLayout.counterMaxLength != -1 && b.text.length() > b.textLayout.counterMaxLength)
|
||||||
return
|
return
|
||||||
|
|
||||||
val textHtml = getHtmlText()
|
val body = getMessageBody()
|
||||||
|
|
||||||
activity.bottomSheet.hideKeyboard()
|
activity.bottomSheet.hideKeyboard()
|
||||||
|
|
||||||
@ -541,7 +433,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
|
|||||||
.setTitle(R.string.messages_compose_confirm_title)
|
.setTitle(R.string.messages_compose_confirm_title)
|
||||||
.setMessage(R.string.messages_compose_confirm_text)
|
.setMessage(R.string.messages_compose_confirm_text)
|
||||||
.setPositiveButton(R.string.send) { _, _ ->
|
.setPositiveButton(R.string.send) { _, _ ->
|
||||||
EdziennikTask.messageSend(App.profileId, recipients, subject.trim(), textHtml).enqueue(activity)
|
EdziennikTask.messageSend(App.profileId, recipients, subject.trim(), body).enqueue(activity)
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
@ -13,15 +13,16 @@ class MessagesComparator : Comparator<Any> {
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
// standard sorting
|
// descending sorting (1. true, 2. false)
|
||||||
|
o1.isStarred && !o2.isStarred -> -1
|
||||||
|
!o1.isStarred && o2.isStarred -> 1
|
||||||
|
// ascending sorting
|
||||||
o1.filterWeight > o2.filterWeight -> 1
|
o1.filterWeight > o2.filterWeight -> 1
|
||||||
o1.filterWeight < o2.filterWeight -> -1
|
o1.filterWeight < o2.filterWeight -> -1
|
||||||
else -> when {
|
// descending sorting
|
||||||
// reversed sorting
|
o1.addedDate > o2.addedDate -> -1
|
||||||
o1.addedDate > o2.addedDate -> -1
|
o1.addedDate < o2.addedDate -> 1
|
||||||
o1.addedDate < o2.addedDate -> 1
|
else -> 0
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,11 @@ class MessageViewHolder(
|
|||||||
b.messageDate.setTextAppearance(activity, style)
|
b.messageDate.setTextAppearance(activity, style)
|
||||||
b.messageDate.typeface = typeface
|
b.messageDate.typeface = typeface
|
||||||
|
|
||||||
|
if (adapter.onStarClick == null) {
|
||||||
|
b.messageStar.isVisible = false
|
||||||
|
}
|
||||||
|
b.messageStar.detachToastHint()
|
||||||
|
|
||||||
val messageInfo = MessagesUtils.getMessageInfo(app, item, 48, 24, 18, 12)
|
val messageInfo = MessagesUtils.getMessageInfo(app, item, 48, 24, 18, 12)
|
||||||
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
b.messageProfileBackground.setImageBitmap(messageInfo.profileImage)
|
||||||
b.messageSender.text = messageInfo.profileName
|
b.messageSender.text = messageInfo.profileName
|
||||||
@ -75,5 +80,11 @@ class MessageViewHolder(
|
|||||||
adapter.onItemClick?.let { listener ->
|
adapter.onItemClick?.let { listener ->
|
||||||
b.root.onClick { listener(item) }
|
b.root.onClick { listener(item) }
|
||||||
}
|
}
|
||||||
|
adapter.onStarClick?.let { listener ->
|
||||||
|
b.messageStar.isVisible = true
|
||||||
|
adapter.manager.setStarIcon(b.messageStar, item)
|
||||||
|
b.messageStar.onClick { listener(item) }
|
||||||
|
b.messageStar.attachToastHint(R.string.hint_message_star)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ object BetterHtml {
|
|||||||
val customSpanClasses = listOf(
|
val customSpanClasses = listOf(
|
||||||
BoldSpan::class.java,
|
BoldSpan::class.java,
|
||||||
ItalicSpan::class.java,
|
ItalicSpan::class.java,
|
||||||
UnderlineSpan::class.java,
|
UnderlineCustomSpan::class.java,
|
||||||
StrikethroughSpan::class.java,
|
StrikethroughSpan::class.java,
|
||||||
SubscriptSizeSpan::class.java,
|
SubscriptSizeSpan::class.java,
|
||||||
SuperscriptSizeSpan::class.java,
|
SuperscriptSizeSpan::class.java,
|
||||||
@ -101,8 +101,9 @@ object BetterHtml {
|
|||||||
Typeface.ITALIC -> ItalicSpan()
|
Typeface.ITALIC -> ItalicSpan()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
is SubscriptSpan -> SubscriptSizeSpan(size = 10, dip = true)
|
is UnderlineSpan -> UnderlineCustomSpan()
|
||||||
is SuperscriptSpan -> SuperscriptSizeSpan(size = 10, dip = true)
|
is SubscriptSpan -> SubscriptSizeSpan()
|
||||||
|
is SuperscriptSpan -> SuperscriptSizeSpan()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-10-7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.managers
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
|
import com.mikepenz.iconics.utils.colorRes
|
||||||
|
import com.mikepenz.iconics.view.IconicsImageView
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.entity.Message
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
|
||||||
|
import pl.szczodrzynski.navlib.colorAttr
|
||||||
|
|
||||||
|
class MessageManager(private val app: App) {
|
||||||
|
|
||||||
|
suspend fun getMessage(profileId: Int, args: Bundle?): MessageFull? {
|
||||||
|
val id = args?.getLong("messageId") ?: return null
|
||||||
|
val json = args.getString("message")
|
||||||
|
val addedDate = args.getLong("sentDate")
|
||||||
|
return getMessage(profileId, id, json, addedDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMessage(
|
||||||
|
profileId: Int,
|
||||||
|
id: Long,
|
||||||
|
json: String?,
|
||||||
|
sentDate: Long = 0L
|
||||||
|
): MessageFull? {
|
||||||
|
val message = if (json != null) {
|
||||||
|
app.gson.fromJson(json, MessageFull::class.java)?.also {
|
||||||
|
if (sentDate > 0L) {
|
||||||
|
it.addedDate = sentDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
app.db.messageDao().getByIdNow(profileId, id)
|
||||||
|
}
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
// make recipients ID-unique
|
||||||
|
// this helps when multiple profiles receive the same message
|
||||||
|
// (there are multiple -1 recipients for the same message ID)
|
||||||
|
val recipientsDistinct = message.recipients?.distinctBy { it.id } ?: return null
|
||||||
|
message.recipients?.clear()
|
||||||
|
message.recipients?.addAll(recipientsDistinct)
|
||||||
|
|
||||||
|
// load recipients for sent messages
|
||||||
|
val teachers = withContext(Dispatchers.IO) {
|
||||||
|
app.db.teacherDao().getAllNow(profileId)
|
||||||
|
}
|
||||||
|
|
||||||
|
message.recipients?.forEach { recipient ->
|
||||||
|
// store the account name as a recipient
|
||||||
|
if (recipient.id == -1L)
|
||||||
|
recipient.fullName = app.profile.accountName ?: app.profile.studentNameLong
|
||||||
|
|
||||||
|
// lookup a teacher by the recipient ID
|
||||||
|
if (recipient.fullName == null)
|
||||||
|
recipient.fullName = teachers.firstOrNull { it.id == recipient.id }?.fullName ?: ""
|
||||||
|
|
||||||
|
// unset the readByEveryone flag
|
||||||
|
if (recipient.readDate < 1 && message.type == Message.TYPE_SENT)
|
||||||
|
message.readByEveryone = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the account name as sender for sent messages
|
||||||
|
if (message.type == Message.TYPE_SENT && message.senderName == null) {
|
||||||
|
message.senderName = app.profile.accountName ?: app.profile.studentNameLong
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the message as seen
|
||||||
|
if (message.body != null && !message.seen) {
|
||||||
|
app.db.metadataDao().setSeen(profileId, message, true)
|
||||||
|
}
|
||||||
|
//msg.recipients = app.db.messageRecipientDao().getAllByMessageId(msg.profileId, msg.id)
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStarIcon(image: IconicsImageView, message: Message) {
|
||||||
|
if (message.isStarred) {
|
||||||
|
image.icon?.colorRes = R.color.md_amber_500
|
||||||
|
image.icon?.icon = CommunityMaterial.Icon3.cmd_star
|
||||||
|
} else {
|
||||||
|
image.icon?.colorAttr(image.context, android.R.attr.textColorSecondary)
|
||||||
|
image.icon?.icon = CommunityMaterial.Icon3.cmd_star_outline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun starMessage(message: Message, isStarred: Boolean) {
|
||||||
|
message.isStarred = isStarred
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
app.db.messageDao().replace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun markAsDeleted(message: Message) {
|
||||||
|
message.type = Message.TYPE_DELETED
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
app.db.messageDao().replace(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-10-7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.managers
|
||||||
|
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||||
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.attachToastHint
|
||||||
|
import pl.szczodrzynski.edziennik.hasSet
|
||||||
|
import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
|
||||||
|
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
|
||||||
|
|
||||||
|
class TextStylingManager(private val app: App) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "TextStylingManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StylingConfig(
|
||||||
|
val editText: TextInputKeyboardEdit,
|
||||||
|
val fontStyleGroup: MaterialButtonToggleGroup,
|
||||||
|
val fontStyleClear: Button,
|
||||||
|
val styles: List<Style>,
|
||||||
|
val textHtml: TextView? = null,
|
||||||
|
val htmlCompatibleMode: Boolean = false,
|
||||||
|
) {
|
||||||
|
data class Style(
|
||||||
|
val button: MaterialButton,
|
||||||
|
val spanClass: Class<*>,
|
||||||
|
val icon: IIcon,
|
||||||
|
@StringRes
|
||||||
|
val hint: Int,
|
||||||
|
) {
|
||||||
|
fun newInstance(): Any = spanClass.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
var watchStyleChecked = true
|
||||||
|
var watchSelectionChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attach(config: StylingConfig) {
|
||||||
|
enableButtons(config, enable = false)
|
||||||
|
config.editText.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
enableButtons(config, enable = hasFocus)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.styles.forEach {
|
||||||
|
it.button.text = it.icon.character.toString()
|
||||||
|
it.button.attachToastHint(it.hint)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.fontStyleGroup.addOnButtonCheckedListener { _, checkedId, isChecked ->
|
||||||
|
val style = config.styles.firstOrNull {
|
||||||
|
it.button.id == checkedId
|
||||||
|
} ?: return@addOnButtonCheckedListener
|
||||||
|
onStyleChecked(config, style, isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.fontStyleClear.setOnClickListener {
|
||||||
|
onStyleClear(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.editText.setSelectionChangedListener { selectionStart, selectionEnd ->
|
||||||
|
onSelectionChanged(config, selectionStart, selectionEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*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()*/
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHtmlText(config: StylingConfig): String {
|
||||||
|
val spanned = config.editText.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
|
||||||
|
config.watchSelectionChanged = false
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
var textHtml = HtmlCompat.toHtml(spanned, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)
|
||||||
|
.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")
|
||||||
|
|
||||||
|
config.watchSelectionChanged = true
|
||||||
|
|
||||||
|
if (config.htmlCompatibleMode) {
|
||||||
|
textHtml = textHtml
|
||||||
|
.replace("<br>", "<p> </p>")
|
||||||
|
.replace("<b>", "<strong>")
|
||||||
|
.replace("</b>", "</strong>")
|
||||||
|
.replace("<i>", "<em>")
|
||||||
|
.replace("</i>", "</em>")
|
||||||
|
.replace("<u>", "<span style=\"text-decoration: underline;\">")
|
||||||
|
.replace("</u>", "</span>")
|
||||||
|
}
|
||||||
|
|
||||||
|
return textHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStyleChecked(
|
||||||
|
config: StylingConfig,
|
||||||
|
style: StylingConfig.Style,
|
||||||
|
isChecked: Boolean
|
||||||
|
) {
|
||||||
|
if (!config.watchStyleChecked)
|
||||||
|
return
|
||||||
|
val span = style.newInstance()
|
||||||
|
|
||||||
|
// see comments in getHtmlText()
|
||||||
|
config.watchSelectionChanged = false
|
||||||
|
if (isChecked)
|
||||||
|
BetterHtml.applyFormat(span, config.editText)
|
||||||
|
else
|
||||||
|
BetterHtml.removeFormat(span, config.editText)
|
||||||
|
config.watchSelectionChanged = true
|
||||||
|
|
||||||
|
config.textHtml?.text = getHtmlText(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStyleClear(config: StylingConfig) {
|
||||||
|
// shortened version on onStyleChecked(), removing all spans
|
||||||
|
config.watchSelectionChanged = false
|
||||||
|
BetterHtml.removeFormat(span = null, config.editText)
|
||||||
|
config.watchSelectionChanged = true
|
||||||
|
config.textHtml?.text = getHtmlText(config)
|
||||||
|
// force update of text style toggle states
|
||||||
|
onSelectionChanged(config, config.editText.selectionStart, config.editText.selectionEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSelectionChanged(config: StylingConfig, selectionStart: Int, selectionEnd: Int) {
|
||||||
|
if (!config.watchSelectionChanged)
|
||||||
|
return
|
||||||
|
val spanned = config.editText.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 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
spanned.setSpan(it, spanStart, spanEnd, Spanned.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
|
||||||
|
}
|
||||||
|
config.watchStyleChecked = false
|
||||||
|
config.styles.forEach {
|
||||||
|
it.button.isChecked = it.spanClass in spans
|
||||||
|
}
|
||||||
|
config.watchStyleChecked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableButtons(config: StylingConfig, enable: Boolean) {
|
||||||
|
config.fontStyleClear.isEnabled = enable
|
||||||
|
config.styles.forEach {
|
||||||
|
it.button.isEnabled = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,10 +7,10 @@ package pl.szczodrzynski.edziennik.utils.span
|
|||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.style.SubscriptSpan
|
import android.text.style.SubscriptSpan
|
||||||
|
|
||||||
class SubscriptSizeSpan(
|
class SubscriptSizeSpan : SubscriptSpan() {
|
||||||
private val size: Int,
|
|
||||||
private val dip: Boolean,
|
private val size = 10
|
||||||
) : SubscriptSpan() {
|
private val dip = true
|
||||||
|
|
||||||
override fun updateDrawState(textPaint: TextPaint) {
|
override fun updateDrawState(textPaint: TextPaint) {
|
||||||
super.updateDrawState(textPaint)
|
super.updateDrawState(textPaint)
|
||||||
|
@ -7,10 +7,10 @@ package pl.szczodrzynski.edziennik.utils.span
|
|||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.style.SuperscriptSpan
|
import android.text.style.SuperscriptSpan
|
||||||
|
|
||||||
class SuperscriptSizeSpan(
|
class SuperscriptSizeSpan : SuperscriptSpan() {
|
||||||
private val size: Int,
|
|
||||||
private val dip: Boolean,
|
private val size = 10
|
||||||
) : SuperscriptSpan() {
|
private val dip = true
|
||||||
|
|
||||||
override fun updateDrawState(textPaint: TextPaint) {
|
override fun updateDrawState(textPaint: TextPaint) {
|
||||||
super.updateDrawState(textPaint)
|
super.updateDrawState(textPaint)
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-10-7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.utils.span
|
||||||
|
|
||||||
|
import android.text.style.UnderlineSpan
|
||||||
|
|
||||||
|
class UnderlineCustomSpan : UnderlineSpan()
|
@ -25,6 +25,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/attendances_no_data"
|
android:text="@string/attendances_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -153,6 +153,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/attendances_no_data"
|
android:text="@string/attendances_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/homework_no_data"
|
android:text="@string/homework_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -2,21 +2,22 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright (c) Kuba Szczodrzyński 2019-11-12.
|
~ Copyright (c) Kuba Szczodrzyński 2019-11-12.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<layout 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">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/colorSurface_6dp"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal">
|
||||||
android:background="@color/colorSurface_6dp">
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/closeButton"
|
android:id="@+id/closeButton"
|
||||||
@ -55,7 +56,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
tools:visibility="gone"/>
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/content"
|
android:id="@+id/content"
|
||||||
@ -81,6 +82,7 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/profileBackground"
|
android:id="@+id/profileBackground"
|
||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
@ -112,26 +114,38 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/sender"
|
android:id="@+id/sender"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
|
android:minHeight="64dp"
|
||||||
android:paddingHorizontal="8dp"
|
android:paddingHorizontal="8dp"
|
||||||
android:paddingTop="12dp"
|
android:paddingVertical="8dp"
|
||||||
android:textAppearance="@style/NavView.TextView.Subtitle"
|
android:textAppearance="@style/NavView.TextView.Subtitle"
|
||||||
tools:text="Allegro - wysyłamy duużo wiadomości!!! Masz nowe oferty! Możesz kupić nowego laptopa! Ale super! Ehh, to jest nadawca a nie temat więc nwm czemu to tutaj wpisałem" />
|
tools:text="Allegro - wysyłamy duużo wiadomości!!! Masz nowe oferty! Możesz kupić nowego laptopa! Ale super! Ehh, to jest nadawca a nie temat więc nwm czemu to tutaj wpisałem" />
|
||||||
|
|
||||||
|
<com.mikepenz.iconics.view.IconicsImageView
|
||||||
|
android:id="@+id/messageStar"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:iiv_color="?android:textColorSecondary"
|
||||||
|
app:iiv_icon="cmd-star-outline"
|
||||||
|
app:iiv_size="24dp"
|
||||||
|
tools:background="@android:drawable/star_off" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/date"
|
android:id="@+id/date"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:layout_marginRight="8dp"
|
android:gravity="center_horizontal"
|
||||||
android:textAppearance="@style/NavView.TextView.Small"
|
android:textAppearance="@style/NavView.TextView.Small"
|
||||||
tools:text="14:26" />
|
tools:text="20 lis 2021\n14:26" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -188,122 +202,61 @@
|
|||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/replyButton"
|
android:id="@+id/replyButton"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="4dp"
|
android:layout_marginHorizontal="4dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_rounded_ripple"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="4dp"
|
android:paddingHorizontal="4dp"
|
||||||
android:paddingVertical="8dp">
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/message_reply"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
tools:drawableTop="@android:drawable/sym_action_email" />
|
||||||
|
|
||||||
<com.mikepenz.iconics.view.IconicsImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/replyIcon"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
app:iiv_color="?android:textColorSecondary"
|
|
||||||
app:iiv_icon="cmd-reply-outline"
|
|
||||||
tools:srcCompat="@android:drawable/ic_menu_revert" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Odpowiedz"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/forwardButton"
|
android:id="@+id/forwardButton"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="4dp"
|
android:layout_marginHorizontal="4dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_rounded_ripple"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="4dp"
|
android:paddingHorizontal="4dp"
|
||||||
android:paddingVertical="8dp">
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/message_forward"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
tools:drawableTop="@android:drawable/stat_sys_phone_call_forward" />
|
||||||
|
|
||||||
<com.mikepenz.iconics.view.IconicsImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/forwardIcon"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
app:iiv_color="?android:textColorSecondary"
|
|
||||||
app:iiv_icon="cmd-arrow-right"
|
|
||||||
tools:srcCompat="@android:drawable/ic_media_ff" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Przekaż dalej"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/deleteButton"
|
android:id="@+id/deleteButton"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="4dp"
|
android:layout_marginHorizontal="4dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_rounded_ripple"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="4dp"
|
android:paddingHorizontal="4dp"
|
||||||
android:paddingVertical="8dp"
|
android:paddingTop="8dp"
|
||||||
android:visibility="visible">
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/message_delete"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
tools:drawableTop="@android:drawable/ic_menu_delete" />
|
||||||
|
|
||||||
<com.mikepenz.iconics.view.IconicsImageView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
app:iiv_color="?android:textColorSecondary"
|
|
||||||
app:iiv_icon="cmd-delete-outline"
|
|
||||||
tools:srcCompat="@android:drawable/ic_menu_delete" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Usuń"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/downloadButton"
|
android:id="@+id/downloadButton"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="4dp"
|
android:layout_marginHorizontal="4dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_rounded_ripple"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="4dp"
|
android:paddingHorizontal="4dp"
|
||||||
android:paddingVertical="8dp"
|
android:paddingTop="16dp"
|
||||||
android:visibility="visible">
|
android:text="@string/message_download"
|
||||||
|
android:textAllCaps="false"
|
||||||
<com.mikepenz.iconics.view.IconicsImageView
|
android:visibility="gone"
|
||||||
android:layout_width="24dp"
|
tools:drawableTop="@android:drawable/stat_sys_download" />
|
||||||
android:layout_height="24dp"
|
|
||||||
app:iiv_color="?android:textColorSecondary"
|
|
||||||
app:iiv_icon="cmd-download-outline"
|
|
||||||
tools:srcCompat="@android:drawable/ic_menu_delete" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Pobierz ponownie"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Small" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/messages_no_data"
|
android:text="@string/messages_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright (c) Kuba Szczodrzyński 2020-4-4.
|
~ Copyright (c) Kuba Szczodrzyński 2020-4-4.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -40,9 +39,11 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:paddingRight="32dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textStyle="normal"
|
|
||||||
android:textAppearance="@style/NavView.TextView.Helper"
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
|
android:textStyle="normal"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/messageProfileBackground"
|
app:layout_constraintStart_toEndOf="@+id/messageProfileBackground"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/messageSender"
|
app:layout_constraintTop_toBottomOf="@+id/messageSender"
|
||||||
@ -72,6 +73,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="8dp"
|
android:layout_marginHorizontal="8dp"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_marginBottom="12dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:paddingRight="32dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="@style/NavView.TextView.Helper"
|
android:textAppearance="@style/NavView.TextView.Helper"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
@ -80,6 +83,23 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/messageSubject"
|
app:layout_constraintTop_toBottomOf="@+id/messageSubject"
|
||||||
tools:text="Znajdź produkty, których szukasz. Witaj Kuba Szczodrzyński (Client" />
|
tools:text="Znajdź produkty, których szukasz. Witaj Kuba Szczodrzyński (Client" />
|
||||||
|
|
||||||
|
<com.mikepenz.iconics.view.IconicsImageView
|
||||||
|
android:id="@+id/messageStar"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:iiv_color="?android:textColorSecondary"
|
||||||
|
app:iiv_icon="cmd-star-outline"
|
||||||
|
app:iiv_size="24dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageDate"
|
||||||
|
tools:background="@android:drawable/star_off" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/messageDate"
|
android:id="@+id/messageDate"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -106,6 +126,6 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="@+id/messageDate"
|
app:layout_constraintBottom_toBottomOf="@+id/messageDate"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/messageDate"
|
app:layout_constraintEnd_toStartOf="@+id/messageDate"
|
||||||
app:layout_constraintTop_toTopOf="@+id/messageDate"
|
app:layout_constraintTop_toTopOf="@+id/messageDate"
|
||||||
tools:srcCompat="@tools:sample/avatars[4]" />
|
tools:background="@tools:sample/avatars[4]" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/notifications_no_data"
|
android:text="@string/notifications_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:drawablePadding="16dp"
|
android:drawablePadding="16dp"
|
||||||
android:fontFamily="sans-serif-light"
|
android:fontFamily="sans-serif-light"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
android:text="@string/grades_no_data"
|
android:text="@string/grades_no_data"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
@ -744,7 +744,7 @@
|
|||||||
<string name="messages_compose_text_hint">Napisz wiadomość…</string>
|
<string name="messages_compose_text_hint">Napisz wiadomość…</string>
|
||||||
<string name="messages_compose_title">Napisz wiadomość</string>
|
<string name="messages_compose_title">Napisz wiadomość</string>
|
||||||
<string name="messages_compose_to_hint">Do</string>
|
<string name="messages_compose_to_hint">Do</string>
|
||||||
<string name="messages_date_time_format" translatable="false">%s, %s</string>
|
<string name="messages_date_time_format" translatable="false">%s\n%s</string>
|
||||||
<string name="messages_delete_confirmation">Czy chcesz usunąć wiadomość?</string>
|
<string name="messages_delete_confirmation">Czy chcesz usunąć wiadomość?</string>
|
||||||
<string name="messages_delete_confirmation_text">Spowoduje to przeniesienie wiadomości do zakładki \"Usunięte\" w aplikacji. Zmiany nie wpłyną na wiadomość w e-dzienniku (nie zostanie ona tam usunięta).</string>
|
<string name="messages_delete_confirmation_text">Spowoduje to przeniesienie wiadomości do zakładki \"Usunięte\" w aplikacji. Zmiany nie wpłyną na wiadomość w e-dzienniku (nie zostanie ona tam usunięta).</string>
|
||||||
<string name="messages_download_error">Błąd pobierania wiadomości</string>
|
<string name="messages_download_error">Błąd pobierania wiadomości</string>
|
||||||
@ -1471,4 +1471,9 @@
|
|||||||
<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="messages_compose_style_clear">Wyczyść format</string>
|
||||||
|
<string name="hint_message_star">Oznacz gwiazdką</string>
|
||||||
|
<string name="message_forward">Przekaż dalej</string>
|
||||||
|
<string name="message_delete">Usuń</string>
|
||||||
|
<string name="message_reply">Odpowiedz</string>
|
||||||
|
<string name="message_download">Pobierz ponownie</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user