mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-02-05 13:17:43 +01:00
アプリ設定/挙動に「翻訳ボタンを表示する」を追加
This commit is contained in:
parent
4f43dba3c4
commit
0396264e27
@ -256,6 +256,8 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".ChooseReceiver" android:exported="false"/>
|
||||
|
||||
<!-- okhttp3クライアントを指定する必要があるため、マニフェスト経由での組み込みは行わない -->
|
||||
<!--<meta-data-->
|
||||
<!--android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"-->
|
||||
|
@ -1,5 +1,6 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
@ -27,6 +28,7 @@ import org.jetbrains.anko.backgroundDrawable
|
||||
import org.jetbrains.anko.textColor
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.ref.WeakReference
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -157,6 +159,7 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
private var etRoundRatio : EditText? = null
|
||||
private var etBoostAlpha : EditText? = null
|
||||
private var etMediaReadTimeout : EditText? = null
|
||||
private var etTranslateAppComponent : EditText? = null
|
||||
|
||||
private var tvTimelineFontUrl : TextView? = null
|
||||
private var timeline_font : String? = null
|
||||
@ -201,6 +204,13 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
private var hasLinkColorUi = false
|
||||
private var hasColumnColorDefaultUi = false
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
checkIntentChoiced()
|
||||
}
|
||||
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
@ -378,6 +388,8 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
, R.id.btnBackgroundColorVotedReset
|
||||
, R.id.btnBackgroundColorFollowRequestedEdit
|
||||
, R.id.btnBackgroundColorFollowRequestedReset
|
||||
, R.id.btnTranslateAppComponentEdit
|
||||
, R.id.btnTranslateAppComponentReset
|
||||
).forEach {
|
||||
findViewById<View>(it)?.setOnClickListener(this)
|
||||
}
|
||||
@ -432,6 +444,9 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
etMediaReadTimeout = findViewById(R.id.etMediaReadTimeout)
|
||||
etMediaReadTimeout?.addTextChangedListener(this)
|
||||
|
||||
etTranslateAppComponent = findViewById(R.id.etTranslateAppComponent)
|
||||
etTranslateAppComponent?.addTextChangedListener(this)
|
||||
|
||||
tvTimelineFontSize = findViewById(R.id.tvTimelineFontSize)
|
||||
tvAcctFontSize = findViewById(R.id.tvAcctFontSize)
|
||||
tvNotificationTlFontSize = findViewById(R.id.tvNotificationTlFontSize)
|
||||
@ -587,6 +602,7 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
etBoostAlpha?.setText(Pref.spBoostAlpha(pref))
|
||||
|
||||
etMediaReadTimeout?.setText(Pref.spMediaReadTimeout(pref))
|
||||
etTranslateAppComponent?.setText(Pref.spTranslateAppComponent(pref))
|
||||
|
||||
timeline_font = Pref.spTimelineFont(pref)
|
||||
timeline_font_bold = Pref.spTimelineFontBold(pref)
|
||||
@ -674,6 +690,7 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
putText(Pref.spRoundRatio, etRoundRatio)
|
||||
putText(Pref.spBoostAlpha, etBoostAlpha)
|
||||
putText(Pref.spMediaReadTimeout, etMediaReadTimeout)
|
||||
putText(Pref.spTranslateAppComponent,etTranslateAppComponent)
|
||||
|
||||
fun putIf(hasUi : Boolean, sp : StringPref, value : String) {
|
||||
if(! hasUi) return
|
||||
@ -1114,6 +1131,54 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
saveUIToData()
|
||||
}
|
||||
|
||||
R.id.btnTranslateAppComponentEdit -> {
|
||||
|
||||
val intent = Intent()
|
||||
intent.action = Intent.ACTION_SEND
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample))
|
||||
|
||||
// このifはwhenにしてはならない。APIバージョン関連の警告が出てしまう
|
||||
@Suppress("CascadeIf")
|
||||
if(intent.resolveActivity(packageManager) == null) {
|
||||
// ACTION_SENDを受け取れるアプリがインストールされてない
|
||||
showToast(this, true, getString(R.string.missing_app_can_receive_action_send))
|
||||
} else if(Build.VERSION.SDK_INT <= 21) {
|
||||
// createChooserにIntentSenderを指定できるのはAndroid 22以降
|
||||
showToast(
|
||||
this,
|
||||
true,
|
||||
getString(R.string.translation_app_chooser_works_android_5_1)
|
||||
)
|
||||
} else try {
|
||||
ChooseReceiver.lastComponentName = null
|
||||
ChooseReceiver.setCallback{ checkIntentChoiced() }
|
||||
|
||||
val receiver = Intent(this, ChooseReceiver::class.java)
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
this,
|
||||
1,
|
||||
receiver,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
intent,
|
||||
getString(R.string.select_translate_app),
|
||||
pendingIntent.intentSender
|
||||
)
|
||||
)
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
showToast(this, ex, "btnTranslateAppComponentEdit failed.")
|
||||
}
|
||||
}
|
||||
|
||||
R.id.btnTranslateAppComponentReset -> {
|
||||
etTranslateAppComponent?.setText("")
|
||||
saveUIToData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1642,4 +1707,16 @@ class ActAppSettingChild : AppCompatActivity()
|
||||
return list[position].id
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIntentChoiced(){
|
||||
if( isDestroyed ) return
|
||||
|
||||
val cn = ChooseReceiver.lastComponentName
|
||||
if(cn != null && etTranslateAppComponent != null) {
|
||||
etTranslateAppComponent?.setText("${cn.packageName}/${cn.className}")
|
||||
saveUIToData()
|
||||
ChooseReceiver.lastComponentName = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,21 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.table.MutedWord
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.TootTextEncoder
|
||||
import jp.juggler.util.LogCategory
|
||||
import jp.juggler.util.copyToClipboard
|
||||
import jp.juggler.util.hideKeyboard
|
||||
import jp.juggler.util.showToast
|
||||
import java.util.*
|
||||
|
||||
class ActText : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
@ -32,281 +31,6 @@ class ActText : AppCompatActivity(), View.OnClickListener {
|
||||
internal const val EXTRA_CONTENT_END = "content_end"
|
||||
internal const val EXTRA_ACCOUNT_DB_ID = "account_db_id"
|
||||
|
||||
private fun StringBuilder.addAfterLine( text : CharSequence) {
|
||||
if( isNotEmpty() && this[length - 1] != '\n') {
|
||||
append('\n')
|
||||
}
|
||||
append(text)
|
||||
}
|
||||
|
||||
private fun addHeader(
|
||||
context : Context,
|
||||
sb : StringBuilder,
|
||||
key_str_id : Int,
|
||||
value : Any?
|
||||
) {
|
||||
if(sb.isNotEmpty() && sb[sb.length - 1] != '\n') {
|
||||
sb.append('\n')
|
||||
}
|
||||
sb.addAfterLine( context.getString(key_str_id))
|
||||
sb.append(": ")
|
||||
sb.append(value?.toString() ?: "(null)")
|
||||
}
|
||||
|
||||
private fun encodeStatus(
|
||||
intent : Intent,
|
||||
context : Context,
|
||||
access_info : SavedAccount,
|
||||
status : TootStatus
|
||||
) {
|
||||
val sb = StringBuilder()
|
||||
|
||||
addHeader(context, sb, R.string.send_header_url, status.url)
|
||||
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_date,
|
||||
TootStatus.formatTime(context, status.time_created_at, false)
|
||||
)
|
||||
|
||||
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_from_acct,
|
||||
access_info.getFullAcct(status.account)
|
||||
)
|
||||
|
||||
val sv : String? = status.spoiler_text
|
||||
if(sv != null && sv.isNotEmpty()) {
|
||||
addHeader(context, sb, R.string.send_header_content_warning, sv)
|
||||
}
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
intent.putExtra(EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(DecodeOptions(context, access_info).decodeHTML(status.content))
|
||||
|
||||
encodePolls(sb,context,status)
|
||||
|
||||
intent.putExtra(EXTRA_CONTENT_END, sb.length)
|
||||
|
||||
dumpAttachment(sb, status.media_attachments)
|
||||
|
||||
sb.addAfterLine( String.format(Locale.JAPAN, "Status-Source: %s", status.json))
|
||||
|
||||
sb.addAfterLine( "")
|
||||
intent.putExtra(EXTRA_TEXT, sb.toString())
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun dumpAttachment(sb : StringBuilder, src : ArrayList<TootAttachmentLike>?) {
|
||||
if(src == null) return
|
||||
var i = 0
|
||||
for(ma in src) {
|
||||
++ i
|
||||
if(ma is TootAttachment) {
|
||||
sb.addAfterLine( "\n")
|
||||
sb.addAfterLine( String.format(Locale.JAPAN, "Media-%d-Url: %s", i, ma.url))
|
||||
sb.addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Remote-Url: %s", i, ma.remote_url)
|
||||
)
|
||||
sb.addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Preview-Url: %s", i, ma.preview_url)
|
||||
)
|
||||
sb. addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Text-Url: %s", i, ma.text_url)
|
||||
)
|
||||
} else if(ma is TootAttachmentMSP) {
|
||||
sb.addAfterLine( "\n")
|
||||
sb. addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Preview-Url: %s", i, ma.preview_url)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun encodePolls(sb :StringBuilder, context:Context,status : TootStatus) {
|
||||
val enquete = status.enquete ?: return
|
||||
val items = enquete.items ?: return
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
|
||||
|
||||
val canVote = when(enquete.pollType) {
|
||||
|
||||
// friends.nico の場合は本文に投票の選択肢が含まれるので
|
||||
// アプリ側での文字列化は不要
|
||||
TootPollsType.FriendsNico -> return
|
||||
|
||||
// MastodonとMisskeyは投票の選択肢が本文に含まれないので
|
||||
// アプリ側で文字列化する
|
||||
|
||||
TootPollsType.Mastodon -> when {
|
||||
enquete.expired -> false
|
||||
now >= enquete.expired_at -> false
|
||||
enquete.myVoted != null -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
TootPollsType.Misskey -> enquete.myVoted == null
|
||||
}
|
||||
|
||||
sb.addAfterLine("\n")
|
||||
|
||||
items.forEachIndexed { index, choice ->
|
||||
encodePollChoice(sb, context, enquete, canVote, index, choice)
|
||||
}
|
||||
|
||||
when(enquete.pollType) {
|
||||
TootPollsType.Mastodon -> encodePollFooterMastodon(sb, context, enquete)
|
||||
|
||||
else->{}
|
||||
}
|
||||
}
|
||||
|
||||
private fun encodePollChoice(
|
||||
sb : StringBuilder,
|
||||
context : Context,
|
||||
enquete : TootPolls,
|
||||
canVote : Boolean,
|
||||
i : Int,
|
||||
item : TootPollsChoice
|
||||
) {
|
||||
|
||||
val text = when(enquete.pollType) {
|
||||
TootPollsType.Misskey -> {
|
||||
val sb2 = StringBuilder().append(item.decoded_text)
|
||||
if(enquete.myVoted != null) {
|
||||
sb2.append(" / ")
|
||||
sb2.append(context.getString(R.string.vote_count_text, item.votes))
|
||||
if(i == enquete.myVoted) sb2.append(' ').append(0x2713.toChar())
|
||||
}
|
||||
sb2
|
||||
}
|
||||
|
||||
TootPollsType.FriendsNico -> {
|
||||
item.decoded_text
|
||||
}
|
||||
|
||||
TootPollsType.Mastodon -> if(canVote) {
|
||||
item.decoded_text
|
||||
} else {
|
||||
val sb2 = StringBuilder().append(item.decoded_text)
|
||||
if(! canVote) {
|
||||
sb2.append(" / ")
|
||||
sb2.append(
|
||||
when(val v = item.votes) {
|
||||
null -> context.getString(R.string.vote_count_unavailable)
|
||||
else -> context.getString(R.string.vote_count_text, v)
|
||||
}
|
||||
)
|
||||
}
|
||||
sb2
|
||||
}
|
||||
}
|
||||
|
||||
sb.addAfterLine(text)
|
||||
}
|
||||
|
||||
private fun encodePollFooterMastodon(
|
||||
sb : StringBuilder,
|
||||
context : Context,
|
||||
enquete : TootPolls
|
||||
) {
|
||||
val line = StringBuilder()
|
||||
|
||||
val votes_count = enquete.votes_count ?: 0
|
||||
when {
|
||||
votes_count == 1 -> line.append(context.getString(R.string.vote_1))
|
||||
votes_count > 1 -> line.append(context.getString(R.string.vote_2, votes_count))
|
||||
}
|
||||
|
||||
when(val t = enquete.expired_at) {
|
||||
|
||||
Long.MAX_VALUE -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
if(line.isNotEmpty()) line.append(" ")
|
||||
line.append(
|
||||
context.getString(
|
||||
R.string.vote_expire_at,
|
||||
TootStatus.formatTime(context, t, false)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
sb.addAfterLine(line)
|
||||
}
|
||||
|
||||
private fun encodeAccount(
|
||||
intent : Intent,
|
||||
context : Context,
|
||||
access_info : SavedAccount,
|
||||
who : TootAccount
|
||||
) {
|
||||
val sb = StringBuilder()
|
||||
|
||||
intent.putExtra(EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(who.display_name)
|
||||
sb.append("\n")
|
||||
sb.append("@")
|
||||
sb.append(access_info.getFullAcct(who))
|
||||
sb.append("\n")
|
||||
|
||||
intent.putExtra(EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(who.url)
|
||||
intent.putExtra(EXTRA_CONTENT_END, sb.length)
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
sb.append(DecodeOptions(context, access_info).decodeHTML(who.note))
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_name, who.display_name)
|
||||
addHeader(context, sb, R.string.send_header_account_acct, access_info.getFullAcct(who))
|
||||
addHeader(context, sb, R.string.send_header_account_url, who.url)
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_image_avatar, who.avatar)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_image_avatar_static,
|
||||
who.avatar_static
|
||||
)
|
||||
addHeader(context, sb, R.string.send_header_account_image_header, who.header)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_image_header_static,
|
||||
who.header_static
|
||||
)
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_created_at, who.created_at)
|
||||
addHeader(context, sb, R.string.send_header_account_statuses_count, who.statuses_count)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_followers_count,
|
||||
who.followers_count
|
||||
)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_following_count,
|
||||
who.following_count
|
||||
)
|
||||
addHeader(context, sb, R.string.send_header_account_locked, who.locked)
|
||||
|
||||
sb.addAfterLine("")
|
||||
intent.putExtra(EXTRA_TEXT, sb.toString())
|
||||
}
|
||||
|
||||
fun open(
|
||||
activity : ActMain,
|
||||
request_code : Int,
|
||||
@ -315,8 +39,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
|
||||
) {
|
||||
val intent = Intent(activity, ActText::class.java)
|
||||
intent.putExtra(EXTRA_ACCOUNT_DB_ID, access_info.db_id)
|
||||
encodeStatus(intent, activity, access_info, status)
|
||||
|
||||
TootTextEncoder.encodeStatus(intent, activity, access_info, status)
|
||||
activity.startActivityForResult(intent, request_code)
|
||||
}
|
||||
|
||||
@ -328,8 +51,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
|
||||
) {
|
||||
val intent = Intent(activity, ActText::class.java)
|
||||
intent.putExtra(EXTRA_ACCOUNT_DB_ID, access_info.db_id)
|
||||
encodeAccount(intent, activity, access_info, who)
|
||||
|
||||
TootTextEncoder.encodeAccount(intent, activity, access_info, who)
|
||||
activity.startActivityForResult(intent, request_code)
|
||||
}
|
||||
|
||||
@ -368,11 +90,11 @@ class ActText : AppCompatActivity(), View.OnClickListener {
|
||||
etText.setText(sv)
|
||||
|
||||
// Android 9 以降ではフォーカスがないとsetSelectionできない
|
||||
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
etText.requestFocus()
|
||||
etText.hideKeyboard()
|
||||
}
|
||||
|
||||
|
||||
etText.setSelection(content_start, content_end)
|
||||
}
|
||||
}
|
||||
|
24
app/src/main/java/jp/juggler/subwaytooter/ChooseReceiver.kt
Normal file
24
app/src/main/java/jp/juggler/subwaytooter/ChooseReceiver.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class ChooseReceiver :BroadcastReceiver(){
|
||||
|
||||
companion object{
|
||||
var lastComponentName: ComponentName? = null
|
||||
var refCallback : WeakReference<()->Unit>? = null
|
||||
|
||||
fun setCallback(cb:()->Unit){
|
||||
refCallback = WeakReference(cb)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context,intent: Intent?) {
|
||||
lastComponentName = intent?.extras?.get(Intent.EXTRA_CHOSEN_COMPONENT) as? ComponentName
|
||||
refCallback?.get()?.invoke()
|
||||
}
|
||||
}
|
@ -394,6 +394,13 @@ object Pref {
|
||||
R.id.swCustomEmojiSeparatorZwsp
|
||||
)
|
||||
|
||||
val bpShowTranslateButton = BooleanPref(
|
||||
"ShowTranslateButton",
|
||||
false,
|
||||
R.id.swShowTranslateButton
|
||||
)
|
||||
|
||||
|
||||
// int
|
||||
|
||||
val ipBackButtonAction = IntPref("back_button_action", 0)
|
||||
@ -493,6 +500,8 @@ object Pref {
|
||||
val spQuickTootMacro = StringPref("QuickTootMacro","")
|
||||
val spQuickTootVisibility = StringPref("QuickTootVisibility","")
|
||||
|
||||
val spTranslateAppComponent = StringPref("TranslateAppComponent","")
|
||||
|
||||
// long
|
||||
val lpTabletTootDefaultAccount = LongPref("tablet_toot_default_account", - 1L)
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupWindow
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.flexbox.FlexWrap
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
@ -19,6 +21,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.util.TootTextEncoder
|
||||
import jp.juggler.subwaytooter.util.startMargin
|
||||
import jp.juggler.subwaytooter.view.CountImageButton
|
||||
import jp.juggler.util.*
|
||||
@ -37,6 +40,16 @@ internal class StatusButtons(
|
||||
|
||||
companion object {
|
||||
val log = LogCategory("StatusButtons")
|
||||
|
||||
fun String.toComponentName() : ComponentName? {
|
||||
try {
|
||||
val idx = indexOf('/')
|
||||
if(idx >= 1) return ComponentName(substring(0 until idx), substring(idx + 1))
|
||||
} catch(ex:Throwable) {
|
||||
log.e(ex,"incorrect component name $this")
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private val access_info : SavedAccount
|
||||
@ -53,6 +66,7 @@ internal class StatusButtons(
|
||||
private val llFollow2 = holder.llFollow2
|
||||
private val btnFollow2 = holder.btnFollow2
|
||||
private val ivFollowedBy2 = holder.ivFollowedBy2
|
||||
private val btnTranslate = holder.btnTranslate
|
||||
private val btnMore = holder.btnMore
|
||||
|
||||
private val color_normal = column.getContentColor()
|
||||
@ -69,6 +83,7 @@ internal class StatusButtons(
|
||||
btnFavourite.setOnLongClickListener(this)
|
||||
btnFollow2.setOnClickListener(this)
|
||||
btnFollow2.setOnLongClickListener(this)
|
||||
btnTranslate.setOnClickListener(this)
|
||||
btnMore.setOnClickListener(this)
|
||||
btnConversation.setOnClickListener(this)
|
||||
btnConversation.setOnLongClickListener(this)
|
||||
@ -210,6 +225,15 @@ internal class StatusButtons(
|
||||
relation
|
||||
}
|
||||
|
||||
if(vg(btnTranslate, Pref.bpShowTranslateButton(activity.pref))) {
|
||||
setButton(
|
||||
btnTranslate,
|
||||
true,
|
||||
color_normal,
|
||||
R.drawable.ic_translate,
|
||||
activity.getString(R.string.translate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setButton(
|
||||
@ -234,6 +258,25 @@ internal class StatusButtons(
|
||||
b.isEnabled = enabled
|
||||
}
|
||||
|
||||
private fun setButton(
|
||||
b : ImageButton,
|
||||
enabled : Boolean,
|
||||
color : Int,
|
||||
drawableId : Int,
|
||||
contentDescription : String
|
||||
) {
|
||||
val alpha = Styler.boost_alpha
|
||||
val d = createColoredDrawable(
|
||||
activity,
|
||||
drawableId,
|
||||
color,
|
||||
alpha
|
||||
)
|
||||
b.setImageDrawable(d)
|
||||
b.contentDescription = contentDescription
|
||||
b.isEnabled = enabled
|
||||
}
|
||||
|
||||
override fun onClick(v : View) {
|
||||
|
||||
close_window?.dismiss()
|
||||
@ -381,6 +424,38 @@ internal class StatusButtons(
|
||||
}
|
||||
}
|
||||
|
||||
btnTranslate -> {
|
||||
|
||||
try {
|
||||
val sv = TootTextEncoder.encodeStatusForTranslate(activity, access_info, status)
|
||||
|
||||
var cn = Pref.spTranslateAppComponent(activity.pref)
|
||||
.toComponentName()
|
||||
if(cn == null) {
|
||||
cn = activity.getString(R.string.translate_app_component_default)
|
||||
.toComponentName()
|
||||
if(cn == null) {
|
||||
showToast(
|
||||
activity,
|
||||
true,
|
||||
"please check translate app component in app setting."
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent()
|
||||
intent.action = Intent.ACTION_SEND
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, sv)
|
||||
intent.component = cn
|
||||
activity.startActivity(intent)
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
showToast(activity, ex, "send failed.")
|
||||
}
|
||||
}
|
||||
|
||||
btnMore -> DlgContextMenu(
|
||||
activity,
|
||||
column,
|
||||
@ -463,6 +538,7 @@ class StatusButtonsViewHolder(
|
||||
lateinit var llFollow2 : View
|
||||
lateinit var btnFollow2 : ImageButton
|
||||
lateinit var ivFollowedBy2 : ImageView
|
||||
lateinit var btnTranslate : ImageButton
|
||||
lateinit var btnMore : ImageButton
|
||||
|
||||
init {
|
||||
@ -555,6 +631,20 @@ class StatusButtonsViewHolder(
|
||||
}.lparams(matchParent, matchParent)
|
||||
}
|
||||
|
||||
btnTranslate = imageButton {
|
||||
background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.btn_bg_transparent
|
||||
)
|
||||
setPadding(paddingH, paddingV, paddingH, paddingV)
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
|
||||
contentDescription = context.getString(R.string.translate)
|
||||
imageResource = R.drawable.ic_translate
|
||||
}.lparams(buttonHeight, buttonHeight) {
|
||||
startMargin = marginBetween
|
||||
}
|
||||
|
||||
btnMore = imageButton {
|
||||
background = ContextCompat.getDrawable(
|
||||
context,
|
||||
|
@ -0,0 +1,307 @@
|
||||
package jp.juggler.subwaytooter.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import jp.juggler.subwaytooter.ActText
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import java.util.*
|
||||
|
||||
object TootTextEncoder {
|
||||
private fun StringBuilder.addAfterLine( text : CharSequence) {
|
||||
if( isNotEmpty() && this[length - 1] != '\n') {
|
||||
append('\n')
|
||||
}
|
||||
append(text)
|
||||
}
|
||||
|
||||
private fun addHeader(
|
||||
context : Context,
|
||||
sb : StringBuilder,
|
||||
key_str_id : Int,
|
||||
value : Any?
|
||||
) {
|
||||
if(sb.isNotEmpty() && sb[sb.length - 1] != '\n') {
|
||||
sb.append('\n')
|
||||
}
|
||||
sb.addAfterLine( context.getString(key_str_id))
|
||||
sb.append(": ")
|
||||
sb.append(value?.toString() ?: "(null)")
|
||||
}
|
||||
|
||||
fun encodeStatus(
|
||||
intent : Intent,
|
||||
context : Context,
|
||||
access_info : SavedAccount,
|
||||
status : TootStatus
|
||||
) {
|
||||
val sb = StringBuilder()
|
||||
|
||||
addHeader(context, sb, R.string.send_header_url, status.url)
|
||||
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_date,
|
||||
TootStatus.formatTime(context, status.time_created_at, false)
|
||||
)
|
||||
|
||||
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_from_acct,
|
||||
access_info.getFullAcct(status.account)
|
||||
)
|
||||
|
||||
val sv : String? = status.spoiler_text
|
||||
if(sv != null && sv.isNotEmpty()) {
|
||||
addHeader(context, sb, R.string.send_header_content_warning, sv)
|
||||
}
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
intent.putExtra(ActText.EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(DecodeOptions(context, access_info).decodeHTML(status.content))
|
||||
|
||||
encodePolls(sb,context,status)
|
||||
|
||||
intent.putExtra(ActText.EXTRA_CONTENT_END, sb.length)
|
||||
|
||||
dumpAttachment(sb, status.media_attachments)
|
||||
|
||||
sb.addAfterLine( String.format(Locale.JAPAN, "Status-Source: %s", status.json))
|
||||
|
||||
sb.addAfterLine( "")
|
||||
intent.putExtra(ActText.EXTRA_TEXT, sb.toString())
|
||||
}
|
||||
|
||||
|
||||
fun encodeStatusForTranslate(
|
||||
context : Context,
|
||||
access_info : SavedAccount,
|
||||
status : TootStatus
|
||||
) :String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
val sv : String? = status.spoiler_text
|
||||
if(sv != null && sv.isNotEmpty()) {
|
||||
sb.append(sv).append("\n\n")
|
||||
}
|
||||
|
||||
sb.append(DecodeOptions(context, access_info).decodeHTML(status.content))
|
||||
|
||||
encodePolls(sb,context,status)
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
|
||||
private fun dumpAttachment(sb : StringBuilder, src : ArrayList<TootAttachmentLike>?) {
|
||||
if(src == null) return
|
||||
var i = 0
|
||||
for(ma in src) {
|
||||
++ i
|
||||
if(ma is TootAttachment) {
|
||||
sb.addAfterLine( "\n")
|
||||
sb.addAfterLine( String.format(Locale.JAPAN, "Media-%d-Url: %s", i, ma.url))
|
||||
sb.addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Remote-Url: %s", i, ma.remote_url)
|
||||
)
|
||||
sb.addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Preview-Url: %s", i, ma.preview_url)
|
||||
)
|
||||
sb. addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Text-Url: %s", i, ma.text_url)
|
||||
)
|
||||
} else if(ma is TootAttachmentMSP) {
|
||||
sb.addAfterLine( "\n")
|
||||
sb. addAfterLine(
|
||||
String.format(Locale.JAPAN, "Media-%d-Preview-Url: %s", i, ma.preview_url)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun encodePolls(sb :StringBuilder, context: Context, status : TootStatus) {
|
||||
val enquete = status.enquete ?: return
|
||||
val items = enquete.items ?: return
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
|
||||
|
||||
val canVote = when(enquete.pollType) {
|
||||
|
||||
// friends.nico の場合は本文に投票の選択肢が含まれるので
|
||||
// アプリ側での文字列化は不要
|
||||
TootPollsType.FriendsNico -> return
|
||||
|
||||
// MastodonとMisskeyは投票の選択肢が本文に含まれないので
|
||||
// アプリ側で文字列化する
|
||||
|
||||
TootPollsType.Mastodon -> when {
|
||||
enquete.expired -> false
|
||||
now >= enquete.expired_at -> false
|
||||
enquete.myVoted != null -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
TootPollsType.Misskey -> enquete.myVoted == null
|
||||
}
|
||||
|
||||
sb.addAfterLine("\n")
|
||||
|
||||
items.forEachIndexed { index, choice ->
|
||||
encodePollChoice(sb, context, enquete, canVote, index, choice)
|
||||
}
|
||||
|
||||
when(enquete.pollType) {
|
||||
TootPollsType.Mastodon -> encodePollFooterMastodon(sb, context, enquete)
|
||||
|
||||
else->{}
|
||||
}
|
||||
}
|
||||
|
||||
private fun encodePollChoice(
|
||||
sb : StringBuilder,
|
||||
context : Context,
|
||||
enquete : TootPolls,
|
||||
canVote : Boolean,
|
||||
i : Int,
|
||||
item : TootPollsChoice
|
||||
) {
|
||||
|
||||
val text = when(enquete.pollType) {
|
||||
TootPollsType.Misskey -> {
|
||||
val sb2 = StringBuilder().append(item.decoded_text)
|
||||
if(enquete.myVoted != null) {
|
||||
sb2.append(" / ")
|
||||
sb2.append(context.getString(R.string.vote_count_text, item.votes))
|
||||
if(i == enquete.myVoted) sb2.append(' ').append(0x2713.toChar())
|
||||
}
|
||||
sb2
|
||||
}
|
||||
|
||||
TootPollsType.FriendsNico -> {
|
||||
item.decoded_text
|
||||
}
|
||||
|
||||
TootPollsType.Mastodon -> if(canVote) {
|
||||
item.decoded_text
|
||||
} else {
|
||||
val sb2 = StringBuilder().append(item.decoded_text)
|
||||
if(! canVote) {
|
||||
sb2.append(" / ")
|
||||
sb2.append(
|
||||
when(val v = item.votes) {
|
||||
null -> context.getString(R.string.vote_count_unavailable)
|
||||
else -> context.getString(R.string.vote_count_text, v)
|
||||
}
|
||||
)
|
||||
}
|
||||
sb2
|
||||
}
|
||||
}
|
||||
|
||||
sb.addAfterLine(text)
|
||||
}
|
||||
|
||||
private fun encodePollFooterMastodon(
|
||||
sb : StringBuilder,
|
||||
context : Context,
|
||||
enquete : TootPolls
|
||||
) {
|
||||
val line = StringBuilder()
|
||||
|
||||
val votes_count = enquete.votes_count ?: 0
|
||||
when {
|
||||
votes_count == 1 -> line.append(context.getString(R.string.vote_1))
|
||||
votes_count > 1 -> line.append(context.getString(R.string.vote_2, votes_count))
|
||||
}
|
||||
|
||||
when(val t = enquete.expired_at) {
|
||||
|
||||
Long.MAX_VALUE -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
if(line.isNotEmpty()) line.append(" ")
|
||||
line.append(
|
||||
context.getString(
|
||||
R.string.vote_expire_at,
|
||||
TootStatus.formatTime(context, t, false)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
sb.addAfterLine(line)
|
||||
}
|
||||
|
||||
fun encodeAccount(
|
||||
intent : Intent,
|
||||
context : Context,
|
||||
access_info : SavedAccount,
|
||||
who : TootAccount
|
||||
) {
|
||||
val sb = StringBuilder()
|
||||
|
||||
intent.putExtra(ActText.EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(who.display_name)
|
||||
sb.append("\n")
|
||||
sb.append("@")
|
||||
sb.append(access_info.getFullAcct(who))
|
||||
sb.append("\n")
|
||||
|
||||
intent.putExtra(ActText.EXTRA_CONTENT_START, sb.length)
|
||||
sb.append(who.url)
|
||||
intent.putExtra(ActText.EXTRA_CONTENT_END, sb.length)
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
sb.append(DecodeOptions(context, access_info).decodeHTML(who.note))
|
||||
|
||||
sb.addAfterLine( "\n")
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_name, who.display_name)
|
||||
addHeader(context, sb, R.string.send_header_account_acct, access_info.getFullAcct(who))
|
||||
addHeader(context, sb, R.string.send_header_account_url, who.url)
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_image_avatar, who.avatar)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_image_avatar_static,
|
||||
who.avatar_static
|
||||
)
|
||||
addHeader(context, sb, R.string.send_header_account_image_header, who.header)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_image_header_static,
|
||||
who.header_static
|
||||
)
|
||||
|
||||
addHeader(context, sb, R.string.send_header_account_created_at, who.created_at)
|
||||
addHeader(context, sb, R.string.send_header_account_statuses_count, who.statuses_count)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_followers_count,
|
||||
who.followers_count
|
||||
)
|
||||
addHeader(
|
||||
context,
|
||||
sb,
|
||||
R.string.send_header_account_following_count,
|
||||
who.following_count
|
||||
)
|
||||
addHeader(context, sb, R.string.send_header_account_locked, who.locked)
|
||||
|
||||
sb.addAfterLine("")
|
||||
intent.putExtra(ActText.EXTRA_TEXT, sb.toString())
|
||||
}
|
||||
|
||||
|
||||
}
|
9
app/src/main/res/drawable/ic_translate.xml
Normal file
9
app/src/main/res/drawable/ic_translate.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
|
||||
</vector>
|
@ -647,6 +647,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -248,6 +248,60 @@
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/show_translate_button"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swShowTranslateButton"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etTranslateAppComponent"
|
||||
android:text="@string/translate_app_component"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etTranslateAppComponent"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:inputType="text"
|
||||
android:hint="@string/translate_app_component_default"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTranslateAppComponentEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTranslateAppComponentReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -907,5 +907,10 @@
|
||||
<string name="hashtag_from_acct">ハッシュタグ(特定ユーザから)</string>
|
||||
<string name="notifications_from_acct">ユーザからの通知</string>
|
||||
<string name="notifications_from">%1$sからの通知</string>
|
||||
|
||||
<string name="show_translate_button">翻訳ボタンを表示する</string>
|
||||
<string name="translate">翻訳</string>
|
||||
<string name="translate_app_component" >翻訳アプリのComponentName</string>
|
||||
<string name="missing_app_can_receive_action_send">ACTION_SENDを受け取れるアプリがありません。</string>
|
||||
<string name="select_translate_app">翻訳アプリを選択した後に設定画面まで戻ってください</string>
|
||||
<string name="translation_app_chooser_works_android_5_1">翻訳アプリを選択する機能はAndroid5.1+で使えます。</string>
|
||||
</resources>
|
||||
|
@ -900,5 +900,12 @@
|
||||
<string name="load_preference_from_web_ui">Load some preferences from WebUI</string>
|
||||
<string name="notifications_from_acct">Notifications from user</string>
|
||||
<string name="notifications_from">Notifications from %1$s</string>
|
||||
<string name="show_translate_button">Show translate button</string>
|
||||
<string name="translate">Translate</string>
|
||||
<string name="translate_app_component" >Translation app component</string>
|
||||
<string name="translate_app_component_default" translatable="false">com.google.android.apps.translate/com.google.android.apps.translate.TranslateActivity</string>
|
||||
<string name="missing_app_can_receive_action_send">Missing app that can receive ACTION_SEND.</string>
|
||||
<string name="select_translate_app">Select translation app, then back to app setting.</string>
|
||||
<string name="translation_app_chooser_works_android_5_1">Sorry, translation app chooser works on Android 5.1+.</string>
|
||||
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user