投稿の編集時に説明文や焦点位置を変更できる
This commit is contained in:
parent
861dae6de1
commit
63ae32c61a
@ -350,17 +350,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||
ty: Float,
|
||||
scale: Float,
|
||||
) {
|
||||
App1.getAppState(this@ActMediaViewer).handler.post(Runnable {
|
||||
if (isDestroyed) return@Runnable
|
||||
if (views.tvStatus.visibility == View.VISIBLE) {
|
||||
views.tvStatus.text = getString(
|
||||
R.string.zooming_of,
|
||||
bitmapW.toInt(),
|
||||
bitmapH.toInt(),
|
||||
scale
|
||||
)
|
||||
}
|
||||
})
|
||||
App1.getAppState(this@ActMediaViewer).handler.post {
|
||||
showZoom(bitmapW.toInt(), bitmapH.toInt(), scale)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -875,4 +867,25 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 画面下部の情報テキストの表示を更新する
|
||||
*/
|
||||
private fun showZoom(
|
||||
w: Int,
|
||||
h: Int,
|
||||
scale: Float,
|
||||
) {
|
||||
if (isDestroyed) return
|
||||
if (views.tvStatus.visibility == View.VISIBLE) {
|
||||
views.tvStatus.text = getString(
|
||||
R.string.zooming_of,
|
||||
w,
|
||||
h,
|
||||
scale,
|
||||
idx + 1,
|
||||
mediaList.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,13 @@ import jp.juggler.subwaytooter.ActPost
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.ApiTask
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.ServiceType
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachmentJson
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachmentType
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.entity.parseItem
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.calcIconRound
|
||||
import jp.juggler.subwaytooter.defaultColorIcon
|
||||
@ -25,7 +29,11 @@ import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.data.CharacterGroup
|
||||
import jp.juggler.util.data.GetContentResultEntry
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.data.decodeJsonArray
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.log.withCaption
|
||||
@ -90,6 +98,7 @@ fun ActPost.showMediaAttachmentOne(iv: MyNetworkImageView, idx: Int) {
|
||||
TootAttachmentType.Video,
|
||||
TootAttachmentType.GIFV,
|
||||
-> R.drawable.ic_videocam
|
||||
|
||||
TootAttachmentType.Audio -> R.drawable.ic_music_note
|
||||
else -> R.drawable.ic_clip
|
||||
}
|
||||
@ -272,35 +281,62 @@ suspend fun ActPost.openFocusPoint(pa: PostAttachment) {
|
||||
)
|
||||
}
|
||||
|
||||
fun ActPost.sendFocusPoint(pa: PostAttachment, attachment: TootAttachment, x: Float, y: Float) {
|
||||
val account = this.account ?: return
|
||||
launchMain {
|
||||
var resultAttachment: TootAttachment? = null
|
||||
runApiTask(account, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
||||
try {
|
||||
client.request(
|
||||
"/api/v1/media/${attachment.id}",
|
||||
buildJsonObject {
|
||||
put("focus", "%.2f,%.2f".format(x, y))
|
||||
}.toPutRequestBuilder()
|
||||
)?.also { result ->
|
||||
resultAttachment = parseItem(result.jsonObject) {
|
||||
tootAttachment(ServiceType.MASTODON, it)
|
||||
}
|
||||
suspend fun ActPost.sendFocusPoint(
|
||||
pa: PostAttachment,
|
||||
attachment: TootAttachment,
|
||||
x: Float,
|
||||
y: Float,
|
||||
): Boolean {
|
||||
val account = this.account ?: error("missing account")
|
||||
val isEdit = states.editStatusId != null
|
||||
if (isEdit) {
|
||||
attachment.focusX = x
|
||||
attachment.focusY = y
|
||||
attachment.updateFocus = formatFocusParameter(x, y)
|
||||
showToast(false, R.string.applied_when_post)
|
||||
showMediaAttachment()
|
||||
return true
|
||||
}
|
||||
|
||||
var resultAttachment: TootAttachment? = null
|
||||
val result = runApiTask(account, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
||||
try {
|
||||
client.request(
|
||||
"/api/v1/media/${attachment.id}",
|
||||
buildJsonObject {
|
||||
put("focus", formatFocusParameter(x, y))
|
||||
}.toPutRequestBuilder()
|
||||
)?.also { result ->
|
||||
resultAttachment = parseItem(result.jsonObject) {
|
||||
tootAttachment(ServiceType.MASTODON, it)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("set focus point failed."))
|
||||
}
|
||||
}?.let { result ->
|
||||
when (val newAttachment = resultAttachment) {
|
||||
null -> showToast(true, result.error)
|
||||
else -> pa.attachment = newAttachment
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
TootApiResult(ex.withCaption("set focus point failed."))
|
||||
}
|
||||
}
|
||||
result ?: return true
|
||||
return when (val newAttachment = resultAttachment) {
|
||||
null -> {
|
||||
showToast(true, result.error)
|
||||
false
|
||||
}
|
||||
|
||||
else -> {
|
||||
pa.attachment = newAttachment
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
private fun formatFocusParameter(x: Float, y: Float) = "%.2f,%.2f".format(x, y)
|
||||
|
||||
suspend fun ActPost.editAttachmentDescription(
|
||||
pa: PostAttachment,
|
||||
) {
|
||||
// 既存の投稿を編集中なら真
|
||||
val isEdit = states.editStatusId != null
|
||||
|
||||
val a = pa.attachment
|
||||
if (a == null) {
|
||||
showToast(true, R.string.attachment_description_cant_edit_while_uploading)
|
||||
@ -310,6 +346,7 @@ suspend fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
val account = this.account ?: return
|
||||
var bitmap: Bitmap? = null
|
||||
try {
|
||||
// サムネイルをロード
|
||||
val url = a.preview_url
|
||||
if (url != null) {
|
||||
val result = runApiTask { client ->
|
||||
@ -331,6 +368,7 @@ suspend fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
// not exit
|
||||
}
|
||||
}
|
||||
// ダイアログを表示
|
||||
showTextInputDialog(
|
||||
title = getString(R.string.attachment_description),
|
||||
bitmap = bitmap,
|
||||
@ -338,21 +376,30 @@ suspend fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
||||
initialText = a.description,
|
||||
onEmptyText = { showToast(true, R.string.description_empty) },
|
||||
) { text ->
|
||||
val (result, newAttachment) = attachmentUploader.setAttachmentDescription(
|
||||
account,
|
||||
attachmentId,
|
||||
text
|
||||
)
|
||||
result ?: return@showTextInputDialog true
|
||||
when (newAttachment) {
|
||||
null -> {
|
||||
result.error?.let { showToast(true, it) }
|
||||
false
|
||||
}
|
||||
else -> {
|
||||
pa.attachment = newAttachment
|
||||
showMediaAttachment()
|
||||
true
|
||||
if (isEdit) {
|
||||
a.description = text
|
||||
a.updateDescription = text
|
||||
showToast(false, R.string.applied_when_post)
|
||||
showMediaAttachment()
|
||||
true
|
||||
} else {
|
||||
val (result, newAttachment) = attachmentUploader.setAttachmentDescription(
|
||||
account,
|
||||
attachmentId,
|
||||
text
|
||||
)
|
||||
when {
|
||||
result == null -> true
|
||||
newAttachment == null -> {
|
||||
result.error?.let { showToast(true, it) }
|
||||
false
|
||||
}
|
||||
|
||||
else -> {
|
||||
pa.attachment = newAttachment
|
||||
showMediaAttachment()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,12 @@ class TootAttachment private constructor(
|
||||
val text_url: String?,
|
||||
|
||||
// ALT text (Mastodon 2.0.0 or later)
|
||||
override val description: String?,
|
||||
// Mastodon 4.0で既存投稿の添付データの説明文を編集可能になる
|
||||
override var description: String?,
|
||||
|
||||
override val focusX: Float,
|
||||
override val focusY: Float,
|
||||
// Mastodon 4.0で既存投稿の添付データのフォーカス位置を編集可能になる
|
||||
override var focusX: Float,
|
||||
override var focusY: Float,
|
||||
|
||||
// MisskeyはメディアごとにNSFWフラグがある
|
||||
val isSensitive: Boolean,
|
||||
@ -44,6 +46,11 @@ class TootAttachment private constructor(
|
||||
// 内部フラグ: 再編集で引き継いだ添付メディアなら真
|
||||
var redraft: Boolean = false
|
||||
|
||||
// 内部フラグ:編集投稿時にメディア属性を更新するなら、その値を指定する
|
||||
var updateDescription: String? = null
|
||||
var updateThumbnail: String? = null
|
||||
var updateFocus: String? = null
|
||||
|
||||
override val urlForDescription: String?
|
||||
get() = remote_url.notEmpty() ?: url
|
||||
|
||||
@ -74,6 +81,9 @@ class TootAttachment private constructor(
|
||||
private const val KEY_X = "x"
|
||||
private const val KEY_Y = "y"
|
||||
private const val KEY_BLURHASH = "blurhash"
|
||||
private const val KEY_UPDATE_DESCRIPTION = "updateDescription"
|
||||
private const val KEY_UPDATE_THUMBNAIL = "updateThumbnail"
|
||||
private const val KEY_UPDATE_FOCUS = "updateFocus"
|
||||
|
||||
private val ext_audio = arrayOf(".mpga", ".mp3", ".aac", ".ogg")
|
||||
|
||||
@ -99,6 +109,7 @@ class TootAttachment private constructor(
|
||||
null, TootAttachmentType.Unknown -> {
|
||||
guessMediaTypeByUrl(remote_url ?: url) ?: TootAttachmentType.Unknown
|
||||
}
|
||||
|
||||
else -> tmpType
|
||||
}
|
||||
val focus = src.jsonObject(KEY_META)?.jsonObject(KEY_FOCUS)
|
||||
@ -115,7 +126,11 @@ class TootAttachment private constructor(
|
||||
text_url = src.string(KEY_TEXT_URL),
|
||||
type = type,
|
||||
url = url,
|
||||
)
|
||||
).apply {
|
||||
updateDescription = src.string(KEY_UPDATE_DESCRIPTION)
|
||||
updateThumbnail = src.string(KEY_UPDATE_THUMBNAIL)
|
||||
updateFocus = src.string(KEY_UPDATE_FOCUS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tootAttachmentMisskey(src: JsonObject): TootAttachment {
|
||||
@ -142,7 +157,11 @@ class TootAttachment private constructor(
|
||||
text_url = url,
|
||||
type = type,
|
||||
url = url,
|
||||
)
|
||||
).apply {
|
||||
updateDescription = src.string(KEY_UPDATE_DESCRIPTION)
|
||||
updateThumbnail = src.string(KEY_UPDATE_THUMBNAIL)
|
||||
updateFocus = src.string(KEY_UPDATE_FOCUS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tootAttachmentNoteStock(src: JsonObject): TootAttachment {
|
||||
@ -266,6 +285,9 @@ class TootAttachment private constructor(
|
||||
put(KEY_DESCRIPTION, description)
|
||||
put(KEY_IS_SENSITIVE, isSensitive)
|
||||
put(KEY_BLURHASH, blurhash)
|
||||
put(KEY_UPDATE_DESCRIPTION, updateDescription)
|
||||
put(KEY_UPDATE_THUMBNAIL, updateThumbnail)
|
||||
put(KEY_UPDATE_FOCUS, updateFocus)
|
||||
|
||||
if (focusX != 0f || focusY != 0f) {
|
||||
put(KEY_META, buildJsonObject {
|
||||
|
@ -305,6 +305,7 @@ class TootStatus(
|
||||
TootVisibility.DirectPrivate,
|
||||
TootVisibility.DirectSpecified,
|
||||
-> hasReceipt(accessInfo)
|
||||
|
||||
else -> visibility
|
||||
}
|
||||
|
||||
@ -642,6 +643,7 @@ class TootStatus(
|
||||
spoilerRaw == null -> "" // CWなし
|
||||
spoilerRaw.replace('\u0323', ' ').isBlank() ->
|
||||
parser.context.getString(R.string.blank_cw)
|
||||
|
||||
else -> spoilerRaw
|
||||
}
|
||||
|
||||
@ -681,6 +683,7 @@ class TootStatus(
|
||||
reply != null -> {
|
||||
TootCard.tootCard(parser, reply)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
@ -1002,6 +1005,18 @@ class TootStatus(
|
||||
parseListOrNull(src.jsonArray("media_attachments")) {
|
||||
tootAttachment(parser, it)
|
||||
}
|
||||
|
||||
// kmy.blue拡張
|
||||
// https://github.com/kmycode/mastodon/wiki/%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E6%8B%A1%E5%BC%B5API
|
||||
parseListOrNull( src.jsonArray("media_attachments_ex")) {
|
||||
tootAttachment(parser, it)
|
||||
}?.notEmpty()?.let {
|
||||
when (val list = media_attachments) {
|
||||
null -> media_attachments = ArrayList<TootAttachmentLike>(it)
|
||||
else -> list.addAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
val visibilityString = when {
|
||||
src.boolean("limited") == true -> "limited"
|
||||
else -> src.string("visibility")
|
||||
@ -1082,6 +1097,7 @@ class TootStatus(
|
||||
log.d("removeQt? after = $after")
|
||||
}
|
||||
}
|
||||
|
||||
else -> sv
|
||||
}
|
||||
}
|
||||
@ -1398,18 +1414,21 @@ class TootStatus(
|
||||
// Z or missing hour part
|
||||
0
|
||||
}
|
||||
|
||||
mArg != null && mArg.isNotEmpty() -> {
|
||||
// HH:mm or H:m
|
||||
val h = hArg.toInt()
|
||||
val m = mArg.toInt()
|
||||
h * 60 + m
|
||||
}
|
||||
|
||||
hArg.length >= 3 -> {
|
||||
// HHmm or Hmm
|
||||
val h = hArg.substring(0, hArg.length - 2).toInt()
|
||||
val m = hArg.substring(hArg.length - 2).toInt()
|
||||
h * 60 + m
|
||||
}
|
||||
|
||||
else -> {
|
||||
// HH or H
|
||||
val h = hArg.toInt()
|
||||
@ -1530,6 +1549,7 @@ class TootStatus(
|
||||
R.string.relative_time_unit_day1,
|
||||
R.string.relative_time_unit_days
|
||||
)
|
||||
|
||||
else ->
|
||||
formatDate(t, date_format2, omitZeroSecond = false, omitYear = true)
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.databinding.DlgFocusPointBinding
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.*
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
private val log = LogCategory("DlgFocusPoint")
|
||||
|
||||
@ -47,13 +47,13 @@ fun decodeAttachmentBitmap(
|
||||
|
||||
suspend fun AppCompatActivity.focusPointDialog(
|
||||
attachment: TootAttachment,
|
||||
callback: (x: Float, y: Float) -> Unit,
|
||||
callback: suspend (x: Float, y: Float) -> Boolean,
|
||||
) {
|
||||
var bitmap: Bitmap? = null
|
||||
try {
|
||||
val url = attachment.preview_url
|
||||
if (url == null) {
|
||||
showToast(false, "missing image url")
|
||||
showToast(false, "missing preview_url")
|
||||
return
|
||||
}
|
||||
val result = runApiTask { client ->
|
||||
@ -79,23 +79,36 @@ suspend fun AppCompatActivity.focusPointDialog(
|
||||
val views = DlgFocusPointBinding.inflate(layoutInflater)
|
||||
dialog.setContentView(views.root)
|
||||
views.ivFocus.setAttachment(attachment, bitmap!!)
|
||||
views.ivFocus.callback = callback
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
views.btnCancel.setOnClickListener {
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
views.btnOk.setOnClickListener {
|
||||
launchMain {
|
||||
try {
|
||||
if (callback(views.ivFocus.focusX, views.ivFocus.focusY)) {
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "can't set focus point.")
|
||||
}
|
||||
}
|
||||
}
|
||||
// dialogが閉じるまで待ってからbitmapをリサイクルする
|
||||
suspendCancellableCoroutine { cont ->
|
||||
views.btnClose.setOnClickListener {
|
||||
if (cont.isActive) cont.resume(Unit) {}
|
||||
dialog.setOnDismissListener {
|
||||
if (cont.isActive) cont.resume(Unit)
|
||||
}
|
||||
cont.invokeOnCancellation {
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||
}
|
||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||
dialog.show()
|
||||
}
|
||||
} finally {
|
||||
bitmap?.recycle()
|
||||
bitmap = null
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,14 @@ import jp.juggler.subwaytooter.databinding.DlgAccountAddBinding
|
||||
import jp.juggler.subwaytooter.databinding.LvAuthTypeBinding
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.notBlank
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.*
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.anko.textColor
|
||||
import org.jetbrains.anko.textResource
|
||||
import java.io.BufferedReader
|
||||
@ -53,8 +56,6 @@ class LoginForm(
|
||||
) = LoginForm(this, onClickOk)
|
||||
}
|
||||
|
||||
private class StringArray : ArrayList<String>()
|
||||
|
||||
enum class Action(
|
||||
val pos: Int,
|
||||
@StringRes val idName: Int,
|
||||
@ -66,6 +67,9 @@ class LoginForm(
|
||||
Token(3, R.string.input_access_token, R.string.input_access_token_desc),
|
||||
}
|
||||
|
||||
// 実行時キャストのためGenericsを含まない型を定義する
|
||||
private class StringArrayList : ArrayList<String>()
|
||||
|
||||
val views = DlgAccountAddBinding.inflate(activity.layoutInflater)
|
||||
val dialog = Dialog(activity)
|
||||
|
||||
@ -93,7 +97,7 @@ class LoginForm(
|
||||
views.etInstance.addTextChangedListener { validateAndShow() }
|
||||
|
||||
showPage(0)
|
||||
initServerNameList()
|
||||
|
||||
validateAndShow()
|
||||
|
||||
dialog.setContentView(views.root)
|
||||
@ -102,67 +106,82 @@ class LoginForm(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
dialog.show()
|
||||
|
||||
initServerNameList()
|
||||
}
|
||||
|
||||
private fun initServerNameList() {
|
||||
val instanceList = HashSet<String>().apply {
|
||||
val progress = ProgressDialogEx(activity)
|
||||
progress.setMessageEx(activity.getString(R.string.autocomplete_list_loading))
|
||||
progress.show()
|
||||
launchMain {
|
||||
try {
|
||||
activity.resources.openRawResource(R.raw.server_list).use { inStream ->
|
||||
val br = BufferedReader(InputStreamReader(inStream, "UTF-8"))
|
||||
while (true) {
|
||||
val s: String =
|
||||
br.readLine()?.trim { it <= ' ' }?.lowercase() ?: break
|
||||
if (s.isEmpty()) continue
|
||||
add(s)
|
||||
add(IDN.toASCII(s, IDN.ALLOW_UNASSIGNED))
|
||||
add(IDN.toUnicode(s, IDN.ALLOW_UNASSIGNED))
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't load server list.")
|
||||
}
|
||||
}.toList().sorted()
|
||||
|
||||
val adapter = object : ArrayAdapter<String>(
|
||||
activity, R.layout.lv_spinner_dropdown, ArrayList()
|
||||
) {
|
||||
val nameFilter: Filter = object : Filter() {
|
||||
override fun convertResultToString(value: Any) =
|
||||
value as String
|
||||
|
||||
override fun performFiltering(constraint: CharSequence?) =
|
||||
FilterResults().also { result ->
|
||||
if (constraint?.isNotEmpty() == true) {
|
||||
val key = constraint.toString().lowercase()
|
||||
// suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある
|
||||
val suggestions = StringArray()
|
||||
for (s in instanceList) {
|
||||
if (s.contains(key)) {
|
||||
suggestions.add(s)
|
||||
if (suggestions.size >= 20) break
|
||||
val instanceList = HashSet<String>().apply {
|
||||
try {
|
||||
withContext(AppDispatchers.IO) {
|
||||
activity.resources.openRawResource(R.raw.server_list).use { inStream ->
|
||||
val br = BufferedReader(InputStreamReader(inStream, "UTF-8"))
|
||||
while (true) {
|
||||
(br.readLine() ?: break)
|
||||
.trim { it <= ' ' }
|
||||
.notEmpty()
|
||||
?.lowercase()
|
||||
?.let {
|
||||
add(it)
|
||||
add(IDN.toASCII(it, IDN.ALLOW_UNASSIGNED))
|
||||
add(IDN.toUnicode(it, IDN.ALLOW_UNASSIGNED))
|
||||
}
|
||||
}
|
||||
}
|
||||
result.values = suggestions
|
||||
result.count = suggestions.size
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't load server list.")
|
||||
}
|
||||
}.toList().sorted()
|
||||
|
||||
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
|
||||
clear()
|
||||
val values = results?.values
|
||||
if (values is StringArray) {
|
||||
for (s in values) {
|
||||
add(s)
|
||||
val adapter = object : ArrayAdapter<String>(
|
||||
activity, R.layout.lv_spinner_dropdown, ArrayList()
|
||||
) {
|
||||
override fun getFilter(): Filter = nameFilter
|
||||
|
||||
val nameFilter: Filter = object : Filter() {
|
||||
override fun convertResultToString(value: Any) =
|
||||
value as String
|
||||
|
||||
override fun performFiltering(constraint: CharSequence?) =
|
||||
FilterResults().also { result ->
|
||||
constraint?.notEmpty()?.toString()?.lowercase()?.let { key ->
|
||||
// suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある
|
||||
val suggestions = StringArrayList()
|
||||
for (s in instanceList) {
|
||||
if (s.contains(key)) {
|
||||
suggestions.add(s)
|
||||
if (suggestions.size >= 20) break
|
||||
}
|
||||
}
|
||||
result.values = suggestions
|
||||
result.count = suggestions.size
|
||||
}
|
||||
}
|
||||
|
||||
override fun publishResults(
|
||||
constraint: CharSequence?,
|
||||
results: FilterResults?,
|
||||
) {
|
||||
clear()
|
||||
(results?.values as? StringArrayList)?.let { addAll(it) }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
adapter.setDropDownViewResource(R.layout.lv_spinner_dropdown)
|
||||
views.etInstance.setAdapter<ArrayAdapter<String>>(adapter)
|
||||
} catch (ex: Throwable) {
|
||||
activity.showToast(ex, "initServerNameList failed.")
|
||||
} finally {
|
||||
progress.dismissSafe()
|
||||
}
|
||||
|
||||
override fun getFilter(): Filter = nameFilter
|
||||
}
|
||||
adapter.setDropDownViewResource(R.layout.lv_spinner_dropdown)
|
||||
views.etInstance.setAdapter<ArrayAdapter<String>>(adapter)
|
||||
}
|
||||
|
||||
// return validated name. else null
|
||||
@ -250,6 +269,7 @@ class LoginForm(
|
||||
textColor = attrColor(R.attr.colorRegexFilterError)
|
||||
text = error
|
||||
}
|
||||
|
||||
else -> {
|
||||
textColor = attrColor(R.attr.colorTextContent)
|
||||
text = (tootInstance.short_description.notBlank()
|
||||
|
@ -13,7 +13,6 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatButton
|
||||
import androidx.appcompat.widget.AppCompatImageButton
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -29,13 +28,44 @@ import jp.juggler.subwaytooter.drawable.PreviewCardBorder
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.*
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||
import jp.juggler.subwaytooter.util.blurhashView
|
||||
import jp.juggler.subwaytooter.util.compatButton
|
||||
import jp.juggler.subwaytooter.util.endMargin
|
||||
import jp.juggler.subwaytooter.util.minHeightCompat
|
||||
import jp.juggler.subwaytooter.util.myNetworkImageView
|
||||
import jp.juggler.subwaytooter.util.myTextView
|
||||
import jp.juggler.subwaytooter.util.setPaddingStartEnd
|
||||
import jp.juggler.subwaytooter.util.startMargin
|
||||
import jp.juggler.subwaytooter.util.startPadding
|
||||
import jp.juggler.subwaytooter.util.trendTagHistoryView
|
||||
import jp.juggler.subwaytooter.view.BlurhashView
|
||||
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.subwaytooter.view.MyTextView
|
||||
import jp.juggler.subwaytooter.view.TagHistoryView
|
||||
import jp.juggler.util.log.Benchmark
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.applyAlphaMultiplier
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import org.jetbrains.anko.*
|
||||
import org.jetbrains.anko.UI
|
||||
import org.jetbrains.anko._LinearLayout
|
||||
import org.jetbrains.anko.allCaps
|
||||
import org.jetbrains.anko.backgroundDrawable
|
||||
import org.jetbrains.anko.backgroundResource
|
||||
import org.jetbrains.anko.bottomPadding
|
||||
import org.jetbrains.anko.dip
|
||||
import org.jetbrains.anko.frameLayout
|
||||
import org.jetbrains.anko.imageButton
|
||||
import org.jetbrains.anko.imageResource
|
||||
import org.jetbrains.anko.imageView
|
||||
import org.jetbrains.anko.linearLayout
|
||||
import org.jetbrains.anko.matchParent
|
||||
import org.jetbrains.anko.padding
|
||||
import org.jetbrains.anko.textColor
|
||||
import org.jetbrains.anko.verticalLayout
|
||||
import org.jetbrains.anko.verticalMargin
|
||||
import org.jetbrains.anko.wrapContent
|
||||
|
||||
class ItemViewHolder(
|
||||
val activity: ActMain,
|
||||
@ -96,7 +126,8 @@ class ItemViewHolder(
|
||||
lateinit var llMedia: View
|
||||
lateinit var btnShowMedia: BlurhashView
|
||||
lateinit var btnHideMedia: ImageButton
|
||||
val tvMediaDescriptions = ArrayList<TextView>()
|
||||
lateinit var tvMediaCount: MyTextView
|
||||
val tvMediaDescriptions = ArrayList<MyTextView>()
|
||||
val ivMediaThumbnails = ArrayList<MyNetworkImageView>()
|
||||
|
||||
lateinit var statusButtonsViewHolder: StatusButtonsViewHolder
|
||||
@ -150,6 +181,7 @@ class ItemViewHolder(
|
||||
lateinit var ivOpenSticker: MyNetworkImageView
|
||||
lateinit var tvOpenSticker: MyTextView
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
lateinit var tvLastStatusAt: MyTextView
|
||||
|
||||
lateinit var accessInfo: SavedAccount
|
||||
@ -513,8 +545,8 @@ class ItemViewHolder(
|
||||
}.lparams(matchParent, thumbnailHeight)
|
||||
}
|
||||
|
||||
private fun _LinearLayout.inflateHorizontalMedia(thumbnailHeight: Int): View {
|
||||
return frameLayout {
|
||||
private fun _LinearLayout.inflateHorizontalMedia(thumbnailHeight: Int) =
|
||||
frameLayout {
|
||||
lparams(matchParent, thumbnailHeight) { topMargin = dip(3) }
|
||||
llMedia = linearLayout {
|
||||
lparams(matchParent, matchParent)
|
||||
@ -532,8 +564,10 @@ class ItemViewHolder(
|
||||
}
|
||||
|
||||
btnHideMedia = imageButton {
|
||||
background =
|
||||
ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp)
|
||||
background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.btn_bg_transparent_round6dp
|
||||
)
|
||||
contentDescription = context.getString(R.string.hide)
|
||||
imageResource = R.drawable.ic_close
|
||||
}.lparams(dip(32), matchParent) {
|
||||
@ -547,7 +581,6 @@ class ItemViewHolder(
|
||||
gravity = Gravity.CENTER
|
||||
}.lparams(matchParent, matchParent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun _LinearLayout.inflateCard(actMain: ActMain) {
|
||||
llCardOuter = verticalLayout {
|
||||
@ -688,10 +721,17 @@ class ItemViewHolder(
|
||||
else -> inflateHorizontalMedia(thumbnailHeight)
|
||||
}
|
||||
|
||||
tvMediaCount = myTextView {
|
||||
gravity = Gravity.END
|
||||
includeFontPadding = false
|
||||
}.lparams(matchParent, wrapContent) {
|
||||
verticalMargin = dip(3)
|
||||
}
|
||||
|
||||
tvMediaDescriptions.clear()
|
||||
repeat(MEDIA_VIEW_COUNT) {
|
||||
tvMediaDescriptions.add(
|
||||
button {
|
||||
myTextView {
|
||||
gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
||||
allCaps = false
|
||||
background =
|
||||
|
@ -4,18 +4,35 @@ import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.actmain.checkAutoCW
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachmentLike
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachmentType
|
||||
import jp.juggler.subwaytooter.api.entity.TootMessageHolder
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootPolls
|
||||
import jp.juggler.subwaytooter.api.entity.TootPollsType
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.calcIconRound
|
||||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.column.isConversation
|
||||
import jp.juggler.subwaytooter.defaultColorIcon
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.stylerBoostAlpha
|
||||
import jp.juggler.subwaytooter.table.daoContentWarning
|
||||
import jp.juggler.subwaytooter.table.daoMediaShown
|
||||
import jp.juggler.subwaytooter.util.OpenSticker
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.ellipsizeDot3
|
||||
import jp.juggler.util.data.notBlank
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.data.notZero
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.setIconDrawableId
|
||||
@ -262,6 +279,7 @@ private fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||
ColumnType.LOCAL, ColumnType.LOCAL_AROUND -> {
|
||||
if (host == accessInfo.apDomain) return
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
@ -300,12 +318,15 @@ private fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||
|
||||
private fun ItemViewHolder.showAttachments(status: TootStatus) {
|
||||
val mediaAttachments = status.media_attachments
|
||||
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
|
||||
if (mediaAttachments.isNullOrEmpty()) {
|
||||
flMedia.visibility = View.GONE
|
||||
llMedia.visibility = View.GONE
|
||||
btnShowMedia.visibility = View.GONE
|
||||
} else {
|
||||
flMedia.visibility = View.VISIBLE
|
||||
tvMediaCount.vg(mediaAttachments.size > 4)?.let {
|
||||
it.text = activity.getString(R.string.media_count, mediaAttachments.size)
|
||||
}
|
||||
|
||||
// hide sensitive media
|
||||
val defaultShown = when {
|
||||
|
@ -322,6 +322,16 @@ class PostImpl(
|
||||
add(a.id.toString())
|
||||
}
|
||||
}
|
||||
attachmentList.mapNotNull {a->
|
||||
buildJsonObject {
|
||||
put("id",a.id.toString())
|
||||
a.updateDescription?.let{ put("description",it)}
|
||||
a.updateThumbnail?.let{ put("thumbnail",it)}
|
||||
a.updateFocus?.let{ put("focus",it)}
|
||||
}.takeIf { it.keys.size >= 2 }
|
||||
}.let{
|
||||
json["media_attributes"] = it.toJsonArray()
|
||||
}
|
||||
}
|
||||
|
||||
if (enqueteItems != null) {
|
||||
|
@ -36,10 +36,9 @@ class FocusPointView : View {
|
||||
private var crossRadius: Float = 0f
|
||||
private var attachment: TootAttachment? = null
|
||||
private var bitmap: Bitmap? = null
|
||||
var callback: (x: Float, y: Float) -> Unit = { _, _ -> }
|
||||
|
||||
private var focusX: Float = 0f
|
||||
private var focusY: Float = 0f
|
||||
var focusX = Float.NaN
|
||||
var focusY = Float.NaN
|
||||
|
||||
private fun init(context: Context) {
|
||||
|
||||
@ -136,18 +135,9 @@ class FocusPointView : View {
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
updateFocusPoint(event)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
updateFocusPoint(event)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
updateFocusPoint(event)
|
||||
callback(focusX, focusY)
|
||||
}
|
||||
MotionEvent.ACTION_DOWN -> updateFocusPoint(event)
|
||||
MotionEvent.ACTION_MOVE -> updateFocusPoint(event)
|
||||
MotionEvent.ACTION_UP -> updateFocusPoint(event)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -5,31 +5,36 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<jp.juggler.subwaytooter.view.FocusPointView
|
||||
android:id="@+id/ivFocus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/ivFocus"
|
||||
/>
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnClose"
|
||||
android:id="@+id/btnCancel"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/close"
|
||||
/>
|
||||
android:text="@string/cancel" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOk"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/ok" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
@ -615,7 +615,7 @@
|
||||
<string name="media_attachment_type_error">El mèdia adjunt és de tipus \"%1$s\". ST no el pot processar, però pots provar d\'obrir-lo en un navegador.</string>
|
||||
<string name="video_buffering">Dosificant…</string>
|
||||
<string name="dont_repeat_download_to_same_url">No es pot repetir la descàrrega del mateix URL en pocs segons.</string>
|
||||
<string name="zooming_of">×%3$.1f
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f
|
||||
\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">Encara s\'està carregant el mèdia.</string>
|
||||
<string name="highlight_word">Ressalta una paraula clau</string>
|
||||
|
@ -800,7 +800,7 @@
|
||||
<string name="new_item">Neuer Eintrag…</string>
|
||||
<string name="highlight_word">Schlüsselwort hervorheben</string>
|
||||
<string name="media_attachment_still_uploading">Immer noch beim Hochladen der Medienelemente.</string>
|
||||
<string name="zooming_of">×%3$.1f
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f
|
||||
\n%1$d×%2$d</string>
|
||||
<string name="dont_repeat_download_to_same_url">Dieselbe URL kann innerhalb weniger Sekunden nicht mehrmals geladen werden.</string>
|
||||
<string name="video_buffering">Zwischenspeichern…</string>
|
||||
|
@ -584,7 +584,7 @@
|
||||
<string name="media_attachment_type_error">Le type de pièce jointe est \"%1$s\". ST ne peut pas le gérer mais vous pouvez essayer de l’ouvrir dans un navigateur.</string>
|
||||
<string name="video_buffering">Mise en mémoire tampon …</string>
|
||||
<string name="dont_repeat_download_to_same_url">Impossible de re-télécharger de la même URL en l’espace de quelques secondes.</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">En cours de téléchargement de médias.</string>
|
||||
<string name="highlight_word">Mettre en évidence le mot-clé</string>
|
||||
<string name="new_item">Nouvel élément …</string>
|
||||
|
@ -793,7 +793,7 @@
|
||||
<string name="your_lists">マイリスト</string>
|
||||
<string name="your_statuses">あなたの投稿</string>
|
||||
<string name="yourself_can_see_your_network">もしあなたがソーシャルグラフを隠す選択をした場合でも、あなた自身はそれを見ることができます。</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="link_color">リンクの色 (アプリ再起動が必要)</string>
|
||||
<string name="missing_closeable_column">閉じれるカラムが表示範囲内にありません</string>
|
||||
<string name="dont_use_custom_tabs">リンクを開く際に (ChromeやFirefoxの) Custom Tabsを使わない</string>
|
||||
@ -1271,4 +1271,7 @@
|
||||
<string name="emoji_texture_pixels">絵文字テクスチャの最大ピクセル数(単位:ピクセル、デフォルト: 128。 タスクキルが必要。端末のRAMが少ない場合は64程度まで下げることをお勧めします)</string>
|
||||
<string name="emoji_picker_category_collapse">絵文字ピッカーのカテゴリを折りたたむ(サーバーに多くの絵文字がある場合はオフにすると非常に遅くなります)</string>
|
||||
<string name="plugin_app_intro">プラグインアプリの紹介</string>
|
||||
<string name="autocomplete_list_loading">入力補完リストの読み込み中…</string>
|
||||
<string name="media_count">%1$d個の添付データ (タップで全て表示)</string>
|
||||
<string name="applied_when_post">投稿送信時に反映されます</string>
|
||||
</resources>
|
||||
|
@ -592,7 +592,7 @@
|
||||
<string name="media_attachment_type_error">첨부 미디어의 유형이 \"%1$s\"입니다. 이 앱은 그걸 다룰 수 없지만 브라우저에서 열어 볼 수는 있습니다.</string>
|
||||
<string name="video_buffering">버퍼링 중…</string>
|
||||
<string name="dont_repeat_download_to_same_url">동일한 URL의 내려받기를 수 초 내에 반복할 수 없음.</string>
|
||||
<string name="zooming_of">×%3$.1f
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f
|
||||
\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">미디어 올리기가 끝나지 않음.</string>
|
||||
<string name="highlight_word">강조표시 키워드</string>
|
||||
|
@ -669,7 +669,7 @@
|
||||
<string name="media_attachment_type_error">Mediavedleggstypen er \"%1$s\". ST kan ikke håndtere den, men du kan prøve å åpne den i en nettleser.</string>
|
||||
<string name="video_buffering">Mellomlagrer…</string>
|
||||
<string name="dont_repeat_download_to_same_url">Kan ikke laste ned samme nettadresse i løpet av et par sekunder.</string>
|
||||
<string name="zooming_of">×%3$.1f
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f
|
||||
\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">Media har ikke blitt lastet opp enda.</string>
|
||||
<string name="highlight_word">Framhev nøkkelord</string>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<string name="emoji_category_foods">食品</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="downloading">正在下载……</string>
|
||||
<string name="zooming_of">×%3$.1f
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f
|
||||
\n%1$d×%2$d</string>
|
||||
<string name="languages">语言</string>
|
||||
<string name="filter_public">公共</string>
|
||||
|
@ -611,7 +611,7 @@
|
||||
<string name="media_attachment_type_error">The type of media attachment is \"%1$s\". ST can\'t handle it, but you can try open in browser.</string>
|
||||
<string name="video_buffering">Buffering…</string>
|
||||
<string name="dont_repeat_download_to_same_url">Can\'t repeat downloading same URL in a few seconds.</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="zooming_of">(%4$d/%5$d) ×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">Still uploading media.</string>
|
||||
<string name="highlight_word">Highlight keyword</string>
|
||||
<string name="new_item">New item…</string>
|
||||
@ -1279,4 +1279,7 @@
|
||||
<string name="emoji_picker_category_collapse">Collapse emoji picker categories (turning off it will very slow if your servers has a lot of emojis)</string>
|
||||
<string name="unfollow_hashtag_confirm">unfollow hashtag \"%1$s\"?</string>
|
||||
<string name="plugin_app_intro">Introduction to plug-in apps</string>
|
||||
<string name="autocomplete_list_loading">loading autocomplete list…</string>
|
||||
<string name="media_count">%1$d attachments (tap to see all)</string>
|
||||
<string name="applied_when_post">applied when post.</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user