依存関係の更新。QRコード生成ライブラリの変更。

This commit is contained in:
tateisu 2024-01-06 02:18:28 +09:00
parent 593fffaad2
commit 5f7f4c34ec
21 changed files with 265 additions and 209 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" /> <option name="version" value="1.9.22" />
</component> </component>
</project> </project>

View File

@ -45,7 +45,7 @@ android {
} }
kotlin { kotlin {
jvmToolchain( Vers.kotlinJvmToolchain) jvmToolchain(Vers.kotlinJvmToolchain)
} }
kotlinOptions { kotlinOptions {
jvmTarget = Vers.kotlinJvmTarget jvmTarget = Vers.kotlinJvmTarget
@ -65,5 +65,7 @@ dependencies {
api(project(":apng")) api(project(":apng"))
implementation(project(":base")) implementation(project(":base"))
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:${Vers.desugarLibVersion}")
testImplementation("junit:junit:${Vers.junitVersion}") testImplementation("junit:junit:${Vers.junitVersion}")
} }

View File

@ -150,7 +150,6 @@ android {
} }
} }
dependencies { dependencies {
// desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する // desugar_jdk_libs 2.0.0 は AGP 7.4.0-alpha10 以降を要求する
@ -172,7 +171,8 @@ dependencies {
implementation("com.github.UnifiedPush:android-connector:2.1.1") implementation("com.github.UnifiedPush:android-connector:2.1.1")
implementation("jp.wasabeef:glide-transformations:4.3.0") implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("com.github.androidmads:QRGenerator:1.0.1") // implementation("com.github.androidmads:QRGenerator:1.0.1")
implementation("com.github.alexzhirkevich:custom-qr-generator:1.6.2")
val apng4AndroidVersion = "2.25.0" val apng4AndroidVersion = "2.25.0"
implementation("com.github.penfeizhou.android.animation:apng:$apng4AndroidVersion") implementation("com.github.penfeizhou.android.animation:apng:$apng4AndroidVersion")

View File

@ -1,95 +1,101 @@
package jp.juggler.subwaytooter.dialog package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.view.View import android.graphics.drawable.ColorDrawable
import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity
import android.widget.TextView import com.github.alexzhirkevich.customqrgenerator.QrData
import androidmads.library.qrgenearator.QRGContents import com.github.alexzhirkevich.customqrgenerator.vector.QrCodeDrawable
import androidmads.library.qrgenearator.QRGEncoder import com.github.alexzhirkevich.customqrgenerator.vector.createQrVectorOptions
import jp.juggler.subwaytooter.ActMain import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorBallShape
import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorColor
import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorFrameShape
import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorLogoPadding
import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorLogoShape
import com.github.alexzhirkevich.customqrgenerator.vector.style.QrVectorPixelShape
import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.R
import jp.juggler.util.coroutine.launchProgress import jp.juggler.subwaytooter.databinding.DlgQrCodeBinding
import jp.juggler.util.coroutine.AppDispatchers
import jp.juggler.util.coroutine.launchAndShowError
import jp.juggler.util.coroutine.withProgress
import jp.juggler.util.log.LogCategory import jp.juggler.util.log.LogCategory
import jp.juggler.util.os.resDrawable
import kotlinx.coroutines.withContext
@SuppressLint("StaticFieldLeak") private val log = LogCategory("DlgQRCode")
object DlgQRCode {
private val log = LogCategory("DlgQRCode") val UInt.int get() = toInt()
internal interface QrCodeCallback { fun AppCompatActivity.dialogQrCode(
fun onQrCode(bitmap: Bitmap?) message: CharSequence,
} url: String,
) = launchAndShowError("dialogQrCode failed.") {
private fun makeQrCode( val drawable = withProgress(
activity: ActMain, caption = getString(R.string.generating_qr_code),
size: Int,
url: String,
callback: QrCodeCallback,
) { ) {
activity.launchProgress( withContext(AppDispatchers.DEFAULT) {
"making QR code", QrCodeDrawable(data = QrData.Url(url), options = qrCodeOptions())
progressInitializer = { }
it.setMessageEx(activity.getString(R.string.generating_qr_code)) }
}, val dialog = Dialog(this@dialogQrCode)
doInBackground = {
try { val views = DlgQrCodeBinding.inflate(layoutInflater).apply {
QRGEncoder( btnCancel.setOnClickListener { dialog.cancel() }
/* data */ url, ivQrCode.setImageDrawable(drawable)
/* bundle */ null, tvMessage.text = message
QRGContents.Type.TEXT, tvUrl.text = "[ $url ]" // なぜか素のURLだと@以降が表示されない
/* dimension */ size,
).apply {
// 背景色
colorBlack = Color.WHITE
// 図柄の色
colorWhite = Color.BLACK
}.bitmap
} catch (ex: Throwable) {
log.e(ex, "QR generation failed.")
null
}
},
afterProc = {
if (it != null) callback.onQrCode(it)
},
)
} }
fun open(activity: ActMain, message: CharSequence, url: String) { dialog.apply {
setContentView(views.root)
val size = (0.5f + 240f * activity.density).toInt() setCancelable(true)
makeQrCode(activity, size, url, object : QrCodeCallback { setCanceledOnTouchOutside(true)
show()
@SuppressLint("InflateParams") }
override fun onQrCode(bitmap: Bitmap?) { }
val viewRoot = activity.layoutInflater.inflate(R.layout.dlg_qr_code, null, false) private fun AppCompatActivity.qrCodeOptions() = createQrVectorOptions {
val dialog = Dialog(activity) background {
dialog.setContentView(viewRoot) drawable = ColorDrawable(Color.WHITE)
dialog.setCancelable(true) }
dialog.setCanceledOnTouchOutside(true)
padding = .125f
var tv = viewRoot.findViewById<TextView>(R.id.tvMessage)
tv.text = message logo {
drawable = resDrawable(R.drawable.qr_code_center)
tv = viewRoot.findViewById(R.id.tvUrl) size = .25f
tv.text = "[ $url ]" // なぜか素のURLだと@以降が表示されない shape = QrVectorLogoShape.Default
padding = QrVectorLogoPadding.Natural(.1f)
val iv = viewRoot.findViewById<ImageView>(R.id.ivQrCode) }
iv.setImageBitmap(bitmap) shapes {
// 市松模様のドット
dialog.setOnDismissListener { darkPixel = QrVectorPixelShape.RoundCorners(.5f)
iv.setImageDrawable(null) // 3隅の真ん中の大きめドット
bitmap?.recycle() ball = QrVectorBallShape.RoundCorners(.25f)
} // 3隅の枠
frame = QrVectorFrameShape.RoundCorners(.25f)
viewRoot.findViewById<View>(R.id.btnCancel).setOnClickListener { dialog.cancel() } }
colors {
dialog.show() val cobalt = 0xFF0088FFU.int
} val cobaltDark = 0xFF004488U.int
}) // 市松模様のドット
dark = QrVectorColor.Solid(cobaltDark)
// 3隅の真ん中の大きめドット
ball = QrVectorColor.RadialGradient(
colors = listOf(
0f to cobaltDark,
1f to cobalt,
),
radius = 2f,
)
// 3隅の枠
frame = QrVectorColor.LinearGradient(
colors = listOf(
0f to cobaltDark,
1f to cobalt,
),
orientation = QrVectorColor.LinearGradient
.Orientation.Vertical
)
} }
} }

View File

@ -23,7 +23,7 @@ import jp.juggler.subwaytooter.column.Column
import jp.juggler.subwaytooter.column.ColumnType import jp.juggler.subwaytooter.column.ColumnType
import jp.juggler.subwaytooter.databinding.DlgContextMenuBinding import jp.juggler.subwaytooter.databinding.DlgContextMenuBinding
import jp.juggler.subwaytooter.dialog.DlgListMember import jp.juggler.subwaytooter.dialog.DlgListMember
import jp.juggler.subwaytooter.dialog.DlgQRCode import jp.juggler.subwaytooter.dialog.dialogQrCode
import jp.juggler.subwaytooter.pref.PrefB import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.pref.PrefI import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.span.MyClickableSpan import jp.juggler.subwaytooter.span.MyClickableSpan
@ -446,11 +446,11 @@ internal class DlgContextMenu(
else -> whoHost else -> whoHost
} }
private fun updateGroup(btn: Button, group: View, toggle: Boolean = false): Boolean { private fun updateGroup(btn: Button, group: View, toggle: Boolean = false) {
if (btn.visibility != View.VISIBLE) { if (btn.visibility != View.VISIBLE) {
group.vg(false) group.vg(false)
return true return
} }
when { when {
@ -463,51 +463,57 @@ internal class DlgContextMenu(
else -> btn.setOnClickListener(this) else -> btn.setOnClickListener(this)
} }
val iconId = if (group.visibility == View.VISIBLE) { val iconId = when (group.visibility) {
R.drawable.ic_arrow_drop_up View.VISIBLE -> R.drawable.ic_arrow_drop_up
} else { else -> R.drawable.ic_arrow_drop_down
R.drawable.ic_arrow_drop_down
} }
val iconColor = activity.attrColor(R.attr.colorTimeSmall) val iconColor = activity.attrColor(R.attr.colorTimeSmall)
val drawable = createColoredDrawable(activity, iconId, iconColor, 1f) val drawable = createColoredDrawable(activity, iconId, iconColor, 1f)
btn.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, null, null, null) btn.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, null, null, null)
return true
} }
private fun onClickUpdateGroup(v: View): Boolean = when (v.id) { private fun onClickUpdateGroup(v: View): Boolean {
R.id.btnGroupStatusCrossAccount -> updateGroup( when (v.id) {
views.btnGroupStatusCrossAccount, R.id.btnGroupStatusCrossAccount -> updateGroup(
views.llGroupStatusCrossAccount, views.btnGroupStatusCrossAccount,
toggle = true views.llGroupStatusCrossAccount,
) toggle = true
)
R.id.btnGroupUserCrossAccount -> updateGroup( R.id.btnGroupUserCrossAccount -> updateGroup(
views.btnGroupUserCrossAccount, views.btnGroupUserCrossAccount,
views.llGroupUserCrossAccount, views.llGroupUserCrossAccount,
toggle = true toggle = true
) )
R.id.btnGroupStatusAround -> updateGroup(
views.btnGroupStatusAround, R.id.btnGroupStatusAround -> updateGroup(
views.llGroupStatusAround, views.btnGroupStatusAround,
toggle = true views.llGroupStatusAround,
) toggle = true
R.id.btnGroupStatusByMe -> updateGroup( )
views.btnGroupStatusByMe,
views.llGroupStatusByMe, R.id.btnGroupStatusByMe -> updateGroup(
toggle = true views.btnGroupStatusByMe,
) views.llGroupStatusByMe,
R.id.btnGroupStatusExtra -> updateGroup( toggle = true
views.btnGroupStatusExtra, )
views.llGroupStatusExtra,
toggle = true R.id.btnGroupStatusExtra -> updateGroup(
) views.btnGroupStatusExtra,
R.id.btnGroupUserExtra -> updateGroup( views.llGroupStatusExtra,
views.btnGroupUserExtra, toggle = true
views.llGroupUserExtra, )
toggle = true
) R.id.btnGroupUserExtra -> updateGroup(
else -> false views.btnGroupUserExtra,
views.llGroupUserExtra,
toggle = true
)
else -> return false
}
return true
} }
private fun ActMain.onClickUserAndStatus( private fun ActMain.onClickUserAndStatus(
@ -552,16 +558,18 @@ internal class DlgContextMenu(
accessInfo, accessInfo,
who who
) )
R.id.btnNickname -> clickNicknameCustomize(accessInfo, who) R.id.btnNickname -> clickNicknameCustomize(accessInfo, who)
R.id.btnAccountQrCode -> DlgQRCode.open( R.id.btnAccountQrCode -> activity.dialogQrCode(
activity, message = whoRef.decoded_display_name,
whoRef.decoded_display_name, url = who.getUserUrl()
who.getUserUrl()
) )
R.id.btnDomainBlock -> clickDomainBlock(accessInfo, who) R.id.btnDomainBlock -> clickDomainBlock(accessInfo, who)
R.id.btnOpenTimeline -> who.apiHost.valid()?.let { timelineLocal(pos, it) } R.id.btnOpenTimeline -> who.apiHost.valid()?.let { timelineLocal(pos, it) }
R.id.btnDomainTimeline -> who.apiHost.valid() R.id.btnDomainTimeline -> who.apiHost.valid()
?.let { timelineDomain(pos, accessInfo, it) } ?.let { timelineDomain(pos, accessInfo, it) }
R.id.btnAvatarImage -> openAvatarImage(who) R.id.btnAvatarImage -> openAvatarImage(who)
R.id.btnQuoteName -> quoteName(who) R.id.btnQuoteName -> quoteName(who)
R.id.btnHideBoost -> userSetShowBoosts(accessInfo, who, false) R.id.btnHideBoost -> userSetShowBoosts(accessInfo, who, false)
@ -574,6 +582,7 @@ internal class DlgContextMenu(
column, column,
getUserApiHost() getUserApiHost()
) )
R.id.btnEndorse -> userEndorsement(accessInfo, who, !relation.endorsed) R.id.btnEndorse -> userEndorsement(accessInfo, who, !relation.endorsed)
R.id.btnCopyAccountId -> who.id.toString().copyToClipboard(activity) R.id.btnCopyAccountId -> who.id.toString().copyToClipboard(activity)
R.id.btnOpenAccountInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/accounts/${who.id}") R.id.btnOpenAccountInAdminWebUi -> openBrowser("https://${accessInfo.apiHost.ascii}/admin/accounts/${who.id}")
@ -612,6 +621,7 @@ internal class DlgContextMenu(
accessInfo, accessInfo,
status status
) )
R.id.btnQuoteUrlStatus -> openPost(status.url?.notEmpty()) R.id.btnQuoteUrlStatus -> openPost(status.url?.notEmpty())
R.id.btnShareUrlStatus -> shareText(status.url?.notEmpty()) R.id.btnShareUrlStatus -> shareText(status.url?.notEmpty())
R.id.btnConversationMute -> conversationMute(accessInfo, status) R.id.btnConversationMute -> conversationMute(accessInfo, status)
@ -622,6 +632,7 @@ internal class DlgContextMenu(
accessInfo, accessInfo,
status status
) )
else -> return false else -> return false
} }
return true return true
@ -675,10 +686,12 @@ internal class DlgContextMenu(
dialog.dismissSafe() dialog.dismissSafe()
followFromAnotherAccount(pos, accessInfo, who) followFromAnotherAccount(pos, accessInfo, who)
} }
R.id.btnProfile -> { R.id.btnProfile -> {
dialog.dismissSafe() dialog.dismissSafe()
userProfileFromAnotherAccount(pos, accessInfo, who) userProfileFromAnotherAccount(pos, accessInfo, who)
} }
R.id.btnSendMessage -> { R.id.btnSendMessage -> {
dialog.dismissSafe() dialog.dismissSafe()
mentionFromAnotherAccount(accessInfo, who) mentionFromAnotherAccount(accessInfo, who)

View File

@ -20,7 +20,7 @@ import jp.juggler.util.log.LogCategory
import jp.juggler.util.log.errorEx import jp.juggler.util.log.errorEx
import jp.juggler.util.log.showToast import jp.juggler.util.log.showToast
import jp.juggler.util.log.withCaption import jp.juggler.util.log.withCaption
import jp.juggler.util.media.MovideResizeMode import jp.juggler.util.media.MovieResizeMode
import jp.juggler.util.media.MovieResizeConfig import jp.juggler.util.media.MovieResizeConfig
import jp.juggler.util.media.ResizeConfig import jp.juggler.util.media.ResizeConfig
import jp.juggler.util.media.ResizeType import jp.juggler.util.media.ResizeType
@ -928,7 +928,7 @@ class SavedAccount(
fun getMovieResizeConfig() = fun getMovieResizeConfig() =
MovieResizeConfig( MovieResizeConfig(
mode = MovideResizeMode.fromInt(movieTranscodeMode), mode = MovieResizeMode.fromInt(movieTranscodeMode),
limitBitrate = movieTranscodeBitrate.toLongOrNull() limitBitrate = movieTranscodeBitrate.toLongOrNull()
?.takeIf { it >= 100_000L } ?: 2_000_000L, ?.takeIf { it >= 100_000L } ?: 2_000_000L,
limitFrameRate = movieTranscodeFramerate.toIntOrNull() limitFrameRate = movieTranscodeFramerate.toIntOrNull()

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="64dp"/>
<stroke android:color="#08f" android:width="10dp"/>
</shape>

View File

@ -30,8 +30,8 @@
tools:ignore="SmallSp"/> tools:ignore="SmallSp"/>
<ImageView <ImageView
android:layout_width="240dp" android:layout_width="280dp"
android:layout_height="240dp" android:layout_height="280dp"
android:layout_margin="10dp" android:layout_margin="10dp"
android:scaleType="centerInside" android:scaleType="centerInside"
android:id="@+id/ivQrCode" android:id="@+id/ivQrCode"

View File

@ -77,7 +77,7 @@ dependencies {
api("androidx.emoji2:emoji2-views-helper:${Vers.emoji2Version}") api("androidx.emoji2:emoji2-views-helper:${Vers.emoji2Version}")
api("androidx.emoji2:emoji2-views:${Vers.emoji2Version}") api("androidx.emoji2:emoji2-views:${Vers.emoji2Version}")
api("androidx.emoji2:emoji2:${Vers.emoji2Version}") api("androidx.emoji2:emoji2:${Vers.emoji2Version}")
api("androidx.exifinterface:exifinterface:1.3.6") api("androidx.exifinterface:exifinterface:1.3.7")
api("androidx.lifecycle:lifecycle-common-java8:${Vers.lifecycleVersion}") api("androidx.lifecycle:lifecycle-common-java8:${Vers.lifecycleVersion}")
api("androidx.lifecycle:lifecycle-livedata-ktx:${Vers.lifecycleVersion}") api("androidx.lifecycle:lifecycle-livedata-ktx:${Vers.lifecycleVersion}")
api("androidx.lifecycle:lifecycle-process:${Vers.lifecycleVersion}") api("androidx.lifecycle:lifecycle-process:${Vers.lifecycleVersion}")
@ -100,8 +100,6 @@ dependencies {
api("com.otaliastudios:transcoder:0.10.5") api("com.otaliastudios:transcoder:0.10.5")
api("com.squareup.okhttp3:okhttp-urlconnection:${Vers.okhttpVersion}") api("com.squareup.okhttp3:okhttp-urlconnection:${Vers.okhttpVersion}")
api("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}") api("com.squareup.okhttp3:okhttp:${Vers.okhttpVersion}")
// api( "io.github.inflationx:calligraphy3:3.1.1")
// api( "io.github.inflationx:viewpump:2.1.1")
api("org.bouncycastle:bcprov-jdk15on:1.70") api("org.bouncycastle:bcprov-jdk15on:1.70")
api("org.jetbrains.kotlin:kotlin-reflect:${Vers.kotlinVersion}") api("org.jetbrains.kotlin:kotlin-reflect:${Vers.kotlinVersion}")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Vers.kotlinxCoroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Vers.kotlinxCoroutinesVersion}")

View File

@ -96,11 +96,7 @@ suspend fun transcodeAudio(
} }
val transformer = Transformer.Builder(context) val transformer = Transformer.Builder(context)
.setLooper(looper) .setLooper(looper)
.setTransformationRequest( .setAudioMimeType(encodeMimeType)
TransformationRequest.Builder()
.setAudioMimeType(encodeMimeType)
.build()
)
.addListener(transformerListener) .addListener(transformerListener)
.build() .build()

View File

@ -153,23 +153,28 @@ fun AppCompatActivity.overrideActivityTransitionCompat(
@AnimRes animEnter: Int, @AnimRes animEnter: Int,
@AnimRes animExit: Int, @AnimRes animExit: Int,
) { ) {
if (Build.VERSION.SDK_INT >= 34) { when {
overrideActivityTransition( Build.VERSION.SDK_INT >= 34 -> {
when (overrideType) { overrideActivityTransition(
TransitionOverrideType.Open -> when (overrideType) {
AppCompatActivity.OVERRIDE_TRANSITION_OPEN TransitionOverrideType.Open ->
AppCompatActivity.OVERRIDE_TRANSITION_OPEN
TransitionOverrideType.Close -> TransitionOverrideType.Close ->
AppCompatActivity.OVERRIDE_TRANSITION_CLOSE AppCompatActivity.OVERRIDE_TRANSITION_CLOSE
}, },
animEnter, animEnter,
animExit animExit
) )
} else { }
overridePendingTransition(
animEnter, else -> {
animExit, @Suppress("DEPRECATION")
) overridePendingTransition(
animEnter,
animExit,
)
}
} }
} }

View File

@ -84,6 +84,30 @@ fun AppCompatActivity.launchAndShowError(
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
suspend fun <T:Any?> AppCompatActivity.withProgress(
caption:String,
progressInitializer: suspend (ProgressDialogEx) -> Unit = {},
block: suspend (progress :ProgressDialogEx)->T,
):T {
val activity = this
var progress: ProgressDialogEx? = null
try {
progress = ProgressDialogEx(activity)
progress.setCancelable(true)
progress.isIndeterminateEx = true
progress.setMessageEx(caption)
progressInitializer(progress)
progress.show()
return supervisorScope {
val task = async(AppDispatchers.MainImmediate) { block(progress) }
progress.setOnCancelListener { task.cancel() }
task.await()
}
} finally {
progress?.dismissSafe()
}
}
fun <T : Any?> AppCompatActivity.launchProgress( fun <T : Any?> AppCompatActivity.launchProgress(
caption: String, caption: String,
doInBackground: suspend CoroutineScope.(ProgressDialogEx) -> T, doInBackground: suspend CoroutineScope.(ProgressDialogEx) -> T,

View File

@ -289,18 +289,14 @@ fun createResizedBitmap(
// 出力用Bitmap作成 // 出力用Bitmap作成
val dst = Bitmap.createBitmap(dstSizeInt.x, dstSizeInt.y, Bitmap.Config.ARGB_8888) val dst = Bitmap.createBitmap(dstSizeInt.x, dstSizeInt.y, Bitmap.Config.ARGB_8888)
try { try {
if (dst == null) { val canvas = Canvas(dst)
context.showToast(false, "bitmap creation failed.") val paint = Paint()
} else { paint.isFilterBitmap = true
val canvas = Canvas(dst) canvas.drawBitmap(sourceBitmap, matrix, paint)
val paint = Paint() log.d("createResizedBitmap: resized to ${dstSizeInt.x}x${dstSizeInt.y}")
paint.isFilterBitmap = true return dst
canvas.drawBitmap(sourceBitmap, matrix, paint)
log.d("createResizedBitmap: resized to ${dstSizeInt.x}x${dstSizeInt.y}")
return dst
}
} catch (ex: Throwable) { } catch (ex: Throwable) {
dst?.recycle() dst.recycle()
throw ex throw ex
} }
} finally { } finally {

View File

@ -12,7 +12,6 @@ import androidx.media3.transformer.Effects
import androidx.media3.transformer.ExportException import androidx.media3.transformer.ExportException
import androidx.media3.transformer.ExportResult import androidx.media3.transformer.ExportResult
import androidx.media3.transformer.ProgressHolder import androidx.media3.transformer.ProgressHolder
import androidx.media3.transformer.TransformationRequest
import androidx.media3.transformer.Transformer import androidx.media3.transformer.Transformer
import androidx.media3.transformer.VideoEncoderSettings import androidx.media3.transformer.VideoEncoderSettings
import com.otaliastudios.transcoder.Transcoder import com.otaliastudios.transcoder.Transcoder
@ -23,9 +22,14 @@ import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy
import jp.juggler.util.coroutine.AppDispatchers import jp.juggler.util.coroutine.AppDispatchers
import jp.juggler.util.data.clip import jp.juggler.util.data.clip
import jp.juggler.util.log.LogCategory import jp.juggler.util.log.LogCategory
import kotlinx.coroutines.* import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -38,19 +42,19 @@ import kotlin.math.sqrt
private val log = LogCategory("MovieUtils") private val log = LogCategory("MovieUtils")
enum class MovideResizeMode(val int: Int) { enum class MovieResizeMode(val int: Int) {
Auto(0), Auto(0),
No(1), No(1),
Always(2), Always(2),
; ;
companion object { companion object {
fun fromInt(i: Int) = values().find { it.int == i } ?: Auto fun fromInt(i: Int) = entries.find { it.int == i } ?: Auto
} }
} }
data class MovieResizeConfig( data class MovieResizeConfig(
val mode: MovideResizeMode, val mode: MovieResizeMode,
val limitFrameRate: Int, val limitFrameRate: Int,
val limitBitrate: Long, val limitBitrate: Long,
val limitSquarePixels: Int, val limitSquarePixels: Int,
@ -78,9 +82,9 @@ data class MovieResizeConfig(
// トランスコードをスキップする判定 // トランスコードをスキップする判定
fun isTranscodeRequired(info: VideoInfo) = when (mode) { fun isTranscodeRequired(info: VideoInfo) = when (mode) {
MovideResizeMode.No -> false MovieResizeMode.No -> false
MovideResizeMode.Always -> true MovieResizeMode.Always -> true
MovideResizeMode.Auto -> MovieResizeMode.Auto ->
info.squarePixels > limitSquarePixels || info.squarePixels > limitSquarePixels ||
(info.actualBps ?: 0).toFloat() > limitBitrate.toFloat() * 1.5f || (info.actualBps ?: 0).toFloat() > limitBitrate.toFloat() * 1.5f ||
(info.frameRatio == null || info.frameRatio < 1f || info.frameRatio > limitFrameRate) (info.frameRatio == null || info.frameRatio < 1f || info.frameRatio > limitFrameRate)
@ -129,9 +133,9 @@ suspend fun transcodeVideoMedia3Transformer(
withContext(AppDispatchers.MainImmediate) { withContext(AppDispatchers.MainImmediate) {
when (resizeConfig.mode) { when (resizeConfig.mode) {
MovideResizeMode.No -> return@withContext inFile MovieResizeMode.No -> return@withContext inFile
MovideResizeMode.Always -> Unit MovieResizeMode.Always -> Unit
MovideResizeMode.Auto -> { MovieResizeMode.Auto -> {
if (!resizeConfig.isTranscodeRequired(info)) { if (!resizeConfig.isTranscodeRequired(info)) {
log.i("transcodeVideoMedia3Transformer: transcode not required.") log.i("transcodeVideoMedia3Transformer: transcode not required.")
return@withContext inFile return@withContext inFile
@ -189,12 +193,6 @@ suspend fun transcodeVideoMedia3Transformer(
} }
}.build() }.build()
val request = TransformationRequest.Builder().apply {
setVideoMimeType(MimeTypes.VIDEO_H264)
setAudioMimeType(MimeTypes.AUDIO_AAC)
// ビットレートがないな…
}.build()
// 完了検知 // 完了検知
val completed = AtomicBoolean(false) val completed = AtomicBoolean(false)
val error = AtomicReference<Throwable>(null) val error = AtomicReference<Throwable>(null)
@ -226,9 +224,11 @@ suspend fun transcodeVideoMedia3Transformer(
// 開始 // 開始
val transformer = Transformer.Builder(context).apply { val transformer = Transformer.Builder(context).apply {
setEncoderFactory(encoderFactory) setEncoderFactory(encoderFactory)
setTransformationRequest(request) setAudioMimeType(MimeTypes.AUDIO_AAC)
setVideoMimeType(MimeTypes.VIDEO_H264)
addListener(listener) addListener(listener)
}.build() }.build()
transformer.start(editedMediaItem, outFile.canonicalPath) transformer.start(editedMediaItem, outFile.canonicalPath)
// 完了まで待機しつつ、定期的に進捗コールバックを呼ぶ // 完了まで待機しつつ、定期的に進捗コールバックを呼ぶ
@ -265,9 +265,9 @@ suspend fun transcodeVideo(
} }
when (resizeConfig.mode) { when (resizeConfig.mode) {
MovideResizeMode.No -> return@withContext inFile MovieResizeMode.No -> return@withContext inFile
MovideResizeMode.Always -> Unit MovieResizeMode.Always -> Unit
MovideResizeMode.Auto -> { MovieResizeMode.Auto -> {
if (info.squarePixels <= resizeConfig.limitSquarePixels && if (info.squarePixels <= resizeConfig.limitSquarePixels &&
(info.actualBps ?: 0).toFloat() <= resizeConfig.limitBitrate * 1.5f && (info.actualBps ?: 0).toFloat() <= resizeConfig.limitBitrate * 1.5f &&
(info.frameRatio?.toInt() ?: 0) <= resizeConfig.limitFrameRate (info.frameRatio?.toInt() ?: 0) <= resizeConfig.limitFrameRate

View File

@ -1,7 +1,9 @@
package jp.juggler.util.os package jp.juggler.util.os
import android.content.Context import android.content.Context
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
/** /**
* インストゥルメントテストのContextは * インストゥルメントテストのContextは
@ -18,3 +20,6 @@ val Context.applicationContextSafe: Context
fun Context.error(@StringRes resId: Int, vararg args: Any?): Nothing = fun Context.error(@StringRes resId: Int, vararg args: Any?): Nothing =
error(getString(resId, *args)) error(getString(resId, *args))
fun Context.resDrawable(@DrawableRes resId: Int) =
ContextCompat.getDrawable(this, resId)

View File

@ -31,7 +31,7 @@ allprojects {
google() google()
mavenCentral() mavenCentral()
// com.github.androidmads:QRGenerator // alexzhirkevich/custom-qr-generator
maven(url = "https://jitpack.io") maven(url = "https://jitpack.io")
} }
} }

View File

@ -1,44 +1,49 @@
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
@Suppress("MemberVisibilityCanBePrivate")
object Vers { object Vers {
const val stBuildToolsVersion = "34.0.0"
const val stCompileSdkVersion = 34
const val stTargetSdkVersion = 34
const val stMinSdkVersion = 26
val javaSourceCompatibility = JavaVersion.VERSION_1_8 val javaSourceCompatibility = JavaVersion.VERSION_1_8
val javaTargetCompatibility = JavaVersion.VERSION_1_8 val javaTargetCompatibility = JavaVersion.VERSION_1_8
const val androidGradlePruginVersion = "8.1.4" const val kotlinVersion = "1.9.22"
const val kotlinJvmTarget = "1.8"
const val kotlinJvmToolchain = 17
const val androidGradlePruginVersion = "8.2.1"
const val androidxAnnotationVersion = "1.6.0" const val androidxAnnotationVersion = "1.6.0"
const val androidxTestEspressoCoreVersion = "3.5.1" const val androidxTestEspressoCoreVersion = "3.5.1"
const val androidxTestExtJunitVersion = "1.1.5" const val androidxTestExtJunitVersion = "1.1.5"
const val androidxTestVersion = "1.5.0" const val androidxTestVersion = "1.5.0"
const val ankoVersion = "0.10.8"
// const val ankoVersion = "0.10.8"
const val appcompatVersion = "1.6.1" const val appcompatVersion = "1.6.1"
const val archVersion = "2.2.0" const val archVersion = "2.2.0"
const val commonsCodecVersion = "1.16.0" const val commonsCodecVersion = "1.16.0"
const val composeVersion = "1.0.5" const val composeVersion = "1.0.5"
const val conscryptVersion = "2.5.2" const val conscryptVersion = "2.5.2"
const val coreKtxVersion = "1.12.0" const val coreKtxVersion = "1.12.0"
const val desugarLibVersion = "2.0.3" const val desugarLibVersion = "2.0.4"
const val detektVersion = "1.23.4" const val detektVersion = "1.23.4"
const val emoji2Version = "1.4.0" const val emoji2Version = "1.4.0"
const val glideVersion = "4.15.1" const val glideVersion = "4.15.1"
const val junitVersion = "4.13.2" const val junitVersion = "4.13.2"
const val koinVersion = "3.5.0" const val koinVersion = "3.5.0"
const val kotlinJvmTarget = "1.8" const val kotlinTestVersion = kotlinVersion // "1.9.22"
const val kotlinJvmToolchain = 17
const val kotlinTestVersion = "1.9.20"
const val kotlinVersion = "1.9.20"
const val kotlinxCoroutinesVersion = "1.7.3" const val kotlinxCoroutinesVersion = "1.7.3"
const val kspVersion = "1.9.20-1.0.14" const val kspVersion = "$kotlinVersion-1.0.16"
const val lifecycleVersion = "2.6.2" const val lifecycleVersion = "2.6.2"
const val materialVersion = "1.10.0" const val materialVersion = "1.11.0"
const val media3Version = "1.2.0" const val media3Version = "1.2.0"
const val okhttpVersion = "5.0.0-alpha.11" const val okhttpVersion = "5.0.0-alpha.11"
const val preferenceKtxVersion = "1.2.1" const val preferenceKtxVersion = "1.2.1"
const val stBuildToolsVersion = "34.0.0"
const val stCompileSdkVersion = 34
const val stMinSdkVersion = 26
const val stTargetSdkVersion = 34
const val startupVersion = "1.1.1" const val startupVersion = "1.1.1"
const val testKtxVersion = "1.5.0" const val testKtxVersion = "1.5.0"
const val webpDecoderVersion = "2.6.$glideVersion" const val webpDecoderVersion = "2.6.$glideVersion"
const val workVersion = "2.8.1" const val workVersion = "2.9.0"
} }

View File

@ -27,19 +27,19 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility =Vers. javaSourceCompatibility sourceCompatibility = Vers.javaSourceCompatibility
targetCompatibility =Vers.javaTargetCompatibility targetCompatibility = Vers.javaTargetCompatibility
} }
kotlin { kotlin {
jvmToolchain(Vers.kotlinJvmToolchain) jvmToolchain(Vers.kotlinJvmToolchain)
} }
kotlinOptions { kotlinOptions {
jvmTarget =Vers. kotlinJvmTarget jvmTarget = Vers.kotlinJvmTarget
} }
tasks.withType<KotlinCompile>().configureEach { tasks.withType<KotlinCompile>().configureEach {
kotlinOptions { kotlinOptions {
jvmTarget =Vers. kotlinJvmTarget jvmTarget = Vers.kotlinJvmTarget
} }
} }
} }

View File

@ -1,6 +1,6 @@
#Mon Jun 13 20:53:58 JST 2022 #Mon Jun 13 20:53:58 JST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -40,6 +40,7 @@ android {
compileOptions { compileOptions {
sourceCompatibility = Vers.javaSourceCompatibility sourceCompatibility = Vers.javaSourceCompatibility
targetCompatibility = Vers.javaTargetCompatibility targetCompatibility = Vers.javaTargetCompatibility
isCoreLibraryDesugaringEnabled = true
} }
kotlinOptions { kotlinOptions {