mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-02-04 12:47:48 +01:00
ColumnViewHolderクラスのメソッドを拡張関数として別ファイルに移動する
This commit is contained in:
parent
aa29a1e5ec
commit
eaed84d8a4
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,299 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import jp.juggler.subwaytooter.action.Action_Account
|
||||||
|
import jp.juggler.subwaytooter.action.Action_List
|
||||||
|
import jp.juggler.subwaytooter.action.Action_Notification
|
||||||
|
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
|
||||||
|
import jp.juggler.util.hideKeyboard
|
||||||
|
import jp.juggler.util.isCheckedNoAnime
|
||||||
|
import jp.juggler.util.showToast
|
||||||
|
import jp.juggler.util.withCaption
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onListListUpdated() {
|
||||||
|
etListName.setText("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.checkRegexFilterError(src: String): String? {
|
||||||
|
try {
|
||||||
|
if (src.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val m = Pattern.compile(src).matcher("")
|
||||||
|
if (m.find()) {
|
||||||
|
// 空文字列にマッチする正規表現はエラー扱いにする
|
||||||
|
// そうしないとCWの警告テキストにマッチしてしまう
|
||||||
|
return activity.getString(R.string.regex_filter_matches_empty_string)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
val message = ex.message
|
||||||
|
return if (message != null && message.isNotEmpty()) {
|
||||||
|
message
|
||||||
|
} else {
|
||||||
|
ex.withCaption(activity.resources, R.string.regex_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.isRegexValid(): Boolean {
|
||||||
|
val s = etRegexFilter.text.toString()
|
||||||
|
val error = checkRegexFilterError(s)
|
||||||
|
tvRegexFilterError.text = error ?: ""
|
||||||
|
return error == null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onCheckedChangedImpl(view: CompoundButton?, isChecked: Boolean) {
|
||||||
|
view ?: return
|
||||||
|
|
||||||
|
val column = this.column
|
||||||
|
|
||||||
|
if (binding_busy || column == null || status_adapter == null) return
|
||||||
|
|
||||||
|
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
|
||||||
|
// リロードやリフレッシュ操作で直るようにする
|
||||||
|
column.addColumnViewHolder(this)
|
||||||
|
|
||||||
|
when (view) {
|
||||||
|
|
||||||
|
cbDontCloseColumn -> {
|
||||||
|
column.dont_close = isChecked
|
||||||
|
showColumnCloseButton()
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbWithAttachment -> {
|
||||||
|
column.with_attachment = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbRemoteOnly -> {
|
||||||
|
column.remote_only = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbWithHighlight -> {
|
||||||
|
column.with_highlight = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowBoost -> {
|
||||||
|
column.dont_show_boost = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowReply -> {
|
||||||
|
column.dont_show_reply = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowReaction -> {
|
||||||
|
column.dont_show_reaction = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowVote -> {
|
||||||
|
column.dont_show_vote = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowNormalToot -> {
|
||||||
|
column.dont_show_normal_toot = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowNonPublicToot -> {
|
||||||
|
column.dont_show_non_public_toot = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowFavourite -> {
|
||||||
|
column.dont_show_favourite = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontShowFollow -> {
|
||||||
|
column.dont_show_follow = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbInstanceLocal -> {
|
||||||
|
column.instance_local = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontStreaming -> {
|
||||||
|
column.dont_streaming = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
activity.app_state.streamManager.updateStreamingColumns()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbDontAutoRefresh -> {
|
||||||
|
column.dont_auto_refresh = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbHideMediaDefault -> {
|
||||||
|
column.hide_media_default = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.fireShowContent(reason = "HideMediaDefault in ColumnSetting", reset = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
cbSystemNotificationNotRelated -> {
|
||||||
|
column.system_notification_not_related = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbEnableSpeech -> {
|
||||||
|
column.enable_speech = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
}
|
||||||
|
|
||||||
|
cbOldApi -> {
|
||||||
|
column.use_old_api = isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onClickImpl(v: View?) {
|
||||||
|
v?: return
|
||||||
|
|
||||||
|
val column = this.column
|
||||||
|
val status_adapter = this.status_adapter
|
||||||
|
if (binding_busy || column == null || status_adapter == null) return
|
||||||
|
|
||||||
|
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
|
||||||
|
// リロードやリフレッシュ操作で直るようにする
|
||||||
|
column.addColumnViewHolder(this)
|
||||||
|
|
||||||
|
when (v) {
|
||||||
|
btnColumnClose -> activity.closeColumn(column)
|
||||||
|
|
||||||
|
btnColumnReload -> {
|
||||||
|
App1.custom_emoji_cache.clearErrorCache()
|
||||||
|
|
||||||
|
if (column.isSearchColumn) {
|
||||||
|
etSearch.hideKeyboard()
|
||||||
|
etSearch.setText(column.search_query)
|
||||||
|
cbResolve.isCheckedNoAnime = column.search_resolve
|
||||||
|
}
|
||||||
|
refreshLayout.isRefreshing = false
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSearch -> {
|
||||||
|
etSearch.hideKeyboard()
|
||||||
|
column.search_query = etSearch.text.toString().trim { it <= ' ' }
|
||||||
|
column.search_resolve = cbResolve.isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSearchClear -> {
|
||||||
|
etSearch.setText("")
|
||||||
|
column.search_query = ""
|
||||||
|
column.search_resolve = cbResolve.isChecked
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
llColumnHeader -> scrollToTop2()
|
||||||
|
|
||||||
|
btnColumnSetting -> {
|
||||||
|
if (showColumnSetting(!isColumnSettingShown)) {
|
||||||
|
hideAnnouncements()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnDeleteNotification -> Action_Notification.deleteAll(
|
||||||
|
activity,
|
||||||
|
column.access_info,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
btnColor ->
|
||||||
|
activity.app_state.columnIndex(column)?.let {
|
||||||
|
ActColumnCustomize.open(activity, it, ActMain.REQUEST_CODE_COLUMN_COLOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
btnLanguageFilter ->
|
||||||
|
activity.app_state.columnIndex(column)?.let {
|
||||||
|
ActLanguageFilter.open(activity, it, ActMain.REQUEST_CODE_LANGUAGE_FILTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
btnListAdd -> {
|
||||||
|
val tv = etListName.text.toString().trim { it <= ' ' }
|
||||||
|
if (tv.isEmpty()) {
|
||||||
|
activity.showToast(true, R.string.list_name_empty)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Action_List.create(activity, column.access_info, tv, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
llRefreshError -> {
|
||||||
|
column.mRefreshLoadingErrorPopupState = 1 - column.mRefreshLoadingErrorPopupState
|
||||||
|
showRefreshError()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnQuickFilterAll -> clickQuickFilter(Column.QUICK_FILTER_ALL)
|
||||||
|
btnQuickFilterMention -> clickQuickFilter(Column.QUICK_FILTER_MENTION)
|
||||||
|
btnQuickFilterFavourite -> clickQuickFilter(Column.QUICK_FILTER_FAVOURITE)
|
||||||
|
btnQuickFilterBoost -> clickQuickFilter(Column.QUICK_FILTER_BOOST)
|
||||||
|
btnQuickFilterFollow -> clickQuickFilter(Column.QUICK_FILTER_FOLLOW)
|
||||||
|
btnQuickFilterPost -> clickQuickFilter(Column.QUICK_FILTER_POST)
|
||||||
|
btnQuickFilterReaction -> clickQuickFilter(Column.QUICK_FILTER_REACTION)
|
||||||
|
btnQuickFilterVote -> clickQuickFilter(Column.QUICK_FILTER_VOTE)
|
||||||
|
|
||||||
|
btnAnnouncements -> toggleAnnouncements()
|
||||||
|
|
||||||
|
btnAnnouncementsPrev -> {
|
||||||
|
column.announcementId =
|
||||||
|
TootAnnouncement.move(column.announcements, column.announcementId, -1)
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
showAnnouncements()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnAnnouncementsNext -> {
|
||||||
|
column.announcementId =
|
||||||
|
TootAnnouncement.move(column.announcements, column.announcementId, +1)
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
showAnnouncements()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnConfirmMail -> {
|
||||||
|
Action_Account.resendConfirmMail(activity, column.access_info)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onLongClickImpl(v: View?): Boolean {
|
||||||
|
v?: return false
|
||||||
|
return when (v) {
|
||||||
|
btnColumnClose ->
|
||||||
|
activity.app_state.columnIndex(column)?.let {
|
||||||
|
activity.closeColumnAll(it)
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,451 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.google.android.flexbox.FlexWrap
|
||||||
|
import com.google.android.flexbox.FlexboxLayout
|
||||||
|
import com.google.android.flexbox.JustifyContent
|
||||||
|
import jp.juggler.emoji.UnicodeEmoji
|
||||||
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
|
import jp.juggler.subwaytooter.api.TootTask
|
||||||
|
import jp.juggler.subwaytooter.api.TootTaskRunner
|
||||||
|
import jp.juggler.subwaytooter.api.entity.CustomEmoji
|
||||||
|
import jp.juggler.subwaytooter.api.entity.TootAnnouncement
|
||||||
|
import jp.juggler.subwaytooter.api.entity.TootReaction
|
||||||
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
|
import jp.juggler.subwaytooter.dialog.EmojiPicker
|
||||||
|
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||||
|
import jp.juggler.subwaytooter.util.*
|
||||||
|
import jp.juggler.util.*
|
||||||
|
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.app_state.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.app_state.saveColumnList()
|
||||||
|
showAnnouncements()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
|
||||||
|
val column = column ?: return
|
||||||
|
|
||||||
|
if (!force && lastAnnouncementShown >= column.announcementUpdated) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastAnnouncementShown = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
|
fun clearExtras() {
|
||||||
|
for (invalidator in extra_invalidator_list) {
|
||||||
|
invalidator.register(null)
|
||||||
|
}
|
||||||
|
extra_invalidator_list.clear()
|
||||||
|
}
|
||||||
|
llAnnouncementExtra.removeAllViews()
|
||||||
|
clearExtras()
|
||||||
|
|
||||||
|
val listShown = TootAnnouncement.filterShown(column.announcements)
|
||||||
|
if (listShown?.isEmpty() != false) {
|
||||||
|
btnAnnouncements.vg(false)
|
||||||
|
llAnnouncementsBox.vg(false)
|
||||||
|
btnAnnouncementsBadge.vg(false)
|
||||||
|
llColumnHeader.invalidate()
|
||||||
|
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 content_color = column.getContentColor()
|
||||||
|
|
||||||
|
val item = listShown.find { it.id == column.announcementId }
|
||||||
|
?: listShown[0]
|
||||||
|
|
||||||
|
val itemIndex = listShown.indexOf(item)
|
||||||
|
|
||||||
|
val enablePaging = listShown.size > 1
|
||||||
|
|
||||||
|
val alphaPrevNext = if (enablePaging) 1f else 0.3f
|
||||||
|
|
||||||
|
setIconDrawableId(
|
||||||
|
activity,
|
||||||
|
btnAnnouncementsPrev,
|
||||||
|
R.drawable.ic_arrow_start,
|
||||||
|
color = content_color,
|
||||||
|
alphaMultiplier = alphaPrevNext
|
||||||
|
)
|
||||||
|
|
||||||
|
setIconDrawableId(
|
||||||
|
activity,
|
||||||
|
btnAnnouncementsNext,
|
||||||
|
R.drawable.ic_arrow_end,
|
||||||
|
color = content_color,
|
||||||
|
alphaMultiplier = alphaPrevNext
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
btnAnnouncementsPrev.vg(expand)?.run {
|
||||||
|
isEnabled = enablePaging
|
||||||
|
}
|
||||||
|
btnAnnouncementsNext.vg(expand)?.run {
|
||||||
|
isEnabled = enablePaging
|
||||||
|
}
|
||||||
|
|
||||||
|
tvAnnouncementsCaption.textColor = content_color
|
||||||
|
tvAnnouncementsIndex.textColor = content_color
|
||||||
|
tvAnnouncementPeriod.textColor = content_color
|
||||||
|
|
||||||
|
val f = activity.timeline_font_size_sp
|
||||||
|
if (!f.isNaN()) {
|
||||||
|
tvAnnouncementsCaption.textSize = f
|
||||||
|
tvAnnouncementsIndex.textSize = f
|
||||||
|
tvAnnouncementPeriod.textSize = f
|
||||||
|
tvAnnouncementContent.textSize = f
|
||||||
|
}
|
||||||
|
val spacing = activity.timeline_spacing
|
||||||
|
if (spacing != null) {
|
||||||
|
tvAnnouncementPeriod.setLineSpacing(0f, spacing)
|
||||||
|
tvAnnouncementContent.setLineSpacing(0f, spacing)
|
||||||
|
}
|
||||||
|
tvAnnouncementsCaption.typeface = ActMain.timeline_font_bold
|
||||||
|
val font_normal = ActMain.timeline_font
|
||||||
|
tvAnnouncementsIndex.typeface = font_normal
|
||||||
|
tvAnnouncementPeriod.typeface = font_normal
|
||||||
|
tvAnnouncementContent.typeface = font_normal
|
||||||
|
|
||||||
|
tvAnnouncementsIndex.vg(expand)?.text =
|
||||||
|
activity.getString(R.string.announcements_index, itemIndex + 1, listShown.size)
|
||||||
|
llAnnouncements.vg(expand)
|
||||||
|
|
||||||
|
var periods: StringBuilder? = null
|
||||||
|
fun String.appendPeriod() {
|
||||||
|
val sb = periods
|
||||||
|
if (sb == null) {
|
||||||
|
periods = StringBuilder(this)
|
||||||
|
} else {
|
||||||
|
sb.append("\n")
|
||||||
|
sb.append(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = content_color
|
||||||
|
tvAnnouncementContent.text = item.decoded_content
|
||||||
|
tvAnnouncementContent.tag = this@showAnnouncements
|
||||||
|
announcementContentInvalidator.register(item.decoded_content)
|
||||||
|
|
||||||
|
// リアクションの表示
|
||||||
|
|
||||||
|
val density = activity.density
|
||||||
|
|
||||||
|
val buttonHeight = ActMain.boostButtonSize
|
||||||
|
val marginBetween = (buttonHeight.toFloat() * 0.2f + 0.5f).toInt()
|
||||||
|
val marginBottom = (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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +ボタン
|
||||||
|
run {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
b.contentDescription = activity.getString(R.string.reaction_add)
|
||||||
|
b.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
|
b.padding = paddingV
|
||||||
|
b.setOnClickListener {
|
||||||
|
addReaction(item, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIconDrawableId(
|
||||||
|
activity,
|
||||||
|
b,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
color = content_color,
|
||||||
|
alphaMultiplier = 1f
|
||||||
|
)
|
||||||
|
|
||||||
|
box.addView(b)
|
||||||
|
}
|
||||||
|
val reactions = item.reactions?.filter { it.count > 0L }?.notEmpty()
|
||||||
|
if (reactions != null) {
|
||||||
|
|
||||||
|
var lastButton: View? = null
|
||||||
|
|
||||||
|
val options = DecodeOptions(
|
||||||
|
activity,
|
||||||
|
column.access_info,
|
||||||
|
decodeEmoji = true,
|
||||||
|
enlargeEmoji = 1.5f,
|
||||||
|
mentionDefaultHostDomain = column.access_info
|
||||||
|
)
|
||||||
|
|
||||||
|
val actMain = activity
|
||||||
|
val disableEmojiAnimation = Pref.bpDisableEmojiAnimation(actMain.pref)
|
||||||
|
|
||||||
|
for (reaction in reactions) {
|
||||||
|
|
||||||
|
val url = if (disableEmojiAnimation) {
|
||||||
|
reaction.static_url.notEmpty() ?: reaction.url.notEmpty()
|
||||||
|
} else {
|
||||||
|
reaction.url.notEmpty() ?: reaction.static_url.notEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val b = Button(activity).also { btn ->
|
||||||
|
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 {
|
||||||
|
ContextCompat.getDrawable(actMain, R.drawable.btn_bg_transparent_round6dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.setTextColor(content_color)
|
||||||
|
|
||||||
|
btn.setPadding(paddingH, paddingV, paddingH, paddingV)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
val invalidator =
|
||||||
|
NetworkEmojiInvalidator(actMain.handler, btn)
|
||||||
|
invalidator.register(sb)
|
||||||
|
extra_invalidator_list.add(invalidator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.setOnClickListener {
|
||||||
|
if (reaction.me) {
|
||||||
|
removeReaction(item, reaction.name)
|
||||||
|
} else {
|
||||||
|
addReaction(item, TootReaction.parseFedibird(jsonObject {
|
||||||
|
put("name", reaction.name)
|
||||||
|
put("count", 1)
|
||||||
|
put("me", true)
|
||||||
|
putNotNull("url", reaction.url)
|
||||||
|
putNotNull("static_url", reaction.static_url)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
box.addView(b)
|
||||||
|
lastButton = b
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
lastButton
|
||||||
|
?.layoutParams
|
||||||
|
?.cast<ViewGroup.MarginLayoutParams>()
|
||||||
|
?.endMargin = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
llAnnouncementExtra.addView(box)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.addReaction(item: TootAnnouncement, sample: TootReaction?) {
|
||||||
|
val column = column ?: return
|
||||||
|
if (sample == null) {
|
||||||
|
EmojiPicker(activity, column.access_info, closeOnSelected = true) { result ->
|
||||||
|
val emoji = result.emoji
|
||||||
|
val code = when (emoji) {
|
||||||
|
is UnicodeEmoji -> emoji.unifiedCode
|
||||||
|
is CustomEmoji -> emoji.shortcode
|
||||||
|
else -> error("unknown emoji type")
|
||||||
|
}
|
||||||
|
ColumnViewHolder.log.d("addReaction: $code ${result.emoji.javaClass.simpleName}")
|
||||||
|
addReaction(item, TootReaction.parseFedibird(jsonObject {
|
||||||
|
put("name", code)
|
||||||
|
put("count", 1)
|
||||||
|
put("me", true)
|
||||||
|
// 以下はカスタム絵文字のみ
|
||||||
|
if (emoji is CustomEmoji) {
|
||||||
|
putNotNull("url", emoji.url)
|
||||||
|
putNotNull("static_url", emoji.static_url)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
TootTaskRunner(activity).run(column.access_info, object : TootTask {
|
||||||
|
override suspend fun background(client: TootApiClient): TootApiResult? {
|
||||||
|
return client.request(
|
||||||
|
"/api/v1/announcements/${item.id}/reactions/${sample.name.encodePercent()}",
|
||||||
|
JsonObject().toPutRequestBuilder()
|
||||||
|
)
|
||||||
|
// 200 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun handleResult(result: TootApiResult?) {
|
||||||
|
result ?: return
|
||||||
|
if (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.removeReaction(item: TootAnnouncement, name: String) {
|
||||||
|
val column = column ?: return
|
||||||
|
TootTaskRunner(activity).run(column.access_info, object : TootTask {
|
||||||
|
override suspend fun background(client: TootApiClient): TootApiResult? {
|
||||||
|
return client.request(
|
||||||
|
"/api/v1/announcements/${item.id}/reactions/${name.encodePercent()}",
|
||||||
|
JsonObject().toDeleteRequestBuilder()
|
||||||
|
)
|
||||||
|
// 200 {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun handleResult(result: TootApiResult?) {
|
||||||
|
result ?: return
|
||||||
|
if (result.jsonObject == null) {
|
||||||
|
activity.showToast(true, result.error)
|
||||||
|
} else {
|
||||||
|
val it = item.reactions?.iterator() ?: return
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,280 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
|
||||||
|
import jp.juggler.subwaytooter.streaming.canSpeech
|
||||||
|
import jp.juggler.subwaytooter.streaming.canStreaming
|
||||||
|
import jp.juggler.subwaytooter.util.endPadding
|
||||||
|
import jp.juggler.subwaytooter.util.startPadding
|
||||||
|
import jp.juggler.subwaytooter.view.ListDivider
|
||||||
|
import jp.juggler.util.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.jetbrains.anko.backgroundColor
|
||||||
|
import org.jetbrains.anko.bottomPadding
|
||||||
|
import org.jetbrains.anko.topPadding
|
||||||
|
|
||||||
|
fun ColumnViewHolder.closeBitmaps() {
|
||||||
|
try {
|
||||||
|
ivColumnBackgroundImage.visibility = View.GONE
|
||||||
|
ivColumnBackgroundImage.setImageDrawable(null)
|
||||||
|
|
||||||
|
last_image_bitmap?.recycle()
|
||||||
|
last_image_bitmap = null
|
||||||
|
|
||||||
|
last_image_task?.cancel()
|
||||||
|
last_image_task = null
|
||||||
|
|
||||||
|
last_image_uri = null
|
||||||
|
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.trace(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
|
||||||
|
try {
|
||||||
|
if (url == null || url.isEmpty() || Pref.bpDontShowColumnBackgroundImage(activity.pref)) {
|
||||||
|
// 指定がないなら閉じる
|
||||||
|
closeBitmaps()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == last_image_uri) {
|
||||||
|
// 今表示してるのと同じ
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直前の処理をキャンセルする。Bitmapも破棄する
|
||||||
|
closeBitmaps()
|
||||||
|
|
||||||
|
// ロード開始
|
||||||
|
last_image_uri = url
|
||||||
|
val screen_w = iv.resources.displayMetrics.widthPixels
|
||||||
|
val screen_h = iv.resources.displayMetrics.heightPixels
|
||||||
|
|
||||||
|
// 非同期処理を開始
|
||||||
|
last_image_task = GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
val bitmap = try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
createResizedBitmap(
|
||||||
|
activity, url.toUri(),
|
||||||
|
if (screen_w > screen_h)
|
||||||
|
screen_w
|
||||||
|
else
|
||||||
|
screen_h
|
||||||
|
)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.trace(ex)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (bitmap != null) {
|
||||||
|
if (!coroutineContext.isActive || url != last_image_uri) {
|
||||||
|
bitmap.recycle()
|
||||||
|
} else {
|
||||||
|
last_image_bitmap = bitmap
|
||||||
|
iv.setImageBitmap(last_image_bitmap)
|
||||||
|
iv.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.trace(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onPageDestroy(page_idx: Int) {
|
||||||
|
// タブレットモードの場合、onPageCreateより前に呼ばれる
|
||||||
|
val column = this.column
|
||||||
|
if (column != null) {
|
||||||
|
ColumnViewHolder.log.d("onPageDestroy [%d] %s", page_idx, tvColumnName.text)
|
||||||
|
saveScrollPosition()
|
||||||
|
listView.adapter = null
|
||||||
|
column.removeColumnViewHolder(this)
|
||||||
|
this.column = null
|
||||||
|
}
|
||||||
|
|
||||||
|
closeBitmaps()
|
||||||
|
|
||||||
|
activity.closeListItemPopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.onPageCreate(column: Column, page_idx: Int, page_count: Int) {
|
||||||
|
binding_busy = true
|
||||||
|
try {
|
||||||
|
this.column = column
|
||||||
|
this.page_idx = page_idx
|
||||||
|
|
||||||
|
ColumnViewHolder.log.d("onPageCreate [%d] %s", page_idx, column.getColumnName(true))
|
||||||
|
|
||||||
|
val bSimpleList =
|
||||||
|
column.type != ColumnType.CONVERSATION && Pref.bpSimpleList(activity.pref)
|
||||||
|
|
||||||
|
tvColumnIndex.text = activity.getString(R.string.column_index, page_idx + 1, page_count)
|
||||||
|
tvColumnStatus.text = "?"
|
||||||
|
ivColumnIcon.setImageResource(column.getIconId())
|
||||||
|
|
||||||
|
listView.adapter = null
|
||||||
|
if (listView.itemDecorationCount == 0) {
|
||||||
|
listView.addItemDecoration(ListDivider(activity))
|
||||||
|
}
|
||||||
|
|
||||||
|
val status_adapter = ItemListAdapter(activity, column, this, bSimpleList)
|
||||||
|
this.status_adapter = status_adapter
|
||||||
|
|
||||||
|
val isNotificationColumn = column.isNotificationColumn
|
||||||
|
|
||||||
|
// 添付メディアや正規表現のフィルタ
|
||||||
|
val bAllowFilter = column.canStatusFilter()
|
||||||
|
|
||||||
|
showColumnSetting(false)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cbDontCloseColumn.isCheckedNoAnime = column.dont_close
|
||||||
|
cbRemoteOnly.isCheckedNoAnime = column.remote_only
|
||||||
|
cbWithAttachment.isCheckedNoAnime = column.with_attachment
|
||||||
|
cbWithHighlight.isCheckedNoAnime = column.with_highlight
|
||||||
|
cbDontShowBoost.isCheckedNoAnime = column.dont_show_boost
|
||||||
|
cbDontShowFollow.isCheckedNoAnime = column.dont_show_follow
|
||||||
|
cbDontShowFavourite.isCheckedNoAnime = column.dont_show_favourite
|
||||||
|
cbDontShowReply.isCheckedNoAnime = column.dont_show_reply
|
||||||
|
cbDontShowReaction.isCheckedNoAnime = column.dont_show_reaction
|
||||||
|
cbDontShowVote.isCheckedNoAnime = column.dont_show_vote
|
||||||
|
cbDontShowNormalToot.isCheckedNoAnime = column.dont_show_normal_toot
|
||||||
|
cbDontShowNonPublicToot.isCheckedNoAnime = column.dont_show_non_public_toot
|
||||||
|
cbInstanceLocal.isCheckedNoAnime = column.instance_local
|
||||||
|
cbDontStreaming.isCheckedNoAnime = column.dont_streaming
|
||||||
|
cbDontAutoRefresh.isCheckedNoAnime = column.dont_auto_refresh
|
||||||
|
cbHideMediaDefault.isCheckedNoAnime = column.hide_media_default
|
||||||
|
cbSystemNotificationNotRelated.isCheckedNoAnime = column.system_notification_not_related
|
||||||
|
cbEnableSpeech.isCheckedNoAnime = column.enable_speech
|
||||||
|
cbOldApi.isCheckedNoAnime = column.use_old_api
|
||||||
|
|
||||||
|
etRegexFilter.setText(column.regex_text)
|
||||||
|
etSearch.setText(column.search_query)
|
||||||
|
cbResolve.isCheckedNoAnime = column.search_resolve
|
||||||
|
|
||||||
|
cbRemoteOnly.vg(column.canRemoteOnly())
|
||||||
|
|
||||||
|
cbWithAttachment.vg(bAllowFilter)
|
||||||
|
cbWithHighlight.vg(bAllowFilter)
|
||||||
|
etRegexFilter.vg(bAllowFilter)
|
||||||
|
llRegexFilter.vg(bAllowFilter)
|
||||||
|
btnLanguageFilter.vg(bAllowFilter)
|
||||||
|
|
||||||
|
cbDontShowBoost.vg(column.canFilterBoost())
|
||||||
|
cbDontShowReply.vg(column.canFilterReply())
|
||||||
|
cbDontShowNormalToot.vg(column.canFilterNormalToot())
|
||||||
|
cbDontShowNonPublicToot.vg(column.canFilterNonPublicToot())
|
||||||
|
cbDontShowReaction.vg(isNotificationColumn && column.isMisskey)
|
||||||
|
cbDontShowVote.vg(isNotificationColumn)
|
||||||
|
cbDontShowFavourite.vg(isNotificationColumn && !column.isMisskey)
|
||||||
|
cbDontShowFollow.vg(isNotificationColumn)
|
||||||
|
|
||||||
|
cbInstanceLocal.vg(column.type == ColumnType.HASHTAG)
|
||||||
|
|
||||||
|
|
||||||
|
cbDontStreaming.vg(column.canStreaming())
|
||||||
|
cbDontAutoRefresh.vg(column.canAutoRefresh())
|
||||||
|
cbHideMediaDefault.vg(column.canNSFWDefault())
|
||||||
|
cbSystemNotificationNotRelated.vg(column.isNotificationColumn)
|
||||||
|
cbEnableSpeech.vg(column.canSpeech())
|
||||||
|
cbOldApi.vg(column.type == ColumnType.DIRECT_MESSAGES)
|
||||||
|
|
||||||
|
|
||||||
|
btnDeleteNotification.vg(column.isNotificationColumn)
|
||||||
|
|
||||||
|
llSearch.vg(column.isSearchColumn)?.let {
|
||||||
|
btnSearchClear.vg(Pref.bpShowSearchClear(activity.pref))
|
||||||
|
}
|
||||||
|
|
||||||
|
llListList.vg(column.type == ColumnType.LIST_LIST)
|
||||||
|
cbResolve.vg(column.type == ColumnType.SEARCH)
|
||||||
|
|
||||||
|
llHashtagExtra.vg(column.hasHashtagExtra)
|
||||||
|
etHashtagExtraAny.setText(column.hashtag_any)
|
||||||
|
etHashtagExtraAll.setText(column.hashtag_all)
|
||||||
|
etHashtagExtraNone.setText(column.hashtag_none)
|
||||||
|
|
||||||
|
// tvRegexFilterErrorの表示を更新
|
||||||
|
if (bAllowFilter) {
|
||||||
|
isRegexValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
val canRefreshTop = column.canRefreshTopBySwipe()
|
||||||
|
val canRefreshBottom = column.canRefreshBottomBySwipe()
|
||||||
|
|
||||||
|
refreshLayout.isEnabled = canRefreshTop || canRefreshBottom
|
||||||
|
refreshLayout.direction = if (canRefreshTop && canRefreshBottom) {
|
||||||
|
SwipyRefreshLayoutDirection.BOTH
|
||||||
|
} else if (canRefreshTop) {
|
||||||
|
SwipyRefreshLayoutDirection.TOP
|
||||||
|
} else {
|
||||||
|
SwipyRefreshLayoutDirection.BOTTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
bRefreshErrorWillShown = false
|
||||||
|
llRefreshError.clearAnimation()
|
||||||
|
llRefreshError.visibility = View.GONE
|
||||||
|
|
||||||
|
//
|
||||||
|
listView.adapter = status_adapter
|
||||||
|
|
||||||
|
//XXX FastScrollerのサポートを諦める。ライブラリはいくつかあるんだけど、設定でON/OFFできなかったり頭文字バブルを無効にできなかったり
|
||||||
|
// listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity))
|
||||||
|
|
||||||
|
column.addColumnViewHolder(this)
|
||||||
|
|
||||||
|
lastAnnouncementShown = -1L
|
||||||
|
|
||||||
|
fun dip(dp: Int): Int = (activity.density * dp + 0.5f).toInt()
|
||||||
|
val context = activity
|
||||||
|
|
||||||
|
val announcementsBgColor = Pref.ipAnnouncementsBgColor(App1.pref).notZero()
|
||||||
|
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||||
|
|
||||||
|
btnAnnouncementsCutout.apply {
|
||||||
|
color = announcementsBgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
llAnnouncementsBox.apply {
|
||||||
|
background = createRoundDrawable(dip(6).toFloat(), announcementsBgColor)
|
||||||
|
val pad_tb = dip(2)
|
||||||
|
setPadding(0, pad_tb, 0, pad_tb)
|
||||||
|
}
|
||||||
|
|
||||||
|
val searchBgColor = Pref.ipSearchBgColor(App1.pref).notZero()
|
||||||
|
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||||
|
|
||||||
|
llSearch.apply {
|
||||||
|
backgroundColor = searchBgColor
|
||||||
|
startPadding = dip(12)
|
||||||
|
endPadding = dip(12)
|
||||||
|
topPadding = dip(3)
|
||||||
|
bottomPadding = dip(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
llListList.apply {
|
||||||
|
backgroundColor = searchBgColor
|
||||||
|
startPadding = dip(12)
|
||||||
|
endPadding = dip(12)
|
||||||
|
topPadding = dip(3)
|
||||||
|
bottomPadding = dip(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
showColumnColor()
|
||||||
|
|
||||||
|
showContent(reason = "onPageCreate", reset = true)
|
||||||
|
} finally {
|
||||||
|
binding_busy = false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,280 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||||
|
import jp.juggler.subwaytooter.view.ListDivider
|
||||||
|
import jp.juggler.util.abs
|
||||||
|
import java.io.Closeable
|
||||||
|
|
||||||
|
private class ErrorFlickListener(
|
||||||
|
private val cvh: ColumnViewHolder,
|
||||||
|
) : View.OnTouchListener, GestureDetector.OnGestureListener {
|
||||||
|
|
||||||
|
private val gd = GestureDetector(cvh.activity, this)
|
||||||
|
val density = cvh.activity.resources.displayMetrics.density
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||||
|
return gd.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShowPress(e: MotionEvent?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDown(e: MotionEvent?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScroll(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
distanceX: Float,
|
||||||
|
distanceY: Float
|
||||||
|
): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent?,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
val vx = velocityX.abs()
|
||||||
|
val vy = velocityY.abs()
|
||||||
|
if (vy < vx * 1.5f) {
|
||||||
|
// フリック方向が上下ではない
|
||||||
|
ColumnViewHolder.log.d("fling? not vertical view. $vx $vy")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val vyDp = vy / density
|
||||||
|
val limit = 1024f
|
||||||
|
ColumnViewHolder.log.d("fling? $vyDp/$limit")
|
||||||
|
if (vyDp >= limit) {
|
||||||
|
val column = cvh.column
|
||||||
|
if (column != null && column.lastTask == null) {
|
||||||
|
column.startLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AdapterItemHeightWorkarea(
|
||||||
|
val listView: RecyclerView,
|
||||||
|
val adapter: ItemListAdapter
|
||||||
|
) : Closeable {
|
||||||
|
|
||||||
|
private val item_width: Int
|
||||||
|
private val widthSpec: Int
|
||||||
|
var lastViewType: Int = -1
|
||||||
|
var lastViewHolder: RecyclerView.ViewHolder? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.item_width = listView.width - listView.paddingLeft - listView.paddingRight
|
||||||
|
this.widthSpec = View.MeasureSpec.makeMeasureSpec(item_width, View.MeasureSpec.EXACTLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
val childViewHolder = lastViewHolder
|
||||||
|
if (childViewHolder != null) {
|
||||||
|
adapter.onViewRecycled(childViewHolder)
|
||||||
|
lastViewHolder = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// この関数はAdapterViewの項目の(marginを含む)高さを返す
|
||||||
|
fun getAdapterItemHeight(adapterIndex: Int): Int {
|
||||||
|
|
||||||
|
fun View.getTotalHeight(): Int {
|
||||||
|
measure(widthSpec, ColumnViewHolder.heightSpec)
|
||||||
|
val lp = layoutParams as? ViewGroup.MarginLayoutParams
|
||||||
|
return measuredHeight + (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
listView.findViewHolderForAdapterPosition(adapterIndex)?.itemView?.let {
|
||||||
|
return it.getTotalHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnViewHolder.log.d("getAdapterItemHeight idx=$adapterIndex createView")
|
||||||
|
|
||||||
|
val viewType = adapter.getItemViewType(adapterIndex)
|
||||||
|
|
||||||
|
var childViewHolder = lastViewHolder
|
||||||
|
if (childViewHolder == null || lastViewType != viewType) {
|
||||||
|
if (childViewHolder != null) {
|
||||||
|
adapter.onViewRecycled(childViewHolder)
|
||||||
|
}
|
||||||
|
childViewHolder = adapter.onCreateViewHolder(listView, viewType)
|
||||||
|
lastViewHolder = childViewHolder
|
||||||
|
lastViewType = viewType
|
||||||
|
}
|
||||||
|
adapter.onBindViewHolder(childViewHolder, adapterIndex)
|
||||||
|
return childViewHolder.itemView.getTotalHeight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
fun ColumnViewHolder.initLoadingTextView() {
|
||||||
|
llLoading.setOnTouchListener(ErrorFlickListener(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特定の要素が特定の位置に来るようにスクロール位置を調整する
|
||||||
|
fun ColumnViewHolder.setListItemTop(listIndex: Int, yArg: Int) {
|
||||||
|
var adapterIndex = column?.toAdapterIndex(listIndex) ?: return
|
||||||
|
|
||||||
|
val adapter = status_adapter
|
||||||
|
if (adapter == null) {
|
||||||
|
ColumnViewHolder.log.e("setListItemTop: missing status adapter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var y = yArg
|
||||||
|
AdapterItemHeightWorkarea(listView, adapter).use { workarea ->
|
||||||
|
while (y > 0 && adapterIndex > 0) {
|
||||||
|
--adapterIndex
|
||||||
|
y -= workarea.getAdapterItemHeight(adapterIndex)
|
||||||
|
y -= ListDivider.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adapterIndex == 0 && y > 0) y = 0
|
||||||
|
listLayoutManager.scrollToPositionWithOffset(adapterIndex, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// この関数は scrollToPositionWithOffset 用のオフセットを返す
|
||||||
|
fun ColumnViewHolder.getListItemOffset(listIndex: Int): Int {
|
||||||
|
|
||||||
|
val adapterIndex = column?.toAdapterIndex(listIndex)
|
||||||
|
?: return 0
|
||||||
|
|
||||||
|
val childView = listLayoutManager.findViewByPosition(adapterIndex)
|
||||||
|
?: throw IndexOutOfBoundsException("findViewByPosition($adapterIndex) returns null.")
|
||||||
|
|
||||||
|
// スクロールとともにtopは減少する
|
||||||
|
// しかしtopMarginがあるので最大値は4である
|
||||||
|
// この関数は scrollToPositionWithOffset 用のオフセットを返すので top - topMargin を返す
|
||||||
|
return childView.top - ((childView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin
|
||||||
|
?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.findFirstVisibleListItem(): Int {
|
||||||
|
|
||||||
|
val adapterIndex = listLayoutManager.findFirstVisibleItemPosition()
|
||||||
|
|
||||||
|
if (adapterIndex == RecyclerView.NO_POSITION)
|
||||||
|
throw IndexOutOfBoundsException()
|
||||||
|
|
||||||
|
return column?.toListIndex(adapterIndex)
|
||||||
|
?: throw IndexOutOfBoundsException()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.scrollToTop() {
|
||||||
|
try {
|
||||||
|
listView.stopScroll()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.e(ex, "stopScroll failed.")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
listLayoutManager.scrollToPositionWithOffset(0, 0)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.e(ex, "scrollToPositionWithOffset failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.scrollToTop2() {
|
||||||
|
val status_adapter = this.status_adapter
|
||||||
|
if (binding_busy || status_adapter == null) return
|
||||||
|
if (status_adapter.itemCount > 0) {
|
||||||
|
scrollToTop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.saveScrollPosition(): Boolean {
|
||||||
|
val column = this.column
|
||||||
|
when {
|
||||||
|
column == null -> ColumnViewHolder.log.d("saveScrollPosition [%d] , column==null", page_idx)
|
||||||
|
|
||||||
|
column.is_dispose.get() -> ColumnViewHolder.log.d(
|
||||||
|
"saveScrollPosition [%d] , column is disposed",
|
||||||
|
page_idx
|
||||||
|
)
|
||||||
|
|
||||||
|
listView.visibility != View.VISIBLE -> {
|
||||||
|
val scroll_save = ScrollPosition()
|
||||||
|
column.scroll_save = scroll_save
|
||||||
|
ColumnViewHolder.log.d(
|
||||||
|
"saveScrollPosition [%d] %s , listView is not visible, save %s,%s",
|
||||||
|
page_idx,
|
||||||
|
column.getColumnName(true),
|
||||||
|
scroll_save.adapterIndex,
|
||||||
|
scroll_save.offset
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val scroll_save = ScrollPosition(this)
|
||||||
|
column.scroll_save = scroll_save
|
||||||
|
ColumnViewHolder.log.d(
|
||||||
|
"saveScrollPosition [%d] %s , listView is visible, save %s,%s",
|
||||||
|
page_idx,
|
||||||
|
column.getColumnName(true),
|
||||||
|
scroll_save.adapterIndex,
|
||||||
|
scroll_save.offset
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.setScrollPosition(sp: ScrollPosition, deltaDp: Float = 0f) {
|
||||||
|
val last_adapter = listView.adapter
|
||||||
|
if (column == null || last_adapter == null) return
|
||||||
|
|
||||||
|
sp.restore(this)
|
||||||
|
|
||||||
|
// 復元した後に意図的に少し上下にずらしたい
|
||||||
|
val dy = (deltaDp * activity.density + 0.5f).toInt()
|
||||||
|
if (dy != 0) listView.postDelayed(Runnable {
|
||||||
|
if (column == null || listView.adapter !== last_adapter) return@Runnable
|
||||||
|
|
||||||
|
try {
|
||||||
|
val recycler = ColumnViewHolder.fieldRecycler.get(listView) as RecyclerView.Recycler
|
||||||
|
val state = ColumnViewHolder.fieldState.get(listView) as RecyclerView.State
|
||||||
|
listLayoutManager.scrollVerticallyBy(dy, recycler, state)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.trace(ex)
|
||||||
|
ColumnViewHolder.log.e("can't access field in class %s", RecyclerView::class.java.simpleName)
|
||||||
|
}
|
||||||
|
}, 20L)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 相対時刻を更新する
|
||||||
|
fun ColumnViewHolder.updateRelativeTime() = rebindAdapterItems()
|
||||||
|
|
||||||
|
fun ColumnViewHolder.rebindAdapterItems() {
|
||||||
|
for (childIndex in 0 until listView.childCount) {
|
||||||
|
val adapterIndex = listView.getChildAdapterPosition(listView.getChildAt(childIndex))
|
||||||
|
if (adapterIndex == RecyclerView.NO_POSITION) continue
|
||||||
|
status_adapter?.notifyItemChanged(adapterIndex)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.TextView
|
||||||
|
import jp.juggler.util.applyAlphaMultiplier
|
||||||
|
import jp.juggler.util.attrColor
|
||||||
|
import jp.juggler.util.getAdaptiveRippleDrawableRound
|
||||||
|
import jp.juggler.util.vg
|
||||||
|
import org.jetbrains.anko.backgroundDrawable
|
||||||
|
import org.jetbrains.anko.textColor
|
||||||
|
|
||||||
|
fun ColumnViewHolder.clickQuickFilter(filter: Int) {
|
||||||
|
column?.quick_filter = filter
|
||||||
|
showQuickFilter()
|
||||||
|
activity.app_state.saveColumnList()
|
||||||
|
column?.startLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showQuickFilter() {
|
||||||
|
val column = this.column ?: return
|
||||||
|
|
||||||
|
svQuickFilter.vg(column.isNotificationColumn) ?: return
|
||||||
|
|
||||||
|
btnQuickFilterReaction.vg(column.isMisskey)
|
||||||
|
btnQuickFilterFavourite.vg(!column.isMisskey)
|
||||||
|
|
||||||
|
val insideColumnSetting = Pref.bpMoveNotificationsQuickFilter(activity.pref)
|
||||||
|
|
||||||
|
val showQuickFilterButton: (btn: View, iconId: Int, selected: Boolean) -> Unit
|
||||||
|
|
||||||
|
if (insideColumnSetting) {
|
||||||
|
svQuickFilter.setBackgroundColor(0)
|
||||||
|
|
||||||
|
val colorFg = activity.attrColor(R.attr.colorContentText)
|
||||||
|
val colorBgSelected = colorFg.applyAlphaMultiplier(0.25f)
|
||||||
|
val colorFgList = ColorStateList.valueOf(colorFg)
|
||||||
|
val colorBg = activity.attrColor(R.attr.colorColumnSettingBackground)
|
||||||
|
showQuickFilterButton = { btn, iconId, selected ->
|
||||||
|
btn.backgroundDrawable =
|
||||||
|
getAdaptiveRippleDrawableRound(
|
||||||
|
activity,
|
||||||
|
if (selected) colorBgSelected else colorBg,
|
||||||
|
colorFg,
|
||||||
|
roundNormal = true
|
||||||
|
)
|
||||||
|
|
||||||
|
when (btn) {
|
||||||
|
is TextView -> btn.textColor = colorFg
|
||||||
|
|
||||||
|
is ImageButton -> {
|
||||||
|
btn.setImageResource(iconId)
|
||||||
|
btn.imageTintList = colorFgList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val colorBg = column.getHeaderBackgroundColor()
|
||||||
|
val colorFg = column.getHeaderNameColor()
|
||||||
|
val colorFgList = ColorStateList.valueOf(colorFg)
|
||||||
|
val colorBgSelected = Color.rgb(
|
||||||
|
(Color.red(colorBg) * 3 + Color.red(colorFg)) / 4,
|
||||||
|
(Color.green(colorBg) * 3 + Color.green(colorFg)) / 4,
|
||||||
|
(Color.blue(colorBg) * 3 + Color.blue(colorFg)) / 4
|
||||||
|
)
|
||||||
|
svQuickFilter.setBackgroundColor(colorBg)
|
||||||
|
|
||||||
|
showQuickFilterButton = { btn, iconId, selected ->
|
||||||
|
|
||||||
|
btn.backgroundDrawable = getAdaptiveRippleDrawableRound(
|
||||||
|
activity,
|
||||||
|
if (selected) colorBgSelected else colorBg,
|
||||||
|
colorFg
|
||||||
|
)
|
||||||
|
|
||||||
|
when (btn) {
|
||||||
|
is TextView -> btn.textColor = colorFg
|
||||||
|
|
||||||
|
is ImageButton -> {
|
||||||
|
btn.setImageResource(iconId)
|
||||||
|
btn.imageTintList = colorFgList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterAll,
|
||||||
|
0,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_ALL
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterMention,
|
||||||
|
R.drawable.ic_reply,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_MENTION
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterFavourite,
|
||||||
|
R.drawable.ic_star,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_FAVOURITE
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterBoost,
|
||||||
|
R.drawable.ic_repeat,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_BOOST
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterFollow,
|
||||||
|
R.drawable.ic_follow_plus,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_FOLLOW
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterPost,
|
||||||
|
R.drawable.ic_send,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_POST
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterReaction,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_REACTION
|
||||||
|
)
|
||||||
|
|
||||||
|
showQuickFilterButton(
|
||||||
|
btnQuickFilterVote,
|
||||||
|
R.drawable.ic_vote,
|
||||||
|
column.quick_filter == Column.QUICK_FILTER_VOTE
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import jp.juggler.util.notZero
|
||||||
|
import jp.juggler.util.vg
|
||||||
|
import org.jetbrains.anko.textColor
|
||||||
|
|
||||||
|
// カラムヘッダなど、負荷が低い部分の表示更新
|
||||||
|
fun ColumnViewHolder.showColumnHeader() {
|
||||||
|
activity.handler.removeCallbacks(procShowColumnHeader)
|
||||||
|
activity.handler.postDelayed(procShowColumnHeader, 50L)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showColumnStatus() {
|
||||||
|
activity.handler.removeCallbacks(procShowColumnStatus)
|
||||||
|
activity.handler.postDelayed(procShowColumnStatus, 50L)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showColumnColor() {
|
||||||
|
val column = this.column
|
||||||
|
if (column == null || column.is_dispose.get()) return
|
||||||
|
|
||||||
|
// カラムヘッダ背景
|
||||||
|
column.setHeaderBackground(llColumnHeader)
|
||||||
|
|
||||||
|
// カラムヘッダ文字色(A)
|
||||||
|
var c = column.getHeaderNameColor()
|
||||||
|
val csl = ColorStateList.valueOf(c)
|
||||||
|
tvColumnName.textColor = c
|
||||||
|
ivColumnIcon.imageTintList = csl
|
||||||
|
btnAnnouncements.imageTintList = csl
|
||||||
|
btnColumnSetting.imageTintList = csl
|
||||||
|
btnColumnReload.imageTintList = csl
|
||||||
|
btnColumnClose.imageTintList = csl
|
||||||
|
|
||||||
|
// カラムヘッダ文字色(B)
|
||||||
|
c = column.getHeaderPageNumberColor()
|
||||||
|
tvColumnIndex.textColor = c
|
||||||
|
tvColumnStatus.textColor = c
|
||||||
|
|
||||||
|
// カラム内部の背景色
|
||||||
|
flColumnBackground.setBackgroundColor(
|
||||||
|
column.column_bg_color.notZero()
|
||||||
|
?: Column.defaultColorContentBg
|
||||||
|
)
|
||||||
|
|
||||||
|
// カラム内部の背景画像
|
||||||
|
ivColumnBackgroundImage.alpha = column.column_bg_image_alpha
|
||||||
|
loadBackgroundImage(ivColumnBackgroundImage, column.column_bg_image)
|
||||||
|
|
||||||
|
// エラー表示
|
||||||
|
tvLoading.textColor = column.getContentColor()
|
||||||
|
|
||||||
|
status_adapter?.findHeaderViewHolder(listView)?.showColor()
|
||||||
|
|
||||||
|
// カラム色を変更したらクイックフィルタの色も変わる場合がある
|
||||||
|
showQuickFilter()
|
||||||
|
|
||||||
|
showAnnouncements(force = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showError(message: String) {
|
||||||
|
hideRefreshError()
|
||||||
|
|
||||||
|
refreshLayout.isRefreshing = false
|
||||||
|
refreshLayout.visibility = View.GONE
|
||||||
|
|
||||||
|
llLoading.visibility = View.VISIBLE
|
||||||
|
tvLoading.text = message
|
||||||
|
btnConfirmMail.vg(column?.access_info?.isConfirmed == false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showColumnCloseButton() {
|
||||||
|
val dont_close = column?.dont_close ?: return
|
||||||
|
btnColumnClose.isEnabled = !dont_close
|
||||||
|
btnColumnClose.alpha = if (dont_close) 0.3f else 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ColumnViewHolder.showContent(
|
||||||
|
reason: String,
|
||||||
|
changeList: List<AdapterChange>? = null,
|
||||||
|
reset: Boolean = false
|
||||||
|
) {
|
||||||
|
// クラッシュレポートにadapterとリストデータの状態不整合が多かったので、
|
||||||
|
// とりあえずリストデータ変更の通知だけは最優先で行っておく
|
||||||
|
try {
|
||||||
|
status_adapter?.notifyChange(reason, changeList, reset)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ColumnViewHolder.log.trace(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
showColumnHeader()
|
||||||
|
showColumnStatus()
|
||||||
|
|
||||||
|
val column = this.column
|
||||||
|
if (column == null || column.is_dispose.get()) {
|
||||||
|
showError("column was disposed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!column.bFirstInitialized) {
|
||||||
|
showError("initializing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.bInitialLoading) {
|
||||||
|
var message: String? = column.task_progress
|
||||||
|
if (message == null) message = "loading?"
|
||||||
|
showError(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val initialLoadingError = column.mInitialLoadingError
|
||||||
|
if (initialLoadingError.isNotEmpty()) {
|
||||||
|
showError(initialLoadingError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val status_adapter = this.status_adapter
|
||||||
|
|
||||||
|
if (status_adapter == null || status_adapter.itemCount == 0) {
|
||||||
|
showError(activity.getString(R.string.list_empty))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
llLoading.visibility = View.GONE
|
||||||
|
|
||||||
|
refreshLayout.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
status_adapter.findHeaderViewHolder(listView)?.bindData(column)
|
||||||
|
|
||||||
|
if (column.bRefreshLoading) {
|
||||||
|
hideRefreshError()
|
||||||
|
} else {
|
||||||
|
refreshLayout.isRefreshing = false
|
||||||
|
showRefreshError()
|
||||||
|
}
|
||||||
|
proc_restoreScrollPosition.run()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showColumnSetting(show: Boolean): Boolean {
|
||||||
|
llColumnSetting.vg(show)
|
||||||
|
llColumnHeader.invalidate()
|
||||||
|
return show
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.showRefreshError() {
|
||||||
|
val column = column
|
||||||
|
if (column == null) {
|
||||||
|
hideRefreshError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val refreshError = column.mRefreshLoadingError
|
||||||
|
// val refreshErrorTime = column.mRefreshLoadingErrorTime
|
||||||
|
if (refreshError.isEmpty()) {
|
||||||
|
hideRefreshError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tvRefreshError.text = refreshError
|
||||||
|
when (column.mRefreshLoadingErrorPopupState) {
|
||||||
|
// initially expanded
|
||||||
|
0 -> {
|
||||||
|
tvRefreshError.isSingleLine = false
|
||||||
|
tvRefreshError.ellipsize = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// tap to minimize
|
||||||
|
1 -> {
|
||||||
|
tvRefreshError.isSingleLine = true
|
||||||
|
tvRefreshError.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bRefreshErrorWillShown) {
|
||||||
|
bRefreshErrorWillShown = true
|
||||||
|
if (llRefreshError.visibility == View.GONE) {
|
||||||
|
llRefreshError.visibility = View.VISIBLE
|
||||||
|
val aa = AlphaAnimation(0f, 1f)
|
||||||
|
aa.duration = 666L
|
||||||
|
llRefreshError.clearAnimation()
|
||||||
|
llRefreshError.startAnimation(aa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ColumnViewHolder.hideRefreshError() {
|
||||||
|
if (!bRefreshErrorWillShown) return
|
||||||
|
bRefreshErrorWillShown = false
|
||||||
|
if (llRefreshError.visibility == View.GONE) return
|
||||||
|
val aa = AlphaAnimation(1f, 0f)
|
||||||
|
aa.duration = 666L
|
||||||
|
aa.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationRepeat(animation: Animation?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationStart(animation: Animation?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animation?) {
|
||||||
|
if (!bRefreshErrorWillShown) llRefreshError.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
llRefreshError.clearAnimation()
|
||||||
|
llRefreshError.startAnimation(aa)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user