mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-01-30 18:44:52 +01:00
(Misskey)投稿のコンテキストメニューから引用リノート。別アカ選択あり。引用リノートで投稿画面を開いたらCWやメンションを引き継がない。(Misskey)投稿のURLをタップした時にアプリ内で開く。(Misskey)外部タンスの投稿を複製したURLを別のタンスに同期する際、投稿元タンスでのuriを取得してから目的のタンスに同期する
This commit is contained in:
parent
b3bf3a878e
commit
0275edfdbc
@ -85,11 +85,9 @@ class ActMain : AppCompatActivity()
|
||||
internal var sent_intent2 : Intent? = null
|
||||
|
||||
internal val reUrlHashTag =
|
||||
Pattern.compile("\\Ahttps://([^/]+)/tags/([^?#・\\s\\-+.,:;/]+)(?:\\z|[?#])")
|
||||
|
||||
@Suppress("HasPlatformType")
|
||||
val reStatusPage = Pattern.compile("\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)/(\\d+)(?:\\z|[?#])")
|
||||
Pattern.compile("""\Ahttps://([^/]+)/tags/([^?#・\s\-+.,:;/]+)(?:\z|[?#])""")
|
||||
|
||||
|
||||
var boostButtonSize = 0
|
||||
var timeline_font : Typeface = Typeface.DEFAULT
|
||||
var timeline_font_bold : Typeface = Typeface.DEFAULT_BOLD
|
||||
@ -1557,10 +1555,10 @@ class ActMain : AppCompatActivity()
|
||||
|
||||
val url = uri.toString()
|
||||
|
||||
var m = reStatusPage.matcher(url)
|
||||
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
||||
var m = TootStatus.reStatusPage.matcher(url)
|
||||
if(m.find()) {
|
||||
try {
|
||||
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
||||
val host = m.group(1)
|
||||
val status_id = EntityIdLong(m.group(3).toLong(10))
|
||||
// ステータスをアプリ内で開く
|
||||
@ -1579,6 +1577,28 @@ class ActMain : AppCompatActivity()
|
||||
return
|
||||
}
|
||||
|
||||
// https://misskey.xyz/notes/(id)
|
||||
m = TootStatus.reStatusPageMisskey.matcher(url)
|
||||
if(m.find()) {
|
||||
try {
|
||||
val host = m.group(1)
|
||||
val status_id = EntityIdString(m.group(2))
|
||||
// ステータスをアプリ内で開く
|
||||
Action_Toot.conversationOtherInstance(
|
||||
this@ActMain,
|
||||
defaultInsertPosition,
|
||||
url,
|
||||
status_id,
|
||||
host,
|
||||
status_id
|
||||
)
|
||||
} catch(ex : Throwable) {
|
||||
showToast(this, ex, "can't parse status id.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ユーザページをアプリ内で開く
|
||||
m = TootAccount.reAccountUrl.matcher(url)
|
||||
if(m.find()) {
|
||||
@ -2204,7 +2224,7 @@ class ActMain : AppCompatActivity()
|
||||
}
|
||||
|
||||
// ステータスページをアプリから開く
|
||||
m = reStatusPage.matcher(opener.url)
|
||||
m = TootStatus.reStatusPage.matcher(opener.url)
|
||||
if(m.find()) {
|
||||
try {
|
||||
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
||||
@ -2234,6 +2254,37 @@ class ActMain : AppCompatActivity()
|
||||
return
|
||||
}
|
||||
|
||||
// ステータスページをアプリから開く
|
||||
m = TootStatus.reStatusPageMisskey.matcher(opener.url)
|
||||
if(m.find()) {
|
||||
try {
|
||||
// https://misskey.xyz/notes/(id)
|
||||
val host = m.group(1)
|
||||
val status_id = EntityIdString(m.group(2))
|
||||
if(accessInto.isNA || ! host.equals(accessInto.host, ignoreCase = true)) {
|
||||
Action_Toot.conversationOtherInstance(
|
||||
this@ActMain,
|
||||
opener.pos,
|
||||
opener.url,
|
||||
status_id,
|
||||
host,
|
||||
status_id
|
||||
)
|
||||
} else {
|
||||
Action_Toot.conversationLocal(
|
||||
this@ActMain,
|
||||
opener.pos,
|
||||
accessInto,
|
||||
status_id
|
||||
)
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
showToast(this, ex, "can't parse status id.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ユーザページをアプリ内で開く
|
||||
m = TootAccount.reAccountUrl.matcher(opener.url)
|
||||
if(m.find()) {
|
||||
|
@ -69,6 +69,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
internal const val KEY_REDRAFT_STATUS = "redraft_status"
|
||||
internal const val KEY_INITIAL_TEXT = "initial_text"
|
||||
internal const val KEY_SENT_INTENT = "sent_intent"
|
||||
internal const val KEY_QUOTED_RENOTE = "quoted_renote"
|
||||
|
||||
internal const val KEY_ATTACHMENT_LIST = "attachment_list"
|
||||
internal const val KEY_VISIBILITY = "visibility"
|
||||
@ -104,24 +105,27 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
|
||||
private val imageHeaderList = arrayOf(
|
||||
Pair("image/jpeg",intArrayOf(0xff,0xd8,0xff,0xe0).toByteArray()),
|
||||
Pair("image/png",intArrayOf(0x89 ,0x50 ,0x4E ,0x47 ,0x0D ,0x0A ,0x1A ,0x0A).toByteArray()),
|
||||
Pair("image/gif", charArrayOf('G' ,'I' ,'F').toByteArray())
|
||||
Pair("image/jpeg", intArrayOf(0xff, 0xd8, 0xff, 0xe0).toByteArray()),
|
||||
Pair(
|
||||
"image/png",
|
||||
intArrayOf(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A).toByteArray()
|
||||
),
|
||||
Pair("image/gif", charArrayOf('G', 'I', 'F').toByteArray())
|
||||
)
|
||||
|
||||
private fun checkImageHeaderList(contentResolver:ContentResolver, uri : Uri) : String? {
|
||||
try{
|
||||
contentResolver.openInputStream(uri)?.use{ inStream ->
|
||||
private fun checkImageHeaderList(contentResolver : ContentResolver, uri : Uri) : String? {
|
||||
try {
|
||||
contentResolver.openInputStream(uri)?.use { inStream ->
|
||||
val data = ByteArray(32)
|
||||
val nRead = inStream.read(data,0,data.size)
|
||||
for( pair in imageHeaderList ){
|
||||
val nRead = inStream.read(data, 0, data.size)
|
||||
for(pair in imageHeaderList) {
|
||||
val type = pair.first
|
||||
val header = pair.second
|
||||
if( nRead >= header.size && data.startWith(header) ) return type
|
||||
if(nRead >= header.size && data.startWith(header)) return type
|
||||
}
|
||||
}
|
||||
}catch(ex:Throwable){
|
||||
log.e(ex,"checkImageHeaderList failed.")
|
||||
} catch(ex : Throwable) {
|
||||
log.e(ex, "checkImageHeaderList failed.")
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -175,22 +179,31 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
initial_text : String? = null,
|
||||
|
||||
// 外部アプリから共有されたインテント
|
||||
sent_intent : Intent? = null
|
||||
sent_intent : Intent? = null,
|
||||
|
||||
// (Misskey) 返信を引用リノートにする
|
||||
quotedRenote : Boolean = false
|
||||
) {
|
||||
val intent = Intent(activity, ActPost::class.java)
|
||||
intent.putExtra(KEY_ACCOUNT_DB_ID, account_db_id)
|
||||
|
||||
if(redraft_status != null) {
|
||||
intent.putExtra(KEY_REDRAFT_STATUS, redraft_status.json.toString())
|
||||
}
|
||||
|
||||
if(reply_status != null) {
|
||||
intent.putExtra(KEY_REPLY_STATUS, reply_status.json.toString())
|
||||
intent.putExtra(KEY_QUOTED_RENOTE, quotedRenote)
|
||||
}
|
||||
|
||||
if(initial_text != null) {
|
||||
intent.putExtra(KEY_INITIAL_TEXT, initial_text)
|
||||
}
|
||||
|
||||
if(sent_intent != null) {
|
||||
intent.putExtra(KEY_SENT_INTENT, sent_intent)
|
||||
}
|
||||
|
||||
activity.startActivityForResult(intent, request_code)
|
||||
}
|
||||
|
||||
@ -320,14 +333,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
||||
if(requestCode == REQUEST_CODE_ATTACHMENT_OLD && resultCode == Activity.RESULT_OK) {
|
||||
data?.handleGetContentResult(contentResolver)?.forEach {
|
||||
addAttachment(it.first, it.second)
|
||||
}
|
||||
|
||||
|
||||
} else if(requestCode == REQUEST_CODE_ATTACHMENT && resultCode == Activity.RESULT_OK) {
|
||||
data?.handleGetContentResult(contentResolver)?.forEach {
|
||||
addAttachment(it.first, it.second)
|
||||
@ -394,7 +405,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
initUI()
|
||||
|
||||
// Android 9 から、明示的にフォーカスを当てる必要がある
|
||||
if( savedInstanceState==null){
|
||||
if(savedInstanceState == null) {
|
||||
etContent.requestFocus()
|
||||
}
|
||||
|
||||
@ -536,45 +547,56 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
if(sv != null && account != null) {
|
||||
try {
|
||||
val reply_status = TootParser(this@ActPost, account).status(sv.toJsonObject())
|
||||
|
||||
|
||||
val isQuoterRenote = intent.getBooleanExtra(KEY_QUOTED_RENOTE, false)
|
||||
|
||||
if(reply_status != null) {
|
||||
// CW をリプライ元に合わせる
|
||||
if(reply_status.spoiler_text?.isNotEmpty() == true) {
|
||||
cbContentWarning.isChecked = true
|
||||
etContentWarning.setText(reply_status.spoiler_text)
|
||||
}
|
||||
|
||||
val mention_list = ArrayList<String>()
|
||||
|
||||
val old_mentions = reply_status.mentions
|
||||
if(old_mentions != null) {
|
||||
for(mention in old_mentions) {
|
||||
val who_acct = mention.acct
|
||||
if(who_acct.isNotEmpty()) {
|
||||
if(account.isMe(who_acct)) continue
|
||||
sv = "@" + account.getFullAcct(who_acct)
|
||||
if(! mention_list.contains(sv)) {
|
||||
mention_list.add(sv)
|
||||
if(isQuoterRenote) {
|
||||
cbQuoteRenote.isChecked = true
|
||||
|
||||
// 引用リノートはCWやメンションを引き継がない
|
||||
|
||||
}else{
|
||||
|
||||
// CW をリプライ元に合わせる
|
||||
if(reply_status.spoiler_text?.isNotEmpty() == true) {
|
||||
cbContentWarning.isChecked = true
|
||||
etContentWarning.setText(reply_status.spoiler_text)
|
||||
}
|
||||
|
||||
val mention_list = ArrayList<String>()
|
||||
|
||||
val old_mentions = reply_status.mentions
|
||||
if(old_mentions != null) {
|
||||
for(mention in old_mentions) {
|
||||
val who_acct = mention.acct
|
||||
if(who_acct.isNotEmpty()) {
|
||||
if(account.isMe(who_acct)) continue
|
||||
sv = "@" + account.getFullAcct(who_acct)
|
||||
if(! mention_list.contains(sv)) {
|
||||
mention_list.add(sv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 元レスのacctを追加する
|
||||
val who_acct = account.getFullAcct(reply_status.account)
|
||||
if(! account.isMe(reply_status.account) // 自己レスにはメンションを追加しない
|
||||
&& ! mention_list.contains("@$who_acct") // 既に含まれているならメンションを追加しない
|
||||
) {
|
||||
mention_list.add("@$who_acct")
|
||||
}
|
||||
|
||||
val sb = StringBuilder()
|
||||
for(acct in mention_list) {
|
||||
if(sb.isNotEmpty()) sb.append(' ')
|
||||
sb.append(acct)
|
||||
}
|
||||
if(sb.isNotEmpty()) {
|
||||
appendContentText(sb.append(' ').toString())
|
||||
|
||||
// 元レスのacctを追加する
|
||||
val who_acct = account.getFullAcct(reply_status.account)
|
||||
if(! account.isMe(reply_status.account) // 自己レスにはメンションを追加しない
|
||||
&& ! mention_list.contains("@$who_acct") // 既に含まれているならメンションを追加しない
|
||||
) {
|
||||
mention_list.add("@$who_acct")
|
||||
}
|
||||
|
||||
val sb = StringBuilder()
|
||||
for(acct in mention_list) {
|
||||
if(sb.isNotEmpty()) sb.append(' ')
|
||||
sb.append(acct)
|
||||
}
|
||||
if(sb.isNotEmpty()) {
|
||||
appendContentText(sb.append(' ').toString())
|
||||
}
|
||||
}
|
||||
|
||||
// リプライ表示をつける
|
||||
@ -607,13 +629,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
appendContentText(account?.default_text, selectBefore = true)
|
||||
@ -650,14 +669,14 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
// 再編集の場合はdefault_textは反映されない
|
||||
|
||||
val decodeOptions = DecodeOptions(this)
|
||||
|
||||
var text :Spannable
|
||||
|
||||
var text : Spannable
|
||||
|
||||
text = decodeOptions.decodeHTML(base_status.content)
|
||||
etContent.text = text
|
||||
etContent.setSelection(text.length )
|
||||
|
||||
text =decodeOptions.decodeEmoji(base_status.spoiler_text)
|
||||
etContent.setSelection(text.length)
|
||||
|
||||
text = decodeOptions.decodeEmoji(base_status.spoiler_text)
|
||||
etContentWarning.setText(text)
|
||||
etContentWarning.setSelection(text.length)
|
||||
cbContentWarning.isChecked = text.isNotEmpty()
|
||||
@ -716,7 +735,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
showQuotedRenote()
|
||||
}
|
||||
|
||||
|
||||
override fun onDestroy() {
|
||||
post_helper.onDestroy()
|
||||
|
||||
@ -783,8 +801,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
if(svEmoji.isEmpty()) return
|
||||
|
||||
val editable = etContent.text
|
||||
if( editable == null ) {
|
||||
val sb = StringBuilder ()
|
||||
if(editable == null) {
|
||||
val sb = StringBuilder()
|
||||
if(selectBefore) {
|
||||
val start = 0
|
||||
sb.append(' ')
|
||||
@ -796,7 +814,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
etContent.setText(sb)
|
||||
etContent.setSelection(sb.length)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
if(editable.isNotEmpty()
|
||||
&& ! CharacterGroup.isWhitespace(editable[editable.length - 1].toInt())
|
||||
) {
|
||||
@ -860,7 +878,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
etContentWarning = findViewById(R.id.etContentWarning)
|
||||
etContent = findViewById(R.id.etContent)
|
||||
|
||||
cbQuoteRenote= findViewById(R.id.cbQuoteRenote)
|
||||
cbQuoteRenote = findViewById(R.id.cbQuoteRenote)
|
||||
|
||||
cbEnquete = findViewById(R.id.cbEnquete)
|
||||
llEnquete = findViewById(R.id.llEnquete)
|
||||
@ -895,10 +913,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
btnPost.setOnClickListener(this)
|
||||
btnRemoveReply.setOnClickListener(this)
|
||||
|
||||
val btnPlugin :ImageButton = findViewById(R.id.btnPlugin)
|
||||
val btnEmojiPicker :ImageButton = findViewById(R.id.btnEmojiPicker)
|
||||
val btnMore: ImageButton = findViewById(R.id.btnMore)
|
||||
|
||||
val btnPlugin : ImageButton = findViewById(R.id.btnPlugin)
|
||||
val btnEmojiPicker : ImageButton = findViewById(R.id.btnEmojiPicker)
|
||||
val btnMore : ImageButton = findViewById(R.id.btnMore)
|
||||
|
||||
btnPlugin.setOnClickListener(this)
|
||||
btnEmojiPicker.setOnClickListener(this)
|
||||
btnMore.setOnClickListener(this)
|
||||
@ -909,11 +927,11 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
iv.setErrorImageResId(Styler.getAttributeResourceId(this, R.attr.ic_unknown))
|
||||
}
|
||||
|
||||
setIcon(btnPost,R.drawable.btn_post)
|
||||
setIcon(btnMore,R.drawable.btn_more)
|
||||
setIcon(btnPlugin,R.drawable.ic_plugin)
|
||||
setIcon(btnEmojiPicker,R.drawable.ic_face)
|
||||
setIcon(btnAttachment,R.drawable.btn_attachment)
|
||||
setIcon(btnPost, R.drawable.btn_post)
|
||||
setIcon(btnMore, R.drawable.btn_more)
|
||||
setIcon(btnPlugin, R.drawable.ic_plugin)
|
||||
setIcon(btnEmojiPicker, R.drawable.ic_face)
|
||||
setIcon(btnAttachment, R.drawable.btn_attachment)
|
||||
|
||||
cbContentWarning.setOnCheckedChangeListener { _, _ ->
|
||||
updateContentWarning()
|
||||
@ -946,15 +964,15 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
etContent.contentMineTypeArray =
|
||||
acceptable_mime_types.toArray(arrayOfNulls<String>(ActPost.acceptable_mime_types.size))
|
||||
etContent.commitContentListener = commitContentListener
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun setIcon(iv:ImageView,drawableId:Int) {
|
||||
private fun setIcon(iv : ImageView, drawableId : Int) {
|
||||
Styler.setIconDrawableId(
|
||||
this,
|
||||
iv,
|
||||
drawableId,
|
||||
Styler.getAttributeColor(this,R.attr.colorColumnHeaderName)
|
||||
Styler.getAttributeColor(this, R.attr.colorColumnHeaderName)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1049,7 +1067,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
post_helper.setInstance(a.host, a.isMisskey)
|
||||
|
||||
// 先読みしてキャッシュに保持しておく
|
||||
App1.custom_emoji_lister.getList(a.host,a.isMisskey) {
|
||||
App1.custom_emoji_lister.getList(a.host, a.isMisskey) {
|
||||
// 何もしない
|
||||
}
|
||||
|
||||
@ -1447,7 +1465,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun performAttachmentOld() {
|
||||
// SAFのIntentで開く
|
||||
try {
|
||||
@ -1562,13 +1579,13 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
// image/j()pg だの image/j(e)pg だの、mime type を誤記するアプリがあまりに多い
|
||||
// クレームで消耗するのを減らすためにファイルヘッダを確認する
|
||||
if(mimeTypeArg == null || mimeTypeArg.startsWith("image/")){
|
||||
val sv = checkImageHeaderList(contentResolver,uri)
|
||||
if( sv != null) return sv
|
||||
if(mimeTypeArg == null || mimeTypeArg.startsWith("image/")) {
|
||||
val sv = checkImageHeaderList(contentResolver, uri)
|
||||
if(sv != null) return sv
|
||||
}
|
||||
|
||||
|
||||
// 既に引数で与えられてる
|
||||
if(mimeTypeArg?.isNotEmpty() == true){
|
||||
if(mimeTypeArg?.isNotEmpty() == true) {
|
||||
return mimeTypeArg
|
||||
}
|
||||
|
||||
@ -1583,8 +1600,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun addAttachment(
|
||||
uri : Uri,
|
||||
@ -1882,11 +1897,13 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
}
|
||||
|
||||
private fun showVisibility() {
|
||||
setIcon(btnVisibility,Styler.getVisibilityIcon(
|
||||
this
|
||||
, account?.isMisskey == true
|
||||
, visibility ?: TootVisibility.Public
|
||||
))
|
||||
setIcon(
|
||||
btnVisibility, Styler.getVisibilityIcon(
|
||||
this
|
||||
, account?.isMisskey == true
|
||||
, visibility ?: TootVisibility.Public
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun performVisibility() {
|
||||
@ -1999,7 +2016,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
|
||||
post_helper.attachment_list = this.attachment_list
|
||||
|
||||
post_helper.emojiMapCustom = App1.custom_emoji_lister.getMap(account.host,account.isMisskey)
|
||||
post_helper.emojiMapCustom =
|
||||
App1.custom_emoji_lister.getMap(account.host, account.isMisskey)
|
||||
|
||||
post_helper.redraft_status_id = redraft_status_id
|
||||
|
||||
@ -2020,7 +2038,7 @@ 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
|
||||
cbQuoteRenote.visibility = if(isReply && isMisskey) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
internal fun showReplyTo() {
|
||||
@ -2092,7 +2110,7 @@ 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_QUOTED_RENOTE, cbQuoteRenote.isChecked)
|
||||
json.put(DRAFT_IS_ENQUETE, isEnquete)
|
||||
|
||||
val array = JSONArray()
|
||||
@ -2246,7 +2264,6 @@ 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)
|
||||
@ -2296,7 +2313,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
||||
in_reply_to_image = reply_image
|
||||
in_reply_to_url = reply_url
|
||||
}
|
||||
|
||||
|
||||
|
||||
updateContentWarning()
|
||||
showMediaAttachment()
|
||||
|
@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.graphics.PorterDuff
|
||||
import android.support.v4.app.ShareCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
@ -68,6 +67,7 @@ internal class DlgContextMenu(
|
||||
val btnBoostAnotherAccount = viewRoot.findViewById<View>(R.id.btnBoostAnotherAccount)
|
||||
val btnReactionAnotherAccount = viewRoot.findViewById<View>(R.id.btnReactionAnotherAccount)
|
||||
val btnReplyAnotherAccount = viewRoot.findViewById<View>(R.id.btnReplyAnotherAccount)
|
||||
val btnQuotedRenote = viewRoot.findViewById<View>(R.id.btnQuotedRenote)
|
||||
val btnDelete = viewRoot.findViewById<View>(R.id.btnDelete)
|
||||
val btnRedraft = viewRoot.findViewById<View>(R.id.btnRedraft)
|
||||
|
||||
@ -124,6 +124,7 @@ internal class DlgContextMenu(
|
||||
btnBoostAnotherAccount.setOnClickListener(this)
|
||||
btnReactionAnotherAccount.setOnClickListener(this)
|
||||
btnReplyAnotherAccount.setOnClickListener(this)
|
||||
btnQuotedRenote.setOnClickListener(this)
|
||||
btnReport.setOnClickListener(this)
|
||||
btnMuteApp.setOnClickListener(this)
|
||||
btnDelete.setOnClickListener(this)
|
||||
@ -728,7 +729,12 @@ internal class DlgContextMenu(
|
||||
access_info,
|
||||
status
|
||||
)
|
||||
|
||||
R.id.btnQuotedRenote-> Action_Toot.replyFromAnotherAccount(
|
||||
activity,
|
||||
access_info,
|
||||
status,
|
||||
quotedRenote = true
|
||||
)
|
||||
R.id.btnConversationAnotherAccount -> status?.let { status ->
|
||||
Action_Toot.conversationOtherInstance(activity, pos, status)
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.EmptyCallback
|
||||
import jp.juggler.subwaytooter.util.LogCategory
|
||||
import jp.juggler.subwaytooter.util.showToast
|
||||
import jp.juggler.subwaytooter.util.toPostRequestBuilder
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.json.JSONObject
|
||||
@ -893,40 +890,25 @@ object Action_Toot {
|
||||
// reply
|
||||
|
||||
fun reply(
|
||||
activity : ActMain, access_info : SavedAccount, status : TootStatus
|
||||
activity : ActMain,
|
||||
access_info : SavedAccount,
|
||||
status : TootStatus,
|
||||
quotedRenote : Boolean = false
|
||||
) {
|
||||
ActPost.open(
|
||||
activity,
|
||||
ActMain.REQUEST_CODE_POST,
|
||||
access_info.db_id,
|
||||
reply_status = status
|
||||
reply_status = status,
|
||||
quotedRenote = quotedRenote
|
||||
)
|
||||
}
|
||||
|
||||
fun replyFromAnotherAccount(
|
||||
activity : ActMain, timeline_account : SavedAccount, status : TootStatus?
|
||||
) {
|
||||
if(status == null) return
|
||||
val who_host = timeline_account.host
|
||||
AccountPicker.pick(
|
||||
activity,
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = activity.getString(R.string.account_picker_reply),
|
||||
accountListArg = makeAccountListNonPseudo(activity, who_host)
|
||||
) { ai ->
|
||||
if(ai.host.equals(status.host_access, ignoreCase = true)) {
|
||||
// アクセス元ホストが同じならステータスIDを使って返信できる
|
||||
reply(activity, ai, status)
|
||||
} else {
|
||||
// それ以外の場合、ステータスのURLを検索APIに投げることで返信できる
|
||||
replyRemote(activity, ai, status.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun replyRemote(
|
||||
activity : ActMain, access_info : SavedAccount, remote_status_url : String?
|
||||
activity : ActMain,
|
||||
access_info : SavedAccount,
|
||||
remote_status_url : String?,
|
||||
quotedRenote : Boolean = false
|
||||
) {
|
||||
if(remote_status_url == null || remote_status_url.isEmpty()) return
|
||||
|
||||
@ -951,7 +933,7 @@ object Action_Toot {
|
||||
|
||||
val ls = local_status
|
||||
if(ls != null) {
|
||||
reply(activity, access_info, ls)
|
||||
reply(activity, access_info, ls, quotedRenote = quotedRenote)
|
||||
} else {
|
||||
showToast(activity, true, result.error)
|
||||
}
|
||||
@ -959,6 +941,47 @@ object Action_Toot {
|
||||
})
|
||||
}
|
||||
|
||||
fun replyFromAnotherAccount(
|
||||
activity : ActMain,
|
||||
timeline_account : SavedAccount,
|
||||
status : TootStatus?,
|
||||
quotedRenote : Boolean = false
|
||||
) {
|
||||
status ?: return
|
||||
val who_host = timeline_account.host
|
||||
|
||||
val accountCallback : SavedAccountCallback = { ai ->
|
||||
if(ai.host.equals(status.host_access, ignoreCase = true)) {
|
||||
// アクセス元ホストが同じならステータスIDを使って返信できる
|
||||
reply(activity, ai, status, quotedRenote = quotedRenote)
|
||||
} else {
|
||||
// それ以外の場合、ステータスのURLを検索APIに投げることで返信できる
|
||||
replyRemote(activity, ai, status.url, quotedRenote = quotedRenote)
|
||||
}
|
||||
}
|
||||
|
||||
if(quotedRenote) {
|
||||
AccountPicker.pick(
|
||||
activity,
|
||||
bAllowPseudo = false,
|
||||
bAllowMisskey = true,
|
||||
bAllowMastodon = false,
|
||||
bAuto = true,
|
||||
message = activity.getString(R.string.account_picker_quoted_renote),
|
||||
callback = accountCallback
|
||||
)
|
||||
} else {
|
||||
AccountPicker.pick(
|
||||
activity,
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = activity.getString(R.string.account_picker_reply),
|
||||
accountListArg = makeAccountListNonPseudo(activity, who_host),
|
||||
callback = accountCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 投稿画面を開く。初期テキストを指定する
|
||||
fun redraft(
|
||||
activity : ActMain,
|
||||
@ -1175,7 +1198,7 @@ object Action_Toot {
|
||||
activity : ActMain,
|
||||
timeline_account : SavedAccount,
|
||||
status : TootStatus?,
|
||||
code :String? = null
|
||||
code : String? = null
|
||||
) {
|
||||
status ?: return
|
||||
|
||||
@ -1200,4 +1223,5 @@ object Action_Toot {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,12 @@ package jp.juggler.subwaytooter.api
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.table.ClientInfo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import okhttp3.*
|
||||
import org.hjson.JsonObject
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
@ -117,7 +115,13 @@ class TootApiClient(
|
||||
}
|
||||
}
|
||||
|
||||
val DEFAULT_JSON_ERROR_PARSER = { json : JSONObject -> json.parseString("error") }
|
||||
val DEFAULT_JSON_ERROR_PARSER = { json : JSONObject ->
|
||||
val v = json.opt("error")
|
||||
when(v) {
|
||||
null,JSONObject.NULL -> null
|
||||
else -> v.toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun simplifyErrorHtml(
|
||||
response : Response,
|
||||
@ -516,7 +520,7 @@ class TootApiClient(
|
||||
val result = TootApiResult.makeWithCaption(instance)
|
||||
if(result.error != null) return result
|
||||
|
||||
val account = this.account ?: return result.setError("account is null")
|
||||
val account = this.account // may null
|
||||
|
||||
try {
|
||||
if(! sendRequest(result) {
|
||||
@ -525,7 +529,7 @@ class TootApiClient(
|
||||
|
||||
request_builder.url("https://$instance$path")
|
||||
|
||||
val access_token = account.getAccessToken()
|
||||
val access_token = account?.getAccessToken()
|
||||
if(access_token?.isNotEmpty() == true) {
|
||||
request_builder.header("Authorization", "Bearer $access_token")
|
||||
}
|
||||
@ -801,13 +805,13 @@ class TootApiClient(
|
||||
|
||||
val user : JSONObject = token_info.optJSONObject("user")
|
||||
?: return result.setError("missing user in the response.")
|
||||
|
||||
|
||||
token_info.remove("user")
|
||||
|
||||
val apiKey = "$access_token$appSecret".encodeUTF8().digestSHA256().encodeHexLower()
|
||||
|
||||
// ユーザ情報を読めたならtokenInfoを保存する
|
||||
EntityId.mayNull( user.parseString("id") )?.putTo(token_info,KEY_USER_ID)
|
||||
EntityId.mayNull(user.parseString("id"))?.putTo(token_info, KEY_USER_ID)
|
||||
token_info.put(KEY_IS_MISSKEY, true)
|
||||
token_info.put(KEY_AUTH_VERSION, AUTH_VERSION)
|
||||
token_info.put(KEY_API_KEY_MISSKEY, apiKey)
|
||||
@ -1042,7 +1046,7 @@ class TootApiClient(
|
||||
// misskeyのインスタンス情報を読めたら、それはmisskeyのインスタンス
|
||||
val r2 = getInstanceInformationMisskey() ?: return null
|
||||
if(r2.jsonObject != null) return r2
|
||||
|
||||
|
||||
// マストドンのインスタンス情報を読めたら、それはマストドンのインスタンス
|
||||
val r1 = getInstanceInformationMastodon() ?: return null
|
||||
if(r1.jsonObject != null) return r1
|
||||
@ -1465,8 +1469,40 @@ fun TootApiClient.syncAccountByAcct(accessInfo : SavedAccount, acct : String) :
|
||||
}
|
||||
}
|
||||
|
||||
fun TootApiClient.syncStatus(accessInfo : SavedAccount, url : String) =
|
||||
if(accessInfo.isMisskey) {
|
||||
fun TootApiClient.syncStatus(accessInfo : SavedAccount, urlArg : String) : TootApiResult? {
|
||||
|
||||
var url = urlArg
|
||||
|
||||
// misskey の投稿URLは外部タンスの投稿を複製したものの可能性がある
|
||||
// これを投稿元タンスのURLに変換しないと、投稿の同期には使えない
|
||||
val m = TootStatus.reStatusPageMisskey.matcher(urlArg)
|
||||
if(m.find()) {
|
||||
val host = m.group(1)
|
||||
val client2 = TootApiClient(context, callback = callback)
|
||||
client2.instance =host
|
||||
val params = JSONObject().put("uri", urlArg)
|
||||
val result = client2.request("/api/ap/show", params.toPostRequestBuilder())
|
||||
if(result == null || result.error != null) return result
|
||||
|
||||
val obj = parseMisskeyApShow(
|
||||
TootParser(context, accessInfo,serviceType = ServiceType.MISSKEY),
|
||||
result.jsonObject
|
||||
) as? TootStatus
|
||||
|
||||
if( obj != null ){
|
||||
if( host .equals(accessInfo.host,ignoreCase = true)){
|
||||
result.data = obj
|
||||
return result
|
||||
}
|
||||
val uri = obj.uri
|
||||
if(uri?.isNotEmpty() == true){
|
||||
url = uri
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return if(accessInfo.isMisskey) {
|
||||
val params = accessInfo.putMisskeyApiToken().put("uri", url)
|
||||
val result = request("/api/ap/show", params.toPostRequestBuilder())
|
||||
if(result != null) {
|
||||
@ -1490,6 +1526,8 @@ fun TootApiClient.syncStatus(accessInfo : SavedAccount, url : String) =
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inline fun <Z : Any?> String?.useNotEmpty(block : (String) -> Z?) : Z? =
|
||||
if(this?.isNotEmpty() == true) {
|
||||
|
@ -658,6 +658,15 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
|
||||
@Suppress("HasPlatformType")
|
||||
private val reTootUriAP2 = Pattern.compile("https?://([^/]+)/@[A-Za-z0-9_]+/(\\d+)")
|
||||
|
||||
// 公開ステータスページのURL マストドン
|
||||
@Suppress("HasPlatformType")
|
||||
val reStatusPage = Pattern.compile("""\Ahttps://([^/]+)/@([A-Za-z0-9_]+)/(\d+)(?:\z|[?#])""")
|
||||
|
||||
// 公開ステータスページのURL Misskey
|
||||
@Suppress("HasPlatformType")
|
||||
val reStatusPageMisskey = Pattern.compile("""\Ahttps://([^/]+)/notes/([0-9a-f]{24})\b""")
|
||||
|
||||
|
||||
const val INVALID_ID = - 1L
|
||||
|
||||
fun parseListTootsearch(
|
||||
|
@ -101,6 +101,7 @@
|
||||
android:text="@string/share_url_more"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBoostedBy"
|
||||
android:layout_width="match_parent"
|
||||
@ -206,6 +207,21 @@
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnQuotedRenote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="32dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/quote_renote"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProfilePin"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -18,6 +18,7 @@
|
||||
<string name="account_picker_open_setting">どのアカウントの設定を開きますか?</string>
|
||||
<string name="account_picker_open_user_who">どのアカウントでユーザ %1$s のプロフィールを確認しますか?</string>
|
||||
<string name="account_picker_reply">どのアカウントで返信しますか?</string>
|
||||
<string name="account_picker_quoted_renote">どのアカウントで引用リノートしますか?</string>
|
||||
<string name="account_picker_toot">どのアカウントでトゥートしますか?</string>
|
||||
<string name="account_remove">アカウントの削除</string>
|
||||
<string name="account_select_please">アカウントを選択してください</string>
|
||||
@ -809,5 +810,6 @@
|
||||
<string name="quick_toot_bar_background_color">簡易投稿入力の背景色</string>
|
||||
<string name="dont_show_preview_card">プレビューカードを表示しない</string>
|
||||
<string name="instance_does_not_support_push_api_pleroma">このインスタンスはプッシュ購読APIに対応していません。多分Pleromaです。</string>
|
||||
<string name="quote_renote">引用リノート… (Misskey)</string>
|
||||
|
||||
</resources>
|
||||
|
@ -339,6 +339,7 @@
|
||||
<string name="minimum_column_width">Minimum column width (default=300(dp),app restart required)</string>
|
||||
<string name="media_thumbnail_height">Media Thumbnail height (default=64(dp), app restart required)</string>
|
||||
<string name="account_picker_reply">Which account do you reply from?</string>
|
||||
<string name="account_picker_quoted_renote">Which account do you quoted renote from?</string>
|
||||
<string name="reply_from_another_account">Reply from another account</string>
|
||||
<string name="launcher_icon_by">Thanks to フタバ for the application icon.</string>
|
||||
<string name="ssl_bug_7_0">Android 7.0 can only use the elliptic curve \"prime256v1\".\nUnfortunately your instance seems to not support it.\nThis bug has been fixed in Android 7.1.1.\nYou can either upgrade your OS or ask the administrator of your instance to add support to the elliptic curve \"prime256v1\".\nSee also https://code.google.com/p/android/issues/detail?id=224438</string>
|
||||
@ -827,5 +828,6 @@
|
||||
<string name="quick_toot_bar_background_color">Quick toot bar background color</string>
|
||||
<string name="dont_show_preview_card">Don\'t show preview card</string>
|
||||
<string name="instance_does_not_support_push_api_pleroma">This instance does not support push subscription API. maybe it is Pleroma</string>
|
||||
<string name="quote_renote">Quoted renote… (Misskey)</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user