アプリ設定/挙動に「翻訳ボタンを表示する」を追加

This commit is contained in:
tateisu 2019-06-26 05:28:02 +09:00
parent 4f43dba3c4
commit 0396264e27
12 changed files with 596 additions and 288 deletions

View File

@ -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"-->

View File

@ -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
}
}
}

View File

@ -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)
}
}

View 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()
}
}

View File

@ -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)

View File

@ -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,

View File

@ -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())
}
}

View 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>

View File

@ -647,6 +647,8 @@
</LinearLayout>
<View style="@style/setting_divider"/>
</LinearLayout>

View File

@ -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>

View File

@ -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>

View File

@ -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>