package jp.juggler.subwaytooter import android.annotation.SuppressLint import android.graphics.Bitmap import android.net.Uri import android.os.AsyncTask import android.support.v4.view.ViewCompat import android.text.Editable import android.text.TextWatcher import android.view.View import android.view.inputmethod.EditorInfo import android.widget.Button import android.widget.CheckBox import android.widget.CompoundButton import android.widget.EditText import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection import java.util.regex.Pattern import jp.juggler.subwaytooter.action.Action_List import jp.juggler.subwaytooter.action.Action_Notification import jp.juggler.subwaytooter.table.AcctColor import android.support.v7.widget.ListRecyclerView import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.view.ListDivider import java.io.Closeable import java.lang.reflect.Field class ColumnViewHolder( val activity : ActMain, viewRoot : View ) : View.OnClickListener, SwipyRefreshLayout.OnRefreshListener, CompoundButton.OnCheckedChangeListener { companion object { private val log = LogCategory("ColumnViewHolder") private fun vg(v : View, visible : Boolean) { v.visibility = if(visible) View.VISIBLE else View.GONE } val fieldRecycler : Field by lazy { val field = RecyclerView::class.java.getDeclaredField("mRecycler") field.isAccessible = true field } val fieldState : Field by lazy { val field = RecyclerView::class.java.getDeclaredField("mState") field.isAccessible = true field } val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) } var column : Column? = null private var status_adapter : ItemListAdapter? = null private var page_idx : Int = 0 private val tvLoading : TextView val listView : ListRecyclerView val refreshLayout : SwipyRefreshLayout lateinit var listLayoutManager : LinearLayoutManager private val llColumnHeader : View private val tvColumnIndex : TextView private val tvColumnContext : TextView private val ivColumnIcon : ImageView private val tvColumnName : TextView private val llColumnSetting : View private val btnSearch : View private val etSearch : EditText private val cbResolve : CheckBox private val etRegexFilter : EditText private val tvRegexFilterError : TextView private val btnColumnSetting : ImageButton private val btnColumnReload : ImageButton private val btnColumnClose : ImageButton private val flColumnBackground : View private val ivColumnBackgroundImage : ImageView private val llSearch : View private val cbDontCloseColumn : CheckBox private val cbWithAttachment : CheckBox private val cbWithHighlight : CheckBox private val cbDontShowBoost : CheckBox private val cbDontShowFollow : CheckBox private val cbDontShowFavourite : CheckBox private val cbDontShowReply : CheckBox private val cbDontStreaming : CheckBox private val cbDontAutoRefresh : CheckBox private val cbHideMediaDefault : CheckBox private val cbEnableSpeech : CheckBox private val llRegexFilter : View private val btnDeleteNotification : Button private val llListList : View private val etListName : EditText private val btnListAdd : View private val isPageDestroyed : Boolean get() = column == null || activity.isFinishing private var loading_busy : Boolean = false private var last_image_uri : String? = null private var last_image_bitmap : Bitmap? = null private var last_image_task : AsyncTask? = null private val isRegexValid : Boolean get() { val s = etRegexFilter.text.toString() if(s.isEmpty()) { tvRegexFilterError.text = "" return true } try { val m = Pattern.compile(s).matcher("") if(m.find()) { // XXX: 空文字列にマッチする正規表現はエラー扱いにする? しなくてもよい? // tvRegexFilterError.text = activity.getString(R.string.) } } catch(ex : Throwable) { val message = ex.message tvRegexFilterError.text = if(message != null && message.isNotEmpty()) { message } else { ex.withCaption(activity.resources, R.string.regex_error) } return false } return true } val isColumnSettingShown : Boolean get() = llColumnSetting.visibility == View.VISIBLE // val headerView : HeaderViewHolderBase? // get() = status_adapter?.header val scrollPosition : ScrollPosition get() = ScrollPosition(this) ///////////////////////////////////////////////////////////////// // Column から呼ばれる init { if(activity.timeline_font != null) { viewRoot.scan { v -> try { if(v is Button) { // ボタンは触らない } else if(v is TextView) { v.typeface = activity.timeline_font } } catch(ex : Throwable) { log.trace(ex) } } } flColumnBackground = viewRoot.findViewById(R.id.flColumnBackground) ivColumnBackgroundImage = viewRoot.findViewById(R.id.ivColumnBackgroundImage) llColumnHeader = viewRoot.findViewById(R.id.llColumnHeader) tvColumnIndex = viewRoot.findViewById(R.id.tvColumnIndex) tvColumnName = viewRoot.findViewById(R.id.tvColumnName) tvColumnContext = viewRoot.findViewById(R.id.tvColumnContext) ivColumnIcon = viewRoot.findViewById(R.id.ivColumnIcon) btnColumnSetting = viewRoot.findViewById(R.id.btnColumnSetting) btnColumnReload = viewRoot.findViewById(R.id.btnColumnReload) btnColumnClose = viewRoot.findViewById(R.id.btnColumnClose) tvLoading = viewRoot.findViewById(R.id.tvLoading) listView = viewRoot.findViewById(R.id.listView) if(Pref.bpShareViewPool(activity.pref)) { listView.recycledViewPool = activity.viewPool } listView.itemAnimator = null // // val animator = listView.itemAnimator // if( animator is DefaultItemAnimator){ // animator.supportsChangeAnimations = false // } btnSearch = viewRoot.findViewById(R.id.btnSearch) etSearch = viewRoot.findViewById(R.id.etSearch) cbResolve = viewRoot.findViewById(R.id.cbResolve) llSearch = viewRoot.findViewById(R.id.llSearch) llListList = viewRoot.findViewById(R.id.llListList) btnListAdd = viewRoot.findViewById(R.id.btnListAdd) etListName = viewRoot.findViewById(R.id.etListName) btnListAdd.setOnClickListener(this) etListName.setOnEditorActionListener { _, actionId, _ -> var handled = false if(actionId == EditorInfo.IME_ACTION_SEND) { btnListAdd.performClick() handled = true } handled } llColumnSetting = viewRoot.findViewById(R.id.llColumnSetting) cbDontCloseColumn = viewRoot.findViewById(R.id.cbDontCloseColumn) cbWithAttachment = viewRoot.findViewById(R.id.cbWithAttachment) cbWithHighlight = viewRoot.findViewById(R.id.cbWithHighlight) cbDontShowBoost = viewRoot.findViewById(R.id.cbDontShowBoost) cbDontShowFollow = viewRoot.findViewById(R.id.cbDontShowFollow) cbDontShowFavourite = viewRoot.findViewById(R.id.cbDontShowFavourite) cbDontShowReply = viewRoot.findViewById(R.id.cbDontShowReply) cbDontStreaming = viewRoot.findViewById(R.id.cbDontStreaming) cbDontAutoRefresh = viewRoot.findViewById(R.id.cbDontAutoRefresh) cbHideMediaDefault = viewRoot.findViewById(R.id.cbHideMediaDefault) cbEnableSpeech = viewRoot.findViewById(R.id.cbEnableSpeech) etRegexFilter = viewRoot.findViewById(R.id.etRegexFilter) llRegexFilter = viewRoot.findViewById(R.id.llRegexFilter) tvRegexFilterError = viewRoot.findViewById(R.id.tvRegexFilterError) btnDeleteNotification = viewRoot.findViewById(R.id.btnDeleteNotification) llColumnHeader.setOnClickListener(this) btnColumnSetting.setOnClickListener(this) btnColumnReload.setOnClickListener(this) btnColumnClose.setOnClickListener(this) btnDeleteNotification.setOnClickListener(this) viewRoot.findViewById(R.id.btnColor).setOnClickListener(this) this.refreshLayout = viewRoot.findViewById(R.id.swipyRefreshLayout) refreshLayout.setOnRefreshListener(this) refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt()) cbDontCloseColumn.setOnCheckedChangeListener(this) cbWithAttachment.setOnCheckedChangeListener(this) cbWithHighlight.setOnCheckedChangeListener(this) cbDontShowBoost.setOnCheckedChangeListener(this) cbDontShowFollow.setOnCheckedChangeListener(this) cbDontShowFavourite.setOnCheckedChangeListener(this) cbDontShowReply.setOnCheckedChangeListener(this) cbDontStreaming.setOnCheckedChangeListener(this) cbDontAutoRefresh.setOnCheckedChangeListener(this) cbHideMediaDefault.setOnCheckedChangeListener(this) cbEnableSpeech.setOnCheckedChangeListener(this) // 入力の追跡 etRegexFilter.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged( s : CharSequence, start : Int, count : Int, after : Int ) { } override fun onTextChanged(s : CharSequence, start : Int, before : Int, count : Int) {} override fun afterTextChanged(s : Editable) { if(loading_busy) return activity.handler.removeCallbacks(proc_start_filter) if(isRegexValid) { activity.handler.postDelayed(proc_start_filter, 1500L) } } }) btnSearch.setOnClickListener(this) etSearch.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ -> if(! loading_busy) { if(actionId == EditorInfo.IME_ACTION_SEARCH) { btnSearch.performClick() return@OnEditorActionListener true } } false }) } private val proc_start_filter = Runnable { if(isPageDestroyed) return@Runnable if(isRegexValid) { val column = this.column ?: return@Runnable column.regex_text = etRegexFilter.text.toString() activity.app_state.saveColumnList() column.startLoading() } } private val proc_restoreScrollPosition = object : Runnable { override fun run() { activity.handler.removeCallbacks(this) if(isPageDestroyed) { log.d("restoreScrollPosition [%d], page is destroyed.") return } val column = this@ColumnViewHolder.column if(column == null) { log.d("restoreScrollPosition [%d], column==null", page_idx) return } if(column.is_dispose.get()) { log.d("restoreScrollPosition [%d], column is disposed", page_idx) return } if(column.hasMultipleViewHolder()) { log.d( "restoreScrollPosition [%d] %s , column has multiple view holder. retry later.", page_idx, column.getColumnName(true) ) // タブレットモードでカラムを追加/削除した際に発生する。 // このタイミングでスクロール位置を復元してもうまくいかないので延期する activity.handler.postDelayed(this, 100L) return } val sp = column.scroll_save ?: //復元後にもここを通るがこれは正常である // warning.d( "restoreScrollPosition [%d] %s , column has no saved scroll position.", page_idx, column.getColumnName( true ) ); return column.scroll_save = null if(listView.visibility != View.VISIBLE) { log.d( "restoreScrollPosition [%d] %s , listView is not visible. saved position %s,%s is dropped.", page_idx, column.getColumnName(true), sp.adapterIndex, sp.offset ) } else { log.d( "restoreScrollPosition [%d] %s , listView is visible. resume %s,%s", page_idx, column.getColumnName(true), sp.adapterIndex, sp.offset ) sp.restore(this@ColumnViewHolder) } } } fun onPageDestroy(page_idx : Int) { // タブレットモードの場合、onPageCreateより前に呼ばれる val column = this@ColumnViewHolder.column if(column != null) { log.d("onPageDestroy [%d] %s", page_idx, tvColumnName.text) saveScrollPosition() listView.adapter = null column.removeColumnViewHolder(this) this@ColumnViewHolder.column = null } closeBitmaps() activity.closeListItemPopup() } fun onPageCreate(column : Column, page_idx : Int, page_count : Int) { loading_busy = true try { this.column = column this.page_idx = page_idx log.d("onPageCreate [%d] %s", page_idx, column.getColumnName(true)) val bSimpleList = column.column_type != Column.TYPE_CONVERSATION && Pref.bpSimpleList(activity.pref) tvColumnIndex.text = activity.getString(R.string.column_index, page_idx + 1, page_count) listView.adapter = null listView.addItemDecoration(ListDivider(activity)) val status_adapter = ItemListAdapter(activity, column, this, bSimpleList) this.status_adapter = status_adapter // status_adapter.header = when(column.column_type) { // Column.TYPE_PROFILE -> ViewHolderHeaderProfile(activity, column, listView) // Column.TYPE_SEARCH -> ViewHolderHeaderSearch(activity, column, listView, activity.getString(R.string.search_desc_mastodon_api)) // Column.TYPE_SEARCH_MSP -> ViewHolderHeaderSearch(activity, column, listView, getSearchDesc(R.raw.search_desc_msp_en, R.raw.search_desc_msp_ja)) // Column.TYPE_SEARCH_TS -> ViewHolderHeaderSearch(activity, column, listView, getSearchDesc(R.raw.search_desc_ts_en, R.raw.search_desc_ts_ja)) // Column.TYPE_INSTANCE_INFORMATION -> ViewHolderHeaderInstance(activity, column, listView) // else -> null // } val isNotificationColumn = column.column_type == Column.TYPE_NOTIFICATIONS // 添付メディアや正規表現のフィルタ val bAllowFilter = column.canStatusFilter() llColumnSetting.visibility = View.GONE cbDontCloseColumn.isChecked = column.dont_close cbWithAttachment.isChecked = column.with_attachment cbWithHighlight.isChecked = column.with_highlight cbDontShowBoost.isChecked = column.dont_show_boost cbDontShowFollow.isChecked = column.dont_show_follow cbDontShowFavourite.isChecked = column.dont_show_favourite cbDontShowReply.isChecked = column.dont_show_reply cbDontStreaming.isChecked = column.dont_streaming cbDontAutoRefresh.isChecked = column.dont_auto_refresh cbHideMediaDefault.isChecked = column.hide_media_default cbEnableSpeech.isChecked = column.enable_speech etRegexFilter.setText(column.regex_text) etSearch.setText(column.search_query) cbResolve.isChecked = column.search_resolve vg(cbWithAttachment, bAllowFilter) vg(cbWithHighlight, bAllowFilter) vg(etRegexFilter, bAllowFilter) vg(llRegexFilter, bAllowFilter) vg(cbDontShowBoost, column.canFilterBoost()) vg(cbDontShowReply, column.canFilterReply()) vg(cbDontShowFavourite, isNotificationColumn) vg(cbDontShowFollow, isNotificationColumn) vg(cbDontStreaming, column.canStreaming()) vg(cbDontAutoRefresh, column.canAutoRefresh()) vg(cbHideMediaDefault, column.canNSFWDefault()) vg(cbEnableSpeech, column.canSpeech()) vg(btnDeleteNotification, column.column_type == Column.TYPE_NOTIFICATIONS) vg(llSearch, column.isSearchColumn) vg(llListList, column.column_type == Column.TYPE_LIST_LIST) vg(cbResolve, column.column_type == Column.TYPE_SEARCH) // tvRegexFilterErrorの表示を更新 if(bAllowFilter) { isRegexValid } when(column.column_type) { Column.TYPE_CONVERSATION, Column.TYPE_INSTANCE_INFORMATION -> refreshLayout.isEnabled = false Column.TYPE_SEARCH -> { refreshLayout.isEnabled = true refreshLayout.direction = SwipyRefreshLayoutDirection.TOP } else -> { refreshLayout.isEnabled = true refreshLayout.direction = SwipyRefreshLayoutDirection.BOTH } } // listLayoutManager = LinearLayoutManager(activity) listView.layoutManager = listLayoutManager listView.adapter = status_adapter //XXX FastScrollerのサポートを諦める。ライブラリはいくつかあるんだけど、設定でON/OFFできなかったり頭文字バブルを無効にできなかったり // listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity)) column.addColumnViewHolder(this) showColumnColor() showContent(reason = "onPageCreate", reset = true) } finally { loading_busy = false } } fun showColumnColor() { val column = this.column if(column != null) { var c = column.header_bg_color if(c == 0) { llColumnHeader.setBackgroundResource(R.drawable.btn_bg_ddd) } else { ViewCompat.setBackground( llColumnHeader, Styler.getAdaptiveRippleDrawable( c, if(column.header_fg_color != 0) column.header_fg_color else Styler.getAttributeColor(activity, R.attr.colorRippleEffect) ) ) } c = column.header_fg_color if(c == 0) { tvColumnIndex.setTextColor( Styler.getAttributeColor( activity, R.attr.colorColumnHeaderPageNumber ) ) tvColumnName.setTextColor( Styler.getAttributeColor( activity, android.R.attr.textColorPrimary ) ) Styler.setIconDefaultColor( activity, ivColumnIcon, column.getIconAttrId(column.column_type) ) Styler.setIconDefaultColor(activity, btnColumnSetting, R.attr.ic_tune) Styler.setIconDefaultColor(activity, btnColumnReload, R.attr.btn_refresh) Styler.setIconDefaultColor(activity, btnColumnClose, R.attr.btn_close) } else { tvColumnIndex.setTextColor(c) tvColumnName.setTextColor(c) Styler.setIconCustomColor( activity, ivColumnIcon, c, column.getIconAttrId(column.column_type) ) Styler.setIconCustomColor(activity, btnColumnSetting, c, R.attr.ic_tune) Styler.setIconCustomColor(activity, btnColumnReload, c, R.attr.btn_refresh) Styler.setIconCustomColor(activity, btnColumnClose, c, R.attr.btn_close) } c = column.column_bg_color if(c == 0) { ViewCompat.setBackground(flColumnBackground, null) } else { flColumnBackground.setBackgroundColor(c) } ivColumnBackgroundImage.alpha = column.column_bg_image_alpha loadBackgroundImage(ivColumnBackgroundImage, column.column_bg_image) status_adapter?.findHeaderViewHolder(listView)?.showColor() } } private fun closeBitmaps() { try { ivColumnBackgroundImage.visibility = View.GONE ivColumnBackgroundImage.setImageDrawable(null) last_image_bitmap?.recycle() last_image_bitmap = null last_image_task?.cancel(true) last_image_task = null last_image_uri = null } catch(ex : Throwable) { log.trace(ex) } } @SuppressLint("StaticFieldLeak") private fun loadBackgroundImage(iv : ImageView, url : String?) { try { if(url == null || url.isEmpty()) { // 指定がないなら閉じる 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 // 非同期処理を開始 val task = object : AsyncTask() { override fun doInBackground(vararg params : Void) : Bitmap? { return try { createResizedBitmap( activity, Uri.parse(url), if(screen_w > screen_h) screen_w else screen_h ) } catch(ex : Throwable) { log.trace(ex) null } } override fun onCancelled(bitmap : Bitmap?) { onPostExecute(bitmap) } override fun onPostExecute(bitmap : Bitmap?) { if(bitmap != null) { if(isCancelled || url != last_image_uri) { bitmap.recycle() } else { last_image_bitmap = bitmap iv.setImageBitmap(last_image_bitmap) iv.visibility = View.VISIBLE } } } } last_image_task = task task.executeOnExecutor(App1.task_executor) } catch(ex : Throwable) { log.trace(ex) } } fun closeColumnSetting() { llColumnSetting.visibility = View.GONE } fun onListListUpdated() { etListName.setText("") } override fun onRefresh(direction : SwipyRefreshLayoutDirection) { val column = this.column ?: return // カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある // リロードやリフレッシュ操作で直るようにする column.addColumnViewHolder(this) if(direction == SwipyRefreshLayoutDirection.TOP && column.canReloadWhenRefreshTop()) { refreshLayout.isRefreshing = false activity.handler.post { this@ColumnViewHolder.column?.startLoading() } return } column.startRefresh(false, direction == SwipyRefreshLayoutDirection.BOTTOM, - 1L, - 1) } override fun onCheckedChanged(view : CompoundButton, isChecked : Boolean) { val column = this@ColumnViewHolder.column if(loading_busy || column == null || status_adapter == null) return // カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある // リロードやリフレッシュ操作で直るようにする column.addColumnViewHolder(this) when(view.id) { R.id.cbDontCloseColumn -> { column.dont_close = isChecked showColumnCloseButton() activity.app_state.saveColumnList() } R.id.cbWithAttachment -> { column.with_attachment = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbWithHighlight -> { column.with_highlight = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbDontShowBoost -> { column.dont_show_boost = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbDontShowReply -> { column.dont_show_reply = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbDontShowFavourite -> { column.dont_show_favourite = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbDontShowFollow -> { column.dont_show_follow = isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.cbDontStreaming -> { column.dont_streaming = isChecked activity.app_state.saveColumnList() if(isChecked) { column.stopStreaming() } else { column.onStart(activity) } } R.id.cbDontAutoRefresh -> { column.dont_auto_refresh = isChecked activity.app_state.saveColumnList() } R.id.cbHideMediaDefault -> { column.hide_media_default = isChecked activity.app_state.saveColumnList() column.fireShowContent(reason = "HideMediaDefault in ColumnSetting", reset = true) } R.id.cbEnableSpeech -> { column.enable_speech = isChecked activity.app_state.saveColumnList() } } } override fun onClick(v : View) { val column = this.column val status_adapter = this.status_adapter if(loading_busy || column == null || status_adapter == null) return // カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある // リロードやリフレッシュ操作で直るようにする column.addColumnViewHolder(this) when(v.id) { R.id.btnColumnClose -> activity.closeColumn(false, column) R.id.btnColumnReload -> { App1.custom_emoji_cache.clearErrorCache() if(column.isSearchColumn) { etSearch.hideKeyboard() etSearch.setText(column.search_query) cbResolve.isChecked = column.search_resolve } refreshLayout.isRefreshing = false column.startLoading() } R.id.btnSearch -> { etSearch.hideKeyboard() column.search_query = etSearch.text.toString().trim { it <= ' ' } column.search_resolve = cbResolve.isChecked activity.app_state.saveColumnList() column.startLoading() } R.id.llColumnHeader -> { if(status_adapter.itemCount > 0) { scrollToTop() } } R.id.btnColumnSetting -> llColumnSetting.visibility = if(llColumnSetting.visibility == View.VISIBLE) View.GONE else View.VISIBLE R.id.btnDeleteNotification -> Action_Notification.deleteAll( activity, column.access_info, false ) R.id.btnColor -> { val idx = activity.app_state.column_list.indexOf(column) ActColumnCustomize.open(activity, idx, ActMain.REQUEST_CODE_COLUMN_COLOR) } R.id.btnListAdd -> { val tv = etListName.text.toString().trim { it <= ' ' } if(tv.isEmpty()) { showToast(activity, true, R.string.list_name_empty) return } Action_List.create(activity, column.access_info, tv, null) } } } private fun showError(message : String) { tvLoading.visibility = View.VISIBLE tvLoading.text = message refreshLayout.isRefreshing = false refreshLayout.visibility = View.GONE } private fun showColumnCloseButton() { val dont_close = column?.dont_close ?: return btnColumnClose.isEnabled = ! dont_close btnColumnClose.alpha = if(dont_close) 0.3f else 1f } // 相対時刻を更新する fun updateRelativeTime() = rebindAdapterItems() fun 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) } } // カラムヘッダなど、負荷が低い部分の表示更新 fun showColumnHeader() { val column = this.column ?: return val acct = column.access_info.acct val ac = AcctColor.load(acct) val nickname = ac.nickname tvColumnContext.text = if(nickname != null && nickname.isNotEmpty()) nickname else acct var c : Int c = ac.color_fg tvColumnContext.setTextColor( if(c != 0) c else Styler.getAttributeColor( activity, R.attr.colorTimeSmall ) ) c = ac.color_bg if(c == 0) { ViewCompat.setBackground(tvColumnContext, null) } else { tvColumnContext.setBackgroundColor(c) } tvColumnContext.setPaddingRelative(activity.acct_pad_lr, 0, activity.acct_pad_lr, 0) tvColumnName.text = column.getColumnName(false) showColumnCloseButton() } internal fun showContent( reason : String, changeList : List? = null, reset : Boolean = false ) { // クラッシュレポートにadapterとリストデータの状態不整合が多かったので、 // とりあえずリストデータ変更の通知だけは最優先で行っておく try { status_adapter?.notifyChange(reason, changeList, reset) } catch(ex : Throwable) { log.trace(ex) } showColumnHeader() 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 } tvLoading.visibility = View.GONE refreshLayout.visibility = View.VISIBLE status_adapter.findHeaderViewHolder(listView)?.bindData(column) if(! column.bRefreshLoading) { refreshLayout.isRefreshing = false val refreshError = column.mRefreshLoadingError if(refreshError.isNotEmpty()) { showToast(activity, true, refreshError) column.mRefreshLoadingError = "" } } // 表示状態が変わった後にもう一度呼び出す必要があるらしい。。。 // 試しにやめてみる status_adapter.notifyChange() proc_restoreScrollPosition.run() } private fun saveScrollPosition() { val column = this.column when { column == null -> log.d("saveScrollPosition [%d] , column==null", page_idx) column.is_dispose.get() -> log.d( "saveScrollPosition [%d] , column is disposed", page_idx ) listView.visibility != View.VISIBLE -> { val scroll_save = ScrollPosition(0, 0) column.scroll_save = scroll_save log.d( "saveScrollPosition [%d] %s , listView is not visible, save %s,%s" , page_idx , column.getColumnName(true) , scroll_save.adapterIndex , scroll_save.offset ) } else -> { val scroll_save = ScrollPosition(this) column.scroll_save = scroll_save log.d( "saveScrollPosition [%d] %s , listView is visible, save %s,%s" , page_idx , column.getColumnName(true) , scroll_save.adapterIndex , scroll_save.offset ) } } } fun 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 = fieldRecycler.get(listView) as RecyclerView.Recycler val state = fieldState.get(listView) as RecyclerView.State listLayoutManager.scrollVerticallyBy(dy, recycler, state) } catch(ex : Throwable) { log.trace(ex) log.e("can't access field in class %s", RecyclerView::class.java.simpleName) } }, 20L) } inner class AdapterItemHeightWorkarea internal constructor(val adapter : ItemListAdapter) : Closeable { private val item_width : Int private val widthSpec : Int private var lastViewType : Int = - 1 private 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 } } fun getAdapterItemHeight(adapterIndex : Int) : Int { var childViewHolder = listView.findViewHolderForAdapterPosition(adapterIndex) if(childViewHolder != null) { childViewHolder.itemView.measure(widthSpec, heightSpec) return childViewHolder.itemView.measuredHeight } log.d("getAdapterItemHeight idx=$adapterIndex createView") val viewType = adapter.getItemViewType(adapterIndex) 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) childViewHolder.itemView.measure(widthSpec, heightSpec) return childViewHolder.itemView.measuredHeight } } // 特定の要素が特定の位置に来るようにスクロール位置を調整する fun setListItemTop(listIndex : Int, yArg : Int) { var adapterIndex = column?.toAdapterIndex(listIndex) ?: return val adapter = status_adapter if(adapter == null) { log.e("setListItemTop: missing status adapter") return } var y = yArg AdapterItemHeightWorkarea(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) } fun getListItemTop(listIndex : Int) : Int { val adapterIndex = column?.toAdapterIndex(listIndex) ?: return 0 val childView = listLayoutManager.findViewByPosition(adapterIndex) ?: throw IndexOutOfBoundsException("findViewByPosition($adapterIndex) returns null.") return childView.top } fun findFirstVisibleListItem() : Int { val adapterIndex = listLayoutManager.findFirstVisibleItemPosition() if(adapterIndex == RecyclerView.NO_POSITION) throw IndexOutOfBoundsException() return column?.toListIndex(adapterIndex) ?: throw IndexOutOfBoundsException() } fun scrollToTop() { try { listLayoutManager.scrollToPositionWithOffset(0, 0) } catch(ignored : Throwable) { } } }