内蔵メディアビューアはリモートURLでダメなら自タンスのURLを試す。TL中項目のビューをXMLからAnko Layoutに変更。

This commit is contained in:
tateisu 2018-01-15 06:47:42 +09:00
parent f071ce191f
commit d95b4e5243
26 changed files with 871 additions and 329 deletions

View File

@ -11,8 +11,10 @@ android {
applicationId "jp.juggler.subwaytooter" applicationId "jp.juggler.subwaytooter"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 27
versionCode 201
versionName "2.0.1" versionCode 202
versionName "2.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// https://stackoverflow.com/questions/47791227/java-lang-illegalstateexception-dex-archives-setting-dex-extension-only-for // https://stackoverflow.com/questions/47791227/java-lang-illegalstateexception-dex-archives-setting-dex-extension-only-for
@ -105,6 +107,15 @@ dependencies {
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4' implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
compile "org.jetbrains.anko:anko:$anko_version"
// Anko Layouts
compile "org.jetbrains.anko:anko-sdk25:$anko_version" // sdk15, sdk19, sdk21, sdk23 are also available
compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
// Coroutine listeners for Anko Layouts
compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
} }
repositories { repositories {

View File

@ -0,0 +1,24 @@
package jp.juggler.subwaytooter.util
import android.support.test.runner.AndroidJUnit4
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
@Suppress("MemberVisibilityCanPrivate")
@RunWith(AndroidJUnit4::class)
class TestBucketList {
@Test fun test1(){
val list = BucketList<String>(bucketCapacity=2)
assertEquals(true,list.isEmpty())
list.addAll( listOf("A","B","C"))
list.addAll( 3, listOf("a","b","c"))
list.addAll( 1, listOf("a","b","c"))
list.removeAt(7)
assertEquals(8,list.size)
listOf("A","a","b","c","B","C","a","c").forEachIndexed { i,v->
assertEquals( v,list[i])
}
}
}

View File

@ -52,7 +52,6 @@ import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.ProgressResponseBody import jp.juggler.subwaytooter.util.ProgressResponseBody
import jp.juggler.subwaytooter.util.Utils import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.PinchBitmapView import jp.juggler.subwaytooter.view.PinchBitmapView
import okhttp3.Response
class ActMediaViewer : AppCompatActivity(), View.OnClickListener { class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
@ -316,8 +315,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
} }
@SuppressLint("StaticFieldLeak") private fun loadBitmap(ta : TootAttachment) { @SuppressLint("StaticFieldLeak") private fun loadBitmap(ta : TootAttachment) {
val url = ta.getLargeUrl(App1.pref) val urlList = ta.getLargeUrlList(App1.pref)
if(url == null) { if(urlList.isEmpty() ) {
showError("missing media attachment url.") showError("missing media attachment url.")
return return
} }
@ -332,7 +331,6 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
private val options = BitmapFactory.Options() private val options = BitmapFactory.Options()
internal var data : ByteArray? = null
internal var bitmap : Bitmap? = null internal var bitmap : Bitmap? = null
private fun decodeBitmap(data : ByteArray, pixel_max : Int) : Bitmap? { private fun decodeBitmap(data : ByteArray, pixel_max : Int) : Bitmap? {
@ -359,50 +357,53 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
return BitmapFactory.decodeByteArray(data, 0, data.size, options) return BitmapFactory.decodeByteArray(data, 0, data.size, options)
} }
internal fun getHttpCached(client : TootApiClient, url : String) : TootApiResult { internal fun getHttpCached(client : TootApiClient, url : String) : TootApiResult? {
val response : Response val result = TootApiResult.makeWithCaption(url)
try { if(!client.sendRequest(result){
val request = okhttp3.Request.Builder() okhttp3.Request.Builder()
.url(url) .url(url)
.cacheControl(App1.CACHE_5MIN) .cacheControl(App1.CACHE_5MIN)
.build() .build()
}) return result
client.publishApiProgress(getString(R.string.request_api, request.method(), url))
val call = App1.ok_http_client2.newCall(request)
response = call.execute()
} catch(ex : Throwable) {
return TootApiResult(Utils.formatError(ex, "network error."))
}
if( client.isApiCancelled ) return null
val response = requireNotNull(result.response)
if(! response.isSuccessful) { if(! response.isSuccessful) {
return TootApiResult(TootApiClient.formatResponse(response, "response error")) return result.setError( TootApiClient.formatResponse(response,result.caption))
} }
return try { try {
result.data = ProgressResponseBody.bytes(response) { bytesRead, bytesTotal ->
data = ProgressResponseBody.bytes(response) { bytesRead, bytesTotal ->
// 50MB以上のデータはキャンセルする // 50MB以上のデータはキャンセルする
if(Math.max(bytesRead, bytesTotal) >= 50000000) { if(Math.max(bytesRead, bytesTotal) >= 50000000) {
throw RuntimeException("media attachment is larger than 50000000") throw RuntimeException("media attachment is larger than 50000000")
} }
client.publishApiProgressRatio(bytesRead.toInt(), bytesTotal.toInt()) client.publishApiProgressRatio(bytesRead.toInt(), bytesTotal.toInt())
} }
if( client.isApiCancelled ) return null
TootApiResult("")
} catch(ex : Throwable) { } catch(ex : Throwable) {
TootApiResult(Utils.formatError(ex, "content error.")) result.setError( TootApiClient.formatResponse(response,result.caption,"?"))
} }
return result
} }
override fun background(client : TootApiClient) : TootApiResult { override fun background(client : TootApiClient) : TootApiResult? {
val result = getHttpCached(client, url) if( urlList.isEmpty()) return TootApiResult("missing url")
val data = this.data ?: return result var result : TootApiResult? = null
client.publishApiProgress("decoding image…") for(url in urlList) {
val bitmap = decodeBitmap(data, 2048) result = getHttpCached(client, url)
this.bitmap = bitmap val data = result?.data as? ByteArray
return if(bitmap != null) result else TootApiResult("image decode failed.") if(data != null) {
client.publishApiProgress("decoding image…")
val bitmap = decodeBitmap(data, 2048)
if( bitmap != null ) {
this.bitmap = bitmap
break
}
}
}
return result
} }
override fun handleResult(result : TootApiResult?) { override fun handleResult(result : TootApiResult?) {

View File

@ -291,6 +291,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
R.id.btnRemoveReply -> removeReply() R.id.btnRemoveReply -> removeReply()
R.id.btnMore -> performMore() R.id.btnMore -> performMore()
R.id.btnPlugin -> openMushroom() R.id.btnPlugin -> openMushroom()
R.id.btnEmojiPicker -> post_helper.openEmojiPickerFromMore()
} }
} }
@ -752,7 +753,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
btnRemoveReply.setOnClickListener(this) btnRemoveReply.setOnClickListener(this)
findViewById<View>(R.id.btnPlugin).setOnClickListener(this) findViewById<View>(R.id.btnPlugin).setOnClickListener(this)
findViewById<View>(R.id.btnEmojiPicker).setOnClickListener(this)
for(iv in ivMedia) { for(iv in ivMedia) {
iv.setOnClickListener(this) iv.setOnClickListener(this)
iv.setDefaultImageResId(Styler.getAttributeResourceId(this, R.attr.ic_loading)) iv.setDefaultImageResId(Styler.getAttributeResourceId(this, R.attr.ic_loading))
@ -1569,6 +1571,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
dialog.show(this, null) dialog.show(this, null)
} }
/////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////
// post // post

View File

@ -60,8 +60,8 @@ internal class ItemListAdapter(private val activity : ActMain, private val colum
val holder : ItemViewHolder val holder : ItemViewHolder
val view : View val view : View
if(viewOld == null) { if(viewOld == null) {
view = activity.layoutInflater.inflate(if(bSimpleList) R.layout.lv_status_simple else R.layout.lv_status, parent, false) holder = ItemViewHolder(activity, column, this, bSimpleList)
holder = ItemViewHolder(activity, column, this, view, bSimpleList) view = holder.viewRoot
view.tag = holder view.tag = holder
} else { } else {
view = viewOld view = viewOld

View File

@ -3,17 +3,17 @@ package jp.juggler.subwaytooter
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat import android.support.v4.view.ViewCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils
import android.view.Gravity
import android.view.View import android.view.View
import android.widget.Button import android.view.ViewGroup
import android.widget.ImageButton import android.widget.*
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import java.util.ArrayList import java.util.ArrayList
@ -35,79 +35,88 @@ import jp.juggler.subwaytooter.table.ContentWarning
import jp.juggler.subwaytooter.table.MediaShown import jp.juggler.subwaytooter.table.MediaShown
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.span.EmojiImageSpan import jp.juggler.subwaytooter.span.EmojiImageSpan
import jp.juggler.subwaytooter.util.HTMLDecoder import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.view.* import jp.juggler.subwaytooter.view.*
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import org.jetbrains.anko.*
import org.json.JSONObject import org.json.JSONObject
internal class ItemViewHolder( internal class ItemViewHolder(
val activity : ActMain, val activity : ActMain,
val column : Column, val column : Column,
private val list_adapter : ItemListAdapter, private val list_adapter : ItemListAdapter,
view : View,
private val bSimpleList : Boolean private val bSimpleList : Boolean
) : View.OnClickListener, View.OnLongClickListener { ) : View.OnClickListener, View.OnLongClickListener {
companion object { companion object {
private val log = LogCategory("ItemViewHolder") private val log = LogCategory("ItemViewHolder")
} }
var viewRoot : View
private lateinit var llBoosted : View
private lateinit var ivBoosted : ImageView
private lateinit var tvBoosted : TextView
private lateinit var tvBoostedAcct : TextView
private lateinit var tvBoostedTime : TextView
private lateinit var llFollow : View
private lateinit var ivFollow : MyNetworkImageView
private lateinit var tvFollowerName : TextView
private lateinit var tvFollowerAcct : TextView
private lateinit var btnFollow : ImageButton
private lateinit var ivFollowedBy : ImageView
private lateinit var llStatus : View
private lateinit var ivThumbnail : MyNetworkImageView
private lateinit var tvName : TextView
private lateinit var tvTime : TextView
private lateinit var tvAcct : TextView
private lateinit var llContentWarning : View
private lateinit var tvContentWarning : MyTextView
private lateinit var btnContentWarning : Button
private lateinit var llContents : View
private lateinit var tvMentions : MyTextView
private lateinit var tvContent : MyTextView
private lateinit var flMedia : View
private lateinit var btnShowMedia : TextView
private lateinit var ivMedia1 : MyNetworkImageView
private lateinit var ivMedia2 : MyNetworkImageView
private lateinit var ivMedia3 : MyNetworkImageView
private lateinit var ivMedia4 : MyNetworkImageView
private lateinit var btnHideMedia : View
private lateinit var llButtonBar : View
private lateinit var btnConversation : ImageButton
private lateinit var btnReply : ImageButton
private lateinit var btnBoost : Button
private lateinit var btnFavourite : Button
private lateinit var llFollow2 : View
private lateinit var btnFollow2 : ImageButton
private lateinit var ivFollowedBy2 : ImageView
private lateinit var btnMore : ImageButton
private lateinit var llSearchTag : View
private lateinit var btnSearchTag : Button
private lateinit var llList : View
private lateinit var btnListTL : Button
private lateinit var btnListMore : ImageButton
private lateinit var llExtra : LinearLayout
private lateinit var tvApplication : TextView
private val access_info : SavedAccount private val access_info : SavedAccount
private val llBoosted : View
private val ivBoosted : ImageView
private val tvBoosted : TextView
private val tvBoostedAcct : TextView
private val tvBoostedTime : TextView
private val llFollow : View
private val ivFollow : MyNetworkImageView
private val tvFollowerName : TextView
private val tvFollowerAcct : TextView
private val btnFollow : ImageButton
private val ivFollowedBy : ImageView
private val llStatus : View
private val ivThumbnail : MyNetworkImageView
private val tvName : TextView
private val tvTime : TextView
private val tvAcct : TextView
private val llContentWarning : View
private val tvContentWarning : MyTextView
private val btnContentWarning : Button
private val llContents : View
private val tvMentions : MyTextView
private val tvContent : MyTextView
private val flMedia : View
private val btnShowMedia : TextView
private val ivMedia1 : MyNetworkImageView
private val ivMedia2 : MyNetworkImageView
private val ivMedia3 : MyNetworkImageView
private val ivMedia4 : MyNetworkImageView
private val buttons_for_status : StatusButtons? private val buttons_for_status : StatusButtons?
private val llSearchTag : View
private val btnSearchTag : Button
private val llList : View
private val btnListTL : Button
private val llExtra : LinearLayout
private val tvApplication : TextView?
private var item : Any? = null private var item : Any? = null
private var status__showing : TootStatus? = null private var status__showing : TootStatus? = null
@ -126,22 +135,17 @@ internal class ItemViewHolder(
private val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>() private val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>()
init { init {
this.viewRoot = inflate(activity.UI {})
this.access_info = column.access_info this.access_info = column.access_info
this.tvName = view.findViewById(R.id.tvName)
this.tvFollowerName = view.findViewById(R.id.tvFollowerName)
this.tvBoosted = view.findViewById(R.id.tvBoosted)
if(activity.timeline_font != null || activity.timeline_font_bold != null) { if(activity.timeline_font != null || activity.timeline_font_bold != null) {
Utils.scanView(view) { v -> Utils.scanView(this.viewRoot) { v ->
try { try {
if(v is Button) { if(v is Button) {
// ボタンは太字なので触らない // ボタンは太字なので触らない
} else if(v is TextView) { } else if(v is TextView) {
val typeface = when(v.getId()) { val typeface = when {
R.id.tvName, v === tvName || v === tvFollowerName || v === tvBoosted -> activity.timeline_font_bold ?: activity.timeline_font
R.id.tvFollowerName,
R.id.tvBoosted -> activity.timeline_font_bold ?: activity.timeline_font
else -> activity.timeline_font ?: activity.timeline_font_bold else -> activity.timeline_font ?: activity.timeline_font_bold
} }
if(typeface != null) v.typeface = typeface if(typeface != null) v.typeface = typeface
@ -156,49 +160,27 @@ internal class ItemViewHolder(
tvBoosted.typeface = Typeface.DEFAULT_BOLD tvBoosted.typeface = Typeface.DEFAULT_BOLD
} }
this.llBoosted = view.findViewById(R.id.llBoosted) if(bSimpleList) {
this.ivBoosted = view.findViewById(R.id.ivBoosted) llButtonBar.visibility = View.GONE
this.tvBoostedTime = view.findViewById(R.id.tvBoostedTime) this.buttons_for_status = null
this.tvBoostedAcct = view.findViewById(R.id.tvBoostedAcct) } else {
llButtonBar.visibility = View.VISIBLE
this.llFollow = view.findViewById(R.id.llFollow) this.buttons_for_status = StatusButtons(
this.ivFollow = view.findViewById(R.id.ivFollow) activity,
this.tvFollowerAcct = view.findViewById(R.id.tvFollowerAcct) column,
this.btnFollow = view.findViewById(R.id.btnFollow) false,
this.ivFollowedBy = view.findViewById(R.id.ivFollowedBy)
btnConversation = btnConversation,
this.llStatus = view.findViewById(R.id.llStatus) btnReply = btnReply,
btnBoost = btnBoost,
this.ivThumbnail = view.findViewById(R.id.ivThumbnail) btnFavourite = btnFavourite,
this.tvTime = view.findViewById(R.id.tvTime) llFollow2 = llFollow2,
this.tvAcct = view.findViewById(R.id.tvAcct) btnFollow2 = btnFollow2,
ivFollowedBy2 = ivFollowedBy2,
this.llContentWarning = view.findViewById(R.id.llContentWarning) btnMore = btnMore
this.tvContentWarning = view.findViewById(R.id.tvContentWarning)
this.btnContentWarning = view.findViewById(R.id.btnContentWarning) )
}
this.llContents = view.findViewById(R.id.llContents)
this.tvContent = view.findViewById(R.id.tvContent)
this.tvMentions = view.findViewById(R.id.tvMentions)
this.llExtra = view.findViewById(R.id.llExtra)
this.buttons_for_status = if(bSimpleList) null else StatusButtons(activity, column, view, false)
this.flMedia = view.findViewById(R.id.flMedia)
this.btnShowMedia = view.findViewById(R.id.btnShowMedia)
this.ivMedia1 = view.findViewById(R.id.ivMedia1)
this.ivMedia2 = view.findViewById(R.id.ivMedia2)
this.ivMedia3 = view.findViewById(R.id.ivMedia3)
this.ivMedia4 = view.findViewById(R.id.ivMedia4)
this.llSearchTag = view.findViewById(R.id.llSearchTag)
this.btnSearchTag = view.findViewById(R.id.btnSearchTag)
this.tvApplication = view.findViewById(R.id.tvApplication)
this.llList = view.findViewById(R.id.llList)
this.btnListTL = view.findViewById(R.id.btnListTL)
val btnListMore = view.findViewById<View>(R.id.btnListMore)
btnListTL.setOnClickListener(this) btnListTL.setOnClickListener(this)
btnListMore.setOnClickListener(this) btnListMore.setOnClickListener(this)
@ -230,10 +212,7 @@ internal class ItemViewHolder(
tvMentions.movementMethod = MyLinkMovementMethod tvMentions.movementMethod = MyLinkMovementMethod
tvContentWarning.movementMethod = MyLinkMovementMethod tvContentWarning.movementMethod = MyLinkMovementMethod
val v : View btnHideMedia.setOnClickListener(this)
//
v = view.findViewById(R.id.btnHideMedia)
v.setOnClickListener(this)
val lp = flMedia.layoutParams val lp = flMedia.layoutParams
lp.height = activity.app_state.media_thumb_height lp.height = activity.app_state.media_thumb_height
@ -248,9 +227,7 @@ internal class ItemViewHolder(
tvContentWarning.textSize = activity.timeline_font_size_sp tvContentWarning.textSize = activity.timeline_font_size_sp
tvContent.textSize = activity.timeline_font_size_sp tvContent.textSize = activity.timeline_font_size_sp
btnShowMedia.textSize = activity.timeline_font_size_sp btnShowMedia.textSize = activity.timeline_font_size_sp
if(tvApplication != null) { tvApplication.textSize = activity.timeline_font_size_sp
tvApplication.textSize = activity.timeline_font_size_sp
}
btnListTL.textSize = activity.timeline_font_size_sp btnListTL.textSize = activity.timeline_font_size_sp
} }
@ -288,9 +265,11 @@ internal class ItemViewHolder(
llList.visibility = View.GONE llList.visibility = View.GONE
llExtra.removeAllViews() llExtra.removeAllViews()
if(item == null) return if(item == null) return
var c: Int var c : Int
c = if(column.content_color != 0) column.content_color else content_color_default c = if(column.content_color != 0) column.content_color else content_color_default
tvBoosted.setTextColor(c) tvBoosted.setTextColor(c)
@ -300,15 +279,15 @@ internal class ItemViewHolder(
tvContentWarning.setTextColor(c) tvContentWarning.setTextColor(c)
tvContent.setTextColor(c) tvContent.setTextColor(c)
//NSFWは文字色固定 btnShowMedia.setTextColor( c ); //NSFWは文字色固定 btnShowMedia.setTextColor( c );
tvApplication?.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 this.acct_color = c
tvBoostedTime.setTextColor(c) tvBoostedTime.setTextColor(c)
tvTime.setTextColor(c) tvTime.setTextColor(c)
// tvBoostedAcct.setTextColor( c ); // tvBoostedAcct.setTextColor( c );
// tvFollowerAcct.setTextColor( c ); // tvFollowerAcct.setTextColor( c );
// tvAcct.setTextColor( c ); // tvAcct.setTextColor( c );
this.item = item this.item = item
when(item) { when(item) {
@ -563,18 +542,16 @@ internal class ItemViewHolder(
buttons_for_status?.bind(status, (item as? TootNotification)) buttons_for_status?.bind(status, (item as? TootNotification))
if(tvApplication != null) { val application = status.application
val application = status.application when(column.column_type) {
when(column.column_type) {
Column.TYPE_CONVERSATION -> if(application == null) {
Column.TYPE_CONVERSATION -> if(application == null) { tvApplication.visibility = View.GONE
tvApplication.visibility = View.GONE } else {
} else { tvApplication.visibility = View.VISIBLE
tvApplication.visibility = View.VISIBLE tvApplication.text = activity.getString(R.string.application_is, application.name ?: "")
tvApplication.text = activity.getString(R.string.application_is, application.name ?: "")
}
else -> tvApplication.visibility = View.GONE
} }
else -> tvApplication.visibility = View.GONE
} }
} }
@ -597,7 +574,7 @@ internal class ItemViewHolder(
val start = sb.length val start = sb.length
sb.append(status.visibility) sb.append(status.visibility)
val end = sb.length val end = sb.length
val iconResId = Styler.getAttributeResourceId(activity,visIconAttrId ) 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)
} }
@ -700,34 +677,30 @@ internal class ItemViewHolder(
val pos = activity.nextPosition(column) val pos = activity.nextPosition(column)
val item = this.item val item = this.item
val notification = (item as? TootNotification) val notification = (item as? TootNotification)
when(v.id) { when(v) {
R.id.btnHideMedia -> status__showing?.let { status -> btnHideMedia -> status__showing?.let { status ->
MediaShown.save(status, false) MediaShown.save(status, false)
btnShowMedia.visibility = View.VISIBLE btnShowMedia.visibility = View.VISIBLE
} }
R.id.btnShowMedia -> status__showing?.let { status -> btnShowMedia -> status__showing?.let { status ->
MediaShown.save(status, true) MediaShown.save(status, true)
btnShowMedia.visibility = View.GONE btnShowMedia.visibility = View.GONE
} }
R.id.ivMedia1 -> clickMedia(0) ivMedia1 -> clickMedia(0)
R.id.ivMedia2 -> clickMedia(1) ivMedia2 -> clickMedia(1)
R.id.ivMedia3 -> clickMedia(2) ivMedia3 -> clickMedia(2)
R.id.ivMedia4 -> clickMedia(3) ivMedia4 -> clickMedia(3)
R.id.ivCardThumbnail -> status__showing?.card?.url?.let { url -> btnContentWarning -> status__showing?.let { status ->
if(url.isNotEmpty()) App1.openCustomTab(activity, url)
}
R.id.btnContentWarning -> status__showing?.let { status ->
val new_shown = llContents.visibility == View.GONE val new_shown = llContents.visibility == View.GONE
ContentWarning.save(status, new_shown) ContentWarning.save(status, new_shown)
list_adapter.notifyDataSetChanged() list_adapter.notifyDataSetChanged()
} }
R.id.ivThumbnail -> status_account?.let { who -> ivThumbnail -> status_account?.let { who ->
if(access_info.isPseudo) { if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show() DlgContextMenu(activity, column, who, null, notification).show()
} else { } else {
@ -735,7 +708,7 @@ internal class ItemViewHolder(
} }
} }
R.id.llBoosted -> boost_account?.let { who -> llBoosted -> boost_account?.let { who ->
if(access_info.isPseudo) { if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show() DlgContextMenu(activity, column, who, null, notification).show()
} else { } else {
@ -743,18 +716,18 @@ internal class ItemViewHolder(
} }
} }
R.id.llFollow -> follow_account?.let { who -> llFollow -> follow_account?.let { who ->
if(access_info.isPseudo) { if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show() DlgContextMenu(activity, column, who, null, notification).show()
} else { } else {
Action_User.profileLocal(activity, pos, access_info, who) Action_User.profileLocal(activity, pos, access_info, who)
} }
} }
R.id.btnFollow -> follow_account?.let { who -> btnFollow -> follow_account?.let { who ->
DlgContextMenu(activity, column, who, null, notification).show() DlgContextMenu(activity, column, who, null, notification).show()
} }
R.id.btnSearchTag -> when(item) { btnSearchTag -> when(item) {
is TootGap -> column.startGap(item) is TootGap -> column.startGap(item)
is TootDomainBlock -> { is TootDomainBlock -> {
@ -772,11 +745,11 @@ internal class ItemViewHolder(
} }
} }
R.id.btnListTL -> if(item is TootList) { btnListTL -> if(item is TootList) {
activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id) activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id)
} }
R.id.btnListMore -> if(item is TootList) { btnListMore -> if(item is TootList) {
ActionsDialog() ActionsDialog()
.addAction(activity.getString(R.string.list_timeline)) { .addAction(activity.getString(R.string.list_timeline)) {
activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id) activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id)
@ -794,6 +767,13 @@ internal class ItemViewHolder(
} }
.show(activity, item.title) .show(activity, item.title)
} }
else -> when(v.id) {
R.id.ivCardThumbnail -> status__showing?.card?.url?.let { url ->
if(url.isNotEmpty()) App1.openCustomTab(activity, url)
}
}
} }
} }
@ -801,41 +781,41 @@ internal class ItemViewHolder(
val notification = (item as? TootNotification) val notification = (item as? TootNotification)
when(v.id) { when(v) {
R.id.ivThumbnail -> { 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 return true
} }
R.id.llBoosted -> { 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 return true
} }
R.id.llFollow -> { 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 return true
} }
R.id.btnFollow -> { 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 return true
} }
R.id.btnSearchTag -> { btnSearchTag -> {
val item = this.item val item = this.item
when(item) { when(item) {
// is TootGap -> column.startGap(item) // is TootGap -> column.startGap(item)
// //
// is TootDomainBlock -> { // is TootDomainBlock -> {
// val domain = item.domain // val domain = item.domain
// AlertDialog.Builder(activity) // AlertDialog.Builder(activity)
// .setMessage(activity.getString(R.string.confirm_unblock_domain, domain)) // .setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
// .setNegativeButton(R.string.cancel, null) // .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() // .show()
// } // }
is String -> { is String -> {
// search_tag は#を含まない // search_tag は#を含まない
@ -843,10 +823,10 @@ internal class ItemViewHolder(
val host = access_info.host val host = access_info.host
val url = "https://$host/tags/$tagEncoded" val url = "https://$host/tags/$tagEncoded"
Action_HashTag.timelineOtherInstance( Action_HashTag.timelineOtherInstance(
activity =activity, activity = activity,
pos =activity.nextPosition(column), pos = activity.nextPosition(column),
url =url, url = url,
host =host, host = host,
tag_without_sharp = item tag_without_sharp = item
) )
} }
@ -918,7 +898,7 @@ internal class ItemViewHolder(
addLinkAndCaption(sb, activity.getString(R.string.card_header_provider), card.provider_url, card.provider_name) addLinkAndCaption(sb, activity.getString(R.string.card_header_provider), card.provider_url, card.provider_name)
val description = card.description val description = card.description
if( description != null && description.isNotEmpty() ) { if(description != null && description.isNotEmpty()) {
if(sb.isNotEmpty()) sb.append("<br>") if(sb.isNotEmpty()) sb.append("<br>")
sb.append(HTMLDecoder.encodeEntity(description)) sb.append(HTMLDecoder.encodeEntity(description))
} }
@ -928,7 +908,7 @@ internal class ItemViewHolder(
llExtra.addView(tv) llExtra.addView(tv)
val image = card.image val image = card.image
if( image != null && image.isNotEmpty()) { 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() lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val iv = MyNetworkImageView(activity) val iv = MyNetworkImageView(activity)
@ -1027,7 +1007,7 @@ internal class ItemViewHolder(
try { try {
form.put("item_index", Integer.toString(idx)) form.put("item_index", Integer.toString(idx))
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.e(ex,"json encode failed.") log.e(ex, "json encode failed.")
ex.printStackTrace() ex.printStackTrace()
} }
@ -1056,6 +1036,414 @@ internal class ItemViewHolder(
} }
}) })
} }
private fun inflate(ui : AnkoContext<Context>) = with(ui) {
verticalLayout {
// トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい
lparams(matchParent, wrapContent)
topPadding = dip(3)
bottomPadding = dip(3)
descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
llBoosted = linearLayout {
lparams(matchParent, wrapContent) {
bottomMargin = dip(6)
}
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
gravity = Gravity.CENTER_VERTICAL
ivBoosted = imageView {
scaleType = ImageView.ScaleType.FIT_END
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}.lparams(dip(48), dip(32)) {
endMargin = dip(4)
}
verticalLayout {
lparams(dip(0), wrapContent) {
weight = 1f
}
linearLayout {
lparams(matchParent, wrapContent)
tvBoostedAcct = textView {
ellipsize = TextUtils.TruncateAt.END
gravity = Gravity.END
maxLines = 1
textColor = Styler.getAttributeColor(context, R.attr.colorTimeSmall)
textSize = 12f // textSize の単位はSP
// tools:text ="who@hoge"
}.lparams(dip(0), wrapContent) {
weight = 1f
}
tvBoostedTime = textView {
startPadding = dip(2)
gravity = Gravity.END
textColor = Styler.getAttributeColor(context, R.attr.colorTimeSmall)
textSize = 12f // textSize の単位はSP
// tools:ignore="RtlSymmetry"
// tools:text="2017-04-16 09:37:14"
}.lparams(wrapContent, wrapContent)
}
tvBoosted = textView {
// tools:text = "~にブーストされました"
}.lparams(matchParent, wrapContent)
}
}
llFollow = linearLayout {
lparams(matchParent, wrapContent)
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
gravity = Gravity.CENTER_VERTICAL
ivFollow = myNetworkImageView {
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.FIT_END
}.lparams(dip(48), dip(40)) {
endMargin = dip(4)
}
verticalLayout {
lparams(dip(0), wrapContent) {
weight = 1f
}
tvFollowerName = textView {
// tools:text="Follower Name"
}.lparams(matchParent, wrapContent)
tvFollowerAcct = textView {
setPaddingStartEnd(dip(4), dip(4))
textColor = Styler.getAttributeColor(context, R.attr.colorTimeSmall)
textSize = 12f // SP
// tools:text="aaaaaaaaaaaaaaaa"
}.lparams(matchParent, wrapContent)
}
frameLayout {
lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
btnFollow = imageButton {
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"
}.lparams(matchParent, matchParent)
ivFollowedBy = imageView {
scaleType = ImageView.ScaleType.CENTER
// tools:src="?attr/ic_followed_by"
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}.lparams(matchParent, matchParent)
}
}
llStatus = verticalLayout {
lparams(matchParent, wrapContent)
linearLayout {
lparams(matchParent, wrapContent)
tvAcct = textView {
ellipsize = TextUtils.TruncateAt.END
gravity = Gravity.END
maxLines = 1
textColor = Styler.getAttributeColor(context, R.attr.colorTimeSmall)
textSize = 12f // SP
// tools:text="who@hoge"
}.lparams(dip(0), wrapContent) {
weight = 1f
}
tvTime = textView {
gravity = Gravity.END
startPadding = dip(2)
textColor = Styler.getAttributeColor(context, R.attr.colorTimeSmall)
textSize = 12f // SP
// tools:ignore="RtlSymmetry"
// tools:text="2017-04-16 09:37:14"
}.lparams(wrapContent, wrapContent)
}
linearLayout {
lparams(matchParent, wrapContent)
ivThumbnail = myNetworkImageView {
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)) {
topMargin = dip(4)
endMargin = dip(4)
}
verticalLayout {
lparams(dip(0), wrapContent) {
weight = 1f
}
tvName = textView {
// tools:text="Displayname"
}.lparams(matchParent, wrapContent)
llContentWarning = linearLayout {
lparams(matchParent, wrapContent) {
topMargin = dip(3)
isBaselineAligned = false
}
gravity = Gravity.CENTER_VERTICAL
btnContentWarning = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
minWidthCompat = dip(40)
padding = dip(4)
//tools:text="見る"
}.lparams(wrapContent, dip(40)) {
endMargin = dip(8)
}
verticalLayout {
lparams(dip(0), wrapContent) {
weight = 1f
}
tvMentions = myTextView {
}.lparams(matchParent, wrapContent)
tvContentWarning = myTextView {
}.lparams(matchParent, wrapContent) {
topMargin = dip(3)
}
}
}
llContents = verticalLayout {
lparams(matchParent, wrapContent)
tvContent = myTextView {
setLineSpacing(lineSpacingExtra, 1.1f)
// tools:text="Contents\nContents"
}.lparams(matchParent, wrapContent) {
topMargin = dip(3)
}
flMedia = frameLayout {
lparams(matchParent, dip(64)) {
topMargin = dip(3)
}
linearLayout {
lparams(matchParent, matchParent)
ivMedia1 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(0, matchParent) {
weight = 1f
}
ivMedia2 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(0, matchParent) {
startMargin = dip(8)
weight = 1f
}
ivMedia3 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(0, matchParent) {
startMargin = dip(8)
weight = 1f
}
ivMedia4 = myNetworkImageView {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_ddd)
contentDescription = context.getString(R.string.thumbnail)
scaleType = ImageView.ScaleType.CENTER_CROP
}.lparams(0, matchParent) {
startMargin = dip(8)
weight = 1f
}
btnHideMedia = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
contentDescription = "@string/hide"
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_close)
}.lparams(dip(32), matchParent) {
startMargin = dip(8)
}
}
btnShowMedia = textView {
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)
}.lparams(matchParent, matchParent)
}
llExtra = verticalLayout {
lparams(matchParent, wrapContent) {
topMargin = dip(0)
}
}
}
// button bar
llButtonBar = linearLayout {
lparams(wrapContent, dip(40)) {
topMargin = dip(3)
}
btnConversation = imageButton {
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)
}.lparams(wrapContent, matchParent)
btnReply = imageButton {
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)
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)
}
btnBoost = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
compoundDrawablePadding = dip(4)
minWidthCompat = dip(48)
setPaddingStartEnd(dip(4), dip(4))
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)
}
btnFavourite = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
compoundDrawablePadding = dip(4)
minWidthCompat = dip(48)
setPaddingStartEnd(dip(4), dip(4))
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)
}
llFollow2 = frameLayout {
lparams(dip(40), dip(40)) {
startMargin = dip(2)
}
btnFollow2 = imageButton {
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"
minimumWidth = dip(40)
}.lparams(matchParent, matchParent)
ivFollowedBy2 = imageView {
scaleType = ImageView.ScaleType.CENTER
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)
contentDescription = context.getString(R.string.more)
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_more)
minimumWidth = dip(40)
}.lparams(wrapContent, matchParent) {
startMargin = dip(2)
}
}
tvApplication = textView {
gravity = Gravity.END
}.lparams(matchParent, wrapContent)
}
}
}
llSearchTag = linearLayout {
lparams(matchParent, wrapContent)
btnSearchTag = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
allCaps = false
}.lparams(matchParent, wrapContent)
}
llList = linearLayout {
lparams(matchParent, wrapContent)
btnListTL = button {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
allCaps = false
}.lparams(0, wrapContent) {
weight = 1f
}
btnListMore = imageButton {
background = ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent)
imageResource = Styler.getAttributeResourceId(context, R.attr.btn_more)
contentDescription = context.getString(R.string.more)
}.lparams(dip(40), dip(40)) {
startMargin = dip(4)
}
}
}
}
} }

View File

@ -19,20 +19,23 @@ import jp.juggler.subwaytooter.util.LogCategory
internal class StatusButtons( internal class StatusButtons(
private val activity : ActMain, private val activity : ActMain,
private val column : Column, private val column : Column,
viewRoot : View, private val bSimpleList : Boolean,
private val bSimpleList : Boolean
private val btnConversation : ImageButton,
private val btnReply : ImageButton,
private val btnBoost : Button,
private val btnFavourite : Button,
private val llFollow2 : View,
private val btnFollow2 : ImageButton,
private val ivFollowedBy2 : ImageView,
private val btnMore : ImageButton
) : View.OnClickListener, View.OnLongClickListener { ) : View.OnClickListener, View.OnLongClickListener {
companion object { companion object {
val log = LogCategory("StatusButtons") val log = LogCategory("StatusButtons")
} }
private val btnBoost : Button
private val btnFavourite : Button
private val btnFollow2 : ImageButton
private val ivFollowedBy2 : ImageView
private val llFollow2 : View
private val access_info : SavedAccount private val access_info : SavedAccount
private var relation : UserRelation? = null private var relation : UserRelation? = null
private var status : TootStatus? = null private var status : TootStatus? = null
@ -43,12 +46,6 @@ internal class StatusButtons(
init { init {
this.access_info = column.access_info this.access_info = column.access_info
btnBoost = viewRoot.findViewById(R.id.btnBoost)
btnFavourite = viewRoot.findViewById(R.id.btnFavourite)
btnFollow2 = viewRoot.findViewById(R.id.btnFollow2)
ivFollowedBy2 = viewRoot.findViewById(R.id.ivFollowedBy2)
llFollow2 = viewRoot.findViewById(R.id.llFollow2)
val listener = this val listener = this
btnBoost.setOnClickListener(listener) btnBoost.setOnClickListener(listener)
@ -57,20 +54,12 @@ internal class StatusButtons(
btnFavourite.setOnLongClickListener(listener) btnFavourite.setOnLongClickListener(listener)
btnFollow2.setOnClickListener(listener) btnFollow2.setOnClickListener(listener)
btnFollow2.setOnLongClickListener(listener) btnFollow2.setOnLongClickListener(listener)
btnMore.setOnClickListener(listener)
btnConversation.setOnClickListener(listener)
btnConversation.setOnLongClickListener(listener)
btnReply.setOnClickListener(listener)
btnReply.setOnLongClickListener(listener)
with(viewRoot.findViewById<View>(R.id.btnMore)) {
setOnClickListener(listener)
}
with(viewRoot.findViewById<View>(R.id.btnConversation)) {
setOnClickListener(listener)
setOnLongClickListener(listener)
}
with(viewRoot.findViewById<View>(R.id.btnReply)) {
setOnClickListener(listener)
setOnLongClickListener(listener)
}
} }
fun bind(status : TootStatus, notification : TootNotification?) { fun bind(status : TootStatus, notification : TootNotification?) {
@ -81,6 +70,7 @@ internal class StatusButtons(
val color_accent = Styler.getAttributeColor(activity, R.attr.colorImageButtonAccent) 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 { when {
TootStatus.VISIBILITY_DIRECT == status.visibility -> setButton(btnBoost, false, color_accent, R.attr.ic_mail, "") 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, "") TootStatus.VISIBILITY_PRIVATE == status.visibility -> setButton(btnBoost, false, color_accent, R.attr.ic_lock, "")
@ -131,66 +121,70 @@ internal class StatusButtons(
val status = this.status ?: return val status = this.status ?: return
when(v.id) { when(v) {
R.id.btnConversation -> Action_Toot.conversation(activity, activity.nextPosition(column), access_info, status) btnConversation -> Action_Toot.conversation(activity, activity.nextPosition(column), access_info, status)
R.id.btnReply -> if( ! access_info.isPseudo) { btnReply -> if( ! access_info.isPseudo) {
Action_Toot.reply(activity, access_info, status) Action_Toot.reply(activity, access_info, status)
} else { } else {
Action_Toot.replyFromAnotherAccount(activity, access_info, status) Action_Toot.replyFromAnotherAccount(activity, access_info, status)
} }
R.id.btnBoost -> if(access_info.isPseudo) { btnBoost -> {
Action_Toot.boostFromAnotherAccount(activity, access_info, status) if(access_info.isPseudo) {
} else { Action_Toot.boostFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willRoost = ! status.reblogged // トグル動作
val willRoost = ! status.reblogged
// 簡略表示なら結果をトースト表示
val callback = when { // 簡略表示なら結果をトースト表示
! bSimpleList -> null val callback = when {
willRoost -> activity.boost_complete_callback ! bSimpleList -> null
else -> activity.unboost_complete_callback willRoost -> activity.boost_complete_callback
else -> activity.unboost_complete_callback
}
Action_Toot.boost(
activity,
access_info,
status,
NOT_CROSS_ACCOUNT,
willRoost,
false,
callback
)
} }
Action_Toot.boost(
activity,
access_info,
status,
NOT_CROSS_ACCOUNT,
willRoost,
false,
callback
)
} }
R.id.btnFavourite -> if(access_info.isPseudo) { btnFavourite -> {
Action_Toot.favouriteFromAnotherAccount(activity, access_info, status) if(access_info.isPseudo) {
} else { Action_Toot.favouriteFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willFavourite = ! status.favourited // トグル動作
val willFavourite = ! status.favourited
// 簡略表示なら結果をトースト表示
val callback = when { // 簡略表示なら結果をトースト表示
! bSimpleList -> null val callback = when {
status.favourited -> activity.unfavourite_complete_callback ! bSimpleList -> null
else -> activity.favourite_complete_callback status.favourited -> activity.unfavourite_complete_callback
else -> activity.favourite_complete_callback
}
Action_Toot.favourite(
activity,
access_info,
status,
NOT_CROSS_ACCOUNT,
willFavourite,
callback
)
} }
Action_Toot.favourite(
activity,
access_info,
status,
NOT_CROSS_ACCOUNT,
willFavourite,
callback
)
} }
R.id.btnFollow2 -> { btnFollow2 -> {
val account = status.account val account = status.account
val relation = this.relation ?: return val relation = this.relation ?: return
@ -235,7 +229,7 @@ internal class StatusButtons(
} }
} }
R.id.btnMore -> DlgContextMenu(activity, column, status.account, status, notification).show() btnMore -> DlgContextMenu(activity, column, status.account, status, notification).show()
} }
} }
@ -246,15 +240,22 @@ internal class StatusButtons(
val status = this.status ?: return true val status = this.status ?: return true
when(v.id) { when(v) {
R.id.btnConversation -> Action_Toot.conversationOtherInstance(activity, activity.nextPosition(column), status) btnConversation -> Action_Toot.conversationOtherInstance(
R.id.btnBoost -> Action_Toot.boostFromAnotherAccount(activity, access_info, status) activity, activity.nextPosition(column), status)
R.id.btnFavourite -> Action_Toot.favouriteFromAnotherAccount(activity, access_info, status)
R.id.btnReply -> Action_Toot.replyFromAnotherAccount(activity, access_info, status) btnBoost -> Action_Toot.boostFromAnotherAccount(
activity, access_info, status)
btnFavourite -> Action_Toot.favouriteFromAnotherAccount(
activity, access_info, status)
btnReply -> Action_Toot.replyFromAnotherAccount(
activity, access_info, status)
btnFollow2 -> Action_Follow.followFromAnotherAccount(
activity, activity.nextPosition(column), access_info, status.account)
R.id.btnFollow2 -> {
Action_Follow.followFromAnotherAccount(activity, activity.nextPosition(column), access_info, status.account)
}
} }
return true return true
} }

View File

@ -38,7 +38,19 @@ class StatusButtonsPopup(
init { init {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
this.viewRoot = activity.layoutInflater.inflate(R.layout.list_item_popup, null, false) this.viewRoot = activity.layoutInflater.inflate(R.layout.list_item_popup, null, false)
this.buttons_for_status = StatusButtons(activity, column, viewRoot, bSimpleList) this.buttons_for_status = StatusButtons(
activity,
column,
bSimpleList,
btnConversation =viewRoot.findViewById(R.id.btnConversation),
btnReply = viewRoot.findViewById(R.id.btnReply),
btnBoost = viewRoot.findViewById(R.id.btnBoost),
btnFavourite = viewRoot.findViewById(R.id.btnFavourite),
llFollow2 = viewRoot.findViewById(R.id.llFollow2),
btnFollow2 = viewRoot.findViewById(R.id.btnFollow2),
ivFollowedBy2 = viewRoot.findViewById(R.id.ivFollowedBy2),
btnMore = viewRoot.findViewById(R.id.btnMore)
)
} }
fun dismiss() { fun dismiss() {

View File

@ -63,7 +63,17 @@ class TootAttachment(src : JSONObject) : TootAttachmentLike {
if( remote_url?.isNotEmpty() == true ) remote_url else url if( remote_url?.isNotEmpty() == true ) remote_url else url
} }
} }
fun getLargeUrlList(pref : SharedPreferences) : ArrayList<String> {
val result = ArrayList<String>()
if( pref.getBoolean(Pref.KEY_PRIOR_LOCAL_URL, false) ){
if( url?.isNotEmpty() ==true) result.add(url)
if( remote_url?.isNotEmpty()==true) result.add( remote_url)
} else {
if( remote_url?.isNotEmpty()==true) result.add( remote_url)
if( url?.isNotEmpty() ==true) result.add(url)
}
return result
}
} }
// v1.3 から 添付ファイルの画像のピクセルサイズが取得できるようになった // v1.3 から 添付ファイルの画像のピクセルサイズが取得できるようになった

View File

@ -0,0 +1,17 @@
package jp.juggler.subwaytooter.util
import android.view.ViewManager
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.subwaytooter.view.MyTextView
import org.jetbrains.anko.custom.ankoView
// Anko Layout中にカスタムビューを指定する為に拡張関数を定義する
inline fun ViewManager.myNetworkImageView(init: MyNetworkImageView.() -> Unit): MyNetworkImageView {
return ankoView({ MyNetworkImageView(it) }, theme = 0, init = init)
}
inline fun ViewManager.myTextView(init: MyTextView.() -> Unit): MyTextView {
return ankoView({ MyTextView(it) }, theme = 0, init = init)
}

View File

@ -0,0 +1,49 @@
package jp.juggler.subwaytooter.util
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
// marginStart,marginEnd と leftMargin,topMargin の表記ゆれの対策
var ViewGroup.MarginLayoutParams.startMargin : Int
get() = marginStart
set(start) {
marginStart = start
}
// marginStart,marginEnd と leftMargin,topMargin の表記ゆれの対策
var ViewGroup.MarginLayoutParams.endMargin : Int
get() = marginEnd
set(end) {
marginEnd = end
}
// paddingStart,paddingEndにはsetterが提供されてない問題の対策
// 表記もtopPadding,bottomPaddingと揃えてある
var View.startPadding : Int
get() = paddingStart
set(start) {
setPaddingRelative(start, paddingTop, paddingEnd, paddingBottom)
}
// paddingStart,paddingEndにはsetterが提供されてない問題の対策
// 表記もtopPadding,bottomPaddingと揃えてある
var View.endPadding : Int
get() = paddingEnd
set(end) {
setPaddingRelative(paddingStart, paddingTop, end, paddingBottom)
}
// paddingStart,paddingEndにはsetterが提供されてない問題の対策
fun View.setPaddingStartEnd(start : Int, end : Int) {
setPaddingRelative(start, paddingTop, end, paddingBottom)
}
// XMLのandroid:minWidthと同じことをしたい場合、View#setMinimumWidthとTextView#setMinWidthの両方を呼び出す必要がある
// http://www.thekingsmuseum.info/entry/2015/12/01/233134
var TextView.minWidthCompat : Int
get() = minWidth
set(value) {
minimumWidth = value
minWidth = value
}

View File

@ -5,10 +5,11 @@ import java.util.ArrayList
import java.util.NoSuchElementException import java.util.NoSuchElementException
import java.util.RandomAccess import java.util.RandomAccess
class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : AbstractList<E>(), MutableIterable<E>, RandomAccess { class BucketList<E> constructor(
val bucketCapacity : Int = 1024
) : AbstractList<E>(), MutableIterable<E>, RandomAccess {
companion object { companion object {
private val pos_internal = object : ThreadLocal<BucketPos>() { private val pos_internal = object : ThreadLocal<BucketPos>() {
override fun initialValue() : BucketPos { override fun initialValue() : BucketPos {
return BucketPos() return BucketPos()
@ -22,10 +23,11 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
return 0 == size return 0 == size
} }
private class Bucket<E> internal constructor(capacity : Int) : ArrayList<E>(capacity) { private class Bucket<E>(
internal var total_start : Int = 0 capacity : Int,
internal var total_end : Int = 0 var total_start : Int = 0,
} var total_end : Int = 0
) : ArrayList<E>(capacity)
private val groups = ArrayList<Bucket<E>>() private val groups = ArrayList<Bucket<E>>()
@ -44,8 +46,11 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
size = n size = n
} }
private class BucketPos(var group_index : Int = 0, var bucket_index : Int = 0) { internal class BucketPos(
internal fun update(group_index : Int, bucket_index : Int) : BucketPos { var group_index : Int = 0,
var bucket_index : Int = 0
) {
internal fun set(group_index : Int, bucket_index : Int) : BucketPos {
this.group_index = group_index this.group_index = group_index
this.bucket_index = bucket_index this.bucket_index = bucket_index
return this return this
@ -53,15 +58,18 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
} }
// allocalted を指定しない場合は BucketPosを生成します // allocalted を指定しない場合は BucketPosを生成します
private fun findPos(total_index : Int, allocated : BucketPos? = pos_internal.get()) : BucketPos { private fun findPos(
total_index : Int,
result : BucketPos = pos_internal.get()
) : BucketPos {
if(total_index < 0 || total_index >= size) { if(total_index < 0 || total_index >= size) {
throw IndexOutOfBoundsException("findPos: bad index=$total_index, size=$size") throw IndexOutOfBoundsException("findPos: bad index=$total_index, size=$size")
} }
// binary search // binary search
val groups_size = groups.size
var gs = 0 var gs = 0
var ge = groups_size var ge = groups.size
while(true) { while(true) {
val gi = (gs + ge) shr 1 val gi = (gs + ge) shr 1
val group = groups[gi] val group = groups[gi]
@ -69,8 +77,7 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
total_index < group.total_start -> ge = gi total_index < group.total_start -> ge = gi
total_index >= group.total_end -> gs = gi + 1 total_index >= group.total_end -> gs = gi + 1
else -> { else -> {
return (allocated ?: BucketPos()) return result.set(gi, total_index - group.total_start)
.update(gi, total_index - group.total_start)
} }
} }
} }
@ -87,9 +94,9 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
if(c_size == 0) return false if(c_size == 0) return false
// 最後のバケツに収まるなら、最後のバケツの中に追加する // 最後のバケツに収まるなら、最後のバケツの中に追加する
if(groups.size > 0) { if( groups.isNotEmpty() ) {
val bucket = groups[groups.size - 1] val bucket = groups[groups.size -1]
if(bucket.size + c_size <= bucketCapacity) { if( bucket.size + c_size <= bucketCapacity) {
bucket.addAll(elements) bucket.addAll(elements)
bucket.total_end += c_size bucket.total_end += c_size
size += c_size size += c_size
@ -111,7 +118,7 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
// indexが終端なら、終端に追加する // indexが終端なら、終端に追加する
// バケツがカラの場合もここ // バケツがカラの場合もここ
if(index == size) { if(index >= size) {
return addAll(elements) return addAll(elements)
} }
@ -193,5 +200,4 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
override fun iterator() : MutableIterator<E> { override fun iterator() : MutableIterator<E> {
return MyIterator() return MyIterator()
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -189,12 +189,29 @@
/> />
</FrameLayout> </FrameLayout>
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:baselineAligned="false"
android:text="@string/status" android:orientation="horizontal"
/> >
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/status"
android:layout_gravity="bottom"
/>
<ImageButton
android:id="@+id/btnEmojiPicker"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/open_picker_emoji"
android:src="?attr/ic_face"
/>
</LinearLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -375,7 +375,7 @@
<string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string> <string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string>
<string name="text_to_speech_shutdown">TextToSpeech shutdown…</string> <string name="text_to_speech_shutdown">TextToSpeech shutdown…</string>
<string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string> <string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string>
<string name="client_name">Client name (access token update required)</string> <string name="client_name">Client name (access token set required)</string>
<string name="toot_search">Toot search</string> <string name="toot_search">Toot search</string>
<string name="mastodon_search_portal">MSP(JP)</string> <string name="mastodon_search_portal">MSP(JP)</string>
<string name="search_desc_mastodon_api">Account/Hashtag search using Mastodon API.</string> <string name="search_desc_mastodon_api">Account/Hashtag search using Mastodon API.</string>
@ -519,7 +519,7 @@
<string name="card_header_card">Card</string> <string name="card_header_card">Card</string>
<string name="card_header_author">Author</string> <string name="card_header_author">Author</string>
<string name="card_header_provider">Provider</string> <string name="card_header_provider">Provider</string>
<string name="allow_non_space_before_emoji_code">Allow non-space character before emoji shortcode (Affect to display and post. to update display, please restart app and reload column. The emojis converted at posting can\'t be restored to shortcode.)</string> <string name="allow_non_space_before_emoji_code">Allow non-space character before emoji shortcode (Affect to display and post. to set display, please restart app and reload column. The emojis converted at posting can\'t be restored to shortcode.)</string>
<string name="emoji">Emoji</string> <string name="emoji">Emoji</string>
<string name="not_blocked">Not blocked.</string> <string name="not_blocked">Not blocked.</string>
<string name="not_muted">Not muted.</string> <string name="not_muted">Not muted.</string>
@ -594,7 +594,7 @@
<string name="new_item">New item…</string> <string name="new_item">New item…</string>
<string name="word_empty">Please input keyword.</string> <string name="word_empty">Please input keyword.</string>
<string name="already_exist">already exist.</string> <string name="already_exist">already exist.</string>
<string name="highlight_desc">Swipe to delete. You may need reload column to check update.</string> <string name="highlight_desc">Swipe to delete. You may need reload column to check set.</string>
<string name="check_sound">Check sound</string> <string name="check_sound">Check sound</string>
<string name="keyword">Keyword</string> <string name="keyword">Keyword</string>
<string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string> <string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string>

View File

@ -134,5 +134,6 @@
<attr name="ic_left" format="reference" /> <attr name="ic_left" format="reference" />
<attr name="ic_right" format="reference" /> <attr name="ic_right" format="reference" />
<attr name="ic_volume_up" format="reference" /> <attr name="ic_volume_up" format="reference" />
<attr name="ic_face" format="reference" />
</resources> </resources>

View File

@ -24,7 +24,7 @@
<string name="home">Home</string> <string name="home">Home</string>
<string name="local_timeline">Local timeline</string> <string name="local_timeline">Local timeline</string>
<string name="federate_timeline">Federated timeline</string> <string name="federate_timeline">Federated timeline</string>
<string name="login_required">Please update your access token from the account setting.</string> <string name="login_required">Please set your access token from the account setting.</string>
<string name="cancelled">Cancelled</string> <string name="cancelled">Cancelled</string>
<string name="account_pick">Select an account</string> <string name="account_pick">Select an account</string>
<string name="account_confirmed">Account confirmed.</string> <string name="account_confirmed">Account confirmed.</string>
@ -367,7 +367,7 @@
<string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string> <string name="text_to_speech_initialize_failed">TextToSpeech initializing failed. status=%1$s</string>
<string name="text_to_speech_shutdown">TextToSpeech shutdown…</string> <string name="text_to_speech_shutdown">TextToSpeech shutdown…</string>
<string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string> <string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string>
<string name="client_name">Client name (access token update required)</string> <string name="client_name">Client name (access token set required)</string>
<string name="toot_search">Toot search</string> <string name="toot_search">Toot search</string>
<string name="toot_search_msp">Toot search(MSP)</string> <string name="toot_search_msp">Toot search(MSP)</string>
<string name="toot_search_ts">Toot search(ts)</string> <string name="toot_search_ts">Toot search(ts)</string>
@ -514,7 +514,7 @@
<string name="card_header_card">Card</string> <string name="card_header_card">Card</string>
<string name="card_header_author">Author</string> <string name="card_header_author">Author</string>
<string name="card_header_provider">Provider</string> <string name="card_header_provider">Provider</string>
<string name="allow_non_space_before_emoji_code">Allow non-space character before emoji shortcode (Affect to display and post. to update display, please restart app and reload column. The emojis converted at posting can\'t be restored to shortcode.)</string> <string name="allow_non_space_before_emoji_code">Allow non-space character before emoji shortcode (Affect to display and post. to set display, please restart app and reload column. The emojis converted at posting can\'t be restored to shortcode.)</string>
<string name="emoji">Emoji</string> <string name="emoji">Emoji</string>
<string name="not_blocked">Not blocked.</string> <string name="not_blocked">Not blocked.</string>
<string name="not_muted">Not muted.</string> <string name="not_muted">Not muted.</string>
@ -583,7 +583,7 @@
<string name="new_item">New item…</string> <string name="new_item">New item…</string>
<string name="word_empty">Please input keyword.</string> <string name="word_empty">Please input keyword.</string>
<string name="already_exist">already exist.</string> <string name="already_exist">already exist.</string>
<string name="highlight_desc">Swipe to delete. You may need reload column to check update.</string> <string name="highlight_desc">Swipe to delete. You may need reload column to check set.</string>
<string name="check_sound">Check sound</string> <string name="check_sound">Check sound</string>
<string name="keyword">Keyword</string> <string name="keyword">Keyword</string>
<string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string> <string name="domain_block_from_pseudo">Can\'t use domain block from pseudo account.</string>

View File

@ -107,6 +107,7 @@
<item name="ic_left">@drawable/ic_left</item> <item name="ic_left">@drawable/ic_left</item>
<item name="ic_right">@drawable/ic_right</item> <item name="ic_right">@drawable/ic_right</item>
<item name="ic_volume_up">@drawable/ic_volume_up</item> <item name="ic_volume_up">@drawable/ic_volume_up</item>
<item name="ic_face">@drawable/ic_face</item>
</style> </style>
@ -217,7 +218,7 @@
<item name="ic_left">@drawable/ic_left_dark</item> <item name="ic_left">@drawable/ic_left_dark</item>
<item name="ic_right">@drawable/ic_right_dark</item> <item name="ic_right">@drawable/ic_right_dark</item>
<item name="ic_volume_up">@drawable/ic_volume_up_dark</item> <item name="ic_volume_up">@drawable/ic_volume_up_dark</item>
<item name="ic_face">@drawable/ic_face_dark</item>
</style> </style>
<style name="AppTheme.Dark.NoActionBar" parent="AppTheme.Dark"> <style name="AppTheme.Dark.NoActionBar" parent="AppTheme.Dark">

View File

@ -3,6 +3,7 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.10' ext.kotlin_version = '1.2.10'
ext.anko_version='0.10.4'
repositories { repositories {
jcenter() jcenter()