投稿時に焦点を設定。通報時に投稿元タンスに転送。
This commit is contained in:
parent
0c7bab2085
commit
b5b6855a81
|
@ -61,6 +61,7 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
|||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpanClickCallback
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.FocusPointView
|
||||
import jp.juggler.subwaytooter.view.MyEditText
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import okhttp3.MediaType
|
||||
|
@ -1035,18 +1036,64 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
.setItems(
|
||||
arrayOf<CharSequence>(
|
||||
getString(R.string.set_description),
|
||||
getString(R.string.set_focus_point),
|
||||
getString(R.string.delete)
|
||||
)
|
||||
) { _, i ->
|
||||
when(i) {
|
||||
0 -> editAttachmentDescription(pa)
|
||||
1 -> deleteAttachment(pa)
|
||||
1 -> openFocusPoint(pa)
|
||||
2->deleteAttachment(pa)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun openFocusPoint(pa : PostAttachment) {
|
||||
val attachment = pa.attachment
|
||||
if( attachment != null) {
|
||||
DlgFocusPoint(this, attachment)
|
||||
.setCallback(object: FocusPointView.Callback{
|
||||
override fun onFocusPointUpdate(x : Float, y : Float) {
|
||||
val account = this@ActPost.account ?:return
|
||||
|
||||
TootTaskRunner(this@ActPost,TootTaskRunner.PROGRESS_NONE).run(account,object:TootTask{
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
try{
|
||||
val json = JSONObject()
|
||||
json.put("focus","%.2f,%.2f".format(x,y))
|
||||
val result = client.request(
|
||||
"/api/v1/media/"+ attachment.id,
|
||||
Request.Builder().put(RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_JSON,json.toString()
|
||||
)))
|
||||
new_attachment = parseItem(::TootAttachment, result?.jsonObject)
|
||||
return result
|
||||
}catch(ex:Throwable){
|
||||
return TootApiResult(ex.withCaption("set focus point failed."))
|
||||
}
|
||||
}
|
||||
|
||||
var new_attachment : TootAttachment? = null
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
result ?: return
|
||||
if( new_attachment != null ){
|
||||
pa.attachment = attachment
|
||||
}else{
|
||||
showToast(this@ActPost,true,result.error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun deleteAttachment(pa : PostAttachment) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.confirm_delete_attachment)
|
||||
|
|
|
@ -52,15 +52,25 @@ object Action_User {
|
|||
if(! bMute)
|
||||
RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "")
|
||||
else if(bMuteNotification)
|
||||
RequestBody.create(TootApiClient.MEDIA_TYPE_JSON, "{\"notifications\": true}")
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_JSON,
|
||||
"{\"notifications\": true}"
|
||||
)
|
||||
else
|
||||
RequestBody.create(TootApiClient.MEDIA_TYPE_JSON, "{\"notifications\": false}")
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_JSON,
|
||||
"{\"notifications\": false}"
|
||||
)
|
||||
)
|
||||
|
||||
val result = client.request("/api/v1/accounts/" + who.id + if(bMute) "/mute" else "/unmute", request_builder)
|
||||
val result = client.request(
|
||||
"/api/v1/accounts/" + who.id + if(bMute) "/mute" else "/unmute",
|
||||
request_builder
|
||||
)
|
||||
val jsonObject = result?.jsonObject
|
||||
if(jsonObject != null) {
|
||||
relation = saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
|
||||
relation =
|
||||
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -90,7 +100,11 @@ object Action_User {
|
|||
}
|
||||
}
|
||||
|
||||
showToast(activity, false, if(relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded)
|
||||
showToast(
|
||||
activity,
|
||||
false,
|
||||
if(relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded
|
||||
)
|
||||
|
||||
} else {
|
||||
showToast(activity, false, result.error)
|
||||
|
@ -117,14 +131,17 @@ object Action_User {
|
|||
val request_builder = Request.Builder().post(
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "" // 空データ
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val result = client.request(
|
||||
"/api/v1/accounts/" + who.id + if(bBlock) "/block" else "/unblock", request_builder
|
||||
"/api/v1/accounts/" + who.id + if(bBlock) "/block" else "/unblock",
|
||||
request_builder
|
||||
)
|
||||
val jsonObject = result?.jsonObject
|
||||
if(jsonObject != null) {
|
||||
relation = saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
|
||||
relation =
|
||||
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -157,7 +174,11 @@ object Action_User {
|
|||
}
|
||||
}
|
||||
|
||||
showToast(activity, false, if(relation.blocking) R.string.block_succeeded else R.string.unblock_succeeded)
|
||||
showToast(
|
||||
activity,
|
||||
false,
|
||||
if(relation.blocking) R.string.block_succeeded else R.string.unblock_succeeded
|
||||
)
|
||||
|
||||
} else {
|
||||
showToast(activity, false, result.error)
|
||||
|
@ -174,10 +195,10 @@ object Action_User {
|
|||
access_info : SavedAccount,
|
||||
who : TootAccount
|
||||
) {
|
||||
if( access_info.isPseudo){
|
||||
if(access_info.isPseudo) {
|
||||
// ココを通る動線はないっぽいが、念のため
|
||||
profileFromAnotherAccount(activity, pos, access_info, who)
|
||||
}else{
|
||||
} else {
|
||||
activity.addColumn(pos, access_info, Column.TYPE_PROFILE, who.id)
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +215,11 @@ object Action_User {
|
|||
internal var who_local : TootAccount? = null
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
|
||||
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, who_url.encodePercent()) + "&resolve=1"
|
||||
val path = String.format(
|
||||
Locale.JAPAN,
|
||||
Column.PATH_SEARCH,
|
||||
who_url.encodePercent()
|
||||
) + "&resolve=1"
|
||||
val result = client.request(path)
|
||||
val jsonObject = result?.jsonObject
|
||||
|
||||
|
@ -241,7 +266,10 @@ object Action_User {
|
|||
activity,
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = activity.getString(R.string.account_picker_open_user_who, AcctColor.getNickname(who.acct)),
|
||||
message = activity.getString(
|
||||
R.string.account_picker_open_user_who,
|
||||
AcctColor.getNickname(who.acct)
|
||||
),
|
||||
accountListArg = makeAccountListNonPseudo(activity, who_host)
|
||||
) { ai ->
|
||||
if(ai.host.equals(access_info.host, ignoreCase = true)) {
|
||||
|
@ -295,7 +323,10 @@ object Action_User {
|
|||
activity,
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = activity.getString(R.string.account_picker_open_user_who, AcctColor.getNickname(user + "@" + host)),
|
||||
message = activity.getString(
|
||||
R.string.account_picker_open_user_who,
|
||||
AcctColor.getNickname(user + "@" + host)
|
||||
),
|
||||
accountListArg = makeAccountListNonPseudo(activity, host)
|
||||
) { ai ->
|
||||
profileFromUrl(
|
||||
|
@ -310,10 +341,10 @@ object Action_User {
|
|||
|
||||
// 通報フォームを開く
|
||||
fun reportForm(
|
||||
activity : ActMain, account : SavedAccount, who : TootAccount, status : TootStatus
|
||||
activity : ActMain, access_info : SavedAccount, who : TootAccount, status : TootStatus
|
||||
) {
|
||||
ReportForm.showReportForm(activity, who, status) { dialog, comment ->
|
||||
report(activity, account, who, status, comment) { _ ->
|
||||
ReportForm.showReportForm(activity, access_info, who, status) { dialog, comment, forward ->
|
||||
report(activity, access_info, who, status, comment, forward) { _ ->
|
||||
// 成功したらダイアログを閉じる
|
||||
try {
|
||||
dialog.dismiss()
|
||||
|
@ -331,6 +362,7 @@ object Action_User {
|
|||
who : TootAccount,
|
||||
status : TootStatus,
|
||||
comment : String,
|
||||
forward : Boolean,
|
||||
onReportComplete : TootApiResultCallback
|
||||
) {
|
||||
if(access_info.isMe(who)) {
|
||||
|
@ -340,14 +372,18 @@ object Action_User {
|
|||
|
||||
TootTaskRunner(activity).run(access_info, object : TootTask {
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val sb = ("account_id=" + who.id.toString()
|
||||
+ "&comment=" + comment.encodePercent()
|
||||
+ "&status_ids[]=" + status.id.toString())
|
||||
val sb = (
|
||||
"account_id=" + who.id.toString()
|
||||
+ "&comment=" + comment.encodePercent()
|
||||
+ "&status_ids[]=" + status.id.toString()
|
||||
+ "&forward=" + if(forward) "true" else "false"
|
||||
)
|
||||
|
||||
val request_builder = Request.Builder().post(
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, sb
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
return client.request("/api/v1/reports", request_builder)
|
||||
}
|
||||
|
@ -390,9 +426,11 @@ object Action_User {
|
|||
val request_builder = Request.Builder().post(
|
||||
RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_JSON, content.toString()
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
val result = client.request("/api/v1/accounts/" + who.id + "/follow", request_builder)
|
||||
val result =
|
||||
client.request("/api/v1/accounts/" + who.id + "/follow", request_builder)
|
||||
val jsonObject = result?.jsonObject
|
||||
if(jsonObject != null) {
|
||||
relation = parseItem(::TootRelationShip, jsonObject)
|
||||
|
|
|
@ -271,6 +271,71 @@ class TootApiClient(
|
|||
}
|
||||
}
|
||||
|
||||
// レスポンスがエラーかボディがカラならエラー状態を設定する
|
||||
// 例外を出すかも
|
||||
internal fun readBodyBytes(
|
||||
result : TootApiResult,
|
||||
progressPath : String? = null,
|
||||
jsonErrorParser : (json : JSONObject) -> String? = DEFAULT_JSON_ERROR_PARSER
|
||||
) : ByteArray? {
|
||||
|
||||
if(isApiCancelled) return null
|
||||
|
||||
val response = result.response !!
|
||||
|
||||
val request = response.request()
|
||||
if(request != null) {
|
||||
publishApiProgress(
|
||||
context.getString(
|
||||
R.string.reading_api,
|
||||
request.method(),
|
||||
progressPath ?: result.caption
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val bodyBytes = response.body()?.bytes()
|
||||
if(isApiCancelled) return null
|
||||
|
||||
if(! response.isSuccessful || bodyBytes?.isEmpty() != false) {
|
||||
|
||||
result.error = TootApiClient.formatResponse(
|
||||
response,
|
||||
result.caption,
|
||||
if(bodyBytes?.isNotEmpty() == true) bodyBytes.decodeUTF8() else NO_INFORMATION,
|
||||
jsonErrorParser
|
||||
)
|
||||
}
|
||||
|
||||
return if(result.error != null) {
|
||||
null
|
||||
} else {
|
||||
result.bodyString = "(binary data)"
|
||||
result.data = bodyBytes
|
||||
bodyBytes
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseBytes(
|
||||
result : TootApiResult,
|
||||
progressPath : String? = null,
|
||||
jsonErrorParser : (json : JSONObject) -> String? = DEFAULT_JSON_ERROR_PARSER
|
||||
) : TootApiResult? {
|
||||
|
||||
val response = result.response !! // nullにならないはず
|
||||
|
||||
try {
|
||||
readBodyBytes(result, progressPath, jsonErrorParser)
|
||||
?: return if(isApiCancelled) null else result
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
result.error =
|
||||
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun parseString(
|
||||
result : TootApiResult,
|
||||
progressPath : String? = null,
|
||||
|
@ -706,6 +771,17 @@ class TootApiClient(
|
|||
|
||||
}
|
||||
|
||||
fun getHttpBytes(url : String) : TootApiResult? {
|
||||
val result = TootApiResult.makeWithCaption(url)
|
||||
if(result.error != null) return result
|
||||
|
||||
if(! sendRequest(result, progressPath = url) {
|
||||
Request.Builder().url(url).build()
|
||||
}) return result
|
||||
return parseBytes(result)
|
||||
|
||||
}
|
||||
|
||||
fun webSocket(path : String, ws_listener : WebSocketListener) : TootApiResult? {
|
||||
val result = TootApiResult.makeWithCaption(instance)
|
||||
if(result.error != null) return result
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
package jp.juggler.subwaytooter.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.TootTask
|
||||
import jp.juggler.subwaytooter.api.TootTaskRunner
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.FocusPointView
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
class DlgFocusPoint(val activity : Activity, val attachment : TootAttachment) :
|
||||
View.OnClickListener {
|
||||
|
||||
companion object {
|
||||
val log = LogCategory("DlgFocusPoint")
|
||||
}
|
||||
|
||||
val dialog : Dialog
|
||||
val focusPointView : FocusPointView
|
||||
var bitmap : Bitmap? = null
|
||||
|
||||
init {
|
||||
val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_focus_point, null, false)
|
||||
focusPointView = viewRoot.findViewById(R.id.ivFocus)
|
||||
viewRoot.findViewById<View>(R.id.btnClose).setOnClickListener(this)
|
||||
|
||||
this.dialog = Dialog(activity)
|
||||
dialog.setContentView(viewRoot)
|
||||
dialog.setOnDismissListener {
|
||||
bitmap?.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v : View) {
|
||||
when(v.id) {
|
||||
R.id.btnClose -> dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
fun setCallback(callback : FocusPointView.Callback?) : DlgFocusPoint {
|
||||
focusPointView.callback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
fun show() {
|
||||
val url = attachment.preview_url
|
||||
if(url == null) {
|
||||
showToast(activity, false, "missing image url")
|
||||
return
|
||||
}
|
||||
|
||||
TootTaskRunner(activity).run(object : TootTask {
|
||||
|
||||
private val options = BitmapFactory.Options()
|
||||
|
||||
private fun decodeBitmap(data : ByteArray, pixel_max : Int) : Bitmap? {
|
||||
options.inJustDecodeBounds = true
|
||||
options.inScaled = false
|
||||
options.outWidth = 0
|
||||
options.outHeight = 0
|
||||
BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
var w = options.outWidth
|
||||
var h = options.outHeight
|
||||
if(w <= 0 || h <= 0) {
|
||||
log.e("can't decode bounds.")
|
||||
return null
|
||||
}
|
||||
var bits = 0
|
||||
while(w > pixel_max || h > pixel_max) {
|
||||
++ bits
|
||||
w = w shr 1
|
||||
h = h shr 1
|
||||
}
|
||||
options.inJustDecodeBounds = false
|
||||
options.inSampleSize = 1 shl bits
|
||||
return BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
||||
}
|
||||
|
||||
var bitmap : Bitmap? = null
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
|
||||
val result = client.getHttpBytes(url)
|
||||
|
||||
try {
|
||||
val data = result?.data as? ByteArray ?: return result
|
||||
bitmap = decodeBitmap(data, 1024)
|
||||
if(bitmap == null) return TootApiResult("image decode failed.")
|
||||
} catch(ex : Throwable) {
|
||||
return TootApiResult(ex.withCaption("preview loading failed."))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
val bitmap = this.bitmap
|
||||
if(bitmap == null) {
|
||||
showToast(activity, true, result?.error ?: "?")
|
||||
try {
|
||||
dialog.dismiss()
|
||||
} catch(ignored : Throwable) {
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(activity.isFinishing) {
|
||||
bitmap.recycle()
|
||||
try {
|
||||
dialog.dismiss()
|
||||
} catch(ignored : Throwable) {
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this@DlgFocusPoint.bitmap = bitmap
|
||||
|
||||
focusPointView.setAttachment(attachment, bitmap)
|
||||
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
dialog.show()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
|
@ -5,12 +5,14 @@ import android.app.Activity
|
|||
import android.app.Dialog
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.showToast
|
||||
|
||||
object ReportForm {
|
||||
|
@ -18,15 +20,32 @@ object ReportForm {
|
|||
@SuppressLint("InflateParams")
|
||||
fun showReportForm(
|
||||
activity : Activity,
|
||||
access_info : SavedAccount,
|
||||
who : TootAccount,
|
||||
status : TootStatus?,
|
||||
onClickOk : (dialog : Dialog, comment : String) -> Unit
|
||||
onClickOk : (dialog : Dialog, comment : String,forward:Boolean) -> Unit
|
||||
) {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dlg_report_user, null, false)
|
||||
|
||||
val tvUser = view.findViewById<TextView>(R.id.tvUser)
|
||||
val tvStatus = view.findViewById<TextView>(R.id.tvStatus)
|
||||
val etComment = view.findViewById<EditText>(R.id.etComment)
|
||||
val tvUser :TextView = view.findViewById(R.id.tvUser)
|
||||
val tvStatus :TextView = view.findViewById(R.id.tvStatus)
|
||||
val etComment :EditText = view.findViewById(R.id.etComment)
|
||||
|
||||
val cbForward : CheckBox = view.findViewById(R.id.cbForward)
|
||||
val tvForwardDesc:TextView = view.findViewById(R.id.tvForwardDesc)
|
||||
val canForward = access_info.host != who.host
|
||||
|
||||
|
||||
cbForward.isChecked = false
|
||||
if(!canForward){
|
||||
cbForward.visibility = View.GONE
|
||||
tvForwardDesc.visibility = View.GONE
|
||||
}else{
|
||||
cbForward.visibility = View.VISIBLE
|
||||
tvForwardDesc.visibility = View.VISIBLE
|
||||
cbForward.text = activity.getString(R.string.report_forward_to,who.host)
|
||||
}
|
||||
|
||||
|
||||
tvUser.text = who.acct
|
||||
tvStatus.text = status?.decoded_content ?: ""
|
||||
|
@ -40,12 +59,15 @@ object ReportForm {
|
|||
return@OnClickListener
|
||||
}
|
||||
|
||||
onClickOk(dialog, comment)
|
||||
onClickOk(dialog, comment,cbForward.isChecked)
|
||||
})
|
||||
view.findViewById<View>(R.id.btnCancel).setOnClickListener { dialog.cancel() }
|
||||
|
||||
|
||||
dialog.window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package jp.juggler.subwaytooter.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.os.SystemClock
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||
import jp.juggler.subwaytooter.util.clipRange
|
||||
|
||||
class FocusPointView : View {
|
||||
|
||||
interface Callback {
|
||||
fun onFocusPointUpdate(x : Float, y : Float)
|
||||
}
|
||||
|
||||
constructor(context : Context) : super(context) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context : Context, attrs : AttributeSet?) : super(context, attrs) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
private val paint = Paint()
|
||||
private val rect = Rect()
|
||||
private val rectF = RectF()
|
||||
private var strokeWidth : Float = 0f
|
||||
private var circleRadius : Float = 0f
|
||||
private var crossRadius : Float = 0f
|
||||
private var attachment : TootAttachment? = null
|
||||
private var bitmap : Bitmap? = null
|
||||
var callback : Callback? = null
|
||||
|
||||
private var focusX : Float = 0f
|
||||
private var focusY : Float = 0f
|
||||
|
||||
private fun init(context : Context) {
|
||||
|
||||
paint.isFilterBitmap = true
|
||||
paint.isAntiAlias = true
|
||||
|
||||
val density = context.resources.displayMetrics.density
|
||||
this.strokeWidth = density * 2f
|
||||
this.circleRadius = density * 10f
|
||||
this.crossRadius = density * 13f
|
||||
}
|
||||
|
||||
fun setAttachment(
|
||||
attachment : TootAttachment,
|
||||
bitmap : Bitmap
|
||||
) {
|
||||
this.attachment = attachment
|
||||
this.bitmap = bitmap
|
||||
this.focusX = attachment.focusX
|
||||
this.focusY = attachment.focusY
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private var scale : Float = 0f
|
||||
private var draw_w : Float = 0f
|
||||
private var draw_h : Float = 0f
|
||||
private var draw_x : Float = 0f
|
||||
private var draw_y : Float = 0f
|
||||
|
||||
override fun onDraw(canvas : Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
val bitmap = this.bitmap
|
||||
val attachment = this.attachment
|
||||
if(bitmap == null || attachment == null || bitmap.isRecycled) return
|
||||
|
||||
// draw bitmap
|
||||
|
||||
val view_w = this.width.toFloat()
|
||||
val view_h = this.height.toFloat()
|
||||
if(view_w <= 0f || view_h <= 0f) return
|
||||
|
||||
val bitmap_w = bitmap.width.toFloat()
|
||||
val bitmap_h = bitmap.height.toFloat()
|
||||
if(bitmap_w <= 0f || bitmap_h <= 0f) return
|
||||
|
||||
val view_aspect = view_w / view_h
|
||||
val bitmap_aspect = bitmap_w / bitmap_h
|
||||
|
||||
if(bitmap_aspect >= view_aspect) {
|
||||
scale = view_w / bitmap_w
|
||||
draw_w = view_w
|
||||
draw_h = scale * bitmap_h
|
||||
} else {
|
||||
scale = view_h / bitmap_h
|
||||
draw_w = scale * bitmap_w
|
||||
draw_h = view_h
|
||||
}
|
||||
draw_x = (view_w - draw_w) * 0.5f
|
||||
draw_y = (view_h - draw_h) * 0.5f
|
||||
|
||||
rect.left = 0
|
||||
rect.top = 0
|
||||
rect.right = bitmap_w.toInt()
|
||||
rect.bottom = bitmap_h.toInt()
|
||||
|
||||
rectF.left = draw_x
|
||||
rectF.top = draw_y
|
||||
rectF.right = draw_x + draw_w
|
||||
rectF.bottom = draw_y + draw_h
|
||||
|
||||
paint.style = Paint.Style.FILL
|
||||
canvas.drawBitmap(bitmap, rect, rectF, paint)
|
||||
|
||||
// draw focus point
|
||||
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeWidth = strokeWidth
|
||||
paint.color = when((SystemClock.elapsedRealtime() / 500L) % 3) {
|
||||
2L -> Color.RED
|
||||
1L -> Color.BLUE
|
||||
else -> Color.GREEN
|
||||
}
|
||||
|
||||
val point_x = (focusX + 1f) * 0.5f * draw_w + draw_x
|
||||
val point_y = (- focusY + 1f) * 0.5f * draw_h + draw_y
|
||||
canvas.drawCircle(point_x, point_y, circleRadius, paint)
|
||||
canvas.drawLine(point_x, point_y - crossRadius, point_x, point_y + crossRadius, paint)
|
||||
canvas.drawLine(point_x - crossRadius, point_y, point_x + crossRadius, point_y, paint)
|
||||
|
||||
postInvalidateDelayed(500L)
|
||||
}
|
||||
|
||||
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?.onFocusPointUpdate(focusX, focusY)
|
||||
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateFocusPoint(event : MotionEvent) {
|
||||
focusX = clipRange(- 1f, 1f, ((event.x - draw_x) / draw_w) * 2f - 1f)
|
||||
focusY = - clipRange(- 1f, 1f, ((event.y - draw_y) / draw_h) * 2f - 1f)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
}
|
|
@ -501,13 +501,13 @@ class MyNetworkImageView : AppCompatImageView {
|
|||
// ビューのサイズが0より大きい
|
||||
val view_w = width.toFloat()
|
||||
val view_h = height.toFloat()
|
||||
if(view_w <= 0 || view_h <= 0) return
|
||||
if(view_w <= 0f || view_h <= 0f) return
|
||||
|
||||
// 画像のサイズが0より大きい
|
||||
val drawable = this.drawable ?: return
|
||||
val drawable_w = drawable.intrinsicWidth.toFloat()
|
||||
val drawable_h = drawable.intrinsicHeight.toFloat()
|
||||
if(drawable_w <= 0 || drawable_h <= 0) return
|
||||
if(drawable_w <= 0f || drawable_h <= 0f) return
|
||||
|
||||
when(scaleType) {
|
||||
ImageView.ScaleType.CENTER_CROP, ImageView.ScaleType.MATRIX -> {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<jp.juggler.subwaytooter.view.FocusPointView
|
||||
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"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnClose"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/close"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -1,102 +1,131 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/user"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textSize="20sp"
|
||||
android:id="@+id/tvUser"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/status"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:id="@+id/tvStatus"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/report_reason"
|
||||
android:labelFor="@+id/etComment"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:inputType="text"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCancel"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/cancel"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/user"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOk"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
<TextView
|
||||
android:id="@+id/tvUser"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/ok"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/status"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:labelFor="@+id/etComment"
|
||||
android:text="@string/report_reason"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:inputType="text"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvForwardDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/report_forward_desc"
|
||||
/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbForward"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCancel"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
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>
|
||||
</ScrollView>
|
|
@ -524,7 +524,7 @@
|
|||
<string name="not_blocked">Not blocked.</string>
|
||||
<string name="not_muted">Not muted.</string>
|
||||
<string name="media_description">(attachment %1$d) %2$s</string>
|
||||
<string name="set_description">set description(Mastodon 2.0.0 or later)</string>
|
||||
<string name="set_description">set description(Mastodon 2.0 or later)</string>
|
||||
<string name="attachment_description_cant_edit_while_uploading">Attachment description can\'t be edit while uploading.</string>
|
||||
<string name="attachment_description">attachment description</string>
|
||||
<string name="description_empty">description is not specified.</string>
|
||||
|
@ -614,8 +614,11 @@
|
|||
<string name="notification_type_boost">boost</string>
|
||||
<string name="notification_type_favourite">fav.</string>
|
||||
<string name="dont_show_normal_toot">Don\'t show normal toot</string>
|
||||
<string name="set_focus_point">set focus point (Mastodon 2.3 or later)</string>
|
||||
<string name="report_forward_desc">The account is from another instance. Send an anonymized copy of the report there as well?</string>
|
||||
<string name="report_forward_to">Forward to %1$s</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
|
||||
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->
|
||||
|
|
|
@ -807,7 +807,7 @@
|
|||
<string name="not_blocked">ブロックできません</string>
|
||||
<string name="not_muted">ミュートできません</string>
|
||||
<string name="media_description">(添付 %1$d) %2$s</string>
|
||||
<string name="set_description">説明文を設定(マストドン2.0.0以降)</string>
|
||||
<string name="set_description">説明文を設定(マストドン2.0以降)</string>
|
||||
<string name="attachment_description_cant_edit_while_uploading">添付メディアのアップロード中は説明文を設定できません</string>
|
||||
<string name="attachment_description">添付メディアの説明文</string>
|
||||
<string name="description_empty">説明文が指定されてません</string>
|
||||
|
@ -897,5 +897,8 @@
|
|||
<string name="notification_type_boost">ブースト</string>
|
||||
<string name="notification_type_favourite">お気に入り</string>
|
||||
<string name="dont_show_normal_toot">通常トゥートを表示しない</string>
|
||||
<string name="set_focus_point">焦点を設定 (マストドン2.3以降)</string>
|
||||
<string name="report_forward_desc">対象ユーザは別のインスタンスに所属しています。通報内容を投稿元タンスに匿名で転送しますか?</string>
|
||||
<string name="report_forward_to">%1$s に転送する</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -519,7 +519,7 @@
|
|||
<string name="not_blocked">Not blocked.</string>
|
||||
<string name="not_muted">Not muted.</string>
|
||||
<string name="media_description">(attachment %1$d) %2$s</string>
|
||||
<string name="set_description">set description(Mastodon 2.0.0 or later)</string>
|
||||
<string name="set_description">set description(Mastodon 2.0 or later)</string>
|
||||
<string name="attachment_description_cant_edit_while_uploading">Attachment description can\'t be edit while uploading.</string>
|
||||
<string name="attachment_description">attachment description</string>
|
||||
<string name="description_empty">description is not specified.</string>
|
||||
|
@ -603,4 +603,7 @@
|
|||
<string name="notification_type_boost">boost</string>
|
||||
<string name="notification_type_favourite">fav.</string>
|
||||
<string name="dont_show_normal_toot">Don\'t show normal toot</string>
|
||||
<string name="set_focus_point">set focus point (Mastodon 2.3 or later)</string>
|
||||
<string name="report_forward_desc">The account is from another instance. Send an anonymized copy of the report there as well?</string>
|
||||
<string name="report_forward_to">Forward to %1$s</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue