- RecyclerView.RecycledViewPool を使ってタブレットモードでの応答性を改善

- そのかわりFastScroller機能がなくなりました…
- アプリ設定に「リフレッシュ時に常にギャップを挟む」を追加。主にデバッグ用途
- TLからプロフを開いた時にヘッダ画像の下の方にフェードされない余白ができるバグの修正
This commit is contained in:
tateisu 2018-01-18 02:39:16 +09:00
parent 4acc51dd24
commit 3bc2a7384d
45 changed files with 1419 additions and 1060 deletions

View File

@ -10,6 +10,7 @@
<w>apng</w>
<w>bumptech</w>
<w>coroutine</w>
<w>doctype</w>
<w>dont</w>
<w>emoji</w>
<w>emojione</w>

View File

@ -116,6 +116,9 @@ dependencies {
// Coroutine listeners for Anko Layouts
compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
// compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
}
repositories {

View File

@ -227,7 +227,7 @@ class TestTootApiClient {
return true
}
override fun close(code : Int, reason : String?) : Boolean { // TODO
override fun close(code : Int, reason : String?) : Boolean {
return true
}

View File

@ -1,96 +0,0 @@
package jp.juggler.subwaytooter.mock
import android.content.SharedPreferences
class MockSharedPreferences(
val map : HashMap<String, Any> = HashMap()
) : SharedPreferences {
override fun contains(key : String?) = map.contains(key)
override fun getBoolean(key : String?, defValue : Boolean)
= map.get(key) as? Boolean ?: defValue
override fun getInt(key : String?, defValue : Int)
= map.get(key) as? Int ?: defValue
override fun getLong(key : String?, defValue : Long)
= map.get(key) as? Long ?: defValue
override fun getFloat(key : String?, defValue : Float)
= map.get(key) as? Float ?: defValue
override fun getString(key : String?, defValue : String?)
= map.get(key) as? String ?: defValue
override fun getStringSet(key : String?, defValues : MutableSet<String>?)
= map.get(key) as? MutableSet<String> ?: defValues
override fun edit() : SharedPreferences.Editor {
return Editor(this)
}
override fun getAll() : MutableMap<String, *> {
TODO("not implemented")
}
override fun registerOnSharedPreferenceChangeListener(
listener : SharedPreferences.OnSharedPreferenceChangeListener?
) {
TODO("not implemented")
}
override fun unregisterOnSharedPreferenceChangeListener(
listener : SharedPreferences.OnSharedPreferenceChangeListener?
) {
TODO("not implemented")
}
companion object {
val REMOVED_OBJECT = Any()
}
class Editor(private val pref : MockSharedPreferences) : SharedPreferences.Editor {
private val changeSet = HashMap<String, Any>()
override fun commit() : Boolean {
for((k, v) in changeSet) {
if(v === REMOVED_OBJECT) {
pref.map.remove(k)
} else {
pref.map.put(k, v)
}
}
return true
}
override fun apply() {
commit()
}
override fun clear() : SharedPreferences.Editor {
changeSet.clear()
return this
}
override fun remove(key : String) : SharedPreferences.Editor {
changeSet.put(key, REMOVED_OBJECT)
return this
}
private fun putAny(k : String, v : Any?) : SharedPreferences.Editor {
changeSet.put(k, v ?: REMOVED_OBJECT)
return this
}
override fun putLong(key : String, value : Long) = putAny(key, value)
override fun putInt(key : String, value : Int) = putAny(key, value)
override fun putBoolean(key : String, value : Boolean) = putAny(key, value)
override fun putFloat(key : String, value : Float) = putAny(key, value)
override fun putString(key : String, value : String?) = putAny(key, value)
override fun putStringSet(key : String, value : MutableSet<String>?) = putAny(key, value)
}
}

View File

@ -0,0 +1,16 @@
package android.support.v7.widget
import android.content.Context
import android.util.AttributeSet
class ListRecyclerView : RecyclerView {
companion object {
// private val log = LogCategory("ListRecyclerView")
}
constructor(context : Context) : super(context)
constructor(context : Context, attrs : AttributeSet) : super(context, attrs)
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
}

View File

@ -1,322 +0,0 @@
//package com.bumptech.glide.load.resource.gif
//
//import android.annotation.SuppressLint
//import android.annotation.TargetApi
//import android.graphics.*
//import android.graphics.drawable.Animatable
//import android.graphics.drawable.Drawable
//import android.os.Build
//import android.view.Gravity
//
//import com.bumptech.glide.gifdecoder.GifDecoder
//import com.bumptech.glide.load.Transformation
//import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
//
//import java.lang.reflect.Field
//
//import jp.juggler.subwaytooter.util.LogCategory
//
//import com.bumptech.glide.gifdecoder.GifDecoder.TOTAL_ITERATION_COUNT_FOREVER
//
//@SuppressLint("ObsoleteSdkInt")
//@Suppress("unused")
//class MyGifDrawableOld : Drawable, GifFrameLoader.FrameCallback, Animatable {
//
//
// private val paint : Paint
// private val destRect = Rect()
// private val state : GifDrawable.GifState
// private val decoder : GifDecoder
// private val frameLoader : GifFrameLoader
//
// /**
// * True if the drawable is currently animating.
// */
// private var isRunning : Boolean = false
// /**
// * True if the drawable should animate while visible.
// */
// private var isStarted : Boolean = false
// /**
// * True if the drawable's resources have been recycled.
// */
// // For testing.
// private var isRecycled : Boolean = false
//
// override fun onFrameReady() {
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
// }
//
// /**
// * True if the drawable is currently visible. Default to true because on certain platforms (at least 4.1.1),
// * setVisible is not called on [Drawables][android.graphics.drawable.Drawable] during
// * [android.widget.ImageView.setImageDrawable]. See issue #130.
// */
// private var isVisibleX = true
// /**
// * The number of times we've looped over all the frames in the gif.
// */
// private var loopCount : Int = 0
// /**
// * The number of times to loop through the gif animation.
// */
// private var maxLoopCount = Animatable.LOOP_FOREVER
//
// private var applyGravity : Boolean = false
//
// private var mCornerRadius : Float = 0.toFloat()
//
// val firstFrame : Bitmap
// get() = state.firstFrame
//
// val frameTransformation : Transformation<Bitmap>
// get() = state.frameTransformation
//
// val data : ByteArray
// get() = state.data
//
// val frameCount : Int
// get() = decoder.frameCount
//
//
// constructor(other : GifDrawable, radius : Float) : this(cloneState(other)) {
// this.mCornerRadius = radius
// }
//
//
// private constructor(state : GifDrawable.GifState) {
//
// this.state = state
// this.decoder = GifDecoder(state.bitmapProvider)
// this.paint = Paint(Paint.FILTER_BITMAP_FLAG or Paint.ANTI_ALIAS_FLAG)
//
// decoder.setData(state.gifHeader, state.data)
// frameLoader = GifFrameLoader(
// state.context,
// this,
// decoder,
// state.targetWidth,
// state.targetHeight
// )
// frameLoader.setFrameTransformation(
// state.frameTransformation
// )
// }
//
// // Visible for testing.
// internal constructor(
// decoder : GifDecoder,
// frameLoader : GifFrameLoader,
// firstFrame : Bitmap,
// bitmapPool : BitmapPool,
// paint : Paint
// ) {
// this.decoder = decoder
// this.frameLoader = frameLoader
// this.state = GifDrawable.GifState(null)
// this.paint = paint
// state.bitmapPool = bitmapPool
// state.firstFrame = firstFrame
// }
//
// fun setFrameTransformation(frameTransformation : Transformation<Bitmap>?, firstFrame : Bitmap?) {
// if(firstFrame == null) {
// throw NullPointerException("The first frame of the GIF must not be null")
// }
// if(frameTransformation == null) {
// throw NullPointerException("The frame transformation must not be null")
// }
// state.frameTransformation = frameTransformation
// state.firstFrame = firstFrame
// frameLoader.setFrameTransformation(frameTransformation)
// }
//
// private fun resetLoopCount() {
// loopCount = 0
// }
//
// override fun start() {
// log.d("start")
// isStarted = true
// resetLoopCount()
// if(isVisibleX) {
// startRunning()
// }
// }
//
// override fun stop() {
// isStarted = false
// stopRunning()
//
// // On APIs > honeycomb we know our drawable is not being displayed anymore when it's callback is cleared and so
// // we can use the absence of a callback as an indication that it's ok to clear our temporary data. Prior to
// // honeycomb we can't tell if our callback is null and instead eagerly reset to avoid holding on to resources we
// // no longer need.
// if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// reset()
// }
// }
//
// /**
// * Clears temporary data and resets the drawable back to the first frame.
// */
// private fun reset() {
// frameLoader.clear()
// invalidateSelf()
// }
//
// private fun startRunning() {
// // If we have only a single frame, we don't want to decode it endlessly.
// if(decoder.frameCount == 1) {
// invalidateSelf()
// } else if(! isRunning) {
// isRunning = true
// frameLoader.start()
// invalidateSelf()
// }
// }
//
// private fun stopRunning() {
// isRunning = false
// frameLoader.stop()
// }
//
// override fun setVisible(visible : Boolean, restart : Boolean) : Boolean {
// isVisibleX = visible
// if(! visible) {
// stopRunning()
// } else if(isStarted) {
// startRunning()
// }
// return super.setVisible(visible, restart)
// }
//
// override fun getIntrinsicWidth() : Int {
// return state.firstFrame.width
// }
//
// override fun getIntrinsicHeight() : Int {
// return state.firstFrame.height
// }
//
// override fun isRunning() : Boolean {
// return isRunning
// }
//
// // For testing.
// internal fun setIsRunning(isRunning : Boolean) {
// this.isRunning = isRunning
// }
//
// override fun onBoundsChange(bounds : Rect) {
// super.onBoundsChange(bounds)
// applyGravity = true
// }
//
// override fun draw(canvas : Canvas) {
// if(isRecycled) {
// return
// }
//
// if(applyGravity) {
// Gravity.apply(Gravity.FILL, intrinsicWidth, intrinsicHeight, bounds, destRect)
// applyGravity = false
// }
//
// val currentFrame = frameLoader.currentFrame
// val toDraw = currentFrame ?: state.firstFrame
//
// if(mCornerRadius <= 0f) {
// paint.shader = null
// canvas.drawBitmap(toDraw, null, destRect, paint)
// } else {
// drawRoundImage(canvas, toDraw)
// }
// }
//
// private val mShaderMatrix = Matrix()
// private val mDstRectF = RectF()
//
// private fun drawRoundImage(canvas : Canvas, src : Bitmap?) {
// if(src == null) return
// val src_w = src.width
// val src_h = src.height
// if(src_w < 1 || src_h < 1) return
// // int outWidth = destRect.width();
// // int outHeight = destRect.height();
//
// mDstRectF.set(destRect)
// mShaderMatrix.preScale(mDstRectF.width() / src_w, mDstRectF.height() / src_h)
//
// val mBitmapShader = BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
// mBitmapShader.setLocalMatrix(mShaderMatrix)
//
// paint.shader = mBitmapShader
// canvas.drawRoundRect(mDstRectF, mCornerRadius, mCornerRadius, paint)
//
// }
//
// override fun setAlpha(i : Int) {
// paint.alpha = i
// }
//
// override fun setColorFilter(colorFilter : ColorFilter?) {
// paint.colorFilter = colorFilter
// }
//
// override fun getOpacity() : Int {
// // We can't tell, so default to transparent to be safe.
// return PixelFormat.TRANSPARENT
// }
//
// @TargetApi(Build.VERSION_CODES.HONEYCOMB)
// override fun onFrameReady(frameIndex : Int) {
// if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && callback == null) {
// stop()
// reset()
// return
// }
//
// invalidateSelf()
//
// if(frameIndex == decoder.frameCount - 1) {
// loopCount ++
// }
//
// if(maxLoopCount != GlideDrawable.LOOP_FOREVER && loopCount >= maxLoopCount) {
// stop()
// }
// }
//
// override fun getConstantState() : Drawable.ConstantState? {
// return state
// }
//
// /**
// * Clears any resources for loading frames that are currently held on to by this object.
// */
// fun recycle() {
// isRecycled = true
// state.bitmapPool.put(state.firstFrame)
// frameLoader.clear()
// frameLoader.stop()
// }
//
// override fun isAnimated() : Boolean {
// return true
// }
//
// override fun setLoopCount(loopCount : Int) {
// if(loopCount <= 0 && loopCount != GlideDrawable.LOOP_FOREVER && loopCount != GlideDrawable.LOOP_INTRINSIC) {
// throw IllegalArgumentException("Loop count must be greater than 0, or equal to " + "GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC")
// }
//
// maxLoopCount = if(loopCount == GlideDrawable.LOOP_INTRINSIC) {
// val intrinsicCount = decoder.totalIterationCount
// if(intrinsicCount == TOTAL_ITERATION_COUNT_FOREVER) GlideDrawable.LOOP_FOREVER else intrinsicCount
// } else {
// loopCount
// }
// }
//
//}

View File

@ -140,7 +140,7 @@ class MyGifDrawable internal constructor(state : GifDrawable.GifState) : Drawabl
) : this(
GifDrawable.GifState(
GifFrameLoader(
// TODO(b/27524013): Factor out this call to Glide.get()
// XXX(b/27524013): Factor out this call to Glide.get()
Glide.get(context),
gifDecoder,
targetFrameWidth,

View File

@ -155,7 +155,7 @@ class ActAppSetting : AppCompatActivity()
// initialize Switch and CheckBox
for(info in Pref.map.values) {
if( info is Pref.BooleanPref) {
if( info is Pref.BooleanPref && info.id != 0 ) {
val view = findViewById<CompoundButton>(info.id)
view.setOnCheckedChangeListener(this)
booleanViewList.add(BooleanViewInfo(info, view))

View File

@ -232,6 +232,15 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
Styler.fixHorizontalPadding(findViewById(R.id.svContent))
llColumnHeader = findViewById(R.id.llColumnHeader)
ivColumnHeader = findViewById(R.id.ivColumnHeader)
tvColumnName = findViewById(R.id.tvColumnName)
flColumnBackground = findViewById(R.id.flColumnBackground)
ivColumnBackground = findViewById(R.id.ivColumnBackground)
tvSampleAcct = findViewById(R.id.tvSampleAcct)
tvSampleContent = findViewById(R.id.tvSampleContent)
findViewById<View>(R.id.btnHeaderBackgroundEdit).setOnClickListener(this)
findViewById<View>(R.id.btnHeaderBackgroundReset).setOnClickListener(this)
findViewById<View>(R.id.btnHeaderTextEdit).setOnClickListener(this)
@ -245,14 +254,6 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
findViewById<View>(R.id.btnContentColor).setOnClickListener(this)
findViewById<View>(R.id.btnContentColorReset).setOnClickListener(this)
llColumnHeader = findViewById(R.id.llColumnHeader)
ivColumnHeader = findViewById(R.id.ivColumnHeader)
tvColumnName = findViewById(R.id.tvColumnName)
flColumnBackground = findViewById(R.id.flColumnBackground)
ivColumnBackground = findViewById(R.id.ivColumnBackground)
tvSampleAcct = findViewById(R.id.tvSampleAcct)
tvSampleContent = findViewById(R.id.tvSampleContent)
content_color_default = tvSampleContent.textColors.defaultColor

View File

@ -146,6 +146,9 @@ class ActMain : AppCompatActivity()
private lateinit var vFooterDivider1 : View
private lateinit var vFooterDivider2 : View
val viewPool = RecyclerView.RecycledViewPool()
var timeline_font : Typeface? = null
var timeline_font_bold : Typeface? = null
@ -210,7 +213,7 @@ class ActMain : AppCompatActivity()
if(tag is ItemViewHolder) {
column = tag.column
break
} else if(tag is HeaderViewHolderProfile) {
} else if(tag is ViewHolderHeaderProfile) {
column = tag.column
break
} else if(tag is TabletColumnViewHolder) {
@ -1033,7 +1036,7 @@ class ActMain : AppCompatActivity()
var media_thumb_height = 64
sv = Pref.spMediaThumbHeight(pref)
if(sv?.isNotEmpty() == true) {
if(sv.isNotEmpty()) {
try {
val iv = Integer.parseInt(sv)
if(iv >= 32) {

View File

@ -5,8 +5,7 @@ import android.content.Context
import android.net.Uri
import android.os.AsyncTask
import android.os.SystemClock
import android.view.View
import android.widget.ListView
import android.support.v7.widget.RecyclerView
import jp.juggler.subwaytooter.api.*
import org.json.JSONException
@ -35,6 +34,7 @@ import jp.juggler.subwaytooter.util.VersionString
import jp.juggler.subwaytooter.util.WordTrieTree
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.ListDivider
class Column(
val app_state : AppState,
@ -226,16 +226,6 @@ class Column(
@Suppress("HasPlatformType")
private val reSinceId = Pattern.compile("[&?]since_id=(\\d+)") // より新しいデータの取得に使う
private val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
private fun getListItemHeight(listView : ListView, idx : Int) : Int {
val item_width = listView.width - listView.paddingLeft - listView.paddingRight
val widthSpec = View.MeasureSpec.makeMeasureSpec(item_width, View.MeasureSpec.EXACTLY)
val childView = listView.adapter.getView(idx, null, listView)
childView.measure(widthSpec, heightSpec)
return childView.measuredHeight
}
val COLUMN_REGEX_FILTER_DEFAULT = { _ : CharSequence? -> false }
}
@ -1687,8 +1677,7 @@ class Column(
// 初期ロードの直後は先頭に移動する
try {
val holder = viewHolder
holder?.listView?.setSelection(0)
viewHolder?.listLayoutManager?.scrollToPositionWithOffset(0,0)
} catch(ignored : Throwable) {
}
@ -1809,6 +1798,7 @@ class Column(
var src = TootAccount.parseList(context, access_info, jsonArray)
list_tmp = addAll(null, src)
if(! bBottom) {
var bGapAdded = false
while(true) {
if(isCancelled) {
log.d("refresh-account-top: cancelled.")
@ -1830,6 +1820,7 @@ class Column(
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -1841,12 +1832,16 @@ class Column(
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
src = TootAccount.parseList(context, access_info, jsonArray)
addAll(list_tmp, src)
}
if(Pref.bpForceGap(context) && !isCancelled && !bGapAdded && list_tmp?.isNotEmpty() ==true ){
addOne(list_tmp, TootGap(max_id, last_since_id))
}
}
}
return result
@ -1912,6 +1907,7 @@ class Column(
src.sort()
list_tmp = addAll(null, src)
if(! bBottom) {
var bGapAdded = false
while(true) {
if(isCancelled) {
log.d("refresh-list-top: cancelled.")
@ -1933,7 +1929,7 @@ class Column(
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded =true
break
}
@ -1945,6 +1941,7 @@ class Column(
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded =true
break
}
@ -1952,6 +1949,9 @@ class Column(
src.sort()
addAll(list_tmp, src)
}
if(Pref.bpForceGap(context) && !isCancelled && !bGapAdded && list_tmp?.isNotEmpty() ==true ){
addOne(list_tmp, TootGap(max_id, last_since_id))
}
}
}
return result
@ -1968,6 +1968,7 @@ class Column(
var src = parseList(::TootReport, jsonArray)
list_tmp = addAll(null, src)
if(! bBottom) {
var bGapAdded = false
while(true) {
if(isCancelled) {
log.d("refresh-report-top: cancelled.")
@ -1989,7 +1990,7 @@ class Column(
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -2001,12 +2002,16 @@ class Column(
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
src = parseList(::TootReport, jsonArray)
addAll(list_tmp, src)
}
if(Pref.bpForceGap(context) && !isCancelled && !bGapAdded && list_tmp?.isNotEmpty() ==true ){
addOne(list_tmp, TootGap(max_id, last_since_id))
}
}
}
return result
@ -2029,7 +2034,7 @@ class Column(
if(! src.isEmpty()) {
PollingWorker.injectData(context, access_info.db_id, src)
}
var bGapAdded = false
while(true) {
if(isCancelled) {
log.d("refresh-notification-top: cancelled.")
@ -2051,6 +2056,7 @@ class Column(
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -2062,6 +2068,7 @@ class Column(
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -2071,6 +2078,9 @@ class Column(
PollingWorker.injectData(context, access_info.db_id, src)
}
}
if(Pref.bpForceGap(context) && !isCancelled && !bGapAdded && list_tmp?.isNotEmpty() ==true ){
addOne(list_tmp, TootGap(max_id, last_since_id))
}
} else {
while(true) {
if(isCancelled) {
@ -2189,6 +2199,7 @@ class Column(
}
}
} else {
var bGapAdded = false
while(true) {
if(isCancelled) {
log.d("refresh-status-top: cancelled.")
@ -2211,6 +2222,7 @@ class Column(
log.d("refresh-status-top: read enough. make gap.")
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -2219,6 +2231,7 @@ class Column(
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
@ -2230,12 +2243,16 @@ class Column(
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap(max_id, last_since_id))
bGapAdded = true
break
}
src = parser.statusList(jsonArray)
addWithFilterStatus(list_tmp, src)
}
if(Pref.bpForceGap(context) && !isCancelled && !bGapAdded && list_tmp?.isNotEmpty() ==true ){
addOne(list_tmp, TootGap(max_id, last_since_id))
}
}
}
return result
@ -2862,44 +2879,76 @@ class Column(
// 特定の要素が特定の位置に来るようにスクロール位置を調整する
private fun setItemTop(holder : ColumnViewHolder, idxArg : Int, yArg : Int) {
var idx = idxArg
var y = yArg
val listView = holder.listView
val hasHeader = holder.headerView != null
if(hasHeader) {
// Adapter中から見たpositionとListViewから見たpositionにズレができる
++ idx
}
val headerViewHolder = listView.findViewHolderForAdapterPosition(0)
var idx = if(headerViewHolder!= null) idxArg+1 else idxArg
var y = yArg
while(y > 0 && idx > 0) {
-- idx
y -= getListItemHeight(listView, idx)
y -= listView.dividerHeight
y -= holder.getListItemHeight( idx)
y -= ListDivider.height
}
listView.setSelectionFromTop(idx, y)
holder.listLayoutManager.scrollToPositionWithOffset(idx, y)
}
private fun getItemTop(holder : ColumnViewHolder, idxArg : Int) : Int {
var idx = idxArg
val listView = holder.listView
val hasHeader = holder.headerView != null
val headerViewHolder = listView.findViewHolderForAdapterPosition(0)
val layoutManager = holder.listLayoutManager
val idx = if(headerViewHolder!= null) idxArg+1 else idxArg
if(hasHeader) {
// Adapter中から見たpositionとListViewから見たpositionにズレができる
++ idx
}
val vs = listView.firstVisiblePosition
val ve = listView.lastVisiblePosition
if(idx < vs || ve < idx) {
val vs = layoutManager.findFirstVisibleItemPosition()
val ve = layoutManager.findLastVisibleItemPosition()
if( vs == RecyclerView.NO_POSITION || ve == RecyclerView.NO_POSITION ){
throw IndexOutOfBoundsException("listView has no visible position")
}else if(idx < vs || ve < idx) {
throw IndexOutOfBoundsException("not in visible range")
}
val child_idx = idx - vs
return listView.getChildAt(child_idx).top
}
enum class HeaderType(val viewType:Int){
Profile(1),
Search(2),
Instance(3),
}
fun getHeaderType():HeaderType?{
return when(column_type) {
Column.TYPE_PROFILE -> HeaderType.Profile
Column.TYPE_SEARCH -> HeaderType.Search
Column.TYPE_SEARCH_MSP -> HeaderType.Search
Column.TYPE_SEARCH_TS -> HeaderType.Search
Column.TYPE_INSTANCE_INFORMATION -> HeaderType.Instance
else -> null
}
}
private fun loadSearchDesc(raw_en : Int, raw_ja : Int) : String {
val res_id = if("ja" == context.getString(R.string.language_code)) raw_ja else raw_en
val data = Utils.loadRawResource(context, res_id)
return if(data == null) "?" else Utils.decodeUTF8(data)
}
private var cacheHeaderDesc: String? = null
fun getHeaderDesc() : String? {
var cache = cacheHeaderDesc
if( cache != null ) return cache
cache = when(column_type) {
Column.TYPE_SEARCH -> context.getString(R.string.search_desc_mastodon_api)
Column.TYPE_SEARCH_MSP -> loadSearchDesc(R.raw.search_desc_msp_en, R.raw.search_desc_msp_ja)
Column.TYPE_SEARCH_TS -> loadSearchDesc(R.raw.search_desc_ts_en, R.raw.search_desc_ts_ja)
else -> ""
}
cacheHeaderDesc = cache
return cache
}
////////////////////////////////////////////////////////////////////////
// Streaming
@ -3163,7 +3212,7 @@ class Column(
if(holder != null) {
if(list_data.size > 0) {
try {
restore_idx = holder.listView.firstVisiblePosition
restore_idx = holder.listLayoutManager.findFirstVisibleItemPosition()
restore_y = getItemTop(holder, restore_idx)
} catch(ex : IndexOutOfBoundsException) {
restore_idx = - 1

View File

@ -26,12 +26,20 @@ import jp.juggler.subwaytooter.action.Action_List
import jp.juggler.subwaytooter.action.Action_Notification
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.view.MyListView
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.subwaytooter.util.Utils
import android.support.v7.widget.ListRecyclerView
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import jp.juggler.subwaytooter.view.ListDivider
import java.lang.reflect.Field
internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnClickListener, SwipyRefreshLayout.OnRefreshListener, CompoundButton.OnCheckedChangeListener {
class ColumnViewHolder(
val activity : ActMain,
root : View
) : View.OnClickListener,
SwipyRefreshLayout.OnRefreshListener,
CompoundButton.OnCheckedChangeListener {
companion object {
private val log = LogCategory("ColumnViewHolder")
@ -39,6 +47,21 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
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
@ -46,9 +69,10 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
private var page_idx : Int = 0
private val tvLoading : TextView
val listView : MyListView
val listView : ListRecyclerView
val refreshLayout : SwipyRefreshLayout
lateinit var listLayoutManager : LinearLayoutManager
private val llColumnHeader : View
private val tvColumnIndex : TextView
private val tvColumnContext : TextView
@ -97,7 +121,6 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
private var last_image_bitmap : Bitmap? = null
private var last_image_task : AsyncTask<Void, Void, Bitmap?>? = null
private val isRegexValid : Boolean
get() {
val s = etRegexFilter.text.toString()
@ -105,13 +128,13 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
tvRegexFilterError.text = ""
return true
}
try{
try {
val m = Pattern.compile(s).matcher("")
if( m.find() ) {
if(m.find()) {
// XXX: 空文字列にマッチする正規表現はエラー扱いにする? しなくてもよい?
// tvRegexFilterError.text = activity.getString(R.string.)
}
}catch(ex : Throwable) {
} catch(ex : Throwable) {
val message = ex.message
tvRegexFilterError.text = if(message != null && message.isNotEmpty()) {
message
@ -126,18 +149,15 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
val isColumnSettingShown : Boolean
get() = llColumnSetting.visibility == View.VISIBLE
val headerView : HeaderViewHolderBase?
get() = status_adapter?.header
// val headerView : HeaderViewHolderBase?
// get() = status_adapter?.header
val scrollPosition : ScrollPosition
get() = ScrollPosition(listView)
get() = ScrollPosition(this)
/////////////////////////////////////////////////////////////////
// Column から呼ばれる
init {
if(activity.timeline_font != null) {
@ -170,6 +190,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
tvLoading = root.findViewById(R.id.tvLoading)
listView = root.findViewById(R.id.listView)
listView.recycledViewPool = activity.viewPool
btnSearch = root.findViewById(R.id.btnSearch)
etSearch = root.findViewById(R.id.etSearch)
@ -272,7 +293,6 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
}
private val proc_restoreScrollPosition = object : Runnable {
override fun run() {
activity.handler.removeCallbacks(this)
@ -294,7 +314,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
if(column.hasMultipleViewHolder()) {
log.d("restoreScrollPosition [%d] %s , column has multiple view holder. retry later.", page_idx, column .getColumnName(true))
log.d("restoreScrollPosition [%d] %s , column has multiple view holder. retry later.", page_idx, column.getColumnName(true))
// タブレットモードでカラムを追加/削除した際に発生する。
// このタイミングでスクロール位置を復元してもうまくいかないので延期する
@ -302,19 +322,19 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
return
}
val sp = column .scroll_save ?: //復元後にもここを通るがこれは正常である
val sp = column.scroll_save ?: //復元後にもここを通るがこれは正常である
// log.d( "restoreScrollPosition [%d] %s , column has no saved scroll position.", page_idx, column.getColumnName( true ) );
return
column .scroll_save = null
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.pos, sp.top
log.d("restoreScrollPosition [%d] %s , listView is not visible. saved position %s,%s is dropped.", page_idx, column.getColumnName(true), sp.pos, sp.top
)
} else {
log.d("restoreScrollPosition [%d] %s , listView is visible. resume %s,%s", page_idx, column .getColumnName(true), sp.pos, sp.top
log.d("restoreScrollPosition [%d] %s , listView is visible. resume %s,%s", page_idx, column.getColumnName(true), sp.pos, sp.top
)
sp.restore(listView)
sp.restore(this@ColumnViewHolder)
}
}
@ -349,18 +369,19 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
tvColumnIndex.text = activity.getString(R.string.column_index, page_idx + 1, page_count)
listView.adapter = null
val status_adapter = ItemListAdapter(activity, column, bSimpleList)
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 -> HeaderViewHolderProfile(activity, column, listView)
Column.TYPE_SEARCH -> HeaderViewHolderSearchDesc(activity, column, listView, activity.getString(R.string.search_desc_mastodon_api))
Column.TYPE_SEARCH_MSP -> HeaderViewHolderSearchDesc(activity, column, listView, getSearchDesc(R.raw.search_desc_msp_en, R.raw.search_desc_msp_ja))
Column.TYPE_SEARCH_TS -> HeaderViewHolderSearchDesc(activity, column, listView, getSearchDesc(R.raw.search_desc_ts_en, R.raw.search_desc_ts_ja))
Column.TYPE_INSTANCE_INFORMATION -> HeaderViewHolderInstance(activity, column, listView)
else -> null
}
// 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
@ -381,7 +402,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
cbHideMediaDefault.isChecked = column.hide_media_default
cbEnableSpeech.isChecked = column.enable_speech
etRegexFilter.setText(column.regex_text )
etRegexFilter.setText(column.regex_text)
etSearch.setText(column.search_query)
cbResolve.isChecked = column.search_resolve
@ -418,6 +439,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
refreshLayout.isEnabled = true
refreshLayout.direction = SwipyRefreshLayoutDirection.TOP
}
else -> {
refreshLayout.isEnabled = true
refreshLayout.direction = SwipyRefreshLayoutDirection.BOTH
@ -425,9 +447,12 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
//
listLayoutManager = LinearLayoutManager(activity)
listView.layoutManager = listLayoutManager
listView.adapter = status_adapter
listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity))
listView.onItemClickListener = status_adapter
//XXX FastScrollerのサポートを諦める。ライブラリはいくつかあるんだけど、設定でON/OFFできなかったり頭文字バブルを無効にできなかったり
// listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity))
column.addColumnViewHolder(this)
@ -439,12 +464,6 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
}
private fun getSearchDesc(raw_en : Int, raw_ja : Int) : String {
val res_id = if("ja" == activity.getString(R.string.language_code)) raw_ja else raw_en
val data = Utils.loadRawResource(activity, res_id)
return if(data == null) "?" else Utils.decodeUTF8(data)
}
fun showColumnColor() {
val column = this.column
@ -491,7 +510,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
loadBackgroundImage(ivColumnBackgroundImage, column.column_bg_image)
status_adapter?.header?.showColor()
status_adapter?.getHeaderViewHolder(listView)?.showColor()
}
}
@ -503,7 +522,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
last_image_bitmap?.recycle()
last_image_bitmap = null
last_image_task ?.cancel(true)
last_image_task?.cancel(true)
last_image_task = null
last_image_uri = null
@ -517,7 +536,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
@SuppressLint("StaticFieldLeak")
private fun loadBackgroundImage(iv : ImageView, url : String?) {
try {
if(url == null || url.isEmpty() ) {
if(url == null || url.isEmpty()) {
// 指定がないなら閉じる
closeBitmaps()
return
@ -589,9 +608,9 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
// リロードやリフレッシュ操作で直るようにする
column .addColumnViewHolder(this)
column.addColumnViewHolder(this)
if(direction == SwipyRefreshLayoutDirection.TOP && column .canReloadWhenRefreshTop()) {
if(direction == SwipyRefreshLayoutDirection.TOP && column.canReloadWhenRefreshTop()) {
refreshLayout.isRefreshing = false
activity.handler.post {
this@ColumnViewHolder.column?.startLoading()
@ -599,7 +618,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
return
}
column .startRefresh(false, direction == SwipyRefreshLayoutDirection.BOTTOM, - 1L, - 1)
column.startRefresh(false, direction == SwipyRefreshLayoutDirection.BOTTOM, - 1L, - 1)
}
override fun onCheckedChanged(view : CompoundButton, isChecked : Boolean) {
@ -609,76 +628,75 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
// カラムを追加/削除したときに ColumnからColumnViewHolderへの参照が外れることがある
// リロードやリフレッシュ操作で直るようにする
column .addColumnViewHolder(this)
column.addColumnViewHolder(this)
when(view.id) {
R.id.cbDontCloseColumn -> {
column .dont_close = isChecked
column.dont_close = isChecked
showColumnCloseButton()
activity.app_state.saveColumnList()
}
R.id.cbWithAttachment -> {
column .with_attachment = isChecked
column.with_attachment = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbWithHighlight -> {
column .with_highlight = isChecked
column.with_highlight = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbDontShowBoost -> {
column .dont_show_boost = isChecked
column.dont_show_boost = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbDontShowReply -> {
column .dont_show_reply = isChecked
column.dont_show_reply = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbDontShowFavourite -> {
column .dont_show_favourite = isChecked
column.dont_show_favourite = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbDontShowFollow -> {
column .dont_show_follow = isChecked
column.dont_show_follow = isChecked
activity.app_state.saveColumnList()
column .startLoading()
column.startLoading()
}
R.id.cbDontStreaming -> {
column .dont_streaming = isChecked
column.dont_streaming = isChecked
activity.app_state.saveColumnList()
if(isChecked) {
column .stopStreaming()
column.stopStreaming()
} else {
column .onStart(activity)
column.onStart(activity)
}
}
R.id.cbDontAutoRefresh -> {
column .dont_auto_refresh = isChecked
column.dont_auto_refresh = isChecked
activity.app_state.saveColumnList()
}
R.id.cbHideMediaDefault -> {
column .hide_media_default = isChecked
column.hide_media_default = isChecked
activity.app_state.saveColumnList()
column .fireShowContent()
column.fireShowContent()
}
R.id.cbEnableSpeech -> {
column .enable_speech = isChecked
column.enable_speech = isChecked
activity.app_state.saveColumnList()
}
}
@ -716,7 +734,11 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
column.startLoading()
}
R.id.llColumnHeader -> if(status_adapter .count > 0) listView.setSelectionFromTop(0, 0)
R.id.llColumnHeader ->{
if(status_adapter.itemCount > 0){
listLayoutManager.scrollToPositionWithOffset(0,0)
}
}
R.id.btnColumnSetting -> llColumnSetting.visibility = if(llColumnSetting.visibility == View.VISIBLE) View.GONE else View.VISIBLE
@ -750,10 +772,10 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
private fun showColumnCloseButton() {
val column = this@ColumnViewHolder.column ?: return
// カラム保護の状態
btnColumnClose.isEnabled = ! column .dont_close
btnColumnClose.alpha = if(column .dont_close) 0.3f else 1f
btnColumnClose.isEnabled = ! column.dont_close
btnColumnClose.alpha = if(column.dont_close) 0.3f else 1f
}
// カラムヘッダなど、負荷が低い部分の表示更新
@ -764,10 +786,10 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
val ac = AcctColor.load(acct)
val nickname = ac.nickname
tvColumnContext.text = if( nickname != null && nickname.isNotEmpty() ) nickname else acct
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))
@ -790,7 +812,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
// クラッシュレポートにadapterとリストデータの状態不整合が多かったので、
// とりあえずリストデータ変更の通知だけは最優先で行っておく
try {
status_adapter ?.notifyDataSetChanged()
status_adapter?.notifyDataSetChanged()
} catch(ex : Throwable) {
log.trace(ex)
}
@ -816,14 +838,14 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
val initialLoadingError = column.mInitialLoadingError
if( initialLoadingError.isNotEmpty() ) {
if(initialLoadingError.isNotEmpty()) {
showError(initialLoadingError)
return
}
val status_adapter = this.status_adapter
if(status_adapter == null || status_adapter.count == 0) {
if(status_adapter == null || status_adapter.itemCount == 0) {
showError(activity.getString(R.string.list_empty))
return
}
@ -832,12 +854,12 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
refreshLayout.visibility = View.VISIBLE
status_adapter.header?.bindData(column)
status_adapter.getHeaderViewHolder(listView)?.bindData(column)
if(! column.bRefreshLoading) {
refreshLayout.isRefreshing = false
val refreshError = column.mRefreshLoadingError
if(refreshError.isNotEmpty() ) {
if(refreshError.isNotEmpty()) {
Utils.showToast(activity, true, refreshError)
column.mRefreshLoadingError = ""
}
@ -867,7 +889,7 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
else -> {
val scroll_save = ScrollPosition(listView)
val scroll_save = ScrollPosition(this)
column.scroll_save = scroll_save
log.d("saveScrollPosition [%d] %s , listView is visible, save %s,%s"
, page_idx
@ -879,17 +901,49 @@ internal class ColumnViewHolder(val activity : ActMain, root : View) : View.OnCl
}
}
fun setScrollPosition(sp : ScrollPosition, delta : Float) {
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float) {
val last_adapter = listView.adapter
if(column == null || last_adapter == null) return
sp.restore(listView)
sp.restore(this)
listView.postDelayed(Runnable {
val dy = (deltaDp * activity.density +0.5f).toInt()
if(dy != 0) listView.postDelayed(Runnable {
if(column == null || listView.adapter !== last_adapter) return@Runnable
listView.scrollListBy((delta * activity.density).toInt())
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)
}
fun getListItemHeight( idx : Int) : Int {
val item_width = listView.width - listView.paddingLeft - listView.paddingRight
val widthSpec = View.MeasureSpec.makeMeasureSpec(item_width, View.MeasureSpec.EXACTLY)
var childViewHolder = listView.findViewHolderForAdapterPosition(idx)
if( childViewHolder != null ) {
childViewHolder.itemView.measure(widthSpec, heightSpec)
return childViewHolder.itemView.measuredHeight
}
val adapter = status_adapter
if( adapter != null) {
log.d("getListItemHeight idx=$idx createView")
val viewType = adapter.getItemViewType(idx)
childViewHolder = adapter.createViewHolder(listView, viewType)
adapter.bindViewHolder(childViewHolder, idx)
childViewHolder.itemView.measure(widthSpec, heightSpec)
return childViewHolder.itemView.measuredHeight
}
log.e("getListItemHeight: missing status adapter")
return 0
}
}

View File

@ -1,48 +0,0 @@
package jp.juggler.subwaytooter
import android.view.View
import android.widget.Button
import android.widget.TextView
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
internal abstract class HeaderViewHolderBase(val activity : ActMain, val column : Column, val viewRoot : View) {
companion object {
private val log = LogCategory("HeaderViewHolderBase")
}
val access_info : SavedAccount
internal abstract fun showColor()
internal abstract fun bindData(column : Column)
init {
this.access_info = column.access_info
// 初期化の間にthisを使うと警告が出るが、必要な処理なので…
@Suppress("LeakingThis")
viewRoot.tag = this
if(activity.timeline_font != null) {
Utils.scanView(viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
v.typeface = activity.timeline_font
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
}
}
}

View File

@ -1,62 +0,0 @@
package jp.juggler.subwaytooter
import android.view.View
import android.widget.Button
import android.widget.TextView
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyListView
internal class HeaderViewHolderSearchDesc(
arg_activity : ActMain,
arg_column : Column,
parent : MyListView,
html : String
) : HeaderViewHolderBase(
arg_activity,
arg_column,
arg_activity.layoutInflater.inflate(R.layout.lv_header_search_desc, parent, false)
) {
init {
Utils.scanView(viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
if(activity.timeline_font != null) {
v.typeface = activity.timeline_font
}
if(! activity.timeline_font_size_sp.isNaN()) {
v.textSize = activity.timeline_font_size_sp
}
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
val sv = DecodeOptions(decodeEmoji = true).decodeHTML(activity, access_info, html)
val tvSearchDesc = viewRoot.findViewById<TextView>(R.id.tvSearchDesc)
tvSearchDesc.visibility = View.VISIBLE
tvSearchDesc.movementMethod = MyLinkMovementMethod
tvSearchDesc.text = sv
}
override fun showColor() {
//
}
override fun bindData(column : Column) {
//
}
companion object {
private val log = LogCategory("HeaderViewHolderSearchDesc")
}
}

View File

@ -1,80 +1,131 @@
package jp.juggler.subwaytooter
import android.view.View
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.BaseAdapter
import jp.juggler.subwaytooter.view.MyListView
internal class ItemListAdapter(
private val activity : ActMain,
private val column : Column,
internal val columnVh : ColumnViewHolder,
private val bSimpleList : Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
internal class ItemListAdapter(private val activity : ActMain, private val column : Column, private val bSimpleList : Boolean)
: BaseAdapter()
, AdapterView.OnItemClickListener {
private val list : List<Any>
var header : HeaderViewHolderBase? = null
init {
this.list = column.list_data
}
override fun getCount() : Int {
return (if(header != null) 1 else 0) + column.list_data.size
override fun getItemCount() : Int {
return when(column.getHeaderType()){
null-> column.list_data.size
else-> column.list_data.size +1
}
}
override fun getViewTypeCount() : Int {
return if(header != null) 2 else 1
}
override fun getItemViewType(position : Int) : Int {
if(header != null) {
if(position == 0) return 1
}
return 0
val headerType = column.getHeaderType()
if( headerType == null || position>0 ) return 0
return headerType.viewType
}
override fun getItem(positionArg : Int) : Any? {
var position = positionArg
if(header != null) {
if(position == 0) return header
-- position
override fun onCreateViewHolder(parent : ViewGroup?, viewType : Int) : RecyclerView.ViewHolder {
when(viewType) {
0 -> {
val holder = ItemViewHolder(activity)
holder.viewRoot.tag = holder
return ViewHolderItem(holder)
}
Column.HeaderType.Profile.viewType -> {
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_profile, parent, false)
val holder = ViewHolderHeaderProfile(activity,viewRoot)
viewRoot.tag = holder
return holder
}
Column.HeaderType.Search.viewType -> {
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_search_desc, parent, false)
val holder = ViewHolderHeaderSearch(activity,viewRoot)
viewRoot.tag = holder
return holder
}
Column.HeaderType.Instance.viewType -> {
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_instance, parent, false)
val holder = ViewHolderHeaderInstance(activity,viewRoot)
viewRoot.tag = holder
return holder
}
else -> throw RuntimeException("unknown viewType: $viewType")
}
return if(position >= 0 && position < column.list_data.size) list[position] else null
}
fun getHeaderViewHolder(listView:RecyclerView): ViewHolderHeaderBase?{
return when(column.getHeaderType()){
null-> null
else-> listView.findViewHolderForAdapterPosition(0) as? ViewHolderHeaderBase
}
}
override fun onBindViewHolder(holder : RecyclerView.ViewHolder, positionArg : Int) {
val headerType = column.getHeaderType()
if(holder is ViewHolderItem) {
val position = if(headerType != null) positionArg - 1 else positionArg
val o = if(position >= 0 && position < list.size) list[position] else null
holder.ivh.bind(this, column, bSimpleList, o)
} else if(holder is ViewHolderHeaderBase) {
holder.bindData(column)
}
}
override fun onViewRecycled(holder : RecyclerView.ViewHolder) {
if(holder is ViewHolderItem) {
holder.ivh.onViewRecycled()
} else if(holder is ViewHolderHeaderBase) {
holder.onViewRecycled()
}
}
// override fun getViewTypeCount() : Int {
// return if(header != null) 2 else 1
// }
// override fun getItem(positionArg : Int) : Any? {
// var position = positionArg
// if(header != null) {
// if(position == 0) return header
// -- position
// }
// return if(position >= 0 && position < column.list_data.size) list[position] else null
// }
override fun getItemId(position : Int) : Long {
return 0
}
// override fun hasStableIds():Boolean = false
override fun getView(positionArg : Int, viewOld : View?, parent : ViewGroup) : View {
var position = positionArg
val header = this.header
if(header != null) {
if(position == 0) return header.viewRoot
-- position
}
val o = if(position >= 0 && position < list.size) list[position] else null
val holder : ItemViewHolder
val view : View
if(viewOld == null) {
holder = ItemViewHolder(activity, column, this, bSimpleList)
view = holder.viewRoot
view.tag = holder
} else {
view = viewOld
holder = view.tag as ItemViewHolder
}
holder.bind(o)
return view
}
override fun onItemClick(parent : AdapterView<*>, view : View, position : Int, id : Long) {
if(bSimpleList) {
(view.tag as? ItemViewHolder)?.onItemClick(parent as MyListView, view)
}
}
// override fun getView(positionArg : Int, viewOld : View?, parent : ViewGroup) : View {
// var position = positionArg
// val header = this.header
// if(header != null) {
// if(position == 0) return header.viewRoot
// -- position
// }
//
// val o = if(position >= 0 && position < list.size) list[position] else null
//
// val holder : ItemViewHolder
// val view : View
// if(viewOld == null) {
//
// } else {
// view = viewOld
// holder = view.tag as ItemViewHolder
// }
// holder.bind(o)
// return view
// }
}

View File

@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.os.SystemClock
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.app.AlertDialog
@ -44,18 +45,20 @@ import org.jetbrains.anko.*
import org.json.JSONObject
internal class ItemViewHolder(
val activity : ActMain,
val column : Column,
private val list_adapter : ItemListAdapter,
private val bSimpleList : Boolean
val activity : ActMain
) : View.OnClickListener, View.OnLongClickListener {
companion object {
private val log = LogCategory("ItemViewHolder")
}
var viewRoot : View
val viewRoot : View
private var bSimpleList : Boolean = false
lateinit var column : Column
private lateinit var list_adapter : ItemListAdapter
private lateinit var llBoosted : View
private lateinit var ivBoosted : ImageView
private lateinit var tvBoosted : TextView
@ -112,10 +115,9 @@ internal class ItemViewHolder(
private lateinit var tvApplication : TextView
private lateinit var access_info : SavedAccount
private val access_info : SavedAccount
private val buttons_for_status : StatusButtons?
private var buttons_for_status : StatusButtons? = null
private var item : Any? = null
@ -136,51 +138,6 @@ internal class ItemViewHolder(
init {
this.viewRoot = inflate(activity.UI {})
this.access_info = column.access_info
if(activity.timeline_font != null || activity.timeline_font_bold != null) {
Utils.scanView(this.viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
val typeface = when {
v === tvName || v === tvFollowerName || v === tvBoosted -> activity.timeline_font_bold ?: activity.timeline_font
else -> activity.timeline_font ?: activity.timeline_font_bold
}
if(typeface != null) v.typeface = typeface
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
} else {
tvName.typeface = Typeface.DEFAULT_BOLD
tvFollowerName.typeface = Typeface.DEFAULT_BOLD
tvBoosted.typeface = Typeface.DEFAULT_BOLD
}
if(bSimpleList) {
llButtonBar.visibility = View.GONE
this.buttons_for_status = null
} else {
llButtonBar.visibility = View.VISIBLE
this.buttons_for_status = StatusButtons(
activity,
column,
false,
btnConversation = btnConversation,
btnReply = btnReply,
btnBoost = btnBoost,
btnFavourite = btnFavourite,
llFollow2 = llFollow2,
btnFollow2 = btnFollow2,
ivFollowedBy2 = ivFollowedBy2,
btnMore = btnMore
)
}
btnListTL.setOnClickListener(this)
btnListMore.setOnClickListener(this)
@ -251,7 +208,91 @@ internal class ItemViewHolder(
this.name_invalidator = NetworkEmojiInvalidator(activity.handler, tvName)
}
fun bind(item : Any?) {
fun onViewRecycled() {
}
fun bind(list_adapter : ItemListAdapter, column : Column, bSimpleList : Boolean, item : Any?) {
this.list_adapter = list_adapter
this.column = column
this.bSimpleList = bSimpleList
this.access_info = column.access_info
if(activity.timeline_font != null || activity.timeline_font_bold != null) {
Utils.scanView(this.viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
val typeface = when {
v === tvName || v === tvFollowerName || v === tvBoosted -> activity.timeline_font_bold
?: activity.timeline_font
else -> activity.timeline_font ?: activity.timeline_font_bold
}
if(typeface != null) v.typeface = typeface
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
} else {
tvName.typeface = Typeface.DEFAULT_BOLD
tvFollowerName.typeface = Typeface.DEFAULT_BOLD
tvBoosted.typeface = Typeface.DEFAULT_BOLD
}
if(bSimpleList) {
viewRoot.setOnTouchListener { _, ev ->
// ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避
val now = SystemClock.elapsedRealtime()
// ポップアップを閉じた直後はタッチダウンを無視する
if(now - StatusButtonsPopup.last_popup_close >= 30L) {
false
} else {
val action = ev.action
log.d("onTouchEvent action=$action")
true
}
}
viewRoot.setOnClickListener { viewClicked ->
activity.closeListItemPopup()
status__showing?.let { status ->
val popup = StatusButtonsPopup(activity, column, bSimpleList)
activity.listItemPopup = popup
popup.show(
list_adapter.columnVh.listView,
viewClicked,
status,
item as? TootNotification
)
}
}
llButtonBar.visibility = View.GONE
this.buttons_for_status = null
} else {
viewRoot.isClickable = false
llButtonBar.visibility = View.VISIBLE
this.buttons_for_status = StatusButtons(
activity,
column,
false,
btnConversation = btnConversation,
btnReply = btnReply,
btnBoost = btnBoost,
btnFavourite = btnFavourite,
llFollow2 = llFollow2,
btnFollow2 = btnFollow2,
ivFollowedBy2 = ivFollowedBy2,
btnMore = btnMore
)
}
this.item = null
this.status__showing = null
this.status_account = null
@ -265,8 +306,6 @@ internal class ItemViewHolder(
llList.visibility = View.GONE
llExtra.removeAllViews()
if(item == null) return
var c : Int
@ -281,7 +320,10 @@ internal class ItemViewHolder(
//NSFWは文字色固定 btnShowMedia.setTextColor( c );
tvApplication.setTextColor(c)
c = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor(activity, R.attr.colorTimeSmall)
c = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor(
activity,
R.attr.colorTimeSmall
)
this.acct_color = c
tvBoostedTime.setTextColor(c)
tvTime.setTextColor(c)
@ -303,9 +345,16 @@ internal class ItemViewHolder(
if(reblog != null) {
showBoost(
item.account
, item.time_created_at
, R.attr.btn_boost
, Utils.formatSpannable1(activity, R.string.display_name_boosted_by, item.account.decoded_display_name)
,
item.time_created_at
,
R.attr.btn_boost
,
Utils.formatSpannable1(
activity,
R.string.display_name_boosted_by,
item.account.decoded_display_name
)
)
showStatus(activity, reblog)
} else {
@ -325,9 +374,16 @@ internal class ItemViewHolder(
TootNotification.TYPE_FAVOURITE -> {
if(n_account != null) showBoost(
n_account
, n.time_created_at
, if(access_info.isNicoru(n_account)) R.attr.ic_nicoru else R.attr.btn_favourite
, Utils.formatSpannable1(activity, R.string.display_name_favourited_by, n_account.decoded_display_name)
,
n.time_created_at
,
if(access_info.isNicoru(n_account)) R.attr.ic_nicoru else R.attr.btn_favourite
,
Utils.formatSpannable1(
activity,
R.string.display_name_favourited_by,
n_account.decoded_display_name
)
)
if(n_status != null) showStatus(activity, n_status)
}
@ -335,9 +391,16 @@ internal class ItemViewHolder(
TootNotification.TYPE_REBLOG -> {
if(n_account != null) showBoost(
n_account
, n.time_created_at
, R.attr.btn_boost
, Utils.formatSpannable1(activity, R.string.display_name_boosted_by, n_account.decoded_display_name)
,
n.time_created_at
,
R.attr.btn_boost
,
Utils.formatSpannable1(
activity,
R.string.display_name_boosted_by,
n_account.decoded_display_name
)
)
if(n_status != null) showStatus(activity, n_status)
@ -347,9 +410,16 @@ internal class ItemViewHolder(
if(n_account != null) {
showBoost(
n_account
, n.time_created_at
, R.attr.ic_follow_plus
, Utils.formatSpannable1(activity, R.string.display_name_followed_by, n_account.decoded_display_name)
,
n.time_created_at
,
R.attr.ic_follow_plus
,
Utils.formatSpannable1(
activity,
R.string.display_name_followed_by,
n_account.decoded_display_name
)
)
showAccount(n_account)
}
@ -359,9 +429,16 @@ internal class ItemViewHolder(
if(! bSimpleList) {
if(n_account != null) showBoost(
n_account
, n.time_created_at
, R.attr.btn_reply
, Utils.formatSpannable1(activity, R.string.display_name_replied_by, n_account.decoded_display_name)
,
n.time_created_at
,
R.attr.btn_reply
,
Utils.formatSpannable1(
activity,
R.string.display_name_replied_by,
n_account.decoded_display_name
)
)
}
if(n_status != null) showStatus(activity, n_status)
@ -435,7 +512,10 @@ internal class ItemViewHolder(
tvName.text = who.decoded_display_name
name_invalidator.register(who.decoded_display_name)
ivThumbnail.setImageUrl(
activity.pref, 16f, access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar)
activity.pref,
16f,
access_info.supplyBaseUrl(who.avatar_static),
access_info.supplyBaseUrl(who.avatar)
)
// }
@ -544,11 +624,12 @@ internal class ItemViewHolder(
val application = status.application
if(application != null
&&( column.column_type == Column.TYPE_CONVERSATION || Pref.bpShowAppName(activity.pref) )
&& (column.column_type == Column.TYPE_CONVERSATION || Pref.bpShowAppName(activity.pref))
) {
tvApplication.visibility = View.VISIBLE
tvApplication.text = activity.getString(R.string.application_is, application?.name ?: "")
}else{
tvApplication.text =
activity.getString(R.string.application_is, application.name ?: "")
} else {
tvApplication.visibility = View.GONE
}
}
@ -563,7 +644,12 @@ internal class ItemViewHolder(
sb.append("NSFW")
val end = sb.length
val icon_id = Styler.getAttributeResourceId(activity, R.attr.ic_eye_off)
sb.setSpan(EmojiImageSpan(activity, icon_id), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
EmojiImageSpan(activity, icon_id),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
val visIconAttrId = Styler.getVisibilityIconAttr(status.visibility)
@ -573,7 +659,12 @@ internal class ItemViewHolder(
sb.append(status.visibility)
val end = sb.length
val iconResId = Styler.getAttributeResourceId(activity, visIconAttrId)
sb.setSpan(EmojiImageSpan(activity, iconResId), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
EmojiImageSpan(activity, iconResId),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
if(status.pinned) {
@ -582,11 +673,22 @@ internal class ItemViewHolder(
sb.append("pinned")
val end = sb.length
val icon_id = Styler.getAttributeResourceId(activity, R.attr.ic_pin)
sb.setSpan(EmojiImageSpan(activity, icon_id), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
EmojiImageSpan(activity, icon_id),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
if(sb.isNotEmpty()) sb.append(' ')
sb.append(TootStatus.formatTime(activity, status.time_created_at, column.column_type != Column.TYPE_CONVERSATION))
sb.append(
TootStatus.formatTime(
activity,
status.time_created_at,
column.column_type != Column.TYPE_CONVERSATION
)
)
tvTime.text = sb
}
@ -617,18 +719,25 @@ internal class ItemViewHolder(
tvContent.minLines = r?.originalLineCount ?: - 1
if(r?.decoded_spoiler_text != null) {
// 自動CWの場合はContentWarningのテキストを切り替える
tvContentWarning.text = if(shown) activity.getString(R.string.auto_cw_prefix) else r.decoded_spoiler_text
tvContentWarning.text =
if(shown) activity.getString(R.string.auto_cw_prefix) else r.decoded_spoiler_text
}
}
}
private fun setMedia(iv : MyNetworkImageView, status : TootStatus, media_attachments : ArrayList<TootAttachmentLike>, idx : Int) {
private fun setMedia(
iv : MyNetworkImageView,
status : TootStatus,
media_attachments : ArrayList<TootAttachmentLike>,
idx : Int
) {
val ta = if(idx < media_attachments.size) media_attachments[idx] else null
if(ta != null) {
val url = ta.urlForThumbnail
if(url != null && url.isNotEmpty()) {
iv.visibility = View.VISIBLE
iv.scaleType = if(activity.dont_crop_media_thumbnail) ImageView.ScaleType.FIT_CENTER else ImageView.ScaleType.CENTER_CROP
iv.scaleType =
if(activity.dont_crop_media_thumbnail) ImageView.ScaleType.FIT_CENTER else ImageView.ScaleType.CENTER_CROP
val mediaType = ta.type
when(mediaType) {
@ -638,11 +747,19 @@ internal class ItemViewHolder(
else -> iv.setMediaType(0)
}
iv.setImageUrl(activity.pref, 0f, access_info.supplyBaseUrl(url), access_info.supplyBaseUrl(url))
iv.setImageUrl(
activity.pref,
0f,
access_info.supplyBaseUrl(url),
access_info.supplyBaseUrl(url)
)
val description = ta.description
if(description != null && description.isNotEmpty()) {
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val tv = MyTextView(activity)
tv.layoutParams = lp
@ -651,11 +768,13 @@ internal class ItemViewHolder(
if(! activity.timeline_font_size_sp.isNaN()) {
tv.textSize = activity.timeline_font_size_sp
}
val c = if(column.content_color != 0) column.content_color else content_color_default
val c =
if(column.content_color != 0) column.content_color else content_color_default
tv.setTextColor(c)
//
val desc = activity.getString(R.string.media_description, idx + 1, ta.description)
val desc =
activity.getString(R.string.media_description, idx + 1, ta.description)
tv.text = DecodeOptions(
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
@ -733,13 +852,25 @@ internal class ItemViewHolder(
AlertDialog.Builder(activity)
.setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) }
.setPositiveButton(R.string.ok) { _, _ ->
Action_Instance.blockDomain(
activity,
access_info,
domain,
false
)
}
.show()
}
is String -> {
// search_tag は#を含まない
Action_HashTag.timeline(activity, activity.nextPosition(column), access_info, item)
Action_HashTag.timeline(
activity,
activity.nextPosition(column),
access_info,
item
)
}
}
@ -782,22 +913,53 @@ internal class ItemViewHolder(
when(v) {
ivThumbnail -> {
status_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
status_account?.let { who ->
DlgContextMenu(
activity,
column,
who,
null,
notification
).show()
}
return true
}
llBoosted -> {
boost_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
boost_account?.let { who ->
DlgContextMenu(
activity,
column,
who,
null,
notification
).show()
}
return true
}
llFollow -> {
follow_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
follow_account?.let { who ->
DlgContextMenu(
activity,
column,
who,
null,
notification
).show()
}
return true
}
btnFollow -> {
follow_account?.let { who -> Action_Follow.followFromAnotherAccount(activity, activity.nextPosition(column), access_info, who) }
follow_account?.let { who ->
Action_Follow.followFromAnotherAccount(
activity,
activity.nextPosition(column),
access_info,
who
)
}
return true
}
@ -846,7 +1008,11 @@ internal class ItemViewHolder(
is TootAttachmentMSP -> {
// マストドン検索ポータルのデータではmedia_attachmentsが簡略化されている
// 会話の流れを表示する
Action_Toot.conversationOtherInstance(activity, activity.nextPosition(column), status__showing)
Action_Toot.conversationOtherInstance(
activity,
activity.nextPosition(column),
status__showing
)
}
is TootAttachment -> {
@ -866,19 +1032,11 @@ internal class ItemViewHolder(
}
}
// 簡略ビューの時だけ呼ばれる
// StatusButtonsPopupを表示する
fun onItemClick(listView : MyListView, anchor : View) {
activity.closeListItemPopup()
status__showing?.let { status ->
val popup = StatusButtonsPopup(activity, column, bSimpleList)
activity.listItemPopup = popup
popup.show(listView, anchor, status, item as? TootNotification)
}
}
private fun makeCardView(activity : ActMain, llExtra : LinearLayout, card : TootCard) {
var lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
var lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val tv = MyTextView(activity)
tv.layoutParams = lp
@ -892,8 +1050,18 @@ internal class ItemViewHolder(
val sb = StringBuilder()
addLinkAndCaption(sb, activity.getString(R.string.card_header_card), card.url, card.title)
addLinkAndCaption(sb, activity.getString(R.string.card_header_author), card.author_url, card.author_name)
addLinkAndCaption(sb, activity.getString(R.string.card_header_provider), card.provider_url, card.provider_name)
addLinkAndCaption(
sb,
activity.getString(R.string.card_header_author),
card.author_url,
card.author_name
)
addLinkAndCaption(
sb,
activity.getString(R.string.card_header_provider),
card.provider_url,
card.provider_name
)
val description = card.description
if(description != null && description.isNotEmpty()) {
@ -907,21 +1075,35 @@ internal class ItemViewHolder(
val image = card.image
if(image != null && image.isNotEmpty()) {
lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, activity.app_state.media_thumb_height)
lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
activity.app_state.media_thumb_height
)
lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val iv = MyNetworkImageView(activity)
iv.layoutParams = lp
//
iv.id = R.id.ivCardThumbnail
iv.setOnClickListener(this)
iv.scaleType = if(activity.dont_crop_media_thumbnail) ImageView.ScaleType.FIT_CENTER else ImageView.ScaleType.CENTER_CROP
iv.setImageUrl(activity.pref, 0f, access_info.supplyBaseUrl(image), access_info.supplyBaseUrl(image))
iv.scaleType =
if(activity.dont_crop_media_thumbnail) ImageView.ScaleType.FIT_CENTER else ImageView.ScaleType.CENTER_CROP
iv.setImageUrl(
activity.pref,
0f,
access_info.supplyBaseUrl(image),
access_info.supplyBaseUrl(image)
)
llExtra.addView(iv)
}
}
private fun addLinkAndCaption(sb : StringBuilder, header : String, url : String?, caption : String?) {
private fun addLinkAndCaption(
sb : StringBuilder,
header : String,
url : String?,
caption : String?
) {
if(url.isNullOrEmpty() && caption.isNullOrEmpty()) return
@ -932,13 +1114,15 @@ internal class ItemViewHolder(
if(url != null && url.isNotEmpty()) {
sb.append("<a href=\"").append(HTMLDecoder.encodeEntity(url)).append("\">")
}
sb.append(HTMLDecoder.encodeEntity(
when {
caption != null && caption.isNotEmpty() -> caption
url != null && url.isNotEmpty() -> url
else -> "???"
}
))
sb.append(
HTMLDecoder.encodeEntity(
when {
caption != null && caption.isNotEmpty() -> caption
url != null && url.isNotEmpty() -> url
else -> "???"
}
)
)
if(url != null && url.isNotEmpty()) {
sb.append("</a>")
@ -955,7 +1139,10 @@ internal class ItemViewHolder(
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
if(i == 0)
lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val b = Button(activity)
@ -981,7 +1168,8 @@ internal class ItemViewHolder(
val density = llExtra.resources.displayMetrics.density
val height = (0.5f + 6 * density).toInt()
val view = EnqueteTimerView(activity)
view.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
view.layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
view.setParams(enquete.time_start, NicoEnquete.ENQUETE_EXPIRE)
llExtra.addView(view)
}
@ -1134,7 +1322,8 @@ internal class ItemViewHolder(
}
btnFollow = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
contentDescription = context.getString(R.string.follow)
scaleType = ImageView.ScaleType.CENTER
// tools:src="?attr/ic_follow_plus"
@ -1181,7 +1370,8 @@ internal class ItemViewHolder(
lparams(matchParent, wrapContent)
ivThumbnail = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(dip(48), dip(48)) {
@ -1208,7 +1398,8 @@ internal class ItemViewHolder(
btnContentWarning = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
background =
ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
minWidthCompat = dip(40)
padding = dip(4)
//tools:text="見る"
@ -1253,7 +1444,10 @@ internal class ItemViewHolder(
ivMedia1 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_ddd
)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
@ -1263,7 +1457,10 @@ internal class ItemViewHolder(
ivMedia2 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_ddd
)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
@ -1274,7 +1471,10 @@ internal class ItemViewHolder(
ivMedia3 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_ddd
)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
@ -1285,7 +1485,10 @@ internal class ItemViewHolder(
ivMedia4 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_ddd
)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
@ -1296,9 +1499,13 @@ internal class ItemViewHolder(
btnHideMedia = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
contentDescription = "@string/hide"
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_close)
imageResource =
Styler.getAttributeResourceId(context, R.attr.btn_close)
}.lparams(dip(32), matchParent) {
startMargin = dip(8)
}
@ -1306,10 +1513,14 @@ internal class ItemViewHolder(
btnShowMedia = textView {
backgroundColor = Styler.getAttributeColor(context, R.attr.colorShowMediaBackground)
backgroundColor = Styler.getAttributeColor(
context,
R.attr.colorShowMediaBackground
)
gravity = Gravity.CENTER
text = context.getString(R.string.tap_to_show)
textColor = Styler.getAttributeColor(context, R.attr.colorShowMediaText)
textColor =
Styler.getAttributeColor(context, R.attr.colorShowMediaText)
}.lparams(matchParent, matchParent)
}
@ -1330,18 +1541,26 @@ internal class ItemViewHolder(
btnConversation = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
contentDescription = context.getString(R.string.conversation_view)
minimumWidth = dip(40)
imageResource = Styler.getAttributeResourceId(context, R.attr.ic_conversation)
imageResource =
Styler.getAttributeResourceId(context, R.attr.ic_conversation)
}.lparams(wrapContent, matchParent)
btnReply = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
contentDescription = context.getString(R.string.reply)
minimumWidth = dip(40)
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_reply)
imageResource =
Styler.getAttributeResourceId(context, R.attr.btn_reply)
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)
@ -1349,7 +1568,10 @@ internal class ItemViewHolder(
btnBoost = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
compoundDrawablePadding = dip(4)
minWidthCompat = dip(48)
@ -1359,7 +1581,10 @@ internal class ItemViewHolder(
}
btnFavourite = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
compoundDrawablePadding = dip(4)
minWidthCompat = dip(48)
setPaddingStartEnd(dip(4), dip(4))
@ -1375,7 +1600,10 @@ internal class ItemViewHolder(
btnFollow2 = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
contentDescription = context.getString(R.string.follow)
scaleType = ImageView.ScaleType.CENTER
// tools:src="?attr/ic_follow_plus"
@ -1386,15 +1614,22 @@ internal class ItemViewHolder(
ivFollowedBy2 = imageView {
scaleType = ImageView.ScaleType.CENTER
imageResource = Styler.getAttributeResourceId(context, R.attr.ic_followed_by)
imageResource = Styler.getAttributeResourceId(
context,
R.attr.ic_followed_by
)
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}.lparams(matchParent, matchParent)
}
btnMore = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
background = ContextCompat.getDrawable(
context,
R.drawable.btn_bg_transparent
)
contentDescription = context.getString(R.string.more)
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_more)
imageResource =
Styler.getAttributeResourceId(context, R.attr.btn_more)
minimumWidth = dip(40)
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)

View File

@ -39,7 +39,8 @@ object Pref {
}
class BooleanPref(key : String, private val defVal : Boolean, val id : Int) : BasePref<Boolean>(key) {
class BooleanPref(key : String, private val defVal : Boolean, val id : Int) :
BasePref<Boolean>(key) {
override operator fun invoke(pref : SharedPreferences) : Boolean {
return pref.getBoolean(key, defVal)
@ -83,7 +84,8 @@ object Pref {
}
}
class StringPref(key : String, private val defVal : String, val skipImport : Boolean = false) : BasePref<String>(key) {
class StringPref(key : String, private val defVal : String, val skipImport : Boolean = false) :
BasePref<String>(key) {
override operator fun invoke(pref : SharedPreferences) : String {
return pref.getString(key, defVal)
@ -96,34 +98,175 @@ object Pref {
// boolean
val bpAllowNonSpaceBeforeEmojiShortcode = BooleanPref("allow_non_space_before_emoji_shortcode", false, R.id.swAllowNonSpaceBeforeEmojiShortcode)
val bpDisableEmojiAnimation = BooleanPref("disable_emoji_animation", false, R.id.swDisableEmojiAnimation)
val bpDisableFastScroller = BooleanPref("disable_fast_scroller", true, R.id.swDisableFastScroller)
val bpDisableTabletMode = BooleanPref("disable_tablet_mode", false, R.id.swDisableTabletMode)
val bpDontConfirmBeforeCloseColumn = BooleanPref("DontConfirmBeforeCloseColumn", false, R.id.swDontConfirmBeforeCloseColumn)
val bpDontCropMediaThumb = BooleanPref("DontCropMediaThumb", false, R.id.swDontCropMediaThumb)
val bpDontDuplicationCheck = BooleanPref("dont_duplication_check", false, R.id.swDontDuplicationCheck)
val bpDontRefreshOnResume = BooleanPref("dont_refresh_on_resume", false, R.id.swDontRefreshOnResume)
val bpDontRound = BooleanPref("dont_round", false, R.id.swDontRound)
val bpDontScreenOff = BooleanPref("dont_screen_off", false, R.id.swDontScreenOff)
val bpDontUseActionButtonWithQuickTootBar = BooleanPref("dont_use_action_button", false, R.id.swDontUseActionButtonWithQuickTootBar)
val bpDontUseStreaming = BooleanPref("dont_use_streaming", false, R.id.swDontUseStreaming)
val bpEnableGifAnimation = BooleanPref("enable_gif_animation", false, R.id.swEnableGifAnimation)
val bpExitAppWhenCloseProtectedColumn = BooleanPref("ExitAppWhenCloseProtectedColumn", false, R.id.swExitAppWhenCloseProtectedColumn)
val bpMentionFullAcct = BooleanPref("mention_full_acct", false, R.id.swMentionFullAcct)
val bpNotificationLED = BooleanPref("notification_led", true, R.id.cbNotificationLED)
val bpNotificationSound = BooleanPref("notification_sound", true, R.id.cbNotificationSound)
val bpNotificationVibration = BooleanPref("notification_vibration", true, R.id.cbNotificationVibration)
val bpPostButtonBarTop = BooleanPref("post_button_bar_at_top", true, R.id.swPostButtonBarTop)
val bpPriorChrome = BooleanPref("prior_chrome", true, R.id.swPriorChrome)
val bpPriorLocalURL = BooleanPref("prior_local_url", false, R.id.swPriorLocalURL)
val bpQuickTootBar = BooleanPref("quick_toot_bar", false, R.id.swQuickTootBar)
val bpRelativeTimestamp = BooleanPref("relative_timestamp", true, R.id.swRelativeTimestamp)
val bpShortAcctLocalUser = BooleanPref("short_acct_local_user", true, R.id.swShortAcctLocalUser)
val bpShowFollowButtonInButtonBar = BooleanPref("ShowFollowButtonInButtonBar", false, R.id.swShowFollowButtonInButtonBar)
val bpSimpleList = BooleanPref("simple_list", true, R.id.swSimpleList)
val bpUseInternalMediaViewer = BooleanPref("use_internal_media_viewer", true, R.id.swUseInternalMediaViewer)
val bpShowAppName = BooleanPref("show_app_name", false, R.id.swShowAppName)
val bpAllowNonSpaceBeforeEmojiShortcode = BooleanPref(
"allow_non_space_before_emoji_shortcode",
false,
R.id.swAllowNonSpaceBeforeEmojiShortcode
)
val bpDisableEmojiAnimation = BooleanPref(
"disable_emoji_animation",
false,
R.id.swDisableEmojiAnimation
)
// val bpDisableFastScroller = BooleanPref("disable_fast_scroller", true, 0) // R.id.swDisableFastScroller)
val bpDisableTabletMode = BooleanPref(
"disable_tablet_mode",
false,
R.id.swDisableTabletMode
)
val bpDontConfirmBeforeCloseColumn = BooleanPref(
"DontConfirmBeforeCloseColumn",
false,
R.id.swDontConfirmBeforeCloseColumn
)
val bpDontCropMediaThumb = BooleanPref(
"DontCropMediaThumb",
false,
R.id.swDontCropMediaThumb
)
val bpDontDuplicationCheck = BooleanPref(
"dont_duplication_check",
false,
R.id.swDontDuplicationCheck
)
val bpDontRefreshOnResume = BooleanPref(
"dont_refresh_on_resume",
false,
R.id.swDontRefreshOnResume
)
val bpDontRound = BooleanPref(
"dont_round",
false,
R.id.swDontRound
)
val bpDontScreenOff = BooleanPref(
"dont_screen_off",
false,
R.id.swDontScreenOff
)
val bpDontUseActionButtonWithQuickTootBar = BooleanPref(
"dont_use_action_button",
false,
R.id.swDontUseActionButtonWithQuickTootBar
)
val bpDontUseStreaming = BooleanPref(
"dont_use_streaming",
false,
R.id.swDontUseStreaming
)
val bpEnableGifAnimation = BooleanPref(
"enable_gif_animation",
false,
R.id.swEnableGifAnimation
)
val bpExitAppWhenCloseProtectedColumn = BooleanPref(
"ExitAppWhenCloseProtectedColumn",
false,
R.id.swExitAppWhenCloseProtectedColumn
)
val bpMentionFullAcct = BooleanPref(
"mention_full_acct",
false,
R.id.swMentionFullAcct
)
val bpNotificationLED = BooleanPref(
"notification_led",
true,
R.id.cbNotificationLED
)
val bpNotificationSound = BooleanPref(
"notification_sound",
true,
R.id.cbNotificationSound
)
val bpNotificationVibration = BooleanPref(
"notification_vibration",
true,
R.id.cbNotificationVibration
)
val bpPostButtonBarTop = BooleanPref(
"post_button_bar_at_top",
true,
R.id.swPostButtonBarTop
)
val bpPriorChrome = BooleanPref(
"prior_chrome",
true,
R.id.swPriorChrome
)
val bpPriorLocalURL = BooleanPref(
"prior_local_url",
false,
R.id.swPriorLocalURL
)
val bpQuickTootBar = BooleanPref(
"quick_toot_bar",
false,
R.id.swQuickTootBar
)
val bpRelativeTimestamp = BooleanPref(
"relative_timestamp",
true,
R.id.swRelativeTimestamp
)
val bpShortAcctLocalUser = BooleanPref(
"short_acct_local_user",
true,
R.id.swShortAcctLocalUser
)
val bpShowFollowButtonInButtonBar = BooleanPref(
"ShowFollowButtonInButtonBar",
false,
R.id.swShowFollowButtonInButtonBar
)
val bpSimpleList = BooleanPref(
"simple_list",
true,
R.id.swSimpleList
)
val bpUseInternalMediaViewer = BooleanPref(
"use_internal_media_viewer",
true,
R.id.swUseInternalMediaViewer
)
val bpShowAppName = BooleanPref(
"show_app_name",
false,
R.id.swShowAppName
)
val bpForceGap = BooleanPref(
"force_gap",
false,
R.id.swForceGap
)
// int

View File

@ -68,32 +68,69 @@ internal class StatusButtons(
val color_normal = Styler.getAttributeColor(activity, R.attr.colorImageButton)
val color_accent = Styler.getAttributeColor(activity, R.attr.colorImageButtonAccent)
val fav_icon_attr = if(access_info.isNicoru(status.account)) R.attr.ic_nicoru else R.attr.btn_favourite
val fav_icon_attr =
if(access_info.isNicoru(status.account)) R.attr.ic_nicoru else R.attr.btn_favourite
// ブーストボタン
when {
TootStatus.VISIBILITY_DIRECT == status.visibility -> setButton(btnBoost, false, color_accent, R.attr.ic_mail, "")
TootStatus.VISIBILITY_PRIVATE == status.visibility -> setButton(btnBoost, false, color_accent, R.attr.ic_lock, "")
activity.app_state.isBusyBoost(access_info, status) -> setButton(btnBoost, false, color_normal, R.attr.btn_refresh, "?")
TootStatus.VISIBILITY_DIRECT == status.visibility -> setButton(
btnBoost,
false,
color_accent,
R.attr.ic_mail,
""
)
TootStatus.VISIBILITY_PRIVATE == status.visibility -> setButton(
btnBoost,
false,
color_accent,
R.attr.ic_lock,
""
)
activity.app_state.isBusyBoost(access_info, status) -> setButton(
btnBoost,
false,
color_normal,
R.attr.btn_refresh,
"?"
)
else -> {
val color = if(status.reblogged) color_accent else color_normal
setButton(btnBoost, true, color, R.attr.btn_boost, status.reblogs_count?.toString() ?: "")
setButton(
btnBoost,
true,
color,
R.attr.btn_boost,
status.reblogs_count?.toString() ?: ""
)
}
}
when {
activity.app_state.isBusyFav(access_info, status) -> setButton(btnFavourite, false, color_normal, R.attr.btn_refresh, "?")
activity.app_state.isBusyFav(access_info, status) -> setButton(
btnFavourite,
false,
color_normal,
R.attr.btn_refresh,
"?"
)
else -> {
val color = if(status.favourited) color_accent else color_normal
setButton(btnFavourite, true, color, fav_icon_attr, status.favourites_count?.toString() ?: "")
setButton(
btnFavourite,
true,
color,
fav_icon_attr,
status.favourites_count?.toString() ?: ""
)
}
}
val account = status.account
this.relation = if( ! Pref.bpShowFollowButtonInButtonBar(activity.pref)) {
this.relation = if(! Pref.bpShowFollowButtonInButtonBar(activity.pref)) {
llFollow2.visibility = View.GONE
null
} else {
@ -105,7 +142,13 @@ internal class StatusButtons(
}
private fun setButton(b : Button, enabled : Boolean, color : Int, icon_attr : Int, text : String) {
private fun setButton(
b : Button,
enabled : Boolean,
color : Int,
icon_attr : Int,
text : String
) {
val d = Styler.getAttributeDrawable(activity, icon_attr).mutate()
d.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
b.setCompoundDrawablesRelativeWithIntrinsicBounds(d, null, null, null)
@ -123,9 +166,14 @@ internal class StatusButtons(
when(v) {
btnConversation -> Action_Toot.conversation(activity, activity.nextPosition(column), access_info, status)
btnConversation -> Action_Toot.conversation(
activity,
activity.nextPosition(column),
access_info,
status
)
btnReply -> if( ! access_info.isPseudo) {
btnReply -> if(! access_info.isPseudo) {
Action_Toot.reply(activity, access_info, status)
} else {
Action_Toot.replyFromAnotherAccount(activity, access_info, status)
@ -222,8 +270,8 @@ internal class StatusButtons(
activity.nextPosition(column),
access_info,
account,
bFollow =true,
callback =activity.follow_complete_callback
bFollow = true,
callback = activity.follow_complete_callback
)
}
}
@ -242,19 +290,24 @@ internal class StatusButtons(
when(v) {
btnConversation -> Action_Toot.conversationOtherInstance(
activity, activity.nextPosition(column), status)
activity, activity.nextPosition(column), status
)
btnBoost -> Action_Toot.boostFromAnotherAccount(
activity, access_info, status)
activity, access_info, status
)
btnFavourite -> Action_Toot.favouriteFromAnotherAccount(
activity, access_info, status)
activity, access_info, status
)
btnReply -> Action_Toot.replyFromAnotherAccount(
activity, access_info, status)
activity, access_info, status
)
btnFollow2 -> Action_Follow.followFromAnotherAccount(
activity, activity.nextPosition(column), access_info, status.account)
activity, activity.nextPosition(column), access_info, status.account
)
}
return true

View File

@ -12,7 +12,7 @@ import android.widget.PopupWindow
import jp.juggler.subwaytooter.api.entity.TootNotification
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.view.MyListView
import android.support.v7.widget.ListRecyclerView
class StatusButtonsPopup(
private val activity : ActMain,
@ -29,6 +29,9 @@ class StatusButtonsPopup(
v.measure(spec, spec)
return v.measuredWidth
}
var last_popup_close = 0L
}
private val viewRoot : View
@ -42,7 +45,7 @@ class StatusButtonsPopup(
activity,
column,
bSimpleList,
btnConversation =viewRoot.findViewById(R.id.btnConversation),
btnConversation = viewRoot.findViewById(R.id.btnConversation),
btnReply = viewRoot.findViewById(R.id.btnReply),
btnBoost = viewRoot.findViewById(R.id.btnBoost),
btnFavourite = viewRoot.findViewById(R.id.btnFavourite),
@ -62,7 +65,7 @@ class StatusButtonsPopup(
@SuppressLint("RtlHardcoded")
fun show(
listView : MyListView
listView : ListRecyclerView
, anchor : View
, status : TootStatus
, notification : TootNotification?
@ -82,7 +85,7 @@ class StatusButtonsPopup(
// ポップアップの外側をタッチしたらポップアップを閉じる
// また、そのタッチイベントがlistViewに影響しないようにする
window.dismiss()
listView.last_popup_close = SystemClock.elapsedRealtime()
last_popup_close = SystemClock.elapsedRealtime()
return@OnTouchListener true
}
false

View File

@ -22,7 +22,6 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.WordTrieTree
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
@ -51,11 +50,11 @@ internal class StreamReader(
internal val bDisposed = AtomicBoolean()
internal val bListening = AtomicBoolean()
internal val socket = AtomicReference<WebSocket>(null)
internal val callback_list = LinkedList< (event_type : String, item : Any?)->Unit >()
internal val callback_list = LinkedList<(event_type : String, item : Any?) -> Unit>()
internal val parser : TootParser
init {
this.parser = TootParser(context, access_info,highlightTrie = highlight_trie)
this.parser = TootParser(context, access_info, highlightTrie = highlight_trie)
}
internal fun dispose() {
@ -68,18 +67,21 @@ internal class StreamReader(
startRead()
}
@Synchronized internal fun setHighlightTrie(highlight_trie : WordTrieTree) {
@Synchronized
internal fun setHighlightTrie(highlight_trie : WordTrieTree) {
this.parser.highlightTrie = highlight_trie
}
@Synchronized internal fun addCallback(stream_callback : (event_type : String, item : Any?)->Unit ) {
@Synchronized
internal fun addCallback(stream_callback : (event_type : String, item : Any?) -> Unit) {
for(c in callback_list) {
if(c === stream_callback) return
}
callback_list.add(stream_callback)
}
@Synchronized internal fun removeCallback(stream_callback : (event_type : String, item : Any?)->Unit) {
@Synchronized
internal fun removeCallback(stream_callback : (event_type : String, item : Any?) -> Unit) {
val it = callback_list.iterator()
while(it.hasNext()) {
val c = it.next()
@ -136,7 +138,12 @@ internal class StreamReader(
* Invoked when the peer has indicated that no more incoming messages will be transmitted.
*/
override fun onClosing(webSocket : WebSocket, code : Int, reason : String?) {
log.d("WebSocket onClosing. code=%s,reason=%s,url=%s .", code, reason, webSocket .request().url())
log.d(
"WebSocket onClosing. code=%s,reason=%s,url=%s .",
code,
reason,
webSocket.request().url()
)
webSocket.cancel()
bListening.set(false)
handler.removeCallbacks(proc_reconnect)
@ -148,7 +155,12 @@ internal class StreamReader(
* connection has been successfully released. No further calls to this listener will be made.
*/
override fun onClosed(webSocket : WebSocket, code : Int, reason : String?) {
log.d("WebSocket onClosed. code=%s,reason=%s,url=%s .", code, reason, webSocket .request().url())
log.d(
"WebSocket onClosed. code=%s,reason=%s,url=%s .",
code,
reason,
webSocket.request().url()
)
bListening.set(false)
handler.removeCallbacks(proc_reconnect)
handler.postDelayed(proc_reconnect, 10000L)
@ -160,7 +172,7 @@ internal class StreamReader(
* listener will be made.
*/
override fun onFailure(webSocket : WebSocket, ex : Throwable, response : Response?) {
log.e(ex , "WebSocket onFailure. url=%s .", webSocket .request().url())
log.e(ex, "WebSocket onFailure. url=%s .", webSocket.request().url())
bListening.set(false)
handler.removeCallbacks(proc_reconnect)
@ -187,11 +199,11 @@ internal class StreamReader(
bListening.set(true)
TootTaskRunner(context).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
val result = client.webSocket(end_point, this@Reader)
val result = client.webSocket(end_point, this@Reader)
if(result == null) {
log.d("startRead: cancelled.")
bListening.set(false)
}else {
} else {
val ws = result.data as? WebSocket
if(ws != null) {
socket.set(ws)
@ -202,6 +214,7 @@ internal class StreamReader(
}
return result
}
override fun handleResult(result : TootApiResult?) {
}
})
@ -234,7 +247,7 @@ internal class StreamReader(
accessInfo : SavedAccount,
endPoint : String,
highlightTrie : WordTrieTree?,
streamCallback : (event_type : String, item : Any?)->Unit
streamCallback : (event_type : String, item : Any?) -> Unit
) {
val reader = prepareReader(accessInfo, endPoint, highlightTrie)
@ -245,12 +258,11 @@ internal class StreamReader(
}
}
// カラム破棄やリロードのタイミングで呼ばれる
fun unregister(
accessInfo : SavedAccount,
endPoint : String,
streamCallback : (event_type : String, item : Any?)->Unit
streamCallback : (event_type : String, item : Any?) -> Unit
) {
synchronized(reader_list) {
val it = reader_list.iterator()

View File

@ -42,14 +42,26 @@ object Styler {
val resourceId = a.getResourceId(0, 0)
a.recycle()
if(resourceId == 0)
throw RuntimeException(String.format(Locale.JAPAN, "attr not defined.attr_id=0x%x", attrId))
throw RuntimeException(
String.format(
Locale.JAPAN,
"attr not defined.attr_id=0x%x",
attrId
)
)
return resourceId
}
fun getAttributeDrawable(context : Context, attrId : Int) : Drawable {
val drawableId = getAttributeResourceId(context, attrId)
return ContextCompat.getDrawable(context, drawableId)
?: throw RuntimeException(String.format(Locale.JAPAN, "getDrawable failed. drawableId=0x%x", drawableId))
?: throw RuntimeException(
String.format(
Locale.JAPAN,
"getDrawable failed. drawableId=0x%x",
drawableId
)
)
}
// ImageViewにアイコンを設定する
@ -58,16 +70,21 @@ object Styler {
}
// ImageViewにアイコンを設定する。色を変えてしまう
fun setIconCustomColor(context : Context, imageView : ImageView, color : Int, iconAttrId : Int) {
fun setIconCustomColor(
context : Context,
imageView : ImageView,
color : Int,
iconAttrId : Int
) {
val d = getAttributeDrawable(context, iconAttrId)
d.mutate() // 色指定が他のアイコンに影響しないようにする
d.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
imageView.setImageDrawable(d)
}
fun getVisibilityIconAttr( visibility : String?) : Int {
fun getVisibilityIconAttr(visibility : String?) : Int {
return when(visibility) {
null->R.attr.ic_public
null -> R.attr.ic_public
TootStatus.VISIBILITY_PUBLIC -> R.attr.ic_public
TootStatus.VISIBILITY_UNLISTED -> R.attr.ic_lock_open
TootStatus.VISIBILITY_PRIVATE -> R.attr.ic_lock

View File

@ -5,8 +5,8 @@ import android.view.View
import jp.juggler.subwaytooter.util.LogCategory
internal class TabletColumnViewHolder(activity : ActMain, viewRoot : View)
: RecyclerView.ViewHolder(viewRoot) {
internal class TabletColumnViewHolder(activity : ActMain, viewRoot : View) :
RecyclerView.ViewHolder(viewRoot) {
companion object {
val log = LogCategory("TabletColumnViewHolder")

View File

@ -0,0 +1,48 @@
package jp.juggler.subwaytooter
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.Button
import android.widget.TextView
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
internal abstract class ViewHolderHeaderBase(val activity : ActMain, val viewRoot : View) :
RecyclerView.ViewHolder(viewRoot) {
companion object {
private val log = LogCategory("HeaderViewHolderBase")
}
internal lateinit var column : Column
internal lateinit var access_info : SavedAccount
init {
Utils.scanView(viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
if(activity.timeline_font != null) {
v.typeface = activity.timeline_font
}
if(! activity.timeline_font_size_sp.isNaN()) {
v.textSize = activity.timeline_font_size_sp
}
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
}
internal open fun bindData(column : Column) {
this.column = column
this.access_info = column.access_info
}
internal abstract fun showColor()
internal abstract fun onViewRecycled()
}

View File

@ -3,7 +3,6 @@ package jp.juggler.subwaytooter
import android.content.Intent
import android.net.Uri
import android.view.View
import android.widget.Button
import android.widget.TextView
import jp.juggler.subwaytooter.api.entity.TootInstance
@ -11,23 +10,18 @@ import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyListView
import jp.juggler.subwaytooter.view.MyNetworkImageView
internal class HeaderViewHolderInstance(
internal class ViewHolderHeaderInstance(
arg_activity : ActMain,
arg_column : Column,
parent : MyListView
) : HeaderViewHolderBase(
arg_activity,
arg_column,
arg_activity.layoutInflater.inflate(R.layout.lv_header_instance, parent, false)
) , View.OnClickListener {
viewRoot : View
) : ViewHolderHeaderBase(arg_activity, viewRoot)
, View.OnClickListener {
companion object {
private val log = LogCategory("HeaderViewHolderInstance")
private val log = LogCategory("ViewHolderHeaderInstance")
}
private val btnInstance : TextView
private val tvVersion : TextView
private val tvTitle : TextView
@ -42,19 +36,6 @@ internal class HeaderViewHolderInstance(
init {
if(activity.timeline_font != null) {
Utils.scanView(viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
v.typeface = activity.timeline_font
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
}
//
// CharSequence sv = HTMLDecoder.decodeHTML( activity, access_info, html, false, true, null );
//
@ -85,6 +66,7 @@ internal class HeaderViewHolderInstance(
}
override fun bindData(column : Column) {
super.bindData(column)
val instance = column.instance_information
this.instance = instance
@ -100,16 +82,16 @@ internal class HeaderViewHolderInstance(
val uri = instance.uri ?: ""
btnInstance.text = uri
btnInstance.isEnabled = uri.isNotEmpty()
tvVersion.text = instance .version ?: ""
tvTitle.text = instance .title ?: ""
val email = instance .email ?:""
tvVersion.text = instance.version ?: ""
tvTitle.text = instance.title ?: ""
val email = instance.email ?: ""
btnEmail.text = email
btnEmail.isEnabled = email.isNotEmpty()
val sb = DecodeOptions(decodeEmoji = true)
.decodeHTML(activity, access_info, "<p>" + (instance .description ?: "") + "</p>")
.decodeHTML(activity, access_info, "<p>" + (instance.description ?: "") + "</p>")
var previous_br_count = 0
var i = 0
@ -130,7 +112,7 @@ internal class HeaderViewHolderInstance(
tvDescription.text = sb
val stats = instance.stats
if( stats == null) {
if(stats == null) {
tvUserCount.setText(R.string.not_provided_mastodon_under_1_6)
tvTootCount.setText(R.string.not_provided_mastodon_under_1_6)
tvDomainCount.setText(R.string.not_provided_mastodon_under_1_6)
@ -142,10 +124,10 @@ internal class HeaderViewHolderInstance(
}
val thumbnail = instance.thumbnail
if(thumbnail == null || thumbnail.isEmpty() ) {
if(thumbnail == null || thumbnail.isEmpty()) {
ivThumbnail.setImageUrl(App1.pref, 0f, null)
} else {
ivThumbnail.setImageUrl(App1.pref, 0f,thumbnail, thumbnail)
ivThumbnail.setImageUrl(App1.pref, 0f, thumbnail, thumbnail)
}
}
}
@ -153,11 +135,11 @@ internal class HeaderViewHolderInstance(
override fun onClick(v : View) {
when(v.id) {
R.id.btnInstance -> instance?.uri?.let{ uri ->
R.id.btnInstance -> instance?.uri?.let { uri ->
App1.openCustomTab(activity, "https://$uri/about")
}
R.id.btnEmail -> instance?.email?.let{ email->
R.id.btnEmail -> instance?.email?.let { email ->
try {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
@ -166,22 +148,22 @@ internal class HeaderViewHolderInstance(
activity.startActivity(intent)
} catch(ex : Throwable) {
log.e(ex,"startActivity failed. mail=$email")
log.e(ex, "startActivity failed. mail=$email")
Utils.showToast(activity, true, R.string.missing_mail_app)
}
}
R.id.ivThumbnail -> instance?.thumbnail?.let{ thumbnail ->
R.id.ivThumbnail -> instance?.thumbnail?.let { thumbnail ->
try {
if( thumbnail.isNotEmpty() ){
if(thumbnail.isNotEmpty()) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(thumbnail)
activity.startActivity(intent)
}
} catch(ex : Throwable) {
log.e(ex,"startActivity failed. thumbnail=$thumbnail")
log.e(ex, "startActivity failed. thumbnail=$thumbnail")
Utils.showToast(activity, true, "missing web browser")
}
@ -189,10 +171,7 @@ internal class HeaderViewHolderInstance(
}
}
// private void setContent( @NonNull WebView wv, @NonNull String html, @NonNull String mime_type ){
// html = "<html><meta charset=\"UTF-8\"><p>"+html+"</p></html>";
// wv.clearHistory();
// wv.loadData( Base64.encodeToString( Utils.encodeUTF8(html) ,Base64.NO_WRAP), mime_type+";charset=UTF-8", "base64");
// }
override fun onViewRecycled() {
}
}

View File

@ -7,7 +7,6 @@ import android.view.View
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ListView
import android.widget.TextView
import jp.juggler.emoji.EmojiMap201709
@ -23,15 +22,10 @@ import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyNetworkImageView
internal class HeaderViewHolderProfile(
arg_activity : ActMain,
column : Column,
parent : ListView
) : HeaderViewHolderBase(
arg_activity,
column,
arg_activity.layoutInflater.inflate(R.layout.lv_header_account, parent, false)
), View.OnClickListener, View.OnLongClickListener {
internal class ViewHolderHeaderProfile(
activity : ActMain,
viewRoot : View
) : ViewHolderHeaderBase(activity, viewRoot), View.OnClickListener, View.OnLongClickListener {
private val ivBackground : MyNetworkImageView
private val tvCreated : TextView
@ -64,7 +58,6 @@ internal class HeaderViewHolderProfile(
private val moved_name_invalidator : NetworkEmojiInvalidator
init {
ivBackground = viewRoot.findViewById(R.id.ivBackground)
llProfile = viewRoot.findViewById(R.id.llProfile)
tvCreated = viewRoot.findViewById(R.id.tvCreated)
@ -110,15 +103,7 @@ internal class HeaderViewHolderProfile(
moved_caption_invalidator = NetworkEmojiInvalidator(activity.handler, tvMoved)
moved_name_invalidator = NetworkEmojiInvalidator(activity.handler, tvMovedName)
if(! activity.timeline_font_size_sp.isNaN()) {
tvMovedName.textSize = activity.timeline_font_size_sp
tvMoved.textSize = activity.timeline_font_size_sp
}
if(! activity.acct_font_size_sp.isNaN()) {
tvMovedAcct.textSize = activity.acct_font_size_sp
tvCreated.textSize = activity.acct_font_size_sp
}
ivBackground.measureProfileBg = true
}
override fun showColor() {
@ -132,6 +117,18 @@ internal class HeaderViewHolderProfile(
}
override fun bindData(column : Column) {
super.bindData(column)
if(! activity.timeline_font_size_sp.isNaN()) {
tvMovedName.textSize = activity.timeline_font_size_sp
tvMoved.textSize = activity.timeline_font_size_sp
}
if(! activity.acct_font_size_sp.isNaN()) {
tvMovedAcct.textSize = activity.acct_font_size_sp
tvCreated.textSize = activity.acct_font_size_sp
}
val who = column.who_account
this.who = who
@ -161,15 +158,25 @@ internal class HeaderViewHolderProfile(
tvRemoteProfileWarning.visibility = View.GONE
} else {
tvCreated.text = TootStatus.formatTime(tvCreated.context, who.time_created_at, true)
ivBackground.setImageUrl(activity.pref, 0f, access_info.supplyBaseUrl(who.header_static))
ivBackground.setImageUrl(
activity.pref,
0f,
access_info.supplyBaseUrl(who.header_static)
)
ivAvatar.setImageUrl(activity.pref, 16f, access_info.supplyBaseUrl(who.avatar_static), access_info.supplyBaseUrl(who.avatar))
ivAvatar.setImageUrl(
activity.pref,
16f,
access_info.supplyBaseUrl(who.avatar_static),
access_info.supplyBaseUrl(who.avatar)
)
val name = who.decoded_display_name
tvDisplayName.text = name
name_invalidator.register(name)
tvRemoteProfileWarning.visibility = if(column.access_info.isRemoteUser(who)) View.VISIBLE else View.GONE
tvRemoteProfileWarning.visibility =
if(column.access_info.isRemoteUser(who)) View.VISIBLE else View.GONE
val sb = SpannableStringBuilder()
sb.append("@").append(access_info.getFullAcct(who))
@ -180,7 +187,12 @@ internal class HeaderViewHolderProfile(
val end = sb.length
val info = EmojiMap201709.sShortNameToImageId["lock"]
if(info != null) {
sb.setSpan(EmojiImageSpan(activity, info.image_id), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
EmojiImageSpan(activity, info.image_id),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
tvAcct.text = sb
@ -207,7 +219,11 @@ internal class HeaderViewHolderProfile(
llMoved.visibility = View.VISIBLE
tvMoved.visibility = View.VISIBLE
val caption = Utils.formatSpannable1(activity, R.string.account_moved_to, who.decodeDisplayName(activity))
val caption = Utils.formatSpannable1(
activity,
R.string.account_moved_to,
who.decodeDisplayName(activity)
)
tvMoved.text = caption
moved_caption_invalidator.register(caption)
@ -225,9 +241,16 @@ internal class HeaderViewHolderProfile(
private fun setAcct(tv : TextView, acctLong : String, acctShort : String) {
val ac = AcctColor.load(acctLong)
tv.text = if(AcctColor.hasNickname(ac)) ac.nickname else if(activity.shortAcctLocalUser) "@" + acctShort else acctLong
tv.text = when {
AcctColor.hasNickname(ac) -> ac.nickname
activity.shortAcctLocalUser -> "@" + acctShort
else -> acctLong
}
val acct_color = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor(activity, R.attr.colorTimeSmall)
val acct_color = when {
column.acct_color != 0 -> column.acct_color
else -> Styler.getAttributeColor(activity, R.attr.colorTimeSmall)
}
tv.setTextColor(if(AcctColor.hasColorForeground(ac)) ac.color_fg else acct_color)
if(AcctColor.hasColorBackground(ac)) {
@ -265,23 +288,28 @@ internal class HeaderViewHolderProfile(
column.startLoading()
}
R.id.btnMore -> who ?.let{ who->
R.id.btnMore -> who?.let { who ->
DlgContextMenu(activity, column, who, null, null).show()
}
R.id.btnFollow -> who ?.let{ who->
R.id.btnFollow -> who?.let { who ->
DlgContextMenu(activity, column, who, null, null).show()
}
R.id.btnMoved -> who_moved ?.let{ who_moved->
R.id.btnMoved -> who_moved?.let { who_moved ->
DlgContextMenu(activity, column, who_moved, null, null).show()
}
R.id.llMoved -> who_moved ?.let { who_moved ->
R.id.llMoved -> who_moved?.let { who_moved ->
if(access_info.isPseudo) {
DlgContextMenu(activity, column, who_moved, null, null).show()
} else {
Action_User.profileLocal(activity, activity.nextPosition(column), access_info, who_moved)
Action_User.profileLocal(
activity,
activity.nextPosition(column),
access_info,
who_moved
)
}
}
}
@ -291,12 +319,22 @@ internal class HeaderViewHolderProfile(
when(v.id) {
R.id.btnFollow -> {
Action_Follow.followFromAnotherAccount(activity, activity.nextPosition(column), access_info, who)
Action_Follow.followFromAnotherAccount(
activity,
activity.nextPosition(column),
access_info,
who
)
return true
}
R.id.btnMoved -> {
Action_Follow.followFromAnotherAccount(activity, activity.nextPosition(column), access_info, who_moved)
Action_Follow.followFromAnotherAccount(
activity,
activity.nextPosition(column),
access_info,
who_moved
)
return true
}
}
@ -304,4 +342,7 @@ internal class HeaderViewHolderProfile(
return false
}
override fun onViewRecycled() {
}
}

View File

@ -0,0 +1,37 @@
package jp.juggler.subwaytooter
import android.view.View
import android.widget.TextView
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
internal class ViewHolderHeaderSearch(
arg_activity : ActMain,
viewRoot : View
) : ViewHolderHeaderBase(arg_activity, viewRoot) {
private val tvSearchDesc : TextView
init {
this.tvSearchDesc = viewRoot.findViewById(R.id.tvSearchDesc)
tvSearchDesc.visibility = View.VISIBLE
tvSearchDesc.movementMethod = MyLinkMovementMethod
}
override fun showColor() {
}
override fun bindData(column : Column) {
super.bindData(column)
val html = column.getHeaderDesc() ?: ""
val sv = DecodeOptions(decodeEmoji = true).decodeHTML(activity, access_info, html)
tvSearchDesc.text = sv
}
override fun onViewRecycled() {
}
}

View File

@ -0,0 +1,5 @@
package jp.juggler.subwaytooter
import android.support.v7.widget.RecyclerView
internal class ViewHolderItem(val ivh : ItemViewHolder) : RecyclerView.ViewHolder(ivh.viewRoot)

View File

@ -64,11 +64,13 @@ class TootApiClient(
private val reStartJsonArray = Pattern.compile("\\A\\s*\\[")
private val reStartJsonObject = Pattern.compile("\\A\\s*\\{")
private val reWhiteSpace = Pattern.compile("\\s+")
private val mspTokenUrl = "http://mastodonsearch.jp/api/v1.0.1/utoken"
private val mspSearchUrl = "http://mastodonsearch.jp/api/v1.0.1/cross"
private val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"
fun getMspMaxId(array : JSONArray, max_id : String) : String {
// max_id の更新
val size = array.length()
@ -123,12 +125,14 @@ class TootApiClient(
// HTMLならタグの除去を試みる
val ct = response.body()?.contentType()
if(ct?.subtype() == "html") {
return DecodeOptions().decodeHTML(null, null, sv).toString()
val decoded = DecodeOptions().decodeHTML(null, null, sv).toString()
return reWhiteSpace.matcher(decoded).replaceAll(" ").trim()
}
// XXX: Amazon S3 が403を返した場合にcontent-typeが?/xmlでserverがAmazonならXMLをパースしてエラーを整形することもできるが、多分必要ない
return sv
return reWhiteSpace.matcher(sv).replaceAll(" ").trim()
}
fun formatResponse(

View File

@ -33,7 +33,7 @@ class TootInstance(src : JSONObject) {
// ユーザ数等の数字。マストドン1.6以降。
val stats : Stats?
// FIXME: urls をパースしてない。使ってないから…
// XXX: urls をパースしてない。使ってないから…
init {

View File

@ -148,7 +148,12 @@ object HTMLDecoder {
//////////////////////////////////////////////////////////////////////////////////////
private class TokenParser internal constructor(internal val src : String) {
private val reComment = Pattern.compile("<!--.*?-->", Pattern.DOTALL)
private val reDoctype = Pattern.compile("\\A\\s*<!doctype[^>]*>",Pattern.CASE_INSENSITIVE)
private class TokenParser(srcArg : String) {
internal val src : String
internal var next : Int = 0
internal var tag : String = ""
@ -156,17 +161,21 @@ object HTMLDecoder {
internal var text : String = ""
init {
this.next = 0
var sv = reComment.matcher(srcArg).replaceAll(" ")
sv = reDoctype.matcher(sv).replaceFirst("")
this.src = sv
eat()
}
internal fun eat() {
// end?
if(next >= src.length) {
tag = TAG_END
open_type = OPEN_TYPE_OPEN_CLOSE
return
}
// text ?
var end = src.indexOf('<', next)
if(end == - 1) end = src.length
@ -177,6 +186,7 @@ object HTMLDecoder {
next = end
return
}
// tag ?
end = src.indexOf('>', next)
if(end == - 1) {
@ -187,6 +197,10 @@ object HTMLDecoder {
text = src.substring(next, end)
next = end
val m = reTag.matcher(text)
if(m.find()) {
val is_close = m.group(1).isNotEmpty()
@ -203,6 +217,8 @@ object HTMLDecoder {
this.open_type = OPEN_TYPE_OPEN_CLOSE
}
}
}
private class Node {

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.util
import jp.juggler.subwaytooter.view.MyListView
import android.support.v7.widget.RecyclerView
import jp.juggler.subwaytooter.ColumnViewHolder
class ScrollPosition{
@ -12,19 +13,37 @@ class ScrollPosition{
this.top = top
}
constructor(listView : MyListView) {
if(listView.childCount == 0) {
// constructor(listView : MyListView) {
// if(listView.childCount == 0) {
// top = 0
// pos = top
// } else {
// pos = listView.firstVisiblePosition
// top = listView.getChildAt(0).top
// }
// }
//
// fun restore(listView : MyListView) {
// if(0 <= pos && pos < listView.adapter.count) {
// listView.setSelectionFromTop(pos, top)
// }
// }
constructor(holder:ColumnViewHolder) {
val findPosition = holder.listLayoutManager.findFirstVisibleItemPosition()
if( findPosition == RecyclerView.NO_POSITION){
top = 0
pos = top
} else {
pos = listView.firstVisiblePosition
top = listView.getChildAt(0).top
}else{
pos = findPosition
val firstItemView = holder.listLayoutManager.findViewByPosition(findPosition)
top = firstItemView?.top ?: 0
}
}
fun restore(listView : MyListView) {
if(0 <= pos && pos < listView.adapter.count) {
listView.setSelectionFromTop(pos, top)
fun restore(holder:ColumnViewHolder) {
if(0 <= pos && pos < holder.listView.adapter.itemCount) {
holder.listLayoutManager.scrollToPositionWithOffset(pos,top)
}
}
}

View File

@ -0,0 +1,43 @@
package jp.juggler.subwaytooter.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.support.v7.widget.RecyclerView
import android.graphics.drawable.Drawable
import android.view.View
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.Styler
class ListDivider(context : Context) : RecyclerView.ItemDecoration() {
companion object {
var height : Int =0
}
private val drawable : Drawable
init {
drawable = Styler.getAttributeDrawable(context, R.attr.colorSettingDivider)
height = (context.resources.displayMetrics.density * 1f +0.5f).toInt()
}
override fun getItemOffsets(outRect : Rect, view : View, parent : RecyclerView, state : RecyclerView.State) {
outRect.set(0, 0, 0, height)
}
override fun onDraw(canvas : Canvas, parent : RecyclerView, state : RecyclerView.State) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
for(i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + height
drawable.setBounds(left, top, right, bottom)
drawable.draw(canvas)
}
}
}

View File

@ -6,6 +6,7 @@ import android.os.SystemClock
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ListView
import jp.juggler.subwaytooter.StatusButtonsPopup
import jp.juggler.subwaytooter.util.LogCategory
@ -15,19 +16,16 @@ class MyListView : ListView {
private val log = LogCategory("MyListView")
}
var last_popup_close = 0L
constructor(context : Context) : super(context)
constructor(context : Context, attrs : AttributeSet) : super(context, attrs)
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int) : super(context, attrs, defStyleAttr)
constructor(context : Context, attrs : AttributeSet, defStyleAttr : Int, defStyleRes : Int) : super(context, attrs, defStyleAttr, defStyleRes)
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev : MotionEvent) : Boolean {
// ポップアップを閉じた時にクリックでリストを触ったことになってしまう不具合の回避
val now = SystemClock.elapsedRealtime()
if(now - last_popup_close < 30L) {
if(now - StatusButtonsPopup.last_popup_close < 30L) {
val action = ev.action
if(action == MotionEvent.ACTION_DOWN) {
// ポップアップを閉じた直後はタッチダウンを無視する

View File

@ -126,7 +126,7 @@ class MyNetworkImageView @JvmOverloads constructor(
val d = drawable
if(d is Animatable) {
if(d.isRunning) {
log.d("cancelLoading: Animatable.stop()")
//log.d("cancelLoading: Animatable.stop()")
d.stop()
}
}
@ -446,4 +446,32 @@ class MyNetworkImageView @JvmOverloads constructor(
}
}
/////////////////////////////////////////////////////////////////////
// プロフ表示の背景画像のレイアウト崩れの対策
var measureProfileBg = false
override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int) {
if(measureProfileBg) {
val w_mode = MeasureSpec.getMode(widthMeasureSpec)
val w_size = MeasureSpec.getSize(widthMeasureSpec)
val h_mode = MeasureSpec.getMode(heightMeasureSpec)
val h_size = MeasureSpec.getSize(heightMeasureSpec)
val w = when(w_mode) {
MeasureSpec.EXACTLY -> w_size
MeasureSpec.AT_MOST -> w_size
MeasureSpec.UNSPECIFIED ->0
else -> 0
}
val h = when(h_mode) {
MeasureSpec.EXACTLY -> h_size
MeasureSpec.AT_MOST -> h_size
MeasureSpec.UNSPECIFIED ->0
else -> 0
}
setMeasuredDimension(w, h)
}else{
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}

View File

@ -6,7 +6,7 @@ import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewConfiguration
class MyRecyclerView : RecyclerView {
class TabletModeRecyclerView : RecyclerView {
private var mForbidStartDragging : Boolean = false

View File

@ -207,6 +207,22 @@
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/force_gap_when_refresh"
/>
<LinearLayout style="@style/setting_row_form">
<Switch
android:id="@+id/swForceGap"
style="@style/setting_horizontal_stretch"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:labelFor="@+id/etClientName"
@ -574,21 +590,21 @@
android:text="@string/appearance"
/>
<View style="@style/setting_divider"/>
<!--<View style="@style/setting_divider"/>-->
<TextView
style="@style/setting_row_label"
android:text="@string/disable_fast_scroller"
/>
<!--<TextView-->
<!--style="@style/setting_row_label"-->
<!--android:text="@string/disable_fast_scroller"-->
<!--/>-->
<LinearLayout style="@style/setting_row_form">
<!--<LinearLayout style="@style/setting_row_form">-->
<Switch
android:id="@+id/swDisableFastScroller"
style="@style/setting_horizontal_stretch"
/>
<!--<Switch-->
<!--android:id="@+id/swDisableFastScroller"-->
<!--style="@style/setting_horizontal_stretch"-->
<!--/>-->
</LinearLayout>
<!--</LinearLayout>-->
<View style="@style/setting_divider"/>

View File

@ -48,7 +48,7 @@
android:layout_height="match_parent"
/>
<jp.juggler.subwaytooter.view.MyRecyclerView
<jp.juggler.subwaytooter.view.TabletModeRecyclerView
android:id="@+id/rvPager"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -115,6 +115,7 @@
android:id="@+id/ivBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"
/>

View File

@ -373,19 +373,20 @@
app:srl_direction="both"
>
<jp.juggler.subwaytooter.view.MyListView
<android.support.v7.widget.ListRecyclerView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:divider="?attr/colorSettingDivider"
android:dividerHeight="1dp"
android:fadeScrollbars="false"
android:fastScrollEnabled="true"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:clipToPadding="false"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarStyle="outsideOverlay"
/>
</com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout>

View File

@ -600,6 +600,7 @@
<string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string>
<string name="domain_block_from_local">Can\'t use domain block for local instance.</string>
<string name="always_show_application">Show (via) application name if possible</string>
<string name="force_gap_when_refresh">Force put gap when refreshing top</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->

View File

@ -884,5 +884,6 @@
<string name="domain_block_from_pseudo">疑似アカウントではドメインブロックできません</string>
<string name="domain_block_from_local">自分のタンスにはドメインブロックできません</string>
<string name="always_show_application">アプリ名(via)を可能なら表示する</string>
<string name="force_gap_when_refresh">リフレッシュ時に常にギャップを挟む</string>
</resources>

View File

@ -589,5 +589,6 @@
<string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string>
<string name="domain_block_from_local">Can\'t use domain block for local instance.</string>
<string name="always_show_application">Show (via) application name if possible</string>
<string name="force_gap_when_refresh">Force put gap when refreshing top</string>
</resources>

View File

@ -2,7 +2,7 @@
buildscript {
ext.kotlin_version = '1.2.10'
ext.kotlin_version = '1.2.20'
ext.anko_version='0.10.4'
repositories {

View File

@ -15,3 +15,10 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
####################################
# enable build cache
# https://blog.jetbrains.com/kotlin/2018/01/kotlin-1-2-20-is-out/
org.gradle.caching=true

View File

@ -1,6 +1,6 @@
#Sat Nov 04 23:32:32 JST 2017
#Thu Jan 18 01:59:02 JST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip