アプリ設定に「見た目/Instance tickerを表示する」を追加.

This commit is contained in:
tateisu 2018-12-09 00:29:07 +09:00
parent 1594391db3
commit 52fe206292
10 changed files with 357 additions and 9 deletions

View File

@ -483,7 +483,7 @@ class App1 : Application() {
val response : Response
try {
val request_builder = okhttp3.Request.Builder()
val request_builder = Request.Builder()
request_builder.url(url)
request_builder.cacheControl(CACHE_5MIN)

View File

@ -15,6 +15,7 @@ import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.BucketList
import jp.juggler.subwaytooter.util.InstanceTicker
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.subwaytooter.util.WordTrieTree
import jp.juggler.util.*
@ -222,6 +223,8 @@ class Column(
internal const val TAB_FOLLOWING = 1
internal const val TAB_FOLLOWERS = 2
internal var useInstanceTicker = false
@Suppress("UNCHECKED_CAST")
private inline fun <reified T> getParamAt(params : Array<out Any>, idx : Int) : T {
return params[idx] as T
@ -1892,7 +1895,9 @@ class Column(
stopStreaming()
initFilter()
useInstanceTicker = Pref.bpInstanceTicker( app_state.pref)
mRefreshLoadingErrorPopupState = 0
mRefreshLoadingError = ""
mInitialLoadingError = ""
@ -2580,6 +2585,11 @@ class Column(
override fun doInBackground(vararg unused : Void) : TootApiResult? {
ctStarted.set(true)
if( Pref.bpInstanceTicker(app_state.pref)){
InstanceTicker.load()
}
val client = TootApiClient(context, callback = object : TootApiCallback {
override val isApiCancelled : Boolean
get() = isCancelled || is_dispose.get()

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.os.SystemClock
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
@ -11,6 +12,7 @@ import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.TextUtils
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@ -137,6 +139,11 @@ internal class ItemViewHolder(
private lateinit var tvApplication : TextView
private lateinit var tvMessageHolder : TextView
private lateinit var llInstanceTicker:View
private lateinit var ivInstanceTicker:MyNetworkImageView
private lateinit var tvInstanceTicker:TextView
private lateinit var access_info : SavedAccount
private var buttons_for_status : StatusButtons? = null
@ -378,6 +385,7 @@ internal class ItemViewHolder(
this.viewRoot.setBackgroundColor(0)
this.boostedAction = defaultBoostedAction
llInstanceTicker.visibility = View.GONE
llBoosted.visibility = View.GONE
llReply.visibility = View.GONE
llFollow.visibility = View.GONE
@ -1035,6 +1043,42 @@ internal class ItemViewHolder(
)
// }
if( Column.useInstanceTicker){
try {
val item = InstanceTicker.lastList[who.host]
if( item != null) {
tvInstanceTicker.text = item.name
tvInstanceTicker.textColor = item.colorText
val density = llInstanceTicker.resources.displayMetrics.density
val lp = ivInstanceTicker.layoutParams
lp.height = (density*16f+0.5f).toInt()
lp.width = (density*item.imageWidth+0.5f).toInt()
ivInstanceTicker.layoutParams = lp
ivInstanceTicker.setImageUrl(activity.pref, 0f, item.image)
val colorBg = item.colorBg
when {
colorBg.isEmpty() ->{
tvInstanceTicker.background = null
ivInstanceTicker.background = null
}
colorBg.size == 1 -> {
tvInstanceTicker.setBackgroundColor(colorBg.first())
ivInstanceTicker.setBackgroundColor(colorBg.first())
}
else -> {
ivInstanceTicker.setBackgroundColor(colorBg.last())
tvInstanceTicker.background = colorBg.getGradation()
}
}
llInstanceTicker.visibility = View.VISIBLE
llInstanceTicker.requestLayout()
}
}catch(ex:Throwable){
log.trace(ex)
}
}
var content = status.decoded_content
// ニコフレのアンケートの表示
@ -1863,13 +1907,7 @@ internal class ItemViewHolder(
}
private fun ellipsize(src : String, limit : Int) : String {
return if(src.codePointCount(0, src.length) <= limit) {
src
} else {
"${src.substring(0, src.offsetByCodePoints(0, limit))}"
}
}
private fun addLinkAndCaption(
sb : StringBuilder,
@ -2398,6 +2436,24 @@ internal class ItemViewHolder(
tvName = textView {
}.lparams(matchParent, wrapContent)
llInstanceTicker = linearLayout{
lparams(matchParent, wrapContent)
ivInstanceTicker = myNetworkImageView{
}.lparams(dip(16), dip(16)){
isBaselineAligned = false
}
tvInstanceTicker = textView{
setTextSize(TypedValue.COMPLEX_UNIT_DIP,10f)
gravity=Gravity.CENTER_VERTICAL
setPaddingStartEnd( dip(4f), dip(4f) )
}.lparams(0,dip(16) ){
isBaselineAligned = false
weight=1f
}
}
llReply = linearLayout {
lparams(matchParent, wrapContent) {
bottomMargin = dip(3)

View File

@ -130,6 +130,7 @@ object Pref {
return PreferenceManager.getDefaultSharedPreferences(context)
}
// キー名と設定項目のマップ。インポートやアプリ設定で使う
val map = HashMap<String, BasePref<*>>()
@ -340,6 +341,12 @@ object Pref {
R.id.swScrollTopFromColumnStrip
)
val bpInstanceTicker = BooleanPref(
"InstanceTicker",
false,
R.id.swInstanceTicker
)
// int

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.api
import android.content.Context
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.UserRelation

View File

@ -0,0 +1,248 @@
package jp.juggler.subwaytooter.util
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.SystemClock
import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory
import jp.juggler.util.ellipsize
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern
object InstanceTicker {
private val log = LogCategory("InstanceTicker")
private fun parseHex(group : String) : Int = group.toInt(16)
private val reColor6 =
Pattern.compile("""#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})""", Pattern.CASE_INSENSITIVE)
private val reColor3 =
Pattern.compile("""#([0-9a-f])([0-9a-f])([0-9a-f])\b""", Pattern.CASE_INSENSITIVE)
private fun parseColor(v : String) : Int? {
var m = reColor6.matcher(v)
if(m.find()) {
return Color.rgb(
parseHex(m.group(1)),
parseHex(m.group(2)),
parseHex(m.group(3))
)
}
//
m = reColor3.matcher(v)
if(m.find()) {
return Color.rgb(
parseHex(m.group(1)) * 0x11,
parseHex(m.group(2)) * 0x11,
parseHex(m.group(3)) * 0x11
)
}
if(v.isNotEmpty()) log.e("parseColor: can't parse $v")
return null
}
private fun color(v : String) : Int =
parseColor(v) ?: error("not a color: $v")
enum class Type(
val num : Int,
val imageUrl : String,
val imageWidth : Int,
val colorText : Int,
val colorBg : IntArray
) {
GnuSocial(
0,
"https://cdn.weep.me/img/gnus.png",
16,
color("#fff"),
intArrayOf(color("#000"), color("#a23"))
),
MastodonJapan(
1,
"https://cdn.weep.me/img/mstdn.png",
16,
color("#fff"),
intArrayOf(color("#000"), color("#27c"))
),
MastodonAbroad(
2,
"https://cdn.weep.me/img/mstdn.png",
16,
color("#fff"),
intArrayOf(color("#000"), color("#49c"))
),
Pleroma(
3,
"https://cdn.weep.me/img/plrm.png",
16,
color("#da5"),
intArrayOf(color("#000"), color("#123"))
),
Misskey(
4,
"https://cdn.weep.me/img/msky2.png",
36,
color("#fff"),
intArrayOf(color("#000"), color("#29b"))
),
PeerTube(
5,
"https://cdn.weep.me/img/peertube2.png",
16,
color("#000"),
intArrayOf(color("#000"), color("#fff"), color("#fff"), color("#fff"))
),
// ロシアの大手マイクロブログ
Juick(
6,
"https://cdn.weep.me/img/juick2.png",
16,
color("#fff"),
intArrayOf(color("#000"), color("#000"))
)
;
}
private fun findType(num : Int) : Type? = Type.values().find { it.num == num }
class ColorBg(val array : IntArray) {
companion object {
val map = HashMap<String, GradientDrawable>()
}
val key : String = array.joinToString(",") { it.toString() }
val size = array.size
fun isEmpty() = size == 0
fun first() = array.first()
fun last() = array.last()
fun getGradation() : Drawable? {
var v = map[key]
return if(v != null) {
v
} else {
v = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, array)
map[key] = v
v
}
}
}
class Item(type : Type, cols : List<String>) {
val type : Type
//[1]インスタンス名(ドメイン) (空文字列かもしれない)
val name : String
// [2]ドメイン
val instance : String
// [3]独自文字色
val colorText : Int
// [4]独自背景色
val colorBg : ColorBg
// [5]独自画像
val image : String
// [6]画像の横幅
val imageWidth : Int
init {
this.type = type
this.instance = cols[2]
if(cols[5].isEmpty()) {
// typeのデフォルト画像
this.image = type.imageUrl
this.imageWidth = type.imageWidth
} else {
// 独自画像
this.image = "https://cdn.weep.me/img/${cols[5]}"
// 画像の横幅は省略されてるかもしれない
this.imageWidth = try {
cols[6].toInt()
} catch(ex : Throwable) {
18 // デフォルト値
}
}
if(cols[1] == "閉鎖") {
this.name = "閉鎖済み"
this.colorText = Color.WHITE
this.colorBg = ColorBg(intArrayOf(color("#666")))
} else {
this.name = ellipsize(
when {
imageWidth >= 60 -> "" // 空白を表示する
cols[1].isNotEmpty() -> cols[1]
else -> instance
}, 36
)
this.colorText = parseColor(cols[3]) ?: type.colorText
val ia = cols[4].split(',')
.filter { it.isNotBlank() }
.map { color(it) }
.toIntArray()
this.colorBg = ColorBg(
when {
ia.isNotEmpty() -> ia
else -> type.colorBg
}
)
}
}
}
var lastList = ConcurrentHashMap<String, Item>()
private var timeNextLoad = 0L
private val reLine = Pattern.compile("""([^\x0d\x0a]+)""")
fun load() {
synchronized(this) {
// 頻繁に読み直さない
val now = SystemClock.elapsedRealtime()
if(timeNextLoad - now > 0) return
timeNextLoad = now + 301000L
val text = App1.getHttpCachedString("https://cdn.weep.me/instance/tsv/")
if(text?.isEmpty() != false) return
val list = ConcurrentHashMap<String, Item>()
val m = reLine.matcher(text)
while(m.find()) {
try {
val cols = m.group(1).split('\t')
val type = try {
findType(cols[0].toInt())
} catch(ignored : Throwable) {
null
} ?: Type.MastodonJapan
val item = Item(type, cols)
if(item.instance.isNotEmpty()) list[item.instance] = item
} catch(ex : Throwable) {
log.trace(ex)
}
}
if(list.isNotEmpty()) lastList = list
}
}
}

View File

@ -202,6 +202,13 @@ fun String?.filterNotEmpty() : String? = when {
// return sb.toString()
//}
fun ellipsize(src : String, limit : Int) : String =
if(src.codePointCount(0, src.length) <= limit) {
src
} else {
"${src.substring(0, src.offsetByCodePoints(0, limit))}"
}
fun String.sanitizeBDI() : String {
// 文字列をスキャンしてBDI制御文字をスタックに入れていく

View File

@ -457,5 +457,22 @@
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/show_instance_ticker"
/>
<LinearLayout style="@style/setting_row_form">
<Switch
android:id="@+id/swInstanceTicker"
style="@style/setting_horizontal_stretch"
android:gravity="center"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
</LinearLayout>
</ScrollView>

View File

@ -808,5 +808,6 @@
<string name="profile_directory">プロフィールディレクトリ</string>
<string name="about_this_instance">このインスタンスについて</string>
<string name="top_page">トップページ</string>
<string name="show_instance_ticker">Instance tickerを表示する(カラムのリロードが必要)</string>
</resources>

View File

@ -825,5 +825,6 @@
<string name="profile_directory">Profile directory</string>
<string name="about_this_instance">About this instance</string>
<string name="top_page">Top page</string>
<string name="show_instance_ticker">Show instance ticker (column reload required)</string>
</resources>