SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/columnviewholder/ColumnViewHolderAnnouncemen...

501 lines
16 KiB
Kotlin
Raw Normal View History

2021-06-28 09:09:00 +02:00
package jp.juggler.subwaytooter.columnviewholder
import android.os.SystemClock
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
2021-06-28 09:09:00 +02:00
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
import jp.juggler.subwaytooter.api.entity.TootReaction
import jp.juggler.subwaytooter.api.entity.TootStatus
2021-11-20 13:16:56 +01:00
import jp.juggler.subwaytooter.api.runApiTask
2021-06-28 09:09:00 +02:00
import jp.juggler.subwaytooter.column.Column
import jp.juggler.subwaytooter.column.getContentColor
2022-05-29 15:38:21 +02:00
import jp.juggler.subwaytooter.dialog.launchEmojiPicker
import jp.juggler.subwaytooter.emoji.CustomEmoji
2021-11-20 13:16:56 +01:00
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.subwaytooter.util.*
import jp.juggler.util.coroutine.launchAndShowError
import jp.juggler.util.coroutine.launchMain
import jp.juggler.util.data.*
import jp.juggler.util.log.showToast
import jp.juggler.util.network.toDeleteRequestBuilder
import jp.juggler.util.network.toPutRequestBuilder
import jp.juggler.util.ui.*
import org.jetbrains.anko.allCaps
import org.jetbrains.anko.padding
import org.jetbrains.anko.textColor
fun ColumnViewHolder.hideAnnouncements() {
val column = column ?: return
if (column.announcementHideTime <= 0L) {
column.announcementHideTime = System.currentTimeMillis()
}
activity.appState.saveColumnList()
showAnnouncements()
}
fun ColumnViewHolder.toggleAnnouncements() {
val column = column ?: return
if (llAnnouncementsBox.visibility == View.VISIBLE) {
if (column.announcementHideTime <= 0L) {
column.announcementHideTime = System.currentTimeMillis()
}
} else {
showColumnSetting(false)
column.announcementHideTime = 0L
}
activity.appState.saveColumnList()
showAnnouncements()
}
fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
val column = column ?: return
if (!force && lastAnnouncementShown >= column.announcementUpdated) {
return
}
lastAnnouncementShown = SystemClock.elapsedRealtime()
llAnnouncementExtra.removeAllViews()
clearExtras()
val listShown = TootAnnouncement.filterShown(column.announcements)
if (listShown?.isEmpty() != false) {
2021-06-22 10:31:51 +02:00
showAnnouncementsEmpty()
return
}
btnAnnouncements.vg(true)
val expand = column.announcementHideTime <= 0L
llAnnouncementsBox.vg(expand)
llColumnHeader.invalidate()
btnAnnouncementsBadge.vg(false)
if (!expand) {
val newer = listShown.find { it.updated_at > column.announcementHideTime }
if (newer != null) {
column.announcementId = newer.id
btnAnnouncementsBadge.vg(true)
}
return
}
val item = listShown.find { it.id == column.announcementId }
?: listShown[0]
val itemIndex = listShown.indexOf(item)
val enablePaging = listShown.size > 1
2021-06-22 10:31:51 +02:00
val contentColor = column.getContentColor()
showAnnouncementColors(true, enablePaging, contentColor)
2021-06-22 10:31:51 +02:00
showAnnouncementFonts()
llAnnouncements.vg(true)
tvAnnouncementsIndex.vg(true)?.text =
2021-06-22 10:31:51 +02:00
activity.getString(R.string.announcements_index, itemIndex + 1, listShown.size)
showAnnouncementContent(item, contentColor)
showReactionBox(column, item, contentColor)
}
private fun ColumnViewHolder.clearExtras() {
for (invalidator in extraInvalidatorList) {
invalidator.register(null)
}
extraInvalidatorList.clear()
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showAnnouncementsEmpty() {
btnAnnouncements.vg(false)
llAnnouncementsBox.vg(false)
btnAnnouncementsBadge.vg(false)
llColumnHeader.invalidate()
}
private fun ColumnViewHolder.showAnnouncementColors(
expand: Boolean,
enablePaging: Boolean,
2022-05-29 15:38:21 +02:00
contentColor: Int,
) {
val alphaPrevNext = if (enablePaging) 1f else 0.3f
setIconDrawableId(
activity,
btnAnnouncementsPrev,
R.drawable.ic_arrow_start,
color = contentColor,
alphaMultiplier = alphaPrevNext
)
setIconDrawableId(
activity,
btnAnnouncementsNext,
R.drawable.ic_arrow_end,
color = contentColor,
alphaMultiplier = alphaPrevNext
)
btnAnnouncementsPrev.vg(expand)?.apply {
isEnabledAlpha = enablePaging
}
btnAnnouncementsNext.vg(expand)?.apply {
isEnabledAlpha = enablePaging
}
tvAnnouncementsCaption.textColor = contentColor
tvAnnouncementsIndex.textColor = contentColor
tvAnnouncementPeriod.textColor = contentColor
2021-06-22 10:31:51 +02:00
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showAnnouncementFonts() {
val f = activity.timelineFontSizeSp
if (!f.isNaN()) {
tvAnnouncementsCaption.textSize = f
tvAnnouncementsIndex.textSize = f
tvAnnouncementPeriod.textSize = f
tvAnnouncementContent.textSize = f
}
val spacing = activity.timelineSpacing
if (spacing != null) {
tvAnnouncementPeriod.setLineSpacing(0f, spacing)
tvAnnouncementContent.setLineSpacing(0f, spacing)
}
tvAnnouncementsCaption.typeface = ActMain.timelineFontBold
val fontNormal = ActMain.timelineFont
tvAnnouncementsIndex.typeface = fontNormal
tvAnnouncementPeriod.typeface = fontNormal
tvAnnouncementContent.typeface = fontNormal
2021-06-22 10:31:51 +02:00
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showAnnouncementContent(item: TootAnnouncement, contentColor: Int) {
var periods: StringBuilder? = null
fun String.appendPeriod() {
val sb = periods
if (sb == null) {
periods = StringBuilder(this)
} else {
sb.append("\n")
sb.append(this)
}
}
2021-06-22 10:31:51 +02:00
val (strStart, strEnd) = TootStatus.formatTimeRange(item.starts_at, item.ends_at, item.all_day)
when {
// no periods.
strStart == "" && strEnd == "" -> {
}
// single date
strStart == strEnd -> {
activity.getString(R.string.announcements_period1, strStart)
.appendPeriod()
}
else -> {
activity.getString(R.string.announcements_period2, strStart, strEnd)
.appendPeriod()
}
}
if (item.updated_at > item.published_at) {
val strUpdateAt = TootStatus.formatTime(activity, item.updated_at, false)
activity.getString(R.string.edited_at, strUpdateAt).appendPeriod()
}
val sb = periods
tvAnnouncementPeriod.vg(sb != null)?.text = sb
tvAnnouncementContent.textColor = contentColor
tvAnnouncementContent.text = item.decoded_content
2021-06-22 10:31:51 +02:00
tvAnnouncementContent.tag = this
announcementContentInvalidator.register(item.decoded_content)
2021-06-22 10:31:51 +02:00
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showReactionBox(
column: Column,
item: TootAnnouncement,
contentColor: Int,
) {
// リアクションの表示
val density = activity.density
val buttonHeight = ActMain.boostButtonSize
val marginBetween = (buttonHeight.toFloat() * 0.2f + 0.5f).toInt()
val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
val box = FlexboxLayout(activity).apply {
flexWrap = FlexWrap.WRAP
justifyContent = JustifyContent.FLEX_START
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = (0.5f + density * 3f).toInt()
}
}
// +ボタン
2021-06-22 10:31:51 +02:00
showReactionPlus(box, item, buttonHeight, marginBetween, contentColor, paddingV)
item.reactions?.filter { it.count > 0L }
?.notEmpty()
?.let {
showReactions(
box,
item,
it,
column,
buttonHeight,
marginBetween,
contentColor,
paddingH,
paddingV
)
}
2021-06-22 10:31:51 +02:00
llAnnouncementExtra.addView(box)
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showReactionPlus(
box: FlexboxLayout,
item: TootAnnouncement,
buttonHeight: Int,
marginBetween: Int,
contentColor: Int,
paddingV: Int,
) {
val b = ImageButton(activity)
val blp = FlexboxLayout.LayoutParams(
buttonHeight,
buttonHeight
).apply {
bottomMargin = marginBottom
endMargin = marginBetween
}
b.layoutParams = blp
b.background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent_round6dp
)
2021-06-22 10:31:51 +02:00
b.contentDescription = activity.getString(R.string.reaction_add)
b.scaleType = ImageView.ScaleType.FIT_CENTER
b.padding = paddingV
b.setOnClickListener {
reactionAdd(item, null)
}
2021-06-22 10:31:51 +02:00
setIconDrawableId(
activity,
b,
R.drawable.ic_add,
color = contentColor,
)
box.addView(b)
}
2021-06-22 10:31:51 +02:00
private fun ColumnViewHolder.showReactions(
box: FlexboxLayout,
item: TootAnnouncement,
reactions: List<TootReaction>,
column: Column,
buttonHeight: Int,
marginBetween: Int,
contentColor: Int,
paddingH: Int,
paddingV: Int,
) {
var lastButton: View? = null
val options = DecodeOptions(
activity,
column.accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
authorDomain = column.accessInfo
2021-06-22 10:31:51 +02:00
)
2021-06-22 10:31:51 +02:00
val actMain = activity
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation.value
2021-06-22 10:31:51 +02:00
for (reaction in reactions) {
2021-06-22 10:31:51 +02:00
val url = if (disableEmojiAnimation) {
reaction.staticUrl.notEmpty() ?: reaction.url.notEmpty()
} else {
reaction.url.notEmpty() ?: reaction.staticUrl.notEmpty()
}
val b = AppCompatButton(activity).also { btn ->
2021-06-22 10:31:51 +02:00
btn.layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
endMargin = marginBetween
bottomMargin = marginBottom
}
btn.minWidthCompat = buttonHeight
btn.allCaps = false
btn.tag = reaction
btn.background = if (reaction.me) {
getAdaptiveRippleDrawableRound(
actMain,
actMain.attrColor(R.attr.colorButtonBgCw),
actMain.attrColor(R.attr.colorRippleEffect)
)
} else {
2021-06-22 10:31:51 +02:00
ContextCompat.getDrawable(actMain, R.drawable.btn_bg_transparent_round6dp)
}
2021-06-22 10:31:51 +02:00
btn.setTextColor(contentColor)
2021-06-22 10:31:51 +02:00
btn.setPadding(paddingH, paddingV, paddingH, paddingV)
2021-06-22 10:31:51 +02:00
btn.text = if (url == null) {
EmojiDecoder.decodeEmoji(options, "${reaction.name} ${reaction.count}")
} else {
SpannableStringBuilder("${reaction.name} ${reaction.count}").also { sb ->
sb.setSpan(
NetworkEmojiSpan(url, scale = 1.5f),
0,
reaction.name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
2021-06-22 10:31:51 +02:00
val invalidator =
NetworkEmojiInvalidator(actMain.handler, btn)
invalidator.register(sb)
extraInvalidatorList.add(invalidator)
}
2021-06-22 10:31:51 +02:00
}
2021-06-22 10:31:51 +02:00
btn.setOnClickListener {
if (reaction.me) {
reactionRemove(item, reaction.name)
} else {
reactionAdd(item, TootReaction.parseFedibird(buildJsonObject {
2021-06-22 10:31:51 +02:00
put("name", reaction.name)
put("count", 1)
put("me", true)
putNotNull("url", reaction.url)
putNotNull("static_url", reaction.staticUrl)
}))
}
}
}
2021-06-22 10:31:51 +02:00
box.addView(b)
lastButton = b
}
2021-06-22 10:31:51 +02:00
lastButton
?.layoutParams
?.cast<ViewGroup.MarginLayoutParams>()
?.endMargin = 0
}
fun ColumnViewHolder.reactionAdd(item: TootAnnouncement, sample: TootReaction?) {
val column = column ?: return
2022-05-29 15:38:21 +02:00
if (sample == null) {
2022-05-29 15:38:21 +02:00
launchEmojiPicker(activity, column.accessInfo, closeOnSelected = true) { emoji, _ ->
val code = when (emoji) {
is UnicodeEmoji -> emoji.unifiedCode
is CustomEmoji -> emoji.shortcode
}
2022-05-29 15:38:21 +02:00
ColumnViewHolder.log.d("addReaction: $code ${emoji.javaClass.simpleName}")
reactionAdd(item, TootReaction.parseFedibird(buildJsonObject {
put("name", code)
put("count", 1)
put("me", true)
// 以下はカスタム絵文字のみ
if (emoji is CustomEmoji) {
putNotNull("url", emoji.url)
putNotNull("static_url", emoji.staticUrl)
}
}))
2022-05-29 15:38:21 +02:00
}
return
}
2022-05-29 15:38:21 +02:00
activity.launchAndShowError {
activity.runApiTask(column.accessInfo) { client ->
client.request(
"/api/v1/announcements/${item.id}/reactions/${sample.name.encodePercent()}",
JsonObject().toPutRequestBuilder()
)
// 200 {}
}?.let { result ->
when (result.jsonObject) {
null -> activity.showToast(true, result.error)
else -> {
sample.count = 0
val list = item.reactions
if (list == null) {
item.reactions = mutableListOf(sample)
} else {
val reaction = list.find { it.name == sample.name }
if (reaction == null) {
list.add(sample)
} else {
reaction.me = true
++reaction.count
}
}
column.announcementUpdated = SystemClock.elapsedRealtime()
showAnnouncements()
}
}
}
}
}
fun ColumnViewHolder.reactionRemove(item: TootAnnouncement, name: String) {
val column = column ?: return
launchMain {
activity.runApiTask(column.accessInfo) { client ->
client.request(
"/api/v1/announcements/${item.id}/reactions/${name.encodePercent()}",
JsonObject().toDeleteRequestBuilder()
)
// 200 {}
}?.let { result ->
when (result.jsonObject) {
null -> activity.showToast(true, result.error)
else -> item.reactions?.iterator()?.let {
while (it.hasNext()) {
val reaction = it.next()
if (reaction.name == name) {
reaction.me = false
if (--reaction.count <= 0) it.remove()
break
}
}
column.announcementUpdated = SystemClock.elapsedRealtime()
showAnnouncements()
}
}
}
}
}