内蔵メディアビューアはリモート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"
minSdkVersion 21
targetSdkVersion 27
versionCode 201
versionName "2.0.1"
versionCode 202
versionName "2.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// 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'
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 {

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

View File

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

View File

@ -60,8 +60,8 @@ internal class ItemListAdapter(private val activity : ActMain, private val colum
val holder : ItemViewHolder
val view : View
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, view, bSimpleList)
holder = ItemViewHolder(activity, column, this, bSimpleList)
view = holder.viewRoot
view.tag = holder
} else {
view = viewOld

View File

@ -3,17 +3,17 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.app.AlertDialog
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.view.ViewGroup
import android.widget.*
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.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.span.EmojiImageSpan
import jp.juggler.subwaytooter.util.HTMLDecoder
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.*
import okhttp3.Request
import okhttp3.RequestBody
import org.jetbrains.anko.*
import org.json.JSONObject
internal class ItemViewHolder(
val activity : ActMain,
val column : Column,
private val list_adapter : ItemListAdapter,
view : View,
private val bSimpleList : Boolean
) : View.OnClickListener, View.OnLongClickListener {
companion object {
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 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 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 status__showing : TootStatus? = null
@ -126,22 +135,17 @@ internal class ItemViewHolder(
private val extra_invalidator_list = ArrayList<NetworkEmojiInvalidator>()
init {
this.viewRoot = inflate(activity.UI {})
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) {
Utils.scanView(view) { v ->
Utils.scanView(this.viewRoot) { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
} else if(v is TextView) {
val typeface = when(v.getId()) {
R.id.tvName,
R.id.tvFollowerName,
R.id.tvBoosted -> activity.timeline_font_bold ?: activity.timeline_font
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
@ -156,49 +160,27 @@ internal class ItemViewHolder(
tvBoosted.typeface = Typeface.DEFAULT_BOLD
}
this.llBoosted = view.findViewById(R.id.llBoosted)
this.ivBoosted = view.findViewById(R.id.ivBoosted)
this.tvBoostedTime = view.findViewById(R.id.tvBoostedTime)
this.tvBoostedAcct = view.findViewById(R.id.tvBoostedAcct)
this.llFollow = view.findViewById(R.id.llFollow)
this.ivFollow = view.findViewById(R.id.ivFollow)
this.tvFollowerAcct = view.findViewById(R.id.tvFollowerAcct)
this.btnFollow = view.findViewById(R.id.btnFollow)
this.ivFollowedBy = view.findViewById(R.id.ivFollowedBy)
this.llStatus = view.findViewById(R.id.llStatus)
this.ivThumbnail = view.findViewById(R.id.ivThumbnail)
this.tvTime = view.findViewById(R.id.tvTime)
this.tvAcct = view.findViewById(R.id.tvAcct)
this.llContentWarning = view.findViewById(R.id.llContentWarning)
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)
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)
@ -230,10 +212,7 @@ internal class ItemViewHolder(
tvMentions.movementMethod = MyLinkMovementMethod
tvContentWarning.movementMethod = MyLinkMovementMethod
val v : View
//
v = view.findViewById(R.id.btnHideMedia)
v.setOnClickListener(this)
btnHideMedia.setOnClickListener(this)
val lp = flMedia.layoutParams
lp.height = activity.app_state.media_thumb_height
@ -248,9 +227,7 @@ internal class ItemViewHolder(
tvContentWarning.textSize = activity.timeline_font_size_sp
tvContent.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
}
@ -288,9 +265,11 @@ internal class ItemViewHolder(
llList.visibility = View.GONE
llExtra.removeAllViews()
if(item == null) return
var c: Int
var c : Int
c = if(column.content_color != 0) column.content_color else content_color_default
tvBoosted.setTextColor(c)
@ -300,15 +279,15 @@ internal class ItemViewHolder(
tvContentWarning.setTextColor(c)
tvContent.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)
this.acct_color = c
tvBoostedTime.setTextColor(c)
tvTime.setTextColor(c)
// tvBoostedAcct.setTextColor( c );
// tvFollowerAcct.setTextColor( c );
// tvAcct.setTextColor( c );
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)
// tvBoostedAcct.setTextColor( c );
// tvFollowerAcct.setTextColor( c );
// tvAcct.setTextColor( c );
this.item = item
when(item) {
@ -563,18 +542,16 @@ internal class ItemViewHolder(
buttons_for_status?.bind(status, (item as? TootNotification))
if(tvApplication != null) {
val application = status.application
when(column.column_type) {
Column.TYPE_CONVERSATION -> if(application == null) {
tvApplication.visibility = View.GONE
} else {
tvApplication.visibility = View.VISIBLE
tvApplication.text = activity.getString(R.string.application_is, application.name ?: "")
}
else -> tvApplication.visibility = View.GONE
val application = status.application
when(column.column_type) {
Column.TYPE_CONVERSATION -> if(application == null) {
tvApplication.visibility = View.GONE
} else {
tvApplication.visibility = View.VISIBLE
tvApplication.text = activity.getString(R.string.application_is, application.name ?: "")
}
else -> tvApplication.visibility = View.GONE
}
}
@ -597,7 +574,7 @@ internal class ItemViewHolder(
val start = sb.length
sb.append(status.visibility)
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)
}
@ -700,34 +677,30 @@ internal class ItemViewHolder(
val pos = activity.nextPosition(column)
val item = this.item
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)
btnShowMedia.visibility = View.VISIBLE
}
R.id.btnShowMedia -> status__showing?.let { status ->
btnShowMedia -> status__showing?.let { status ->
MediaShown.save(status, true)
btnShowMedia.visibility = View.GONE
}
R.id.ivMedia1 -> clickMedia(0)
R.id.ivMedia2 -> clickMedia(1)
R.id.ivMedia3 -> clickMedia(2)
R.id.ivMedia4 -> clickMedia(3)
ivMedia1 -> clickMedia(0)
ivMedia2 -> clickMedia(1)
ivMedia3 -> clickMedia(2)
ivMedia4 -> clickMedia(3)
R.id.ivCardThumbnail -> status__showing?.card?.url?.let { url ->
if(url.isNotEmpty()) App1.openCustomTab(activity, url)
}
R.id.btnContentWarning -> status__showing?.let { status ->
btnContentWarning -> status__showing?.let { status ->
val new_shown = llContents.visibility == View.GONE
ContentWarning.save(status, new_shown)
list_adapter.notifyDataSetChanged()
}
R.id.ivThumbnail -> status_account?.let { who ->
ivThumbnail -> status_account?.let { who ->
if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show()
} else {
@ -735,7 +708,7 @@ internal class ItemViewHolder(
}
}
R.id.llBoosted -> boost_account?.let { who ->
llBoosted -> boost_account?.let { who ->
if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show()
} else {
@ -743,18 +716,18 @@ internal class ItemViewHolder(
}
}
R.id.llFollow -> follow_account?.let { who ->
llFollow -> follow_account?.let { who ->
if(access_info.isPseudo) {
DlgContextMenu(activity, column, who, null, notification).show()
} else {
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()
}
R.id.btnSearchTag -> when(item) {
btnSearchTag -> when(item) {
is TootGap -> column.startGap(item)
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)
}
R.id.btnListMore -> if(item is TootList) {
btnListMore -> if(item is TootList) {
ActionsDialog()
.addAction(activity.getString(R.string.list_timeline)) {
activity.addColumn(pos, access_info, Column.TYPE_LIST_TL, item.id)
@ -794,6 +767,13 @@ internal class ItemViewHolder(
}
.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)
when(v.id) {
when(v) {
R.id.ivThumbnail -> {
ivThumbnail -> {
status_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
return true
}
R.id.llBoosted -> {
llBoosted -> {
boost_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
return true
}
R.id.llFollow -> {
llFollow -> {
follow_account?.let { who -> DlgContextMenu(activity, column, who, null, notification).show() }
return true
}
R.id.btnFollow -> {
btnFollow -> {
follow_account?.let { who -> Action_Follow.followFromAnotherAccount(activity, activity.nextPosition(column), access_info, who) }
return true
}
R.id.btnSearchTag -> {
btnSearchTag -> {
val item = this.item
when(item) {
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// 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) }
// .show()
// }
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// 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) }
// .show()
// }
is String -> {
// search_tag は#を含まない
@ -843,10 +823,10 @@ internal class ItemViewHolder(
val host = access_info.host
val url = "https://$host/tags/$tagEncoded"
Action_HashTag.timelineOtherInstance(
activity =activity,
pos =activity.nextPosition(column),
url =url,
host =host,
activity = activity,
pos = activity.nextPosition(column),
url = url,
host = host,
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)
val description = card.description
if( description != null && description.isNotEmpty() ) {
if(description != null && description.isNotEmpty()) {
if(sb.isNotEmpty()) sb.append("<br>")
sb.append(HTMLDecoder.encodeEntity(description))
}
@ -928,7 +908,7 @@ internal class ItemViewHolder(
llExtra.addView(tv)
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.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val iv = MyNetworkImageView(activity)
@ -1027,7 +1007,7 @@ internal class ItemViewHolder(
try {
form.put("item_index", Integer.toString(idx))
} catch(ex : Throwable) {
log.e(ex,"json encode failed.")
log.e(ex, "json encode failed.")
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(
private val activity : ActMain,
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 {
companion object {
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 var relation : UserRelation? = null
private var status : TootStatus? = null
@ -43,12 +46,6 @@ internal class StatusButtons(
init {
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
btnBoost.setOnClickListener(listener)
@ -57,20 +54,12 @@ internal class StatusButtons(
btnFavourite.setOnLongClickListener(listener)
btnFollow2.setOnClickListener(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?) {
@ -81,6 +70,7 @@ internal class StatusButtons(
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
// ブーストボタン
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, "")
@ -131,66 +121,70 @@ internal class StatusButtons(
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)
} else {
Action_Toot.replyFromAnotherAccount(activity, access_info, status)
}
R.id.btnBoost -> if(access_info.isPseudo) {
Action_Toot.boostFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willRoost = ! status.reblogged
// 簡略表示なら結果をトースト表示
val callback = when {
! bSimpleList -> null
willRoost -> activity.boost_complete_callback
else -> activity.unboost_complete_callback
btnBoost -> {
if(access_info.isPseudo) {
Action_Toot.boostFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willRoost = ! status.reblogged
// 簡略表示なら結果をトースト表示
val callback = when {
! bSimpleList -> null
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) {
Action_Toot.favouriteFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willFavourite = ! status.favourited
// 簡略表示なら結果をトースト表示
val callback = when {
! bSimpleList -> null
status.favourited -> activity.unfavourite_complete_callback
else -> activity.favourite_complete_callback
btnFavourite -> {
if(access_info.isPseudo) {
Action_Toot.favouriteFromAnotherAccount(activity, access_info, status)
} else {
// トグル動作
val willFavourite = ! status.favourited
// 簡略表示なら結果をトースト表示
val callback = when {
! bSimpleList -> null
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 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
when(v.id) {
R.id.btnConversation -> Action_Toot.conversationOtherInstance(activity, activity.nextPosition(column), status)
R.id.btnBoost -> Action_Toot.boostFromAnotherAccount(activity, access_info, status)
R.id.btnFavourite -> Action_Toot.favouriteFromAnotherAccount(activity, access_info, status)
R.id.btnReply -> Action_Toot.replyFromAnotherAccount(activity, access_info, status)
when(v) {
btnConversation -> Action_Toot.conversationOtherInstance(
activity, activity.nextPosition(column), 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
}

View File

@ -38,7 +38,19 @@ class StatusButtonsPopup(
init {
@SuppressLint("InflateParams")
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() {

View File

@ -63,7 +63,17 @@ class TootAttachment(src : JSONObject) : TootAttachmentLike {
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 から 添付ファイルの画像のピクセルサイズが取得できるようになった

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.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 {
private val pos_internal = object : ThreadLocal<BucketPos>() {
override fun initialValue() : BucketPos {
return BucketPos()
@ -22,10 +23,11 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
return 0 == size
}
private class Bucket<E> internal constructor(capacity : Int) : ArrayList<E>(capacity) {
internal var total_start : Int = 0
internal var total_end : Int = 0
}
private class Bucket<E>(
capacity : Int,
var total_start : Int = 0,
var total_end : Int = 0
) : ArrayList<E>(capacity)
private val groups = ArrayList<Bucket<E>>()
@ -44,8 +46,11 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
size = n
}
private class BucketPos(var group_index : Int = 0, var bucket_index : Int = 0) {
internal fun update(group_index : Int, bucket_index : Int) : BucketPos {
internal class 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.bucket_index = bucket_index
return this
@ -53,15 +58,18 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
}
// 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) {
throw IndexOutOfBoundsException("findPos: bad index=$total_index, size=$size")
}
// binary search
val groups_size = groups.size
var gs = 0
var ge = groups_size
var ge = groups.size
while(true) {
val gi = (gs + ge) shr 1
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_end -> gs = gi + 1
else -> {
return (allocated ?: BucketPos())
.update(gi, total_index - group.total_start)
return result.set(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(groups.size > 0) {
val bucket = groups[groups.size - 1]
if(bucket.size + c_size <= bucketCapacity) {
if( groups.isNotEmpty() ) {
val bucket = groups[groups.size -1]
if( bucket.size + c_size <= bucketCapacity) {
bucket.addAll(elements)
bucket.total_end += c_size
size += c_size
@ -111,7 +118,7 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
// indexが終端なら、終端に追加する
// バケツがカラの場合もここ
if(index == size) {
if(index >= size) {
return addAll(elements)
}
@ -193,5 +200,4 @@ class BucketList<E> constructor(private val bucketCapacity : Int = 1024) : Abstr
override fun iterator() : MutableIterator<E> {
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>
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/status"
/>
android:baselineAligned="false"
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
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_shutdown">TextToSpeech shutdown…</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="mastodon_search_portal">MSP(JP)</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_author">Author</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="not_blocked">Not blocked.</string>
<string name="not_muted">Not muted.</string>
@ -594,7 +594,7 @@
<string name="new_item">New item…</string>
<string name="word_empty">Please input keyword.</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="keyword">Keyword</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_right" format="reference" />
<attr name="ic_volume_up" format="reference" />
<attr name="ic_face" format="reference" />
</resources>

View File

@ -24,7 +24,7 @@
<string name="home">Home</string>
<string name="local_timeline">Local 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="account_pick">Select an account</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_shutdown">TextToSpeech shutdown…</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_msp">Toot search(MSP)</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_author">Author</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="not_blocked">Not blocked.</string>
<string name="not_muted">Not muted.</string>
@ -583,7 +583,7 @@
<string name="new_item">New item…</string>
<string name="word_empty">Please input keyword.</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="keyword">Keyword</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_right">@drawable/ic_right</item>
<item name="ic_volume_up">@drawable/ic_volume_up</item>
<item name="ic_face">@drawable/ic_face</item>
</style>
@ -217,7 +218,7 @@
<item name="ic_left">@drawable/ic_left_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_face">@drawable/ic_face_dark</item>
</style>
<style name="AppTheme.Dark.NoActionBar" parent="AppTheme.Dark">

View File

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