投稿の編集時に説明文や焦点位置を変更できる

This commit is contained in:
tateisu 2023-04-22 15:41:17 +09:00
parent 861dae6de1
commit 63ae32c61a
19 changed files with 372 additions and 165 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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