diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 965796e7..917371ed 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -200,7 +200,7 @@ class ActMain : AppCompatActivity() /////////////////////////////////////////////////////////////////////////////////////////////// - private val link_click_listener : MyClickableSpanClickCallback = { viewClicked, span -> + internal val link_click_listener : MyClickableSpanClickCallback = { viewClicked, span -> var view = viewClicked var column : Column? = null @@ -353,8 +353,8 @@ class ActMain : AppCompatActivity() // (カラム一覧画面のデフォルト選択位置に使われる) val currentColumn : Int get() = phoneTab( - { pe -> pe.pager.currentItem }, - { _ -> - 1 } + { it.pager.currentItem }, + { - 1 } ) // 新しいカラムをどこに挿入するか @@ -369,8 +369,8 @@ class ActMain : AppCompatActivity() // 新しいカラムをどこに挿入するか private val defaultInsertPosition : Int get() = phoneTab( - { pe -> pe.pager.currentItem + 1 }, - { _ -> Integer.MAX_VALUE } + { it.pager.currentItem + 1 }, + { Integer.MAX_VALUE } ) private fun validateFloat(fv : Float) : Float { @@ -664,15 +664,15 @@ class ActMain : AppCompatActivity() private fun performQuickPost(account : SavedAccount?) { if(account == null) { phoneTab({ env -> - + // スマホモードなら表示中のカラムがあればそれで - val c = try{ + val c = try { app_state.column_list[env.pager.currentItem] - }catch(ex:Throwable){ + } catch(ex : Throwable) { null } - - if( c?.access_info?.isPseudo == false ) { + + if(c?.access_info?.isPseudo == false) { // 疑似アカウントではない performQuickPost(c.access_info) } else { @@ -684,7 +684,7 @@ class ActMain : AppCompatActivity() message = getString(R.string.account_picker_toot) ) { ai -> performQuickPost(ai) } } - }, { _ -> + }, { // アカウント選択してやり直し AccountPicker.pick( this, @@ -702,7 +702,8 @@ class ActMain : AppCompatActivity() post_helper.bNSFW = false post_helper.in_reply_to_id = null post_helper.attachment_list = null - post_helper.emojiMapCustom = App1.custom_emoji_lister.getMap(account.host,account.isMisskey) + post_helper.emojiMapCustom = + App1.custom_emoji_lister.getMap(account.host, account.isMisskey) etQuickToot.hideKeyboard() @@ -1324,7 +1325,6 @@ class ActMain : AppCompatActivity() env.tablet_pager.layoutManager = env.tablet_layout_manager env.tablet_pager.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrollStateChanged(recyclerView : RecyclerView, newState : Int) { super.onScrollStateChanged(recyclerView, newState) @@ -1423,7 +1423,7 @@ class ActMain : AppCompatActivity() if(c == 0) { Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type)) } else { - Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type),c) + Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type), c) } // @@ -1460,7 +1460,7 @@ class ActMain : AppCompatActivity() var slide_ratio = 0f if(vr.first <= vr.last) { val child = env.tablet_layout_manager.findViewByPosition(vr.first) - slide_ratio = Math.abs( (child?.left ?: 0) / nColumnWidth.toFloat()) + slide_ratio = Math.abs((child?.left ?: 0) / nColumnWidth.toFloat()) } llColumnStrip.setVisibleRange(vr.first, vr.last, slide_ratio) @@ -1505,9 +1505,9 @@ class ActMain : AppCompatActivity() // ActOAuthCallbackで受け取ったUriを処理する private fun handleIntentUri(uri : Uri) { - + log.d("handleIntentUri ${uri}") - + when(uri.scheme) { "subwaytooter", "misskeyclientproto" -> return try { handleOAuth2CallbackUri(uri) @@ -2031,7 +2031,7 @@ class ActMain : AppCompatActivity() } } - }, { _ -> + }, { removeColumn(column) if(! app_state.column_list.isEmpty() && page_delete > 0) { @@ -2061,8 +2061,8 @@ class ActMain : AppCompatActivity() var lastColumnIndex = when(_lastColumnIndex) { - 1 -> phoneTab( - { pe -> pe.pager.currentItem }, - { _ -> 0 } + { it.pager.currentItem }, + { 0 } ) else -> _lastColumnIndex } @@ -2272,9 +2272,9 @@ class ActMain : AppCompatActivity() Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger) Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post) } else { - Styler.setIconAttr(this, btnToot, R.attr.ic_edit,c) - Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger,c) - Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post,c) + Styler.setIconAttr(this, btnToot, R.attr.ic_edit, c) + Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger, c) + Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post, c) } c = footer_tab_bg_color @@ -2337,9 +2337,9 @@ class ActMain : AppCompatActivity() for(i in 0 until env.tablet_layout_manager.childCount) { val v = env.tablet_layout_manager.getChildAt(i) - val columnViewHolder =when(v){ - null-> null - else->(env.tablet_pager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder + val columnViewHolder = when(v) { + null -> null + else -> (env.tablet_pager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder } if(columnViewHolder?.isColumnSettingShown == true) { @@ -2569,7 +2569,7 @@ class ActMain : AppCompatActivity() ZipInputStream(FileInputStream(file)).use { zipStream -> while(true) { val entry = zipStream.nextEntry ?: break - ++zipEntryCount + ++ zipEntryCount try { // val entryName = entry.name @@ -2581,13 +2581,13 @@ class ActMain : AppCompatActivity() continue } - if( AppDataExporter.restoreBackgroundImage( + if(AppDataExporter.restoreBackgroundImage( this@ActMain, newColumnList, zipStream, entryName ) - ){ + ) { continue } } finally { @@ -2597,12 +2597,12 @@ class ActMain : AppCompatActivity() } } catch(ex : Throwable) { log.trace(ex) - if(zipEntryCount!=0) { + if(zipEntryCount != 0) { showToast(this@ActMain, ex, "importAppData failed.") } } // zipではなかった場合、zipEntryがない状態になる。例外はPH-1では出なかったが、出ても問題ないようにする。 - if(zipEntryCount==0) { + if(zipEntryCount == 0) { InputStreamReader(FileInputStream(file), "UTF-8").use { inStream -> newColumnList = AppDataExporter.decodeAppData( this@ActMain, @@ -2767,13 +2767,12 @@ class ActMain : AppCompatActivity() } } - private var dlgPrivacyPolicy : WeakReference?=null + private var dlgPrivacyPolicy : WeakReference? = null private fun checkPrivacyPolicy() { // 既に表示中かもしれない - if( dlgPrivacyPolicy?.get()?.isShowing == true) return - + if(dlgPrivacyPolicy?.get()?.isShowing == true) return val res_id = when(getString(R.string.language_code)) { "ja" -> R.raw.privacy_policy_ja @@ -2783,29 +2782,27 @@ class ActMain : AppCompatActivity() // プライバシーポリシーデータの読み込み val bytes = loadRawResource(res_id) - if( bytes.isEmpty() ) return + if(bytes.isEmpty()) return // 同意ずみなら表示しない val digest = bytes.digestSHA256().encodeBase64Url() - if( digest == Pref.spAgreedPrivacyPolicyDigest(pref) ) return + if(digest == Pref.spAgreedPrivacyPolicyDigest(pref)) return val dialog = AlertDialog.Builder(this) .setTitle(R.string.privacy_policy) - .setMessage( bytes.decodeUTF8()) - .setNegativeButton(R.string.cancel){_,_ -> + .setMessage(bytes.decodeUTF8()) + .setNegativeButton(R.string.cancel) { _, _ -> finish() } - .setOnCancelListener{_-> + .setOnCancelListener { finish() } - .setPositiveButton(R.string.agree){_,_ -> - pref.edit().put(Pref.spAgreedPrivacyPolicyDigest,digest).apply() + .setPositiveButton(R.string.agree) { _, _ -> + pref.edit().put(Pref.spAgreedPrivacyPolicyDigest, digest).apply() } .create() dlgPrivacyPolicy = WeakReference(dialog) dialog.show() } - - } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 23b0a5ab..208f779f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -152,6 +152,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba internal const val DRAFT_REPLY_URL = "reply_url" internal const val DRAFT_IS_ENQUETE = "is_enquete" internal const val DRAFT_ENQUETE_ITEMS = "enquete_items" + internal const val DRAFT_QUOTED_RENOTE = "quotedRenote" private const val STATE_MUSHROOM_INPUT = "mushroom_input" private const val STATE_MUSHROOM_START = "mushroom_start" @@ -223,6 +224,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba internal lateinit var etContentWarning : MyEditText internal lateinit var etContent : MyEditText + internal lateinit var cbQuoteRenote : CheckBox + internal lateinit var cbEnquete : CheckBox private lateinit var llEnquete : View internal lateinit var list_etChoice : List @@ -703,14 +706,17 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba selectAccount(null) } + updateContentWarning() showMediaAttachment() showVisibility() updateTextCount() showReplyTo() showEnquete() + showQuotedRenote() } + override fun onDestroy() { post_helper.onDestroy() @@ -765,6 +771,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba updateTextCount() showReplyTo() showEnquete() + showQuotedRenote() } private fun appendContentText( @@ -853,6 +860,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba etContentWarning = findViewById(R.id.etContentWarning) etContent = findViewById(R.id.etContent) + cbQuoteRenote= findViewById(R.id.cbQuoteRenote) + cbEnquete = findViewById(R.id.cbEnquete) llEnquete = findViewById(R.id.llEnquete) @@ -1128,6 +1137,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba log.trace(ex) } showVisibility() + showQuotedRenote() } @SuppressLint("StaticFieldLeak") @@ -1974,6 +1984,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba post_helper.redraft_status_id = redraft_status_id + post_helper.useQuotedRenote = cbQuoteRenote.isChecked + post_helper.post(account) { target_account, status -> val data = Intent() data.putExtra(EXTRA_POSTED_ACCT, target_account.acct) @@ -1986,6 +1998,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba } } + private fun showQuotedRenote() { + val isReply = in_reply_to_id != null + val isMisskey = account?.isMisskey == true + cbQuoteRenote.visibility = if( isReply && isMisskey ) View.VISIBLE else View.GONE + } + internal fun showReplyTo() { if(in_reply_to_id == null) { llReply.visibility = View.GONE @@ -2008,6 +2026,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba in_reply_to_image = null in_reply_to_url = null showReplyTo() + showQuotedRenote() } private fun saveDraft() { @@ -2054,7 +2073,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba json.put(DRAFT_REPLY_IMAGE, in_reply_to_image) json.put(DRAFT_REPLY_URL, in_reply_to_url) + json.put(DRAFT_QUOTED_RENOTE,cbQuoteRenote.isChecked) json.put(DRAFT_IS_ENQUETE, isEnquete) + val array = JSONArray() for(s in str_choice) { array.put(s) @@ -2206,6 +2227,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba val draft_visibility = TootVisibility .parseSavedVisibility(draft.parseString(DRAFT_VISIBILITY)) + val evEmoji = DecodeOptions(this@ActPost, decodeEmoji = true).decodeEmoji(content) etContent.setText(evEmoji) etContent.setSelection(evEmoji.length) @@ -2215,6 +2237,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba cbNSFW.isChecked = nsfw_checked if(draft_visibility != null) this@ActPost.visibility = draft_visibility + cbQuoteRenote.isChecked = draft.optBoolean(DRAFT_QUOTED_RENOTE) cbEnquete.isChecked = draft.optBoolean(DRAFT_IS_ENQUETE, false) val array = draft.optJSONArray(DRAFT_ENQUETE_ITEMS) if(array != null) { @@ -2254,6 +2277,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba in_reply_to_image = reply_image in_reply_to_url = reply_url } + updateContentWarning() showMediaAttachment() @@ -2261,6 +2285,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba updateTextCount() showReplyTo() showEnquete() + showQuotedRenote() if(! list_warning.isEmpty()) { val sb = StringBuilder() diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index 9e5472e4..403b1f5c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -26,6 +26,7 @@ import jp.juggler.subwaytooter.api.TootTaskRunner import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.dialog.ActionsDialog import jp.juggler.subwaytooter.dialog.DlgConfirm +import jp.juggler.subwaytooter.drawable.PreviewCardBorder import jp.juggler.subwaytooter.span.EmojiImageSpan import jp.juggler.subwaytooter.span.MyClickableSpan import jp.juggler.subwaytooter.table.* @@ -120,12 +121,13 @@ internal class ItemViewHolder( private lateinit var tvFilterDetail : TextView private lateinit var tvMediaDescription : TextView + + private lateinit var llCardOuter : View private lateinit var tvCardText : TextView private lateinit var ivCardImage : MyNetworkImageView private lateinit var llExtra : LinearLayout - private lateinit var llConversationIcons : View private lateinit var ivConversationIcon1 : MyNetworkImageView private lateinit var ivConversationIcon2 : MyNetworkImageView @@ -134,8 +136,6 @@ internal class ItemViewHolder( private lateinit var tvConversationIconsMore : TextView private lateinit var tvConversationParticipants : TextView - - private lateinit var tvApplication : TextView private lateinit var tvMessageHolder : TextView @@ -181,7 +181,8 @@ internal class ItemViewHolder( btnFollow.setOnClickListener(this) btnFollow.setOnLongClickListener(this) - ivCardImage.setOnClickListener(this) + llCardOuter.setOnClickListener(this) + llCardOuter.setOnLongClickListener(this) ivThumbnail.setOnClickListener(this) @@ -263,6 +264,13 @@ internal class ItemViewHolder( this.reply_invalidator = NetworkEmojiInvalidator(activity.handler, tvReply) this.follow_invalidator = NetworkEmojiInvalidator(activity.handler, tvFollowerName) this.name_invalidator = NetworkEmojiInvalidator(activity.handler, tvName) + + val cardBackground = llCardOuter.background + if(cardBackground is PreviewCardBorder) { + val density = activity.density + cardBackground.round = (density * 8f) + cardBackground.width = (density * 1f) + } } fun onViewRecycled() { @@ -375,6 +383,7 @@ internal class ItemViewHolder( llTrendTag.visibility = View.GONE llFilter.visibility = View.GONE tvMediaDescription.visibility = View.GONE + llCardOuter.visibility = View.GONE tvCardText.visibility = View.GONE ivCardImage.visibility = View.GONE llConversationIcons.visibility = View.GONE @@ -402,6 +411,12 @@ internal class ItemViewHolder( tvConversationIconsMore.setTextColor(c) tvConversationParticipants.setTextColor(c) + val cardBackground = llCardOuter.background + if(cardBackground is PreviewCardBorder) { + cardBackground.color = c + } + + c = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor( activity, R.attr.colorTimeSmall @@ -484,12 +499,10 @@ internal class ItemViewHolder( } extra_invalidator_list.clear() - } - private fun showConversationIcons(cs:TootConversationSummary) { + private fun showConversationIcons(cs : TootConversationSummary) { - val accounts = cs.accounts val last_account_id = cs.last_status.account.id val accountsOther = cs.accounts.filter { it.get().id != last_account_id } @@ -498,17 +511,17 @@ internal class ItemViewHolder( val size = accountsOther.size - tvConversationParticipants.text =if(size <= 1){ + tvConversationParticipants.text = if(size <= 1) { activity.getString(R.string.conversation_to) - }else{ + } else { activity.getString(R.string.participants) } fun showIcon(iv : MyNetworkImageView, idx : Int) { val bShown = idx < size iv.visibility = if(bShown) View.VISIBLE else View.GONE - if(!bShown) return - + if(! bShown) return + val who = accountsOther[idx].get() iv.setImageUrl( activity.pref, @@ -527,14 +540,14 @@ internal class ItemViewHolder( else -> activity.getString(R.string.participants_and_more) } } - - if( cs.last_status.in_reply_to_id != null ) { + + if(cs.last_status.in_reply_to_id != null) { llSearchTag.visibility = View.VISIBLE btnSearchTag.text = activity.getString(R.string.show_conversation) } } - private fun openConversationSummary(){ + private fun openConversationSummary() { val cs = item as? TootConversationSummary ?: return if(cs.unread) { @@ -545,7 +558,7 @@ internal class ItemViewHolder( reset = true ) // 未読フラグのクリアをサーバに送る - Action_Toot.clearConversationUnread(activity,access_info,cs) + Action_Toot.clearConversationUnread(activity, access_info, cs) } Action_Toot.conversation( @@ -936,7 +949,7 @@ internal class ItemViewHolder( // } var content = status.decoded_content - + // ニコフレのアンケートの表示 val enquete = status.enquete if(enquete != null) { @@ -958,10 +971,7 @@ internal class ItemViewHolder( } // カードの表示(会話ビューのみ) - val card = status.card - if(card != null) { - showPreviewCard(activity, card) - } + showPreviewCard(activity, status) // if( status.decoded_tags == null ){ // tvTags.setVisibility( View.GONE ); @@ -1048,7 +1058,7 @@ internal class ItemViewHolder( setMedia(media_attachments, sb, ivMedia2, 1) setMedia(media_attachments, sb, ivMedia3, 2) setMedia(media_attachments, sb, ivMedia4, 3) - if( sb. isNotEmpty()){ + if(sb.isNotEmpty()) { tvMediaDescription.visibility = View.VISIBLE tvMediaDescription.text = sb } @@ -1326,9 +1336,10 @@ internal class ItemViewHolder( if(column.content_color != 0) column.content_color else content_color_default tv.setTextColor(c) - if( ta.description ?. isNotEmpty() == true){ - if( sbDesc.isNotEmpty()) sbDesc.append("\n") - val desc = activity.getString(R.string.media_description, idx + 1, ta.description) + if(ta.description?.isNotEmpty() == true) { + if(sbDesc.isNotEmpty()) sbDesc.append("\n") + val desc = + activity.getString(R.string.media_description, idx + 1, ta.description) sbDesc.append(desc) } } @@ -1353,8 +1364,6 @@ internal class ItemViewHolder( } private var boostedAction : () -> Unit = defaultBoostedAction - - override fun onClick(v : View) { val pos = activity.nextPosition(column) @@ -1516,8 +1525,26 @@ internal class ItemViewHolder( openFilterMenu(item) } - ivCardImage-> status_showing?.card?.url?.let { url -> - if(url.isNotEmpty()) App1.openCustomTab(activity, url) + llCardOuter -> status_showing?.card?.let { card -> + val originalStatus = card.originalStatus + if(originalStatus != null) { + Action_Toot.conversation( + activity, + activity.nextPosition(column), + access_info, + originalStatus + ) + } else { + val url = card.url + if(url?.isNotEmpty() == true) { + ChromeTabOpener( + activity, + pos, + url, + accessInfo = access_info + ).open() + } + } } llConversationIcons -> openConversationSummary() @@ -1593,6 +1620,12 @@ internal class ItemViewHolder( return true } + llCardOuter -> Action_Toot.conversationOtherInstance( + activity, + activity.nextPosition(column), + status_showing?.card?.originalStatus + ) + btnSearchTag, llTrendTag -> { val item = this.item when(item) { @@ -1666,15 +1699,35 @@ internal class ItemViewHolder( } } - private fun showPreviewCard(activity : ActMain, card : TootCard) { + private fun showPreviewCard(activity : ActMain, status : TootStatus) { + val card = status.card ?: return + + // 会話カラムで返信ステータスなら捏造したカードを表示しない + if(column.column_type == Column.TYPE_CONVERSATION + && card.originalStatus != null + && status.reply != null + ) { + return + } + + llCardOuter.visibility = View.VISIBLE + val sb = StringBuilder() - addLinkAndCaption(sb, activity.getString(R.string.card_header_card), card.url, card.title) + + addLinkAndCaption( + sb, + activity.getString(R.string.card_header_card), + card.url, + card.title + ) + addLinkAndCaption( sb, activity.getString(R.string.card_header_author), card.author_url, card.author_name ) + addLinkAndCaption( sb, activity.getString(R.string.card_header_provider), @@ -1688,12 +1741,20 @@ internal class ItemViewHolder( val limit = Pref.spCardDescriptionLength.toInt(activity.pref) - sb.append(HTMLDecoder.encodeEntity(ellipsize(description,if( limit <= 0 ) 64 else limit))) + sb.append( + HTMLDecoder.encodeEntity( + ellipsize( + description, + if(limit <= 0) 64 else limit + ) + ) + ) } - if( sb.isNotEmpty() ){ - val text = DecodeOptions(activity, access_info).decodeHTML(sb.toString()) - if( text.isNotEmpty()){ + if(sb.isNotEmpty()) { + val text = + DecodeOptions(activity, access_info, forceHtml = true).decodeHTML(sb.toString()) + if(text.isNotEmpty()) { tvCardText.visibility = View.VISIBLE tvCardText.text = text } @@ -1711,11 +1772,11 @@ internal class ItemViewHolder( } } - private fun ellipsize(src:String,limit:Int):String{ - return if( src.codePointCount(0,src.length) <= limit ) { + private fun ellipsize(src : String, limit : Int) : String { + return if(src.codePointCount(0, src.length) <= limit) { src - }else { - "${src.substring(0, src.offsetByCodePoints(0,limit))}…" + } else { + "${src.substring(0, src.offsetByCodePoints(0, limit))}…" } } @@ -1899,7 +1960,7 @@ internal class ItemViewHolder( } if((result.response?.code() ?: - 1) in 200 until 300) { - if(status.increaseReaction(code,true,"addReaction")){ + if(status.increaseReaction(code, true, "addReaction")) { // 1個だけ描画更新するのではなく、TLにある複数の要素をまとめて更新する list_adapter.notifyChange(reason = "addReaction complete", reset = true) } @@ -2021,14 +2082,13 @@ internal class ItemViewHolder( val data = result.jsonObject if(data != null) { if(accessInfo.isMisskey) { - if( enquete.increaseVote(activity,idx,true) ){ + if(enquete.increaseVote(activity, idx, true)) { showToast(context, false, R.string.enquete_voted) - + // 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある list_adapter.notifyChange(reason = "onClickEnqueteChoice", reset = true) } - } else { val message = data.parseString("message") ?: "?" val valid = data.optBoolean("valid") @@ -2499,25 +2559,35 @@ internal class ItemViewHolder( } } - tvMediaDescription = textView{}.lparams(matchParent, wrapContent) + tvMediaDescription = textView {}.lparams(matchParent, wrapContent) - tvCardText = textView{ - - }.lparams(matchParent, wrapContent){ - topMargin = dip(3) - } - - ivCardImage = myNetworkImageView { + llCardOuter = verticalLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(3) + startMargin = dip(12) + endMargin = dip(6) + } + padding = dip(3) + bottomPadding = dip(6) - contentDescription = context.getString(R.string.thumbnail) + background = PreviewCardBorder() - scaleType = if(Pref.bpDontCropMediaThumb(App1.pref)) - ImageView.ScaleType.FIT_CENTER - else - ImageView.ScaleType.CENTER_CROP + tvCardText = textView { + }.lparams(matchParent, wrapContent) { + } - }.lparams(matchParent, activity.app_state.media_thumb_height) { - topMargin = dip(3) + ivCardImage = myNetworkImageView { + + contentDescription = context.getString(R.string.thumbnail) + + scaleType = if(Pref.bpDontCropMediaThumb(App1.pref)) + ImageView.ScaleType.FIT_CENTER + else + ImageView.ScaleType.CENTER_CROP + + }.lparams(matchParent, activity.app_state.media_thumb_height) { + topMargin = dip(3) + } } @@ -2526,7 +2596,6 @@ internal class ItemViewHolder( topMargin = dip(0) } } - } // button bar @@ -2560,34 +2629,34 @@ internal class ItemViewHolder( tvConversationParticipants = textView { text = context.getString(R.string.participants) - }.lparams(wrapContent,wrapContent){ + }.lparams(wrapContent, wrapContent) { endMargin = dip(3) } ivConversationIcon1 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24),dip(24)) { + }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon2 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24),dip(24)) { + }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon3 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24),dip(24)) { + }.lparams(dip(24), dip(24)) { endMargin = dip(3) } ivConversationIcon4 = myNetworkImageView { scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24),dip(24)) { + }.lparams(dip(24), dip(24)) { endMargin = dip(3) } tvConversationIconsMore = textView { - }.lparams(wrapContent,wrapContent) + }.lparams(wrapContent, wrapContent) } llSearchTag = linearLayout { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt index 2a3efac3..de70865a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt @@ -179,7 +179,7 @@ internal class ViewHolderHeaderProfile( btnFollow.setImageDrawable(null) tvRemoteProfileWarning.visibility = View.GONE } else { - tvCreated.text = TootStatus.formatTime(tvCreated.context, who.time_created_at, true) + tvCreated.text = TootStatus.formatTime(tvCreated.context, (whoDetail?:who).time_created_at, true) ivBackground.setImageUrl( activity.pref, 0f, diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt index afcaab40..7a2c110c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt @@ -181,7 +181,6 @@ object Action_Follow { override fun background(client : TootApiClient) : TootApiResult? { var result : TootApiResult? - val parser = TootParser(activity, access_info) if(access_info.isMisskey) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootCard.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootCard.kt index 531c27bb..8c70c23b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootCard.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootCard.kt @@ -1,8 +1,10 @@ package jp.juggler.subwaytooter.api.entity -import org.json.JSONObject - +import jp.juggler.subwaytooter.api.TootParser +import jp.juggler.subwaytooter.util.HTMLDecoder +import jp.juggler.subwaytooter.util.filterNotEmpty import jp.juggler.subwaytooter.util.parseString +import org.json.JSONObject class TootCard( @@ -19,10 +21,12 @@ class TootCard( val image : String?, val type : String?, - val author_name : String?, - val author_url : String?, - val provider_name : String?, - val provider_url : String? + val author_name : String? =null, + val author_url : String? =null, + val provider_name : String? =null, + val provider_url : String? =null, + + val originalStatus :TootStatus? =null ) { constructor(src : JSONObject) : this( @@ -38,4 +42,17 @@ class TootCard( provider_url = src.parseString("provider_url") ) + + constructor(parser: TootParser, src:TootStatus) :this( + originalStatus = src, + url = src.url, + title = "${src.account.display_name} @${parser.linkHelper .getFullAcct(src.account.acct)}", + description = if( parser.serviceType==ServiceType.MISSKEY){ + src.spoiler_text.filterNotEmpty() ?: src.content + }else{ + src.spoiler_text.filterNotEmpty() ?: HTMLDecoder.encodeEntity( src.content ?: "") + }, + image = src.media_attachments ?. firstOrNull() ?. urlForThumbnail ?: src.account.avatar_static, + type = "photo" + ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index d21bd136..d5e3e0f9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -312,6 +312,18 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() { this.deletedAt = src.parseString("deletedAt") this.time_deleted_at = parseTime(deletedAt) + + if( card == null) { + + if(reblog != null && hasAnyContent() ) { + // 引用Renoteにプレビューカードをでっちあげる + card = TootCard(parser, reblog) + } else if(reply != null ) { + // 返信にプレビューカードをでっちあげる + card = TootCard(parser, reply!! ) + } + } + } else { misskeyVisibleIds = null reply = null diff --git a/app/src/main/java/jp/juggler/subwaytooter/drawable/PreviewCardBorder.kt b/app/src/main/java/jp/juggler/subwaytooter/drawable/PreviewCardBorder.kt new file mode 100644 index 00000000..90e95226 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/drawable/PreviewCardBorder.kt @@ -0,0 +1,39 @@ +package jp.juggler.subwaytooter.drawable + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable + +class PreviewCardBorder : Drawable() { + + var color = 0 + var round = 0f + var width = 1f + + private val paint = Paint() + + override fun draw(canvas : Canvas) { + paint.isAntiAlias = true + paint.color = color + paint.style = Paint.Style.STROKE + paint.strokeWidth = width + + val bounds = this.bounds + val left = bounds.left + width/2 + val right = bounds.right -width/2 + val top = bounds.top + width/2 + val bottom = bounds.bottom -width/2 + + canvas.drawRoundRect( left,top,right,bottom,round,round,paint ) + } + + override fun getOpacity() : Int = PixelFormat.TRANSLUCENT + + override fun setAlpha(alpha : Int) =Unit + + override fun setColorFilter(colorFilter : ColorFilter?) =Unit + + +} \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt b/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt index f5645a3f..81c7a16b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/DecodeOptions.kt @@ -21,7 +21,8 @@ class DecodeOptions( var emojiMapProfile : HashMap? = null, var highlightTrie : WordTrieTree? = null, var unwrapEmojiImageTag :Boolean = false, - var enlargeCustomEmoji :Float = 1f + var enlargeCustomEmoji :Float = 1f, + var forceHtml : Boolean = false // force use HTML instead of Misskey Markdown ) { internal fun isMediaAttachment(url : String?) : Boolean { diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index 71fdd5d4..3947ad05 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -468,7 +468,7 @@ object HTMLDecoder { fun decodeHTML(options : DecodeOptions, src : String?) : SpannableStringBuilder { - if( options.linkHelper?.isMisskey == true){ + if( options.linkHelper?.isMisskey == true && !options.forceHtml ){ return MisskeyMarkdownDecoder.decodeMarkdown(options,src) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt index ab73def1..b98bba75 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt @@ -77,6 +77,7 @@ class PostHelper( var enquete_items : ArrayList? = null var emojiMapCustom : HashMap? = null var redraft_status_id : EntityId? = null + var useQuotedRenote : Boolean = false private var last_post_tapped : Long = 0L @@ -358,7 +359,11 @@ class PostHelper( } if(in_reply_to_id != null) { - json.put("replyId", in_reply_to_id.toString()) + if( useQuotedRenote){ + json.put("renoteId", in_reply_to_id.toString()) + }else{ + json.put("replyId", in_reply_to_id.toString()) + } } json.put("viaMobile", true) @@ -470,8 +475,9 @@ class PostHelper( } result = if(isMisskey) { + log.d("misskey json %s",body_string) + client.request("/api/notes/create", request_builder) - // TODO {"error":{}} が返ってきた時にどう扱えばいい? } else { client.request("/api/v1/statuses", request_builder) } @@ -549,7 +555,7 @@ class PostHelper( private var isMisskey = false private val onEmojiListLoad : (list : ArrayList) -> Unit = - { _ : ArrayList -> + { val popup = this@PostHelper.popup if(popup?.isShowing == true) proc_text_changed.run() } @@ -740,9 +746,9 @@ class PostHelper( val remain = limit - code_list.size if(remain > 0) { val s = src.substring(last_colon + 1, end).toLowerCase().replace('-', '_') - val src = EmojiDecoder.searchShortCode(activity, s, remain) - log.d("checkEmoji: search for %s, result=%d", s, src.size) - code_list.addAll(src) + val matches = EmojiDecoder.searchShortCode(activity, s, remain) + log.d("checkEmoji: search for %s, result=%d", s, matches.size) + code_list.addAll(matches) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt index 67d2a781..4a2253db 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt @@ -488,6 +488,12 @@ fun String?.optInt() : Int? { } } +fun String?.filterNotEmpty() :String? = when{ + this==null -> null + this.isEmpty() -> null + else->this +} + //fun String.ellipsize(max : Int) = if(this.length > max) this.substring(0, max - 1) + "…" else this // //fun String.toCamelCase() : String { diff --git a/app/src/main/res/layout/act_post.xml b/app/src/main/res/layout/act_post.xml index 3de077cf..571c1f12 100644 --- a/app/src/main/res/layout/act_post.xml +++ b/app/src/main/res/layout/act_post.xml @@ -83,6 +83,14 @@ android:src="?attr/btn_close" /> + + + + + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 856535b9..8009296c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -784,5 +784,6 @@ 会話の参加者: …他 送り先: + 引用Renoteにする diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44e79e98..cdb57251 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -803,5 +803,6 @@ Conversation participants: … and more To: + Use \"Quoted Renote\"