メディアビューアのmoreメニューで背景パターンを選択できる

This commit is contained in:
tateisu 2021-11-07 05:36:59 +09:00
parent 548323d8ca
commit 2fb86b714e
14 changed files with 216 additions and 139 deletions

View File

@ -30,6 +30,10 @@ android {
vectorDrawables.useSupportLibrary = true
}
viewBinding {
enabled = true
}
kotlinOptions {
jvmTarget = jvm_target
useIR = true

View File

@ -15,8 +15,6 @@ import android.os.Environment
import android.os.SystemClock
import android.view.View
import android.view.Window
import android.widget.CheckBox
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@ -24,13 +22,16 @@ import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.Player.TimelineChangeReason
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.*
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.databinding.ActMediaViewerBinding
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.pref.put
import jp.juggler.subwaytooter.util.ProgressResponseBody
import jp.juggler.subwaytooter.view.PinchBitmapView
import jp.juggler.util.*
@ -95,20 +96,18 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
private lateinit var serviceType: ServiceType
private var showDescription = true
private lateinit var pbvImage: PinchBitmapView
private lateinit var btnPrevious: View
private lateinit var btnNext: View
private lateinit var tvError: TextView
private lateinit var viewBinding: ActMediaViewerBinding
private lateinit var exoPlayer: SimpleExoPlayer
private lateinit var exoView: PlayerView
private lateinit var svDescription: View
private lateinit var tvDescription: TextView
private lateinit var tvStatus: TextView
private lateinit var cbMute: CheckBox
private var lastVolume = Float.NaN
internal var bufferingLastShown: Long = 0
private val tileStep by lazy {
val density = resources.displayMetrics.density
(density * 12f + 0.5f).toInt()
}
private val playerListener = object : Player.Listener {
override fun onTimelineChanged(
@ -202,7 +201,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun onDestroy() {
super.onDestroy()
pbvImage.setBitmap(null)
viewBinding.pbvImage.setBitmap(null)
exoPlayer.release()
}
@ -211,30 +210,27 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
overridePendingTransition(R.anim.fade_in, R.anim.slide_to_bottom)
}
internal fun initUI() {
setContentView(R.layout.act_media_viewer)
viewBinding = ActMediaViewerBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
App1.initEdgeToEdge(this)
pbvImage = findViewById(R.id.pbvImage)
btnPrevious = findViewById(R.id.btnPrevious)
btnNext = findViewById(R.id.btnNext)
exoView = findViewById(R.id.exoView)
tvError = findViewById(R.id.tvError)
svDescription = findViewById(R.id.svDescription)
tvDescription = findViewById(R.id.tvDescription)
tvStatus = findViewById(R.id.tvStatus)
cbMute = findViewById(R.id.cbMute)
viewBinding.pbvImage.background = MediaBackgroundDrawable(
tileStep = tileStep,
kind = MediaBackgroundDrawable.Kind.fromIndex(PrefI.ipMediaBackground(this))
)
val enablePaging = mediaList.size > 1
btnPrevious.isEnabledAlpha = enablePaging
btnNext.isEnabledAlpha = enablePaging
viewBinding.btnPrevious.isEnabledAlpha = enablePaging
viewBinding.btnNext.isEnabledAlpha = enablePaging
btnPrevious.setOnClickListener(this)
btnNext.setOnClickListener(this)
viewBinding.btnPrevious.setOnClickListener(this)
viewBinding.btnNext.setOnClickListener(this)
findViewById<View>(R.id.btnDownload).setOnClickListener(this)
findViewById<View>(R.id.btnMore).setOnClickListener(this)
cbMute.setOnCheckedChangeListener { _, isChecked ->
viewBinding.cbMute.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
// mute
lastVolume = exoPlayer.volume
@ -250,7 +246,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
pbvImage.setCallback(object : PinchBitmapView.Callback {
viewBinding.pbvImage.setCallback(object : PinchBitmapView.Callback {
override fun onSwipe(deltaX: Int, deltaY: Int) {
if (isDestroyed) return
if (deltaX != 0) {
@ -270,8 +266,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
) {
App1.getAppState(this@ActMediaViewer).handler.post(Runnable {
if (isDestroyed) return@Runnable
if (tvStatus.visibility == View.VISIBLE) {
tvStatus.text = getString(
if (viewBinding.tvStatus.visibility == View.VISIBLE) {
viewBinding.tvStatus.text = getString(
R.string.zooming_of,
bitmapW.toInt(),
bitmapH.toInt(),
@ -284,7 +280,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
exoPlayer = SimpleExoPlayer.Builder(this).build()
exoPlayer.addListener(playerListener)
exoView.player = exoPlayer
viewBinding.exoView.player = exoPlayer
}
internal fun loadDelta(delta: Int) {
@ -297,11 +293,11 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
internal fun load(state: Bundle? = null) {
exoPlayer.stop()
pbvImage.visibility = View.GONE
exoView.visibility = View.GONE
tvError.visibility = View.GONE
svDescription.visibility = View.GONE
tvStatus.visibility = View.GONE
viewBinding.pbvImage.visibility = View.GONE
viewBinding.exoView.visibility = View.GONE
viewBinding.tvError.visibility = View.GONE
viewBinding.svDescription.visibility = View.GONE
viewBinding.tvStatus.visibility = View.GONE
if (idx < 0 || idx >= mediaList.size) {
showError(getString(R.string.media_attachment_empty))
@ -310,8 +306,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
val ta = mediaList[idx]
val description = ta.description
if (showDescription && description?.isNotEmpty() == true) {
svDescription.visibility = View.VISIBLE
tvDescription.text = description
viewBinding.svDescription.visibility = View.VISIBLE
viewBinding.tvDescription.text = description
}
when (ta.type) {
@ -331,17 +327,17 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
private fun showError(message: String) {
exoView.visibility = View.GONE
pbvImage.visibility = View.GONE
tvError.visibility = View.VISIBLE
tvError.text = message
viewBinding.exoView.visibility = View.GONE
viewBinding.pbvImage.visibility = View.GONE
viewBinding.tvError.visibility = View.VISIBLE
viewBinding.tvError.text = message
}
@SuppressLint("StaticFieldLeak")
private fun loadVideo(ta: TootAttachment, state: Bundle? = null) {
cbMute.vg(true)
if (cbMute.isChecked && lastVolume.isFinite()) {
viewBinding.cbMute.vg(true)
if (viewBinding.cbMute.isChecked && lastVolume.isFinite()) {
exoPlayer.volume = 0f
}
@ -354,7 +350,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
// https://github.com/google/ExoPlayer/issues/1819
HttpsURLConnection.setDefaultSSLSocketFactory(MySslSocketFactory)
exoView.visibility = View.VISIBLE
viewBinding.exoView.visibility = View.VISIBLE
val defaultBandwidthMeter = DefaultBandwidthMeter.Builder(this).build()
val extractorsFactory = DefaultExtractorsFactory()
@ -576,7 +572,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
@SuppressLint("StaticFieldLeak")
private fun loadBitmap(ta: TootAttachment) {
cbMute.visibility = View.INVISIBLE
viewBinding.cbMute.visibility = View.INVISIBLE
val urlList = ta.getLargeUrlList(App1.pref)
if (urlList.isEmpty()) {
@ -584,11 +580,11 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
return
}
tvStatus.visibility = View.VISIBLE
tvStatus.text = null
viewBinding.tvStatus.visibility = View.VISIBLE
viewBinding.tvStatus.text = null
pbvImage.visibility = View.VISIBLE
pbvImage.setBitmap(null)
viewBinding.pbvImage.visibility = View.VISIBLE
viewBinding.pbvImage.setBitmap(null)
launchMain {
val options = BitmapFactory.Options()
@ -616,7 +612,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}.let { result -> // may null
when (val bitmap = resultBitmap) {
null -> if (result != null) showToast(true, result.error)
else -> pbvImage.setBitmap(bitmap)
else -> viewBinding.pbvImage.setBitmap(bitmap)
}
}
}
@ -624,19 +620,18 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btnPrevious -> loadDelta(-1)
R.id.btnNext -> loadDelta(+1)
R.id.btnDownload -> download(mediaList[idx])
R.id.btnMore -> more(mediaList[idx])
when (v) {
viewBinding.btnPrevious -> loadDelta(-1)
viewBinding.btnNext -> loadDelta(+1)
viewBinding.btnDownload -> download(mediaList[idx])
viewBinding.btnMore -> more(mediaList[idx])
}
} catch (ex: Throwable) {
showToast(ex, "action failed.")
}
}
internal class DownloadHistory(val time: Long, val url: String)
class DownloadHistory(val time: Long, val url: String)
private fun download(ta: TootAttachmentLike) {
if (!checkPermission()) return
@ -733,7 +728,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
internal fun copy(url: String) {
private fun copy(url: String) {
val cm = getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager
?: throw NotImplementedError("missing ClipboardManager system service")
@ -756,16 +751,14 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
internal fun more(ta: TootAttachmentLike) {
val ad = ActionsDialog()
private fun more(ta: TootAttachmentLike) {
val ad = ActionsDialog()
if (ta is TootAttachment) {
val url = ta.getLargeUrl(App1.pref) ?: return
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
ad.addAction(getString(R.string.copy_url)) { copy(url) }
addMoreMenu(ad, "url", ta.url, Intent.ACTION_VIEW)
addMoreMenu(ad, "remote_url", ta.remote_url, Intent.ACTION_VIEW)
addMoreMenu(ad, "preview_url", ta.preview_url, Intent.ACTION_VIEW)
@ -778,6 +771,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
ad.addAction(getString(R.string.copy_url)) { copy(url) }
}
if (TootAttachmentType.Image == mediaList.elementAtOrNull(idx)?.type) {
ad.addAction(getString(R.string.background_pattern)) { mediaBackgroundDialog() }
}
ad.show(this, null)
}
@ -788,9 +785,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
@Suppress("SameParameterValue") action: String,
) {
val uri = url.mayUri() ?: return
val caption = getString(R.string.open_browser_of, captionPrefix)
ad.addAction(caption) {
try {
val intent = Intent(action, uri)
@ -834,4 +829,20 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun mediaBackgroundDialog() {
val ad = ActionsDialog()
for (k in MediaBackgroundDrawable.Kind.values()) {
ad.addAction(k.name) {
val idx = k.toIndex()
App1.pref.edit().put(PrefI.ipMediaBackground, idx).apply()
viewBinding.pbvImage.background = MediaBackgroundDrawable(
tileStep = tileStep,
kind = k
)
}
}
ad.show(this, getString(R.string.background_pattern))
}
}

View File

@ -10,6 +10,7 @@ import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageView
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
import jp.juggler.subwaytooter.pref.*
import jp.juggler.subwaytooter.pref.impl.*
@ -374,7 +375,8 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
spinner(
PrefI.ipAdditionalButtonsPosition,
R.string.additional_buttons_position,
*(AdditionalButtonsPosition.values().sortedBy { it.idx }.map { it.captionId }.toIntArray())
*(AdditionalButtonsPosition.values().sortedBy { it.idx }.map { it.captionId }
.toIntArray())
)
sw(PrefB.bpEnablePixelfed, R.string.enable_connect_to_pixelfed_server)
@ -499,6 +501,11 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
section(R.string.media_attachment) {
sw(PrefB.bpUseInternalMediaViewer, R.string.use_internal_media_viewer)
spinner(PrefI.ipMediaBackground, R.string.background_pattern) {
MediaBackgroundDrawable.Kind.values().map { it.name }
}
sw(PrefB.bpPriorLocalURL, R.string.prior_local_url_when_open_attachment)
text(PrefS.spMediaThumbHeight, R.string.media_thumbnail_height, InputTypeEx.number)
sw(PrefB.bpDontCropMediaThumb, R.string.dont_crop_media_thumbnail)

View File

@ -0,0 +1,75 @@
package jp.juggler.subwaytooter.drawable
import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import kotlin.math.min
class MediaBackgroundDrawable(
private val tileStep: Int,
private val kind: Kind
) : Drawable() {
enum class Kind(@ColorInt val c1: Int, @ColorInt val c2: Int = 0) {
Black(Color.BLACK),
BlackTile(Color.BLACK, Color.BLACK or 0x202020),
Grey(Color.BLACK or 0x787878),
GreyTile(Color.BLACK or 0x707070, Color.BLACK or 0x808080),
White(Color.WHITE),
WhiteTile(Color.WHITE, Color.BLACK or 0xe0e0e0),
;
fun toIndex() = values().indexOf(this)
companion object {
fun fromIndex(idx: Int) = values().elementAtOrNull(idx) ?: BlackTile
}
}
private val rect = Rect()
private val paint = Paint().apply {
style = Paint.Style.FILL
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
override fun getOpacity() = when (paint.alpha) {
255 -> PixelFormat.OPAQUE
0 -> PixelFormat.TRANSPARENT
else -> PixelFormat.TRANSLUCENT
}
override fun draw(canvas: Canvas) {
val bounds = this.bounds
val xStart = bounds.left
val xRepeat = (bounds.right - bounds.left + tileStep - 1) / tileStep
val yStart = bounds.top
val yRepeat = (bounds.bottom - bounds.top + tileStep - 1) / tileStep
for (y in 0 until yRepeat) {
for (x in 0 until xRepeat) {
paint.color = if (kind.c2 != 0 && (x + y).and(1) != 0) {
kind.c2
} else {
kind.c1
}
val xs = xStart + x * tileStep
val ys = yStart + y * tileStep
rect.set(
xs,
ys,
min(bounds.right, xs + tileStep),
min(bounds.bottom, ys + tileStep),
)
canvas.drawRect(rect, paint)
}
}
}
}

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.pref
import android.graphics.Color
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
import jp.juggler.subwaytooter.pref.impl.IntPref
@ -113,4 +114,6 @@ object PrefI {
// val ipTrendTagCountShowing = IntPref("TrendTagCountShowing", 0)
// const val TTCS_WEEKLY = 0
// const val TTCS_DAILY = 1
}
val ipMediaBackground = IntPref("MediaBackground", 1 )
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/media_bg_dark"
android:antialias="false"
android:dither="false"
android:filter="false"
android:tileMode="repeat"
/>

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<ScrollView
@ -10,10 +11,10 @@
android:layout_width="match_parent"
android:layout_height="64dp"
android:clipToPadding="false"
android:paddingStart="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp">
android:paddingStart="12dp"
android:paddingTop="6dp">
<TextView
android:id="@+id/tvDescription"
@ -30,7 +31,7 @@
android:id="@+id/pbvImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/media_background" />
/>
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/exoView"
@ -45,54 +46,22 @@
android:padding="12dp" />
</FrameLayout>
<LinearLayout
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<FrameLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp">
<CheckBox
android:id="@+id/cbMute"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/mute"
/>
<TextView
android:id="@+id/tvStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="6dp"
android:alpha="0.5"
android:gravity="end|bottom"
android:textColor="#ffffff"
android:textSize="12sp" />
</FrameLayout>
app:alignItems="flex_start"
app:flexWrap="wrap"
app:flexDirection="row_reverse"
app:justifyContent="flex_start">
<ImageButton
android:id="@+id/btnPrevious"
android:id="@+id/btnMore"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/previous"
android:contentDescription="@string/more"
android:minWidth="48dp"
android:src="@drawable/ic_arrow_start"
app:tint="?attr/colorVectorDrawable" />
<ImageButton
android:id="@+id/btnNext"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/next"
android:minWidth="48dp"
android:src="@drawable/ic_arrow_end"
android:src="@drawable/ic_more"
app:tint="?attr/colorVectorDrawable" />
<ImageButton
@ -105,29 +74,44 @@
app:tint="?attr/colorVectorDrawable" />
<ImageButton
android:id="@+id/btnMore"
android:id="@+id/btnNext"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/more"
android:contentDescription="@string/next"
android:minWidth="48dp"
android:src="@drawable/ic_more"
android:src="@drawable/ic_arrow_end"
app:tint="?attr/colorVectorDrawable" />
<ImageButton
android:id="@+id/btnPrevious"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/previous"
</LinearLayout>
android:minWidth="48dp"
android:src="@drawable/ic_arrow_start"
app:tint="?attr/colorVectorDrawable" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="12dp"
android:alpha="0.5"
tools:text="x1.3\n1200x1200"
android:gravity="end|center_vertical"
android:textColor="#ffffff"
android:textSize="12dp"
app:layout_flexGrow="1"
tools:ignore="SpUsage" />
<!-- <com.google.android.flexbox.FlexboxLayout-->
<!-- android:id="@+id/flFooter"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:alignItems="flex_start"-->
<!-- app:flexWrap="wrap"-->
<!-- app:justifyContent="flex_start">-->
<!-- </com.google.android.flexbox.FlexboxLayout>-->
<CheckBox
android:id="@+id/cbMute"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="12dp"
android:text="@string/mute" />
</com.google.android.flexbox.FlexboxLayout>
</LinearLayout>

View File

@ -1100,4 +1100,5 @@
<string name="mfm_decoration_enabled">(Misskey)テキスト装飾を表示する</string>
<string name="mfm_show_unsupported_markup">(Misskey)未対応のマークアップを表示する</string>
<string name="show_media_description">添付メディアの説明文を表示する</string>
<string name="background_pattern">背景パターン</string>
</resources>

View File

@ -1111,4 +1111,5 @@
<string name="mfm_decoration_enabled">(Misskey)Show text decorations</string>
<string name="mfm_show_unsupported_markup">(Misskey)Show unsupported markups</string>
<string name="show_media_description">Show media description</string>
<string name="background_pattern">Background pattern</string>
</resources>