diff --git a/app/build.gradle b/app/build.gradle index 28affd2e..942bcbbf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/androidTest/java/jp/juggler/subwaytooter/util/TestBucketList.kt b/app/src/androidTest/java/jp/juggler/subwaytooter/util/TestBucketList.kt new file mode 100644 index 00000000..f83fafc4 --- /dev/null +++ b/app/src/androidTest/java/jp/juggler/subwaytooter/util/TestBucketList.kt @@ -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(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]) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt index e5477056..32147351 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMediaViewer.kt @@ -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?) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index b21396f7..700dcc69 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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(R.id.btnPlugin).setOnClickListener(this) - + findViewById(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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.kt index 92ba4e2a..511955e4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index f1bee487..d9e90cec 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -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() 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(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("
") 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) = 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) + } + } + } + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt index fc699ee4..4c1db05d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt @@ -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(R.id.btnMore)) { - setOnClickListener(listener) - } - - with(viewRoot.findViewById(R.id.btnConversation)) { - setOnClickListener(listener) - setOnLongClickListener(listener) - } - - with(viewRoot.findViewById(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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.kt b/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.kt index fc34ecd6..cecaea18 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.kt @@ -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() { diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt index 7cbffd57..385a7551 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt @@ -63,7 +63,17 @@ class TootAttachment(src : JSONObject) : TootAttachmentLike { if( remote_url?.isNotEmpty() == true ) remote_url else url } } - + fun getLargeUrlList(pref : SharedPreferences) : ArrayList { + val result = ArrayList() + 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 から 添付ファイルの画像のピクセルサイズが取得できるようになった diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/AnkoCustomView.kt b/app/src/main/java/jp/juggler/subwaytooter/util/AnkoCustomView.kt new file mode 100644 index 00000000..1743e435 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/util/AnkoCustomView.kt @@ -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) +} + diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/AnkoHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/AnkoHelper.kt new file mode 100644 index 00000000..1c6c5393 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/util/AnkoHelper.kt @@ -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 + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt b/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt index da506a11..8e600d59 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt @@ -5,10 +5,11 @@ import java.util.ArrayList import java.util.NoSuchElementException import java.util.RandomAccess -class BucketList constructor(private val bucketCapacity : Int = 1024) : AbstractList(), MutableIterable, RandomAccess { +class BucketList constructor( + val bucketCapacity : Int = 1024 +) : AbstractList(), MutableIterable, RandomAccess { companion object { - private val pos_internal = object : ThreadLocal() { override fun initialValue() : BucketPos { return BucketPos() @@ -22,10 +23,11 @@ class BucketList constructor(private val bucketCapacity : Int = 1024) : Abstr return 0 == size } - private class Bucket internal constructor(capacity : Int) : ArrayList(capacity) { - internal var total_start : Int = 0 - internal var total_end : Int = 0 - } + private class Bucket( + capacity : Int, + var total_start : Int = 0, + var total_end : Int = 0 + ) : ArrayList(capacity) private val groups = ArrayList>() @@ -44,8 +46,11 @@ class BucketList 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 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 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 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 constructor(private val bucketCapacity : Int = 1024) : Abstr // indexが終端なら、終端に追加する // バケツがカラの場合もここ - if(index == size) { + if(index >= size) { return addAll(elements) } @@ -193,5 +200,4 @@ class BucketList constructor(private val bucketCapacity : Int = 1024) : Abstr override fun iterator() : MutableIterator { return MyIterator() } - } diff --git a/app/src/main/res/drawable-hdpi/ic_face.png b/app/src/main/res/drawable-hdpi/ic_face.png new file mode 100644 index 00000000..3f02c868 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_face.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_face_dark.png b/app/src/main/res/drawable-hdpi/ic_face_dark.png new file mode 100644 index 00000000..54826eac Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_face_dark.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_face.png b/app/src/main/res/drawable-mdpi/ic_face.png new file mode 100644 index 00000000..f7f04bd6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_face.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_face_dark.png b/app/src/main/res/drawable-mdpi/ic_face_dark.png new file mode 100644 index 00000000..30b99d69 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_face_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_face.png b/app/src/main/res/drawable-xhdpi/ic_face.png new file mode 100644 index 00000000..e75dd83a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_face.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_face_dark.png b/app/src/main/res/drawable-xhdpi/ic_face_dark.png new file mode 100644 index 00000000..6074db03 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_face_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_face.png b/app/src/main/res/drawable-xxhdpi/ic_face.png new file mode 100644 index 00000000..e3f6bd82 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_face.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_face_dark.png b/app/src/main/res/drawable-xxhdpi/ic_face_dark.png new file mode 100644 index 00000000..412e5c52 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_face_dark.png differ diff --git a/app/src/main/res/layout/act_post.xml b/app/src/main/res/layout/act_post.xml index 4b4171d3..0a826e44 100644 --- a/app/src/main/res/layout/act_post.xml +++ b/app/src/main/res/layout/act_post.xml @@ -189,12 +189,29 @@ /> - + android:baselineAligned="false" + android:orientation="horizontal" + > + + + TextToSpeech initializing failed. status=%1$s TextToSpeech shutdown… Show buttons bar at the top of posting screen - Client name (access token update required) + Client name (access token set required) Toot search MSP(JP) Account/Hashtag search using Mastodon API. @@ -519,7 +519,7 @@ Card Author Provider - 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.) + 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.) Emoji Not blocked. Not muted. @@ -594,7 +594,7 @@ New item… Please input keyword. already exist. - Swipe to delete. You may need reload column to check update. + Swipe to delete. You may need reload column to check set. Check sound Keyword Can\'t use domain block from pseudo account. diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index ca6dff42..5fe4df6b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -134,5 +134,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2575450e..6ff4e19c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,7 +24,7 @@ Home Local timeline Federated timeline - Please update your access token from the account setting. + Please set your access token from the account setting. Cancelled Select an account Account confirmed. @@ -367,7 +367,7 @@ TextToSpeech initializing failed. status=%1$s TextToSpeech shutdown… Show buttons bar at the top of posting screen - Client name (access token update required) + Client name (access token set required) Toot search Toot search(MSP) Toot search(ts) @@ -514,7 +514,7 @@ Card Author Provider - 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.) + 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.) Emoji Not blocked. Not muted. @@ -583,7 +583,7 @@ New item… Please input keyword. already exist. - Swipe to delete. You may need reload column to check update. + Swipe to delete. You may need reload column to check set. Check sound Keyword Can\'t use domain block from pseudo account. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3cc5d662..5566a7b6 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -107,6 +107,7 @@ @drawable/ic_left @drawable/ic_right @drawable/ic_volume_up + @drawable/ic_face @@ -217,7 +218,7 @@ @drawable/ic_left_dark @drawable/ic_right_dark @drawable/ic_volume_up_dark - + @drawable/ic_face_dark