BuildConfigを排除。添付データのアップロードにチャネルを2つ使ってたのをやめる
This commit is contained in:
parent
1612b7cffe
commit
f5accd5ffa
|
@ -4,30 +4,24 @@
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
|
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/anko/SubwayTooter.anko.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" filepath="$PROJECT_DIR$/.idea/modules/apng/SubwayTooter.apng.test.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/apng_android/SubwayTooter.apng_android.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/SubwayTooter.app.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/base/SubwayTooter.base.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/colorpicker/SubwayTooter.colorpicker.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.main.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/emoji/SubwayTooter.emoji.unitTest.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" filepath="$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" filepath="$PROJECT_DIR$/.idea/modules/icon_material_symbols/SubwayTooter.icon_material_symbols.iml" />
|
||||||
|
|
|
@ -41,7 +41,6 @@ android {
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
buildConfig true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -96,15 +95,11 @@ android {
|
||||||
dimension "fcmType"
|
dimension "fcmType"
|
||||||
versionNameSuffix "-noFcm"
|
versionNameSuffix "-noFcm"
|
||||||
applicationIdSuffix ".noFcm"
|
applicationIdSuffix ".noFcm"
|
||||||
def scheme = "subwaytooternofcm"
|
manifestPlaceholders = [customScheme: "subwaytooternofcm"]
|
||||||
manifestPlaceholders = [customScheme: scheme]
|
|
||||||
buildConfigField("String", "customScheme", "\"$scheme\"")
|
|
||||||
}
|
}
|
||||||
fcm {
|
fcm {
|
||||||
dimension "fcmType"
|
dimension "fcmType"
|
||||||
def scheme = "subwaytooter"
|
manifestPlaceholders = [customScheme: "subwaytooter"]
|
||||||
manifestPlaceholders = [customScheme: scheme]
|
|
||||||
buildConfigField("String", "customScheme", "\"$scheme\"")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
object ReleaseType {
|
||||||
|
const val isDebug = true
|
||||||
|
const val isRelease = !isDebug
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package jp.juggler.subwaytooter.push
|
||||||
|
|
||||||
|
object FcmFlavor {
|
||||||
|
const val APPLICATION_ID = "jp.juggler.subwaytooter"
|
||||||
|
const val CUSTOM_SCHEME = "subwaytooter"
|
||||||
|
}
|
|
@ -52,7 +52,6 @@ import jp.juggler.util.network.toPost
|
||||||
import jp.juggler.util.network.toPostRequestBuilder
|
import jp.juggler.util.network.toPostRequestBuilder
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.*
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
@ -271,7 +270,7 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
setSwitchColor(views.root)
|
setSwitchColor(views.root)
|
||||||
|
|
||||||
views.apply {
|
views.apply {
|
||||||
btnPushSubscriptionNotForce.vg(BuildConfig.DEBUG)
|
btnPushSubscriptionNotForce.vg(ReleaseType.isDebug)
|
||||||
|
|
||||||
imageResizeItems = SavedAccount.resizeConfigList.map {
|
imageResizeItems = SavedAccount.resizeConfigList.map {
|
||||||
val caption = when (it.type) {
|
val caption = when (it.type) {
|
||||||
|
|
|
@ -691,6 +691,8 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
etEditText.inputType = item.inputType
|
etEditText.inputType = item.inputType
|
||||||
etEditText.setText(text)
|
etEditText.setText(text)
|
||||||
etEditText.setSelection(0, text.length)
|
etEditText.setSelection(0, text.length)
|
||||||
|
|
||||||
|
item.showEditText.invoke(actAppSetting,views.etEditText)
|
||||||
}
|
}
|
||||||
updateErrorView()
|
updateErrorView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import jp.juggler.subwaytooter.actpost.CompletionHelper
|
||||||
import jp.juggler.subwaytooter.actpost.FeaturedTagCache
|
import jp.juggler.subwaytooter.actpost.FeaturedTagCache
|
||||||
import jp.juggler.subwaytooter.actpost.addAttachment
|
import jp.juggler.subwaytooter.actpost.addAttachment
|
||||||
import jp.juggler.subwaytooter.actpost.applyMushroomText
|
import jp.juggler.subwaytooter.actpost.applyMushroomText
|
||||||
import jp.juggler.subwaytooter.actpost.launchAddAttachmentChannelReader
|
|
||||||
import jp.juggler.subwaytooter.actpost.onPickCustomThumbnailImpl
|
import jp.juggler.subwaytooter.actpost.onPickCustomThumbnailImpl
|
||||||
import jp.juggler.subwaytooter.actpost.onPostAttachmentCompleteImpl
|
import jp.juggler.subwaytooter.actpost.onPostAttachmentCompleteImpl
|
||||||
import jp.juggler.subwaytooter.actpost.openAttachment
|
import jp.juggler.subwaytooter.actpost.openAttachment
|
||||||
|
@ -198,13 +197,6 @@ class ActPost : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddAttachmentChannelItem(
|
|
||||||
val uri: Uri,
|
|
||||||
val mimeTypeArg: String?,
|
|
||||||
)
|
|
||||||
|
|
||||||
val addAttachmentChannel = Channel<AddAttachmentChannelItem>(capacity = Channel.BUFFERED)
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -228,8 +220,6 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
progressChannel = Channel(capacity = Channel.CONFLATED)
|
progressChannel = Channel(capacity = Channel.CONFLATED)
|
||||||
|
|
||||||
launchAddAttachmentChannelReader()
|
|
||||||
|
|
||||||
initUI()
|
initUI()
|
||||||
|
|
||||||
// 進捗表示チャネルの回収コルーチン
|
// 進捗表示チャネルの回収コルーチン
|
||||||
|
@ -265,7 +255,6 @@ class ActPost : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
completionHelper.onDestroy()
|
completionHelper.onDestroy()
|
||||||
attachmentUploader.onActivityDestroy()
|
attachmentUploader.onActivityDestroy()
|
||||||
addAttachmentChannel.close()
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
@ -28,8 +27,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.CustomEmojiCache
|
import jp.juggler.subwaytooter.util.CustomEmojiCache
|
||||||
import jp.juggler.subwaytooter.util.CustomEmojiLister
|
import jp.juggler.subwaytooter.util.CustomEmojiLister
|
||||||
import jp.juggler.subwaytooter.util.ProgressResponseBody
|
import jp.juggler.subwaytooter.util.ProgressResponseBody
|
||||||
|
import jp.juggler.subwaytooter.util.getUserAgent
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.data.asciiPattern
|
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.initializeToastUtils
|
import jp.juggler.util.log.initializeToastUtils
|
||||||
|
@ -134,22 +133,10 @@ class App1 : Application() {
|
||||||
// return maxSize * 1024;
|
// return maxSize * 1024;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
val reNotAllowedInUserAgent = "[^\\x21-\\x7e]+".asciiPattern()
|
private var cookieManager: CookieManager? = null
|
||||||
|
private var cookieJar: CookieJar? = null
|
||||||
|
|
||||||
val userAgentDefault =
|
private fun Context.userAgentInterceptor() =
|
||||||
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
|
|
||||||
|
|
||||||
private fun getUserAgent(): String {
|
|
||||||
val userAgentCustom = PrefS.spUserAgent.value
|
|
||||||
return when {
|
|
||||||
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
|
||||||
.find() -> userAgentCustom
|
|
||||||
|
|
||||||
else -> userAgentDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun userAgentInterceptor() =
|
|
||||||
Interceptor { chain ->
|
Interceptor { chain ->
|
||||||
chain.proceed(
|
chain.proceed(
|
||||||
chain.request().newBuilder()
|
chain.request().newBuilder()
|
||||||
|
@ -158,17 +145,14 @@ class App1 : Application() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cookieManager: CookieManager? = null
|
private fun Context.prepareOkHttp(
|
||||||
private var cookieJar: CookieJar? = null
|
|
||||||
|
|
||||||
private fun prepareOkHttp(
|
|
||||||
timeoutSecondsConnect: Int,
|
timeoutSecondsConnect: Int,
|
||||||
timeoutSecondsRead: Int,
|
timeoutSecondsRead: Int,
|
||||||
): OkHttpClient.Builder {
|
): OkHttpClient.Builder {
|
||||||
|
|
||||||
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
|
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
|
||||||
|
|
||||||
var cookieJar = this.cookieJar
|
var cookieJar = this@Companion.cookieJar
|
||||||
if (cookieJar == null) {
|
if (cookieJar == null) {
|
||||||
val cookieManager = CookieManager().apply {
|
val cookieManager = CookieManager().apply {
|
||||||
setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
setCookiePolicy(CookiePolicy.ACCEPT_ALL)
|
||||||
|
@ -176,8 +160,8 @@ class App1 : Application() {
|
||||||
CookieHandler.setDefault(cookieManager)
|
CookieHandler.setDefault(cookieManager)
|
||||||
cookieJar = JavaNetCookieJar(cookieManager)
|
cookieJar = JavaNetCookieJar(cookieManager)
|
||||||
|
|
||||||
this.cookieManager = cookieManager
|
this@Companion.cookieManager = cookieManager
|
||||||
this.cookieJar = cookieJar
|
this@Companion.cookieJar = cookieJar
|
||||||
}
|
}
|
||||||
|
|
||||||
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
|
@ -285,7 +269,7 @@ class App1 : Application() {
|
||||||
val apiReadTimeout = max(3, PrefS.spApiReadTimeout.toInt())
|
val apiReadTimeout = max(3, PrefS.spApiReadTimeout.toInt())
|
||||||
|
|
||||||
// API用のHTTP設定はキャッシュを使わない
|
// API用のHTTP設定はキャッシュを使わない
|
||||||
ok_http_client = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
ok_http_client = appContext.prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// ディスクキャッシュ
|
// ディスクキャッシュ
|
||||||
|
@ -293,14 +277,14 @@ class App1 : Application() {
|
||||||
val cache = Cache(cacheDir, 30000000L)
|
val cache = Cache(cacheDir, 30000000L)
|
||||||
|
|
||||||
// カスタム絵文字用のHTTP設定はキャッシュを使う
|
// カスタム絵文字用のHTTP設定はキャッシュを使う
|
||||||
ok_http_client2 = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
ok_http_client2 = appContext.prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||||
.cache(cache)
|
.cache(cache)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
||||||
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt())
|
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt())
|
||||||
ok_http_client_media_viewer =
|
ok_http_client_media_viewer =
|
||||||
prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
appContext.prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||||
.cache(cache)
|
.cache(cache)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
import jp.juggler.subwaytooter.action.conversationOtherInstance
|
||||||
import jp.juggler.subwaytooter.action.openActPostImpl
|
import jp.juggler.subwaytooter.action.openActPostImpl
|
||||||
|
@ -32,6 +31,7 @@ import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||||
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
||||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||||
import jp.juggler.subwaytooter.pref.prefDevice
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.subwaytooter.push.fcmHandler
|
import jp.juggler.subwaytooter.push.fcmHandler
|
||||||
import jp.juggler.subwaytooter.push.pushRepo
|
import jp.juggler.subwaytooter.push.pushRepo
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
@ -56,7 +56,7 @@ fun ActMain.handleIntentUri(uri: Uri) {
|
||||||
try {
|
try {
|
||||||
log.i("handleIntentUri $uri")
|
log.i("handleIntentUri $uri")
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
BuildConfig.customScheme -> handleCustomSchemaUri(uri)
|
FcmFlavor.CUSTOM_SCHEME -> handleCustomSchemaUri(uri)
|
||||||
else -> handleOtherUri(uri)
|
else -> handleOtherUri(uri)
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -193,7 +193,7 @@ private fun ActMain.handleCustomSchemaUri(uri: Uri) = launchAndShowError {
|
||||||
// subwaytooter://oauth(\d*)/?...
|
// subwaytooter://oauth(\d*)/?...
|
||||||
handleOAuth2Callback(uri)
|
handleOAuth2Callback(uri)
|
||||||
} else {
|
} else {
|
||||||
// ${BuildConfig.customScheme}://notification_click/?db_id=(db_id)
|
// ${FcmFlavor.customScheme}://notification_click/?db_id=(db_id)
|
||||||
handleNotificationClick(uri, dataIdString)
|
handleNotificationClick(uri, dataIdString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,6 +377,7 @@ suspend fun ActMain.updatePushDistributer() {
|
||||||
selectPushDistributor()
|
selectPushDistributor()
|
||||||
// 選択しなかった場合は購読の更新を行わない
|
// 選択しなかった場合は購読の更新を行わない
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
runInProgress(cancellable = false) { reporter ->
|
runInProgress(cancellable = false) { reporter ->
|
||||||
withContext(AppDispatchers.DEFAULT) {
|
withContext(AppDispatchers.DEFAULT) {
|
||||||
|
|
|
@ -8,16 +8,12 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import jp.juggler.subwaytooter.ActPost
|
import jp.juggler.subwaytooter.ActPost
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.ApiTask
|
import jp.juggler.subwaytooter.api.ApiTask
|
||||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
|
||||||
import jp.juggler.subwaytooter.api.entity.ServiceType
|
import jp.juggler.subwaytooter.api.entity.ServiceType
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.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.TootAttachment.Companion.tootAttachmentJson
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAttachmentType
|
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.entity.parseItem
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.calcIconRound
|
import jp.juggler.subwaytooter.calcIconRound
|
||||||
|
@ -28,9 +24,7 @@ import jp.juggler.subwaytooter.dialog.focusPointDialog
|
||||||
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
import jp.juggler.subwaytooter.dialog.showTextInputDialog
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.util.AttachmentRequest
|
import jp.juggler.subwaytooter.util.AttachmentRequest
|
||||||
import jp.juggler.subwaytooter.util.AttachmentUploader
|
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.subwaytooter.util.resolveMimeType
|
|
||||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
|
@ -46,9 +40,6 @@ import jp.juggler.util.log.withCaption
|
||||||
import jp.juggler.util.network.toPutRequestBuilder
|
import jp.juggler.util.network.toPutRequestBuilder
|
||||||
import jp.juggler.util.ui.isLiveActivity
|
import jp.juggler.util.ui.isLiveActivity
|
||||||
import jp.juggler.util.ui.vg
|
import jp.juggler.util.ui.vg
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
|
||||||
import java.nio.channels.ClosedChannelException
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
private val log = LogCategory("ActPostAttachment")
|
private val log = LogCategory("ActPostAttachment")
|
||||||
|
@ -132,140 +123,45 @@ fun ActPost.addAttachment(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
mimeTypeArg: String? = null,
|
mimeTypeArg: String? = null,
|
||||||
) {
|
) {
|
||||||
val item = ActPost.AddAttachmentChannelItem(uri = uri, mimeTypeArg = mimeTypeArg)
|
|
||||||
for (nTry in 1..10) {
|
|
||||||
try {
|
|
||||||
val channelResult = addAttachmentChannel.trySend(item)
|
|
||||||
when {
|
|
||||||
channelResult.isSuccess -> return
|
|
||||||
channelResult.isClosed -> return
|
|
||||||
channelResult.isFailure -> continue
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "addAttachmentChannel.trySend failed.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun ActPost.getInstance(): TootInstance {
|
|
||||||
val client = TootApiClient(
|
|
||||||
context = applicationContext,
|
|
||||||
callback = object : TootApiCallback {
|
|
||||||
override suspend fun isApiCancelled() = isFinishing || isDestroyed
|
|
||||||
}
|
|
||||||
).apply {
|
|
||||||
this.account = this@getInstance.account
|
|
||||||
}
|
|
||||||
val (instance, ri) = TootInstance.get(client = client)
|
|
||||||
if (instance != null) return instance
|
|
||||||
when (ri) {
|
|
||||||
null -> throw CancellationException()
|
|
||||||
else -> error("missing instance information. ${ri.error}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun ActPost.addAttachmentSuspend(
|
|
||||||
uri: Uri,
|
|
||||||
mimeTypeArg: String? = null,
|
|
||||||
) {
|
|
||||||
val actPost = this
|
|
||||||
val account = this.account
|
val account = this.account
|
||||||
|
if (account == null) {
|
||||||
val mimeType = uri.resolveMimeType(mimeTypeArg, this)
|
dialogOrToast(R.string.account_select_please)
|
||||||
?.notEmpty()
|
return
|
||||||
|
} else if (attachmentList.size >= 4) {
|
||||||
val isReply = states.inReplyToId != null
|
dialogOrToast(R.string.attachment_too_many)
|
||||||
|
return
|
||||||
when {
|
|
||||||
actPost.isFinishing || actPost.isDestroyed -> {
|
|
||||||
dialogOrToast("actPost is finishing or destroyed.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
attachmentList.size >= 4 -> {
|
|
||||||
dialogOrToast(R.string.attachment_too_many)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
account == null -> {
|
|
||||||
dialogOrToast(R.string.account_select_please)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mimeType == null -> {
|
|
||||||
dialogOrToast(R.string.mime_type_missing)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val instance = getInstance()
|
saveAttachmentList()
|
||||||
|
val pa = PostAttachment(this)
|
||||||
|
attachmentList.add(pa)
|
||||||
|
showMediaAttachment()
|
||||||
|
|
||||||
when {
|
attachmentUploader.addRequest(
|
||||||
instance.instanceType == InstanceType.Pixelfed && isReply -> {
|
AttachmentRequest(
|
||||||
AttachmentUploader.log.e("pixelfed_does_not_allow_reply_with_media")
|
context = applicationContext,
|
||||||
dialogOrToast(R.string.pixelfed_does_not_allow_reply_with_media)
|
account = account,
|
||||||
return
|
pa = pa,
|
||||||
}
|
uri = uri,
|
||||||
|
mimeTypeArg = mimeTypeArg,
|
||||||
else -> {
|
isReply = states.inReplyToId != null,
|
||||||
saveAttachmentList()
|
imageResizeConfig = account.getResizeConfig(),
|
||||||
val pa = PostAttachment(this)
|
maxBytesVideo = { instance, mediaConfig ->
|
||||||
attachmentList.add(pa)
|
min(
|
||||||
showMediaAttachment()
|
account.getMovieMaxBytes(instance),
|
||||||
val mediaConfig = instance.configuration?.jsonObject("media_attachments")
|
mediaConfig?.int("video_size_limit")
|
||||||
attachmentUploader.addRequest(
|
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||||
AttachmentRequest(
|
|
||||||
context = applicationContext,
|
|
||||||
account = account!!,
|
|
||||||
pa = pa,
|
|
||||||
uri = uri,
|
|
||||||
mimeType = mimeType!!,
|
|
||||||
instance = instance,
|
|
||||||
mediaConfig = mediaConfig,
|
|
||||||
serverMaxSqPixel = mediaConfig?.int("image_matrix_limit")?.takeIf { it > 0 },
|
|
||||||
imageResizeConfig = account.getResizeConfig(),
|
|
||||||
maxBytesVideo = min(
|
|
||||||
account.getMovieMaxBytes(instance),
|
|
||||||
mediaConfig?.int("video_size_limit")
|
|
||||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
|
||||||
),
|
|
||||||
maxBytesImage = min(
|
|
||||||
account.getImageMaxBytes(instance),
|
|
||||||
mediaConfig?.int("image_size_limit")
|
|
||||||
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
|
||||||
),
|
|
||||||
// onUploadEnd = onUploadEnd
|
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
}
|
maxBytesImage = { instance, mediaConfig ->
|
||||||
}
|
min(
|
||||||
}
|
account.getImageMaxBytes(instance),
|
||||||
|
mediaConfig?.int("image_size_limit")
|
||||||
fun ActPost.launchAddAttachmentChannelReader() {
|
?.takeIf { it > 0 } ?: Int.MAX_VALUE,
|
||||||
launchMain {
|
)
|
||||||
while (true) {
|
},
|
||||||
try {
|
)
|
||||||
val item = addAttachmentChannel.receive()
|
)
|
||||||
addAttachmentSuspend(item.uri, item.mimeTypeArg)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
when (ex) {
|
|
||||||
is CancellationException -> {
|
|
||||||
log.i("launchAddAttachmentChannelReader: cancelled.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
is ClosedChannelException, is ClosedReceiveChannelException -> {
|
|
||||||
log.i("launchAddAttachmentChannelReader: channel closed.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
else ->
|
|
||||||
log.e(ex,"launchAddAttachmentChannelReader: addAttachmentSuspend raise error. retry…")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
|
fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.api.auth
|
package jp.juggler.subwaytooter.api.auth
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
|
||||||
import jp.juggler.subwaytooter.api.SendException
|
import jp.juggler.subwaytooter.api.SendException
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -9,6 +8,7 @@ import jp.juggler.subwaytooter.api.entity.EntityId
|
||||||
import jp.juggler.subwaytooter.api.entity.Host
|
import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
|
@ -23,10 +23,10 @@ class AuthMastodon(override val client: TootApiClient) : AuthBase() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LogCategory("MastodonAuth")
|
private val log = LogCategory("MastodonAuth")
|
||||||
|
|
||||||
@Suppress("MayBeConstant")
|
@Suppress("MayBeConstant", "RedundantSuppression")
|
||||||
val DEBUG_AUTH = false
|
val DEBUG_AUTH = false
|
||||||
|
|
||||||
const val callbackUrl = "${BuildConfig.customScheme}://oauth/"
|
const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://oauth/"
|
||||||
|
|
||||||
fun mastodonScope(ti: TootInstance?) = when {
|
fun mastodonScope(ti: TootInstance?) = when {
|
||||||
// 古いサーバ
|
// 古いサーバ
|
||||||
|
|
|
@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.api.auth
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -11,16 +10,25 @@ import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.prefDevice
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.JsonArray
|
||||||
|
import jp.juggler.util.data.JsonObject
|
||||||
|
import jp.juggler.util.data.buildJsonArray
|
||||||
|
import jp.juggler.util.data.cast
|
||||||
|
import jp.juggler.util.data.digestSHA256
|
||||||
|
import jp.juggler.util.data.encodeHexLower
|
||||||
|
import jp.juggler.util.data.encodeUTF8
|
||||||
|
import jp.juggler.util.data.notBlank
|
||||||
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
|
||||||
class AuthMisskey10(override val client: TootApiClient) : AuthBase() {
|
class AuthMisskey10(override val client: TootApiClient) : AuthBase() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LogCategory("MisskeyOldAuth")
|
private val log = LogCategory("MisskeyOldAuth")
|
||||||
private const val callbackUrl = "${BuildConfig.customScheme}://misskey/auth_callback"
|
private const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://misskey/auth_callback"
|
||||||
|
|
||||||
fun isCallbackUrl(uriStr: String) =
|
fun isCallbackUrl(uriStr: String) =
|
||||||
uriStr.startsWith(callbackUrl) ||
|
uriStr.startsWith(callbackUrl) ||
|
||||||
|
@ -213,7 +221,6 @@ class AuthMisskey10(override val client: TootApiClient) : AuthBase() {
|
||||||
linkHelper = LinkHelper.create(ti)
|
linkHelper = LinkHelper.create(ti)
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||||
?.notEmpty() ?: error("missing client id")
|
?.notEmpty() ?: error("missing client id")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.api.auth
|
package jp.juggler.subwaytooter.api.auth
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -11,12 +10,13 @@ import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.prefDevice
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* miauth と呼ばれている認証手順。
|
* miauth と呼ばれている認証手順。
|
||||||
|
@ -26,7 +26,7 @@ class AuthMisskey13(override val client: TootApiClient) : AuthBase() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LogCategory("MisskeyMiAuth")
|
private val log = LogCategory("MisskeyMiAuth")
|
||||||
private const val appIconUrl = "https://m1j.zzz.ac/subwaytooter-miauth-icon.png"
|
private const val appIconUrl = "https://m1j.zzz.ac/subwaytooter-miauth-icon.png"
|
||||||
private const val callbackUrl = "${BuildConfig.customScheme}://miauth/auth_callback"
|
private const val callbackUrl = "${FcmFlavor.CUSTOM_SCHEME}://miauth/auth_callback"
|
||||||
|
|
||||||
fun isCallbackUrl(uriStr: String) = uriStr.startsWith(callbackUrl)
|
fun isCallbackUrl(uriStr: String) = uriStr.startsWith(callbackUrl)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -36,6 +37,8 @@ import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||||
import jp.juggler.subwaytooter.util.openBrowser
|
import jp.juggler.subwaytooter.util.openBrowser
|
||||||
|
import jp.juggler.subwaytooter.util.reNotAllowedInUserAgent
|
||||||
|
import jp.juggler.subwaytooter.util.userAgentDefault
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.data.intentOpenDocument
|
import jp.juggler.util.data.intentOpenDocument
|
||||||
|
@ -111,6 +114,7 @@ class AppSettingItem(
|
||||||
var onClickEdit: ActAppSetting.() -> Unit = {}
|
var onClickEdit: ActAppSetting.() -> Unit = {}
|
||||||
var onClickReset: ActAppSetting.() -> Unit = {}
|
var onClickReset: ActAppSetting.() -> Unit = {}
|
||||||
var showTextView: ActAppSetting.(TextView) -> Unit = {}
|
var showTextView: ActAppSetting.(TextView) -> Unit = {}
|
||||||
|
var showEditText: ActAppSetting.(EditText) -> Unit = {}
|
||||||
|
|
||||||
// for EditText
|
// for EditText
|
||||||
var hint: String? = null
|
var hint: String? = null
|
||||||
|
@ -346,10 +350,10 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
text(PrefS.spClientName, R.string.client_name, InputTypeEx.text)
|
text(PrefS.spClientName, R.string.client_name, InputTypeEx.text)
|
||||||
|
|
||||||
text(PrefS.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) {
|
text(PrefS.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) {
|
||||||
hint = App1.userAgentDefault
|
showEditText = { it.hint = userAgentDefault() }
|
||||||
filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() }
|
filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() }
|
||||||
getError = {
|
getError = {
|
||||||
val m = App1.reNotAllowedInUserAgent.matcher(it)
|
val m = reNotAllowedInUserAgent.matcher(it)
|
||||||
when (m.find()) {
|
when (m.find()) {
|
||||||
true -> getString(R.string.user_agent_error, m.group())
|
true -> getString(R.string.user_agent_error, m.group())
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
@ -17,9 +17,9 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.startup.Initializer
|
import androidx.startup.Initializer
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.pref.LazyContextInitializer
|
import jp.juggler.subwaytooter.pref.LazyContextInitializer
|
||||||
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
|
||||||
|
@ -56,8 +56,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 1,
|
notificationId = 1,
|
||||||
pircTap = 1,
|
pircTap = 1,
|
||||||
pircDelete = 1, // uriでtapとdeleteを区別している
|
pircDelete = 1, // uriでtapとdeleteを区別している
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://notification_delete/",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://notification_delete/",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://notification_click/",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://notification_click/",
|
||||||
),
|
),
|
||||||
PullWorker(
|
PullWorker(
|
||||||
id = "PollingForegrounder",
|
id = "PollingForegrounder",
|
||||||
|
@ -69,8 +69,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 2,
|
notificationId = 2,
|
||||||
pircTap = 2,
|
pircTap = 2,
|
||||||
pircDelete = -1,
|
pircDelete = -1,
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://checker",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://checker",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://checker-tap",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://checker-tap",
|
||||||
),
|
),
|
||||||
ServerTimeout(
|
ServerTimeout(
|
||||||
id = "ErrorNotification",
|
id = "ErrorNotification",
|
||||||
|
@ -82,8 +82,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 3,
|
notificationId = 3,
|
||||||
pircTap = 3,
|
pircTap = 3,
|
||||||
pircDelete = 4,
|
pircDelete = 4,
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://server-timeout",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://server-timeout",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://server-timeout-tap",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://server-timeout-tap",
|
||||||
),
|
),
|
||||||
PushMessage(
|
PushMessage(
|
||||||
id = "PushMessage",
|
id = "PushMessage",
|
||||||
|
@ -95,8 +95,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 5,
|
notificationId = 5,
|
||||||
pircTap = 5,
|
pircTap = 5,
|
||||||
pircDelete = 6,
|
pircDelete = 6,
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://pushMessage",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://pushMessage",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://notification_click/",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://notification_click/",
|
||||||
),
|
),
|
||||||
Alert(
|
Alert(
|
||||||
id = "Alert",
|
id = "Alert",
|
||||||
|
@ -108,8 +108,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 7,
|
notificationId = 7,
|
||||||
pircTap = 7,
|
pircTap = 7,
|
||||||
pircDelete = 8,
|
pircDelete = 8,
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://alert",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://alert",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://alert-tap",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://alert-tap",
|
||||||
),
|
),
|
||||||
PushWorker(
|
PushWorker(
|
||||||
id = "PushMessageWorker",
|
id = "PushMessageWorker",
|
||||||
|
@ -121,8 +121,8 @@ enum class NotificationChannels(
|
||||||
notificationId = 9,
|
notificationId = 9,
|
||||||
pircTap = 9,
|
pircTap = 9,
|
||||||
pircDelete = 10,
|
pircDelete = 10,
|
||||||
uriPrefixDelete = "${BuildConfig.customScheme}://pushWorker",
|
uriPrefixDelete = "${FcmFlavor.CUSTOM_SCHEME}://pushWorker",
|
||||||
uriPrefixTap = "${BuildConfig.customScheme}://pushWorker-tag",
|
uriPrefixTap = "${FcmFlavor.CUSTOM_SCHEME}://pushWorker-tag",
|
||||||
),
|
),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.startup.Initializer
|
import androidx.startup.Initializer
|
||||||
import jp.juggler.subwaytooter.BuildConfig
|
import jp.juggler.subwaytooter.push.FcmFlavor
|
||||||
import jp.juggler.util.os.applicationContextSafe
|
import jp.juggler.util.os.applicationContextSafe
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ val lazyPref
|
||||||
?: LazyContextHolder.prefNullable
|
?: LazyContextHolder.prefNullable
|
||||||
?: error("LazyContextHolder not initialized")
|
?: error("LazyContextHolder not initialized")
|
||||||
|
|
||||||
const val FILE_PROVIDER_AUTHORITY = "${BuildConfig.APPLICATION_ID}.FileProvider"
|
const val FILE_PROVIDER_AUTHORITY = "${FcmFlavor.APPLICATION_ID}.FileProvider"
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
object LazyContextHolder {
|
object LazyContextHolder {
|
||||||
|
|
|
@ -7,11 +7,14 @@ import android.os.Build
|
||||||
import jp.juggler.media.generateTempFile
|
import jp.juggler.media.generateTempFile
|
||||||
import jp.juggler.media.transcodeAudio
|
import jp.juggler.media.transcodeAudio
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
|
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||||
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.getStreamSize
|
import jp.juggler.util.data.getStreamSize
|
||||||
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.errorEx
|
import jp.juggler.util.log.errorEx
|
||||||
import jp.juggler.util.media.ResizeConfig
|
import jp.juggler.util.media.ResizeConfig
|
||||||
|
@ -20,6 +23,7 @@ import jp.juggler.util.media.createResizedBitmap
|
||||||
import jp.juggler.util.media.transcodeVideo
|
import jp.juggler.util.media.transcodeVideo
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class AttachmentRequest(
|
class AttachmentRequest(
|
||||||
|
@ -27,13 +31,11 @@ class AttachmentRequest(
|
||||||
val account: SavedAccount,
|
val account: SavedAccount,
|
||||||
val pa: PostAttachment,
|
val pa: PostAttachment,
|
||||||
val uri: Uri,
|
val uri: Uri,
|
||||||
var mimeType: String,
|
var mimeTypeArg: String?,
|
||||||
val imageResizeConfig: ResizeConfig,
|
val imageResizeConfig: ResizeConfig,
|
||||||
val serverMaxSqPixel: Int?,
|
val maxBytesVideo: (instance: TootInstance, mediaConfig: JsonObject?) -> Int,
|
||||||
val instance: TootInstance,
|
val maxBytesImage: (instance: TootInstance, mediaConfig: JsonObject?) -> Int,
|
||||||
val mediaConfig: JsonObject?,
|
val isReply: Boolean = false,
|
||||||
val maxBytesVideo: Int,
|
|
||||||
val maxBytesImage: Int,
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LogCategory("AttachmentRequest")
|
private val log = LogCategory("AttachmentRequest")
|
||||||
|
@ -51,14 +53,49 @@ class AttachmentRequest(
|
||||||
"audio/x-wav",
|
"audio/x-wav",
|
||||||
"audio/3gpp",
|
"audio/3gpp",
|
||||||
)
|
)
|
||||||
// val badAudioType = setOf(
|
|
||||||
|
// val badAudioType = setOf(
|
||||||
// "audio/mpeg","audio/aac",
|
// "audio/mpeg","audio/aac",
|
||||||
// "audio/m4a","audio/x-m4a","audio/mp4",
|
// "audio/m4a","audio/x-m4a","audio/mp4",
|
||||||
// "video/x-ms-asf",
|
// "video/x-ms-asf",
|
||||||
// )
|
// )
|
||||||
|
private suspend fun Context.getInstance(account: SavedAccount): TootInstance {
|
||||||
|
val client = TootApiClient(
|
||||||
|
context = this,
|
||||||
|
callback = object : TootApiCallback {
|
||||||
|
override suspend fun isApiCancelled() = false
|
||||||
|
}
|
||||||
|
).apply {
|
||||||
|
this.account = account
|
||||||
|
}
|
||||||
|
val (instance, ri) = TootInstance.get(client = client)
|
||||||
|
if (instance != null) return instance
|
||||||
|
when (ri) {
|
||||||
|
null -> throw CancellationException()
|
||||||
|
else -> error("missing instance information. ${ri.error}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _instance: TootInstance? = null
|
||||||
|
|
||||||
|
suspend fun instance(): TootInstance {
|
||||||
|
_instance?.let { return it }
|
||||||
|
return context.getInstance(account).also { _instance = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun mediaConfig(): JsonObject? =
|
||||||
|
instance().configuration?.jsonObject("media_attachments")
|
||||||
|
|
||||||
|
private suspend fun serverMaxSqPixel(): Int? =
|
||||||
|
mediaConfig()?.int("image_matrix_limit")?.takeIf { it > 0 }
|
||||||
|
|
||||||
|
val mimeType
|
||||||
|
get() = uri.resolveMimeType(mimeTypeArg, context)?.notEmpty()
|
||||||
|
?: error(context.getString(R.string.mime_type_missing))
|
||||||
|
|
||||||
suspend fun createOpener(): InputStreamOpener {
|
suspend fun createOpener(): InputStreamOpener {
|
||||||
|
val mimeType = this.mimeType
|
||||||
|
|
||||||
// GIFはそのまま投げる
|
// GIFはそのまま投げる
|
||||||
if (mimeType == MIME_TYPE_GIF) {
|
if (mimeType == MIME_TYPE_GIF) {
|
||||||
|
@ -128,10 +165,12 @@ class AttachmentRequest(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createResizedImageOpener(): InputStreamOpener {
|
private suspend fun createResizedImageOpener(): InputStreamOpener {
|
||||||
try {
|
try {
|
||||||
pa.progress = context.getString(R.string.attachment_handling_compress)
|
pa.progress = context.getString(R.string.attachment_handling_compress)
|
||||||
|
|
||||||
|
val instance = instance()
|
||||||
|
|
||||||
val canUseWebP = try {
|
val canUseWebP = try {
|
||||||
PrefB.bpUseWebP.value && MIME_TYPE_WEBP.mimeTypeIsSupported(instance)
|
PrefB.bpUseWebP.value && MIME_TYPE_WEBP.mimeTypeIsSupported(instance)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -154,7 +193,7 @@ class AttachmentRequest(
|
||||||
uri,
|
uri,
|
||||||
imageResizeConfig,
|
imageResizeConfig,
|
||||||
canSkip = canUseOriginal,
|
canSkip = canUseOriginal,
|
||||||
serverMaxSqPixel = serverMaxSqPixel
|
serverMaxSqPixel = serverMaxSqPixel()
|
||||||
)?.let { bitmap ->
|
)?.let { bitmap ->
|
||||||
try {
|
try {
|
||||||
return bitmap.compressAutoType(canUseWebP)
|
return bitmap.compressAutoType(canUseWebP)
|
||||||
|
@ -273,14 +312,16 @@ class AttachmentRequest(
|
||||||
val tempFile = File(cacheDir, "movie." + Thread.currentThread().id + ".tmp")
|
val tempFile = File(cacheDir, "movie." + Thread.currentThread().id + ".tmp")
|
||||||
val outFile = File(cacheDir, "movie." + Thread.currentThread().id + ".mp4")
|
val outFile = File(cacheDir, "movie." + Thread.currentThread().id + ".mp4")
|
||||||
var resultFile: File? = null
|
var resultFile: File? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 入力ファイルをコピーする
|
// 入力ファイルをコピーする
|
||||||
(context.contentResolver.openInputStream(uri)
|
(context.contentResolver.openInputStream(uri)
|
||||||
?: error("openInputStream returns null.")).use { inStream ->
|
?: error("openInputStream returns null.")).use { inStream ->
|
||||||
FileOutputStream(tempFile).use { inStream.copyTo(it) }
|
FileOutputStream(tempFile).use { inStream.copyTo(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mediaConfig = mediaConfig()
|
||||||
|
|
||||||
// 動画のメタデータを調べる
|
// 動画のメタデータを調べる
|
||||||
val info = tempFile.videoInfo
|
val info = tempFile.videoInfo
|
||||||
|
|
||||||
|
@ -330,11 +371,13 @@ class AttachmentRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createResizedAudioOpener(srcBytes: Long): InputStreamOpener =
|
private suspend fun createResizedAudioOpener(srcBytes: Long): InputStreamOpener {
|
||||||
when {
|
val instance = instance()
|
||||||
|
val mediaConfig = mediaConfig()
|
||||||
|
return when {
|
||||||
mimeType.mimeTypeIsSupported(instance) &&
|
mimeType.mimeTypeIsSupported(instance) &&
|
||||||
goodAudioType.contains(mimeType) &&
|
goodAudioType.contains(mimeType) &&
|
||||||
srcBytes <= maxBytesVideo -> contentUriOpener(
|
srcBytes <= maxBytesVideo(instance,mediaConfig).toLong() -> contentUriOpener(
|
||||||
context.contentResolver,
|
context.contentResolver,
|
||||||
uri,
|
uri,
|
||||||
mimeType,
|
mimeType,
|
||||||
|
@ -360,4 +403,6 @@ class AttachmentRequest(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||||
|
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||||
import jp.juggler.subwaytooter.api.entity.ServiceType
|
import jp.juggler.subwaytooter.api.entity.ServiceType
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
import jp.juggler.subwaytooter.api.entity.TootAttachment
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachment
|
import jp.juggler.subwaytooter.api.entity.TootAttachment.Companion.tootAttachment
|
||||||
|
@ -35,13 +36,14 @@ import jp.juggler.util.network.toPost
|
||||||
import jp.juggler.util.network.toPostRequestBuilder
|
import jp.juggler.util.network.toPostRequestBuilder
|
||||||
import jp.juggler.util.network.toPut
|
import jp.juggler.util.network.toPut
|
||||||
import jp.juggler.util.network.toPutRequestBuilder
|
import jp.juggler.util.network.toPutRequestBuilder
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
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.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import java.util.concurrent.CancellationException
|
import java.nio.channels.ClosedChannelException
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
class AttachmentUploader(
|
class AttachmentUploader(
|
||||||
|
@ -55,130 +57,137 @@ class AttachmentUploader(
|
||||||
private val safeContext = activity.applicationContext!!
|
private val safeContext = activity.applicationContext!!
|
||||||
private var lastAttachmentAdd = 0L
|
private var lastAttachmentAdd = 0L
|
||||||
private var lastAttachmentComplete = 0L
|
private var lastAttachmentComplete = 0L
|
||||||
private var channel: Channel<AttachmentRequest>? = null
|
private val channel = Channel<AttachmentRequest>(capacity = Channel.UNLIMITED)
|
||||||
|
|
||||||
private fun prepareChannel(): Channel<AttachmentRequest> {
|
init {
|
||||||
// double check before/after lock
|
launchIO {
|
||||||
channel?.let { return it }
|
while (true) {
|
||||||
synchronized(this) {
|
try {
|
||||||
channel?.let { return it }
|
val request = channel.receive()
|
||||||
return Channel<AttachmentRequest>(capacity = Channel.UNLIMITED)
|
if (request.pa.isCancelled) continue
|
||||||
.also {
|
withContext(AppDispatchers.MainImmediate) {
|
||||||
channel = it
|
val pa = request.pa
|
||||||
launchIO {
|
pa.status = try {
|
||||||
while (true) {
|
withContext(pa.job + AppDispatchers.IO) {
|
||||||
val request = try {
|
request.upload()
|
||||||
it.receive()
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
when (ex) {
|
|
||||||
is CancellationException, is ClosedReceiveChannelException -> break
|
|
||||||
else -> {
|
|
||||||
safeContext.showToast(ex)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val result = try {
|
request.pa.progress = ""
|
||||||
if (request.pa.isCancelled) continue
|
|
||||||
withContext(request.pa.job + AppDispatchers.IO) {
|
val now = System.currentTimeMillis()
|
||||||
request.upload()
|
if (now - lastAttachmentComplete >= 5000L) {
|
||||||
}
|
safeContext.showToast(false, R.string.attachment_uploaded)
|
||||||
} catch (ex: Throwable) {
|
|
||||||
TootApiResult(ex.withCaption("upload failed."))
|
|
||||||
}
|
}
|
||||||
try {
|
lastAttachmentComplete = now
|
||||||
request.pa.progress = ""
|
|
||||||
withContext(AppDispatchers.MainImmediate) {
|
PostAttachment.Status.Ok
|
||||||
handleResult(request, result)
|
} catch (ex: Throwable) {
|
||||||
}
|
if (ex is CancellationException) {
|
||||||
} catch (ex: Throwable) {
|
// キャンセルはメッセージを出さない
|
||||||
when (ex) {
|
} else if (ex.message?.contains("cancel", ignoreCase = true) == true) {
|
||||||
is CancellationException, is ClosedReceiveChannelException -> break
|
// キャンセルはメッセージを出さない
|
||||||
else -> {
|
} else if (ex is IllegalStateException) {
|
||||||
safeContext.showToast(ex)
|
safeContext.showToast(true, "${ex.message}")
|
||||||
continue
|
} else {
|
||||||
}
|
safeContext.showToast(true, ex.withCaption("upload failed."))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
PostAttachment.Status.Error
|
||||||
}
|
}
|
||||||
|
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
||||||
|
pa.callback?.onPostAttachmentComplete(pa)
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
when (ex) {
|
||||||
|
is CancellationException -> {
|
||||||
|
log.i("AttachmentUploader: channel cancelled.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
is ClosedChannelException, is ClosedReceiveChannelException -> {
|
||||||
|
log.i("AttachmentUploader: channel closed.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> safeContext.showToast(ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onActivityDestroy() {
|
fun onActivityDestroy() {
|
||||||
try {
|
try {
|
||||||
synchronized(this) {
|
channel.close()
|
||||||
channel?.close()
|
} catch (ignored: Throwable) {
|
||||||
channel = null
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "can't close channel.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addRequest(request: AttachmentRequest) {
|
fun addRequest(request: AttachmentRequest) {
|
||||||
|
|
||||||
request.pa.progress = safeContext.getString(R.string.attachment_handling_start)
|
request.pa.progress = safeContext.getString(R.string.attachment_handling_start)
|
||||||
|
|
||||||
// アップロード開始トースト(連発しない)
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
if (now - lastAttachmentAdd >= 5000L) {
|
|
||||||
safeContext.showToast(false, R.string.attachment_uploading)
|
|
||||||
}
|
|
||||||
lastAttachmentAdd = now
|
|
||||||
|
|
||||||
// マストドンは添付メディアをID順に表示するため
|
// マストドンは添付メディアをID順に表示するため
|
||||||
// 画像が複数ある場合は一つずつ処理する必要がある
|
// 画像が複数ある場合は一つずつ処理する必要がある
|
||||||
// 投稿画面ごとに1スレッドだけ作成してバックグラウンド処理を行う
|
// 投稿画面ごとに作成したチャネルにsendして、受け側は順次処理する
|
||||||
launchIO { prepareChannel().send(request) }
|
launchIO {
|
||||||
|
try {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
// アップロード開始トースト(連発しない)
|
||||||
|
if (now - lastAttachmentAdd >= 5000L) {
|
||||||
|
safeContext.showToast(false, R.string.attachment_uploading)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAttachmentAdd = now
|
||||||
|
channel.send(request)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.e(ex, "addRequest failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private suspend fun AttachmentRequest.upload(): TootApiResult? {
|
private suspend fun AttachmentRequest.upload() {
|
||||||
|
val account = this.account
|
||||||
|
val instance = this.instance()
|
||||||
|
val mediaConfig = this.mediaConfig()
|
||||||
|
|
||||||
|
// ensure mimeType
|
||||||
|
this.mimeType
|
||||||
|
|
||||||
|
if (instance.instanceType == InstanceType.Pixelfed && isReply) {
|
||||||
|
error(safeContext.getString(R.string.pixelfed_does_not_allow_reply_with_media))
|
||||||
|
}
|
||||||
|
|
||||||
|
val client = TootApiClient(safeContext, callback = object : TootApiCallback {
|
||||||
|
override suspend fun isApiCancelled() = !coroutineContext.isActive
|
||||||
|
})
|
||||||
|
|
||||||
|
client.account = account
|
||||||
|
client.currentCallCallback = {}
|
||||||
|
|
||||||
|
// 入力データの変換など
|
||||||
|
val opener = this.createOpener()
|
||||||
try {
|
try {
|
||||||
if (mimeType.isEmpty()) return TootApiResult("mime_type is empty.")
|
|
||||||
|
|
||||||
val client = TootApiClient(safeContext, callback = object : TootApiCallback {
|
|
||||||
override suspend fun isApiCancelled() = !coroutineContext.isActive
|
|
||||||
})
|
|
||||||
|
|
||||||
client.account = account
|
|
||||||
client.currentCallCallback = {}
|
|
||||||
|
|
||||||
val (ti, tiResult) = TootInstance.get(client)
|
|
||||||
ti ?: return tiResult
|
|
||||||
|
|
||||||
// 入力データの変換など
|
|
||||||
val opener = this.createOpener()
|
|
||||||
val maxBytes = when (opener.isImage) {
|
val maxBytes = when (opener.isImage) {
|
||||||
true -> maxBytesImage
|
true -> maxBytesImage(instance, mediaConfig)
|
||||||
else -> maxBytesVideo
|
else -> maxBytesVideo(instance, mediaConfig)
|
||||||
}
|
}
|
||||||
if (opener.contentLength > maxBytes) {
|
if (opener.contentLength > maxBytes.toLong()) {
|
||||||
return TootApiResult(
|
error(safeContext.getString(R.string.file_size_too_big, maxBytes / 1_000_000))
|
||||||
safeContext.getString(R.string.file_size_too_big, maxBytes / 1_000_000)
|
} else if (!opener.mimeType.mimeTypeIsSupported(instance)) {
|
||||||
)
|
error(safeContext.getString(R.string.mime_type_not_acceptable, opener.mimeType))
|
||||||
}
|
|
||||||
|
|
||||||
if (!opener.mimeType.mimeTypeIsSupported(instance)) {
|
|
||||||
return TootApiResult(
|
|
||||||
safeContext.getString(R.string.mime_type_not_acceptable, opener.mimeType)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, uri))
|
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, uri))
|
||||||
pa.progress = safeContext.getString(R.string.attachment_handling_uploading, 0)
|
pa.progress = safeContext.getString(R.string.attachment_handling_uploading, 0)
|
||||||
fun writeProgress(percent: Int) {
|
fun writeProgress(percent: Int) {
|
||||||
if (percent < 100) {
|
pa.progress = if (percent < 100) {
|
||||||
pa.progress =
|
safeContext.getString(R.string.attachment_handling_uploading, percent)
|
||||||
safeContext.getString(R.string.attachment_handling_uploading, percent)
|
|
||||||
} else {
|
} else {
|
||||||
pa.progress = safeContext.getString(R.string.attachment_handling_waiting)
|
safeContext.getString(R.string.attachment_handling_waiting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (account.isMisskey) {
|
if (account.isMisskey) {
|
||||||
val multipartBuilder = MultipartBody.Builder()
|
val multipartBuilder = MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
|
|
||||||
|
@ -199,16 +208,13 @@ class AttachmentUploader(
|
||||||
)
|
)
|
||||||
opener.deleteTempFile()
|
opener.deleteTempFile()
|
||||||
|
|
||||||
val jsonObject = result?.jsonObject
|
result ?: throw CancellationException()
|
||||||
if (jsonObject != null) {
|
|
||||||
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MISSKEY, it) }
|
val jsonObject = result.jsonObject
|
||||||
if (a == null) {
|
?: error(result.error ?: "missing error detail")
|
||||||
result.error = "TootAttachment.parse failed"
|
pa.attachment =
|
||||||
} else {
|
parseItem(jsonObject) { tootAttachment(ServiceType.MISSKEY, it) }
|
||||||
pa.attachment = a
|
?: error("TootAttachment.parse failed")
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
} else {
|
} else {
|
||||||
suspend fun postMedia(path: String) = client.request(
|
suspend fun postMedia(path: String) = client.request(
|
||||||
path,
|
path,
|
||||||
|
@ -226,13 +232,14 @@ class AttachmentUploader(
|
||||||
|
|
||||||
suspend fun postV2(): TootApiResult? {
|
suspend fun postV2(): TootApiResult? {
|
||||||
// 3.1.3未満は v1 APIを使う
|
// 3.1.3未満は v1 APIを使う
|
||||||
if (!ti.versionGE(TootInstance.VERSION_3_1_3)) {
|
if (!instance.versionGE(TootInstance.VERSION_3_1_3)) {
|
||||||
return postV1()
|
return postV1()
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2 APIを試す
|
// v2 APIを試す
|
||||||
val result = postMedia("/api/v2/media")
|
val result = postMedia("/api/v2/media")
|
||||||
val code = result?.response?.code // complete,or 4xx error
|
?: throw CancellationException()
|
||||||
|
val code = result.response?.code // complete,or 4xx error
|
||||||
when {
|
when {
|
||||||
// 404ならv1 APIにフォールバック
|
// 404ならv1 APIにフォールバック
|
||||||
code == 404 -> return postV1()
|
code == 404 -> return postV1()
|
||||||
|
@ -242,18 +249,17 @@ class AttachmentUploader(
|
||||||
|
|
||||||
// ポーリングして処理完了を待つ
|
// ポーリングして処理完了を待つ
|
||||||
pa.progress = safeContext.getString(R.string.attachment_handling_waiting_async)
|
pa.progress = safeContext.getString(R.string.attachment_handling_waiting_async)
|
||||||
val id = parseItem(result?.jsonObject) {
|
|
||||||
|
val id = parseItem(result.jsonObject) {
|
||||||
tootAttachment(ServiceType.MASTODON, it)
|
tootAttachment(ServiceType.MASTODON, it)
|
||||||
}?.id
|
}?.id ?: error("/api/v2/media did not return the media ID.")
|
||||||
?: return TootApiResult("/api/v2/media did not return the media ID.")
|
|
||||||
|
|
||||||
var lastResponse = SystemClock.elapsedRealtime()
|
var lastResponse = SystemClock.elapsedRealtime()
|
||||||
loop@ while (true) {
|
while (true) {
|
||||||
|
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
|
|
||||||
val r2 = client.request("/api/v1/media/$id")
|
val r2 = client.request("/api/v1/media/$id")
|
||||||
?: return null // cancelled
|
?: throw CancellationException()
|
||||||
|
|
||||||
val now = SystemClock.elapsedRealtime()
|
val now = SystemClock.elapsedRealtime()
|
||||||
when (r2.response?.code) {
|
when (r2.response?.code) {
|
||||||
|
@ -264,29 +270,22 @@ class AttachmentUploader(
|
||||||
206 -> lastResponse = now
|
206 -> lastResponse = now
|
||||||
|
|
||||||
// temporary errors, check timeout without 206 response.
|
// temporary errors, check timeout without 206 response.
|
||||||
else -> if (now - lastResponse >= 120000L) {
|
else -> if (now - lastResponse >= 120000L) error("timeout.")
|
||||||
return TootApiResult("timeout.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = postV2()
|
val result = postV2()
|
||||||
opener.deleteTempFile()
|
?: throw CancellationException()
|
||||||
|
|
||||||
val jsonObject = result?.jsonObject
|
val jsonObject = result.jsonObject
|
||||||
if (jsonObject != null) {
|
?: error(result.error ?: "missing error detail")
|
||||||
when (val a = parseItem(jsonObject) {
|
|
||||||
tootAttachment(ServiceType.MASTODON, it)
|
pa.attachment = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
||||||
}) {
|
?: error("TootAttachment.parse failed")
|
||||||
null -> result.error = "TootAttachment.parse failed"
|
|
||||||
else -> pa.attachment = a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} finally {
|
||||||
return TootApiResult(ex.withCaption("read failed."))
|
opener.deleteTempFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,38 +305,6 @@ class AttachmentUploader(
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResult(request: AttachmentRequest, result: TootApiResult?) {
|
|
||||||
val pa = request.pa
|
|
||||||
pa.status = when (pa.attachment) {
|
|
||||||
null -> {
|
|
||||||
if (result != null) {
|
|
||||||
when {
|
|
||||||
// キャンセルはトーストを出さない
|
|
||||||
result.error?.contains("cancel", ignoreCase = true) == true -> Unit
|
|
||||||
else -> safeContext.showToast(
|
|
||||||
true,
|
|
||||||
"${result.error} ${result.response?.request?.method} ${result.response?.request?.url}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PostAttachment.Status.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
if (now - lastAttachmentComplete >= 5000L) {
|
|
||||||
safeContext.showToast(false, R.string.attachment_uploaded)
|
|
||||||
}
|
|
||||||
lastAttachmentComplete = now
|
|
||||||
|
|
||||||
PostAttachment.Status.Ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 投稿中に画面回転があった場合、新しい画面のコールバックを呼び出す必要がある
|
|
||||||
pa.callback?.onPostAttachmentComplete(pa)
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////
|
||||||
// 添付データのカスタムサムネイル
|
// 添付データのカスタムサムネイル
|
||||||
suspend fun uploadCustomThumbnail(
|
suspend fun uploadCustomThumbnail(
|
||||||
|
@ -346,67 +313,62 @@ class AttachmentUploader(
|
||||||
pa: PostAttachment,
|
pa: PostAttachment,
|
||||||
): TootApiResult? = try {
|
): TootApiResult? = try {
|
||||||
safeContext.runApiTask(account) { client ->
|
safeContext.runApiTask(account) { client ->
|
||||||
val (ti, ri) = TootInstance.get(client)
|
|
||||||
ti ?: return@runApiTask ri
|
|
||||||
|
|
||||||
val mimeType = src.uri.resolveMimeType(src.mimeType, safeContext)
|
|
||||||
if (mimeType.isNullOrEmpty()) {
|
|
||||||
return@runApiTask TootApiResult(safeContext.getString(R.string.mime_type_missing))
|
|
||||||
}
|
|
||||||
|
|
||||||
val mediaConfig = ti.configuration?.jsonObject("media_attachments")
|
|
||||||
val ar = AttachmentRequest(
|
val ar = AttachmentRequest(
|
||||||
context = safeContext,
|
context = safeContext,
|
||||||
account = account,
|
account = account,
|
||||||
pa = pa,
|
pa = pa,
|
||||||
uri = src.uri,
|
uri = src.uri,
|
||||||
mimeType = mimeType,
|
mimeTypeArg = src.mimeType,
|
||||||
instance = ti,
|
|
||||||
mediaConfig = mediaConfig,
|
|
||||||
imageResizeConfig = ResizeConfig(ResizeType.SquarePixel, 400),
|
imageResizeConfig = ResizeConfig(ResizeType.SquarePixel, 400),
|
||||||
serverMaxSqPixel = mediaConfig?.int("image_matrix_limit")?.takeIf { it > 0 },
|
maxBytesImage = { _, _ -> 1000000 },
|
||||||
maxBytesImage = 1000000,
|
maxBytesVideo = { _, _ -> 1000000 },
|
||||||
maxBytesVideo = 1000000,
|
|
||||||
)
|
)
|
||||||
|
val instance = ar.instance()
|
||||||
|
val mediaConfig = ar.mediaConfig()
|
||||||
|
val maxBytesImage = ar.maxBytesImage(instance, mediaConfig)
|
||||||
|
|
||||||
val opener = ar.createOpener()
|
val opener = ar.createOpener()
|
||||||
if (opener.contentLength > ar.maxBytesImage) {
|
try{
|
||||||
return@runApiTask TootApiResult(
|
|
||||||
getString(
|
|
||||||
R.string.file_size_too_big,
|
|
||||||
ar.maxBytesImage / 1000000
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, src.uri))
|
if (opener.contentLength > maxBytesImage.toLong()) {
|
||||||
|
return@runApiTask TootApiResult(
|
||||||
if (account.isMisskey) {
|
getString(
|
||||||
opener.deleteTempFile()
|
R.string.file_size_too_big,
|
||||||
TootApiResult("custom thumbnail is not supported on misskey account.")
|
maxBytesImage / 1000000
|
||||||
} else {
|
|
||||||
val result = client.request(
|
|
||||||
"/api/v1/media/${pa.attachment?.id}",
|
|
||||||
MultipartBody.Builder()
|
|
||||||
.setType(MultipartBody.FORM)
|
|
||||||
.addFormDataPart(
|
|
||||||
"thumbnail",
|
|
||||||
fileName,
|
|
||||||
opener.toRequestBody(),
|
|
||||||
)
|
)
|
||||||
.build().toPut()
|
)
|
||||||
)
|
|
||||||
opener.deleteTempFile()
|
|
||||||
|
|
||||||
val jsonObject = result?.jsonObject
|
|
||||||
if (jsonObject != null) {
|
|
||||||
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
|
||||||
if (a == null) {
|
|
||||||
result.error = "TootAttachment.parse failed"
|
|
||||||
} else {
|
|
||||||
pa.attachment = a
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result
|
|
||||||
|
val fileName = fixDocumentName(getDocumentName(safeContext.contentResolver, src.uri))
|
||||||
|
|
||||||
|
if (account.isMisskey) {
|
||||||
|
TootApiResult("custom thumbnail is not supported on misskey account.")
|
||||||
|
} else {
|
||||||
|
val result = client.request(
|
||||||
|
"/api/v1/media/${pa.attachment?.id}",
|
||||||
|
MultipartBody.Builder()
|
||||||
|
.setType(MultipartBody.FORM)
|
||||||
|
.addFormDataPart(
|
||||||
|
"thumbnail",
|
||||||
|
fileName,
|
||||||
|
opener.toRequestBody(),
|
||||||
|
)
|
||||||
|
.build().toPut()
|
||||||
|
)
|
||||||
|
|
||||||
|
val jsonObject = result?.jsonObject
|
||||||
|
if (jsonObject != null) {
|
||||||
|
val a = parseItem(jsonObject) { tootAttachment(ServiceType.MASTODON, it) }
|
||||||
|
if (a == null) {
|
||||||
|
result.error = "TootAttachment.parse failed"
|
||||||
|
} else {
|
||||||
|
pa.attachment = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}finally{
|
||||||
|
opener.deleteTempFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package jp.juggler.subwaytooter.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import jp.juggler.subwaytooter.App1
|
||||||
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
|
import jp.juggler.util.data.asciiPattern
|
||||||
|
import jp.juggler.util.getPackageInfoCompat
|
||||||
|
|
||||||
|
val reNotAllowedInUserAgent = "[^\\x21-\\x7e]+".asciiPattern()
|
||||||
|
|
||||||
|
fun Context.userAgentDefault(): String {
|
||||||
|
val versionName = try {
|
||||||
|
packageManager.getPackageInfoCompat(packageName)!!.versionName
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
App1.log.e(ex, "can't get versionName.")
|
||||||
|
"0.0.0"
|
||||||
|
}
|
||||||
|
return "SubwayTooter/${versionName} Android/${Build.VERSION.RELEASE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getUserAgent(): String {
|
||||||
|
val userAgentCustom = PrefS.spUserAgent.value
|
||||||
|
return when {
|
||||||
|
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
||||||
|
.find() -> userAgentCustom
|
||||||
|
|
||||||
|
else -> userAgentDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package jp.juggler.subwaytooter.push
|
||||||
|
|
||||||
|
object FcmFlavor {
|
||||||
|
const val APPLICATION_ID = "jp.juggler.subwaytooter.noFcm"
|
||||||
|
const val CUSTOM_SCHEME = "subwaytooternofcm"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
object ReleaseType {
|
||||||
|
const val isDebug = false
|
||||||
|
const val isRelease = !isDebug
|
||||||
|
}
|
|
@ -5,9 +5,11 @@ import android.net.Uri
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
import androidx.media3.transformer.TransformationException
|
import androidx.media3.transformer.Composition
|
||||||
|
import androidx.media3.transformer.EditedMediaItem
|
||||||
|
import androidx.media3.transformer.ExportException
|
||||||
|
import androidx.media3.transformer.ExportResult
|
||||||
import androidx.media3.transformer.TransformationRequest
|
import androidx.media3.transformer.TransformationRequest
|
||||||
import androidx.media3.transformer.TransformationResult
|
|
||||||
import androidx.media3.transformer.Transformer
|
import androidx.media3.transformer.Transformer
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -57,35 +59,39 @@ suspend fun transcodeAudio(
|
||||||
val tmpFile = context.generateTempFile("transcodeAudio")
|
val tmpFile = context.generateTempFile("transcodeAudio")
|
||||||
|
|
||||||
// Transformerは単一スレッドで処理する要件
|
// Transformerは単一スレッドで処理する要件
|
||||||
val result: TransformationResult = withContext(Dispatchers.Main.immediate) {
|
val result: ExportResult = withContext(Dispatchers.Main.immediate) {
|
||||||
val looper = Looper.getMainLooper()
|
val looper = Looper.getMainLooper()
|
||||||
suspendCancellableCoroutine { cont ->
|
suspendCancellableCoroutine { cont ->
|
||||||
val transformerListener = object : Transformer.Listener {
|
val transformerListener = object : Transformer.Listener {
|
||||||
override fun onTransformationCompleted(
|
override fun onCompleted(
|
||||||
inputMediaItem: MediaItem,
|
composition: Composition,
|
||||||
transformationResult: TransformationResult,
|
exportResult: ExportResult,
|
||||||
) {
|
) {
|
||||||
log.i("onTransformationCompleted inputMediaItem=$inputMediaItem transformationResult=$transformationResult")
|
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||||
if (cont.isActive) cont.resume(transformationResult) {}
|
log.i("onCompleted mediaItem=$mediaItem exportResult=$exportResult")
|
||||||
|
if (cont.isActive) cont.resume(exportResult) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTransformationError(
|
override fun onError(
|
||||||
inputMediaItem: MediaItem,
|
composition: Composition,
|
||||||
exception: TransformationException,
|
exportResult: ExportResult,
|
||||||
|
exportException: ExportException,
|
||||||
) {
|
) {
|
||||||
|
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||||
log.e(
|
log.e(
|
||||||
exception,
|
exportException,
|
||||||
"onTransformationError inputMediaItem=$inputMediaItem"
|
"onError inputMediaItem=$mediaItem, exportResult=$exportResult"
|
||||||
)
|
)
|
||||||
if (cont.isActive) cont.resumeWithException(exception)
|
if (cont.isActive) cont.resumeWithException(exportException)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFallbackApplied(
|
override fun onFallbackApplied(
|
||||||
inputMediaItem: MediaItem,
|
composition: Composition,
|
||||||
originalTransformationRequest: TransformationRequest,
|
originalTransformationRequest: TransformationRequest,
|
||||||
fallbackTransformationRequest: TransformationRequest,
|
fallbackTransformationRequest: TransformationRequest,
|
||||||
) {
|
) {
|
||||||
log.i("onFallbackApplied inputMediaItem=$inputMediaItem original=$originalTransformationRequest fallback=$fallbackTransformationRequest")
|
val mediaItem = composition.sequences[0].editedMediaItems[0].mediaItem
|
||||||
|
log.i("onFallbackApplied mediaItem=$mediaItem original=$originalTransformationRequest fallback=$fallbackTransformationRequest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val transformer = Transformer.Builder(context)
|
val transformer = Transformer.Builder(context)
|
||||||
|
@ -95,11 +101,13 @@ suspend fun transcodeAudio(
|
||||||
.setAudioMimeType(encodeMimeType)
|
.setAudioMimeType(encodeMimeType)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRemoveVideo(true)
|
|
||||||
.setRemoveAudio(false)
|
|
||||||
.addListener(transformerListener)
|
.addListener(transformerListener)
|
||||||
.build()
|
.build()
|
||||||
transformer.startTransformation(inputMediaItem, tmpFile.canonicalPath)
|
|
||||||
|
val editedMediaItem = EditedMediaItem.Builder(inputMediaItem).apply {
|
||||||
|
setRemoveVideo(true)
|
||||||
|
}.build()
|
||||||
|
transformer.start(editedMediaItem, tmpFile.canonicalPath)
|
||||||
cont.invokeOnCancellation {
|
cont.invokeOnCancellation {
|
||||||
transformer.cancel()
|
transformer.cancel()
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,5 +32,3 @@ org.gradle.vfs.watch=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.debug.obsoleteApi=true
|
android.debug.obsoleteApi=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.enableBuildConfigAsBytecode=true
|
|
||||||
|
|
Loading…
Reference in New Issue