requestPermissionsをActivityResultContracts.RequestMultiplePermissionsに移行
This commit is contained in:
parent
bfed497666
commit
c1afdbc0ab
|
@ -586,7 +586,7 @@ class GifDecoder(val callback : GifDecoderCallback) {
|
|||
|
||||
// GIFは最後まで読まないとフレーム数が分からない
|
||||
|
||||
if(frames.isEmpty()) throw error("there is no frame.")
|
||||
if(frames.isEmpty()) error("there is no frame.")
|
||||
callback.onGifHeader(header)
|
||||
callback.onGifAnimationInfo(header, animationControl)
|
||||
for(frame in frames) {
|
||||
|
|
|
@ -283,6 +283,9 @@ dependencies {
|
|||
|
||||
// video transcoder https://github.com/natario1/Transcoder
|
||||
implementation "com.otaliastudios:transcoder:0.10.4"
|
||||
|
||||
// LiveEvent
|
||||
implementation "com.github.hadilq:live-event:1.3.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
|
|
@ -72,12 +72,12 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:maxAspectRatio="100"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
<!-- android:localeConfig="@xml/locales_config" -->
|
||||
|
||||
<activity
|
||||
android:name=".ActMain"
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.Manifest
|
|||
import android.app.Activity
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
|
@ -20,7 +19,6 @@ import android.widget.*
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import jp.juggler.subwaytooter.Styler.defaultColorIcon
|
||||
import jp.juggler.subwaytooter.action.accountRemove
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
|
@ -70,9 +68,6 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
internal const val max_length_note = 160
|
||||
internal const val max_length_fields = 255
|
||||
|
||||
private const val PERMISSION_REQUEST_AVATAR = 1
|
||||
private const val PERMISSION_REQUEST_HEADER = 2
|
||||
|
||||
internal const val MIME_TYPE_JPEG = "image/jpeg"
|
||||
internal const val MIME_TYPE_PNG = "image/png"
|
||||
|
||||
|
@ -153,71 +148,79 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private val arShowAcctColor = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == Activity.RESULT_OK) {
|
||||
showAcctColor()
|
||||
private val arShowAcctColor = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
showAcctColor()
|
||||
}
|
||||
|
||||
private val arNotificationSound = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.decodeRingtonePickerResult()?.let { uri ->
|
||||
notificationSoundUri = uri.toString()
|
||||
saveUIToData()
|
||||
// Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri);
|
||||
// TextView ringView = (TextView) findViewById(R.id.ringtone);
|
||||
// ringView.setText(ringtone.getTitle(getApplicationContext()));
|
||||
// ringtone.setStreamType(AudioManager.STREAM_ALARM);
|
||||
// ringtone.play();
|
||||
// SystemClock.sleep(1000);
|
||||
// ringtone.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private val arNotificationSound = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == Activity.RESULT_OK) {
|
||||
// RINGTONE_PICKERからの選択されたデータを取得する
|
||||
val uri = ar.data?.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
if (uri is Uri) {
|
||||
notificationSoundUri = uri.toString()
|
||||
saveUIToData()
|
||||
// Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri);
|
||||
// TextView ringView = (TextView) findViewById(R.id.ringtone);
|
||||
// ringView.setText(ringtone.getTitle(getApplicationContext()));
|
||||
// ringtone.setStreamType(AudioManager.STREAM_ALARM);
|
||||
// ringtone.play();
|
||||
// SystemClock.sleep(1000);
|
||||
// ringtone.stop();
|
||||
private val arAddAttachment = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data
|
||||
?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
uploadImage(
|
||||
state.propName,
|
||||
it.uri,
|
||||
it.mimeType?.notEmpty() ?: contentResolver.getType(it.uri)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val arAddAttachment = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == Activity.RESULT_OK) {
|
||||
ar.data
|
||||
?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
uploadImage(
|
||||
state.propName,
|
||||
it.uri,
|
||||
it.mimeType?.notEmpty() ?: contentResolver.getType(it.uri)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val arCameraImage = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == Activity.RESULT_OK) {
|
||||
// 画像のURL
|
||||
val uri = ar.data?.data ?: state.uriCameraImage
|
||||
if (uri != null) {
|
||||
val type = contentResolver.getType(uri)
|
||||
uploadImage(state.propName, uri, type)
|
||||
}
|
||||
} else {
|
||||
private val arCameraImage = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) {
|
||||
// 失敗したら DBからデータを削除
|
||||
state.uriCameraImage?.let {
|
||||
contentResolver.delete(it, null, null)
|
||||
}
|
||||
state.uriCameraImage = null
|
||||
} else {
|
||||
// 画像のURL
|
||||
val uri = r.data?.data ?: state.uriCameraImage
|
||||
if (uri != null) {
|
||||
val type = contentResolver.getType(uri)
|
||||
uploadImage(state.propName, uri, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val prPickAvater = PermissionRequester(
|
||||
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
deniedId = R.string.missing_permission_to_access_media,
|
||||
) { openPicker(it) }
|
||||
|
||||
private val prPickHeader = PermissionRequester(
|
||||
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
deniedId = R.string.missing_permission_to_access_media,
|
||||
) { openPicker(it) }
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arShowAcctColor.register(this, log)
|
||||
arNotificationSound.register(this, log)
|
||||
arAddAttachment.register(this, log)
|
||||
arCameraImage.register(this, log)
|
||||
prPickAvater.register(this)
|
||||
prPickHeader.register(this)
|
||||
|
||||
arShowAcctColor.register(this)
|
||||
arNotificationSound.register(this)
|
||||
arAddAttachment.register(this)
|
||||
arCameraImage.register(this)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
savedInstanceState.getString(ACTIVITY_STATE)
|
||||
|
@ -1320,25 +1323,18 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
}
|
||||
|
||||
private fun pickAvatarImage() {
|
||||
openPicker(PERMISSION_REQUEST_AVATAR)
|
||||
openPicker(prPickAvater)
|
||||
}
|
||||
|
||||
private fun pickHeaderImage() {
|
||||
openPicker(PERMISSION_REQUEST_HEADER)
|
||||
openPicker(prPickHeader)
|
||||
}
|
||||
|
||||
private fun openPicker(requestCode: Int) {
|
||||
val permissionCheck = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
||||
preparePermission(requestCode)
|
||||
return
|
||||
}
|
||||
private fun openPicker(permissionRequester: PermissionRequester) {
|
||||
if (!permissionRequester.checkOrLaunch()) return
|
||||
|
||||
val propName = when (requestCode) {
|
||||
PERMISSION_REQUEST_HEADER -> "header"
|
||||
val propName = when (permissionRequester) {
|
||||
prPickHeader -> "header"
|
||||
else -> "avatar"
|
||||
}
|
||||
|
||||
|
@ -1364,23 +1360,6 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
showToast(true, R.string.missing_permission_to_access_media)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
when (requestCode) {
|
||||
PERMISSION_REQUEST_AVATAR, PERMISSION_REQUEST_HEADER ->
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
openPicker(requestCode)
|
||||
} else {
|
||||
showToast(true, R.string.missing_permission_to_access_media)
|
||||
}
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun performAttachment(propName: String) {
|
||||
try {
|
||||
state.propName = propName
|
||||
|
|
|
@ -83,41 +83,38 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
private lateinit var adapter: MyAdapter
|
||||
private lateinit var etSearch: EditText
|
||||
|
||||
private val arNoop = activityResultHandler { }
|
||||
private val arNoop = ActivityResultHandler(log) { }
|
||||
|
||||
private val arImportAppData = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == RESULT_OK) {
|
||||
ar.data?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()
|
||||
?.uri?.let { importAppData2(false, it) }
|
||||
}
|
||||
private val arImportAppData = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()
|
||||
?.uri?.let { importAppData2(false, it) }
|
||||
}
|
||||
|
||||
val arTimelineFont = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == RESULT_OK) {
|
||||
ar.data?.let { handleFontResult(AppSettingItem.TIMELINE_FONT, it, "TimelineFont") }
|
||||
}
|
||||
val arTimelineFont = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.let { handleFontResult(AppSettingItem.TIMELINE_FONT, it, "TimelineFont") }
|
||||
}
|
||||
|
||||
val arTimelineFontBold = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == RESULT_OK) {
|
||||
ar.data?.let {
|
||||
handleFontResult(
|
||||
AppSettingItem.TIMELINE_FONT_BOLD,
|
||||
it,
|
||||
"TimelineFontBold"
|
||||
)
|
||||
}
|
||||
val arTimelineFontBold = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.let {
|
||||
handleFontResult(
|
||||
AppSettingItem.TIMELINE_FONT_BOLD,
|
||||
it,
|
||||
"TimelineFontBold"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arNoop.register(this, log)
|
||||
arImportAppData.register(this, log)
|
||||
arTimelineFont.register(this, log)
|
||||
arTimelineFontBold.register(this, log)
|
||||
arNoop.register(this)
|
||||
arImportAppData.register(this)
|
||||
arTimelineFont.register(this)
|
||||
arTimelineFontBold.register(this)
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
App1.setActivityTheme(this, noActionBar = true)
|
||||
|
@ -1008,7 +1005,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
|
||||
inner class AccountAdapter internal constructor() : BaseAdapter() {
|
||||
|
||||
internal val list = java.util.ArrayList<SavedAccount>()
|
||||
internal val list = ArrayList<SavedAccount>()
|
||||
|
||||
init {
|
||||
for (a in SavedAccount.loadAccountList(this@ActAppSetting)) {
|
||||
|
|
|
@ -19,7 +19,8 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.column.*
|
||||
import jp.juggler.util.*
|
||||
import org.jetbrains.anko.textColor
|
||||
|
@ -70,12 +71,10 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
|||
private var lastImageUri: String? = null
|
||||
private var lastImageBitmap: Bitmap? = null
|
||||
|
||||
private val arColumnBackgroundImage = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == RESULT_OK) {
|
||||
data.handleGetContentResult(contentResolver)
|
||||
.firstOrNull()?.uri?.let { updateBackground(it) }
|
||||
}
|
||||
private val arColumnBackgroundImage = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()?.uri?.let { updateBackground(it) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
|
@ -91,7 +90,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arColumnBackgroundImage.register(this, log)
|
||||
arColumnBackgroundImage.register(this)
|
||||
App1.setActivityTheme(this)
|
||||
initUI()
|
||||
|
||||
|
|
|
@ -54,16 +54,12 @@ class ActHighlightWordEdit
|
|||
|
||||
private var bBusy = false
|
||||
|
||||
private val arNotificationSound = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == Activity.RESULT_OK) {
|
||||
// RINGTONE_PICKERからの選択されたデータを取得する
|
||||
val uri = data.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
if (uri is Uri) {
|
||||
item.sound_uri = uri.toString()
|
||||
item.sound_type = HighlightWord.SOUND_TYPE_CUSTOM
|
||||
showSound()
|
||||
}
|
||||
private val arNotificationSound = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.decodeRingtonePickerResult()?.let { uri ->
|
||||
item.sound_uri = uri.toString()
|
||||
item.sound_type = HighlightWord.SOUND_TYPE_CUSTOM
|
||||
showSound()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +74,7 @@ class ActHighlightWordEdit
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arNotificationSound.register(this, log)
|
||||
arNotificationSound.register(this)
|
||||
App1.setActivityTheme(this)
|
||||
initUI()
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import com.woxthebox.draglistview.swipe.ListSwipeItem
|
|||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.util.*
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
|
@ -33,9 +32,10 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
private var lastRingtone: WeakReference<Ringtone>? = null
|
||||
|
||||
private val arEdit = activityResultHandler { ar ->
|
||||
private val arEdit = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
try {
|
||||
if (ar?.resultCode == RESULT_OK) loadData()
|
||||
loadData()
|
||||
} catch (ex: Throwable) {
|
||||
errorEx(ex, "can't load data")
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arEdit.register(this, log)
|
||||
arEdit.register(this)
|
||||
App1.setActivityTheme(this)
|
||||
initUI()
|
||||
loadData()
|
||||
|
|
|
@ -117,21 +117,18 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
|
|||
private val languageList = ArrayList<MyItem>()
|
||||
private var loadingBusy: Boolean = false
|
||||
|
||||
private val arExport = activityResultHandler { }
|
||||
private val arExport = ActivityResultHandler(log) { }
|
||||
|
||||
private val arImport = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == RESULT_OK) {
|
||||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||
import2(it)
|
||||
}
|
||||
}
|
||||
private val arImport = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()?.uri?.let { import2(it) }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arExport.register(this, log)
|
||||
arImport.register(this, log)
|
||||
arExport.register(this)
|
||||
arImport.register(this)
|
||||
|
||||
App1.setActivityTheme(this)
|
||||
initUI()
|
||||
|
|
|
@ -220,105 +220,94 @@ class ActMain : AppCompatActivity(),
|
|||
|
||||
val viewPool = RecyclerView.RecycledViewPool()
|
||||
|
||||
val arColumnColor = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == Activity.RESULT_OK) {
|
||||
appState.saveColumnList()
|
||||
val idx = data.getIntExtra(ActColumnCustomize.EXTRA_COLUMN_INDEX, 0)
|
||||
appState.column(idx)?.let {
|
||||
val arColumnColor = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
appState.saveColumnList()
|
||||
r.data?.intOrNull(ActColumnCustomize.EXTRA_COLUMN_INDEX)
|
||||
?.let { appState.column(it) }
|
||||
?.let {
|
||||
it.fireColumnColor()
|
||||
it.fireShowContent(
|
||||
reason = "ActMain column color changed",
|
||||
reset = true
|
||||
)
|
||||
}
|
||||
updateColumnStrip()
|
||||
}
|
||||
updateColumnStrip()
|
||||
}
|
||||
|
||||
val arLanguageFilter = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == Activity.RESULT_OK) {
|
||||
appState.saveColumnList()
|
||||
val idx = data.getIntExtra(ActLanguageFilter.EXTRA_COLUMN_INDEX, 0)
|
||||
appState.column(idx)?.onLanguageFilterChanged()
|
||||
}
|
||||
val arLanguageFilter = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
appState.saveColumnList()
|
||||
r.data?.intOrNull(ActLanguageFilter.EXTRA_COLUMN_INDEX)
|
||||
?.let { appState.column(it) }
|
||||
?.onLanguageFilterChanged()
|
||||
}
|
||||
|
||||
val arNickname = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == Activity.RESULT_OK) {
|
||||
updateColumnStrip()
|
||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||
}
|
||||
val arNickname = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
updateColumnStrip()
|
||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||
}
|
||||
|
||||
val arAppSetting = activityResultHandler { ar ->
|
||||
val arAppSetting = ActivityResultHandler(log) { r ->
|
||||
Column.reloadDefaultColor(this, pref)
|
||||
showFooterColor()
|
||||
updateColumnStrip()
|
||||
if (ar?.resultCode == RESULT_APP_DATA_IMPORT) {
|
||||
ar.data?.data?.let { importAppData(it) }
|
||||
if (r.resultCode == RESULT_APP_DATA_IMPORT) {
|
||||
r.data?.data?.let { importAppData(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val arAbout = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == Activity.RESULT_OK) {
|
||||
data.getStringExtra(ActAbout.EXTRA_SEARCH)?.notEmpty()?.let { search ->
|
||||
timeline(
|
||||
defaultInsertPosition,
|
||||
ColumnType.SEARCH,
|
||||
args = arrayOf(search, true)
|
||||
)
|
||||
}
|
||||
val arAbout = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.getStringExtra(ActAbout.EXTRA_SEARCH)?.notEmpty()?.let { search ->
|
||||
timeline(
|
||||
defaultInsertPosition,
|
||||
ColumnType.SEARCH,
|
||||
args = arrayOf(search, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val arAccountSetting = activityResultHandler { ar ->
|
||||
val arAccountSetting = ActivityResultHandler(log) { r ->
|
||||
updateColumnStrip()
|
||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||
when (ar?.resultCode) {
|
||||
RESULT_OK -> ar.data?.data?.let { openBrowser(it) }
|
||||
when (r.resultCode) {
|
||||
RESULT_OK -> r.data?.data?.let { openBrowser(it) }
|
||||
|
||||
ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN ->
|
||||
ar.data?.getLongExtra(ActAccountSetting.EXTRA_DB_ID, -1L)
|
||||
r.data?.getLongExtra(ActAccountSetting.EXTRA_DB_ID, -1L)
|
||||
?.takeIf { it != -1L }
|
||||
?.let { checkAccessToken2(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val arColumnList = activityResultHandler { ar ->
|
||||
val data = ar?.data
|
||||
if (data != null && ar.resultCode == Activity.RESULT_OK) {
|
||||
val order = data.getIntegerArrayListExtra(ActColumnList.EXTRA_ORDER)
|
||||
if (order != null && isOrderChanged(order)) {
|
||||
setColumnsOrder(order)
|
||||
}
|
||||
|
||||
val select = data.getIntExtra(ActColumnList.EXTRA_SELECTION, -1)
|
||||
if (select in 0 until appState.columnCount) {
|
||||
scrollToColumn(select)
|
||||
}
|
||||
}
|
||||
val arColumnList = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.getIntegerArrayListExtra(ActColumnList.EXTRA_ORDER)
|
||||
?.takeIf { isOrderChanged(it) }
|
||||
?.let { setColumnsOrder(it) }
|
||||
r.data?.intOrNull(ActColumnList.EXTRA_SELECTION)
|
||||
?.takeIf { it in 0 until appState.columnCount }
|
||||
?.let { scrollToColumn(it) }
|
||||
}
|
||||
|
||||
val arActText = activityResultHandler { ar ->
|
||||
when (ar?.resultCode) {
|
||||
ActText.RESULT_SEARCH_MSP -> searchFromActivityResult(ar.data, ColumnType.SEARCH_MSP)
|
||||
ActText.RESULT_SEARCH_TS -> searchFromActivityResult(ar.data, ColumnType.SEARCH_TS)
|
||||
val arActText = ActivityResultHandler(log) { r ->
|
||||
when (r.resultCode) {
|
||||
ActText.RESULT_SEARCH_MSP -> searchFromActivityResult(r.data, ColumnType.SEARCH_MSP)
|
||||
ActText.RESULT_SEARCH_TS -> searchFromActivityResult(r.data, ColumnType.SEARCH_TS)
|
||||
ActText.RESULT_SEARCH_NOTESTOCK -> searchFromActivityResult(
|
||||
ar.data,
|
||||
r.data,
|
||||
ColumnType.SEARCH_NOTESTOCK
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val arActPost = activityResultHandler { ar ->
|
||||
ar?.data?.let { data ->
|
||||
if (ar.resultCode == Activity.RESULT_OK) {
|
||||
etQuickPost.setText("")
|
||||
onCompleteActPost(data)
|
||||
}
|
||||
val arActPost = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.let { data ->
|
||||
etQuickPost.setText("")
|
||||
onCompleteActPost(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,15 +319,15 @@ class ActMain : AppCompatActivity(),
|
|||
super.onCreate(savedInstanceState)
|
||||
refActMain = WeakReference(this)
|
||||
|
||||
arColumnColor.register(this, log)
|
||||
arLanguageFilter.register(this, log)
|
||||
arNickname.register(this, log)
|
||||
arAppSetting.register(this, log)
|
||||
arAbout.register(this, log)
|
||||
arAccountSetting.register(this, log)
|
||||
arColumnList.register(this, log)
|
||||
arActPost.register(this, log)
|
||||
arActText.register(this, log)
|
||||
arColumnColor.register(this)
|
||||
arLanguageFilter.register(this)
|
||||
arNickname.register(this)
|
||||
arAppSetting.register(this)
|
||||
arAbout.register(this)
|
||||
arAccountSetting.register(this)
|
||||
arColumnList.register(this)
|
||||
arActPost.register(this)
|
||||
arActText.register(this)
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
App1.setActivityTheme(this, noActionBar = true)
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.ClipData
|
|||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.*
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -16,8 +15,6 @@ import android.os.SystemClock
|
|||
import android.view.View
|
||||
import android.view.Window
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.Player.TimelineChangeReason
|
||||
import com.google.android.exoplayer2.source.LoadEventInfo
|
||||
|
@ -57,8 +54,6 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
internal const val DOWNLOAD_REPEAT_EXPIRE = 3000L
|
||||
internal const val short_limit = 5000L
|
||||
|
||||
private const val PERMISSION_REQUEST_CODE = 1
|
||||
|
||||
internal const val EXTRA_IDX = "idx"
|
||||
internal const val EXTRA_DATA = "data"
|
||||
internal const val EXTRA_SERVICE_TYPE = "serviceType"
|
||||
|
@ -231,6 +226,11 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
private val prDownload = PermissionRequester(
|
||||
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
deniedId = R.string.missing_permission_to_access_media,
|
||||
) { download(mediaList[idx]) }
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
|
@ -247,6 +247,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
prDownload.register(this)
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
App1.setActivityTheme(this, noActionBar = true, forceDark = true)
|
||||
|
||||
|
@ -665,7 +666,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
private fun download(ta: TootAttachmentLike) {
|
||||
if (!checkPermission()) return
|
||||
if (!prDownload.checkOrLaunch()) return
|
||||
|
||||
val downLoadManager: DownloadManager = systemService(this)
|
||||
?: error("missing DownloadManager system service")
|
||||
|
@ -827,39 +828,6 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkPermission(): Boolean {
|
||||
val permissionCheck = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
if (permissionCheck == PackageManager.PERMISSION_GRANTED) return true
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
PERMISSION_REQUEST_CODE
|
||||
)
|
||||
} else {
|
||||
showToast(true, R.string.missing_permission_to_access_media)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
when (permissions.indices.all { grantResults[it] == PackageManager.PERMISSION_GRANTED }) {
|
||||
false -> showToast(true, R.string.missing_permission_to_access_media)
|
||||
else -> download(mediaList[idx])
|
||||
}
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun mediaBackgroundDialog() {
|
||||
val ad = ActionsDialog()
|
||||
for (k in MediaBackgroundDrawable.Kind.values()) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package jp.juggler.subwaytooter
|
|||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
|
@ -62,13 +61,10 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
|||
private var notificationSoundUri: String? = null
|
||||
private var loadingBusy = false
|
||||
|
||||
private val arNotificationSound = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == RESULT_OK) {
|
||||
// RINGTONE_PICKERからの選択されたデータを取得する
|
||||
val uri = ar.data?.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
if (uri is Uri) {
|
||||
notificationSoundUri = uri.toString()
|
||||
}
|
||||
private val arNotificationSound = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.decodeRingtonePickerResult()?.let { uri ->
|
||||
notificationSoundUri = uri.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +75,7 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arNotificationSound.register(this, log)
|
||||
arNotificationSound.register(this)
|
||||
App1.setActivityTheme(this)
|
||||
|
||||
val intent = intent
|
||||
|
|
|
@ -139,28 +139,23 @@ class ActPost : AppCompatActivity(),
|
|||
var isPostComplete: Boolean = false
|
||||
var scheduledStatus: TootScheduled? = null
|
||||
|
||||
// カスタムサムネイルを指定する添付メディア
|
||||
var paThumbnailTarget: PostAttachment? = null
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
val isMultiWindowPost: Boolean
|
||||
get() = intent.getBooleanExtra(EXTRA_MULTI_WINDOW, false)
|
||||
|
||||
val arMushroom = activityResultHandler { ar ->
|
||||
if (ar?.resultCode == RESULT_OK) {
|
||||
ar.data?.getStringExtra("replace_key")
|
||||
?.let { text ->
|
||||
when (states.mushroomInput) {
|
||||
0 -> applyMushroomText(views.etContent, text)
|
||||
1 -> applyMushroomText(views.etContentWarning, text)
|
||||
else -> for (i in 0..3) {
|
||||
if (states.mushroomInput == i + 2) {
|
||||
applyMushroomText(etChoices[i], text)
|
||||
}
|
||||
}
|
||||
val arMushroom = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.getStringExtra("replace_key")?.let { text ->
|
||||
when (states.mushroomInput) {
|
||||
0 -> applyMushroomText(views.etContent, text)
|
||||
1 -> applyMushroomText(views.etContentWarning, text)
|
||||
else -> for (i in 0..3) {
|
||||
if (states.mushroomInput == i + 2) {
|
||||
applyMushroomText(etChoices[i], text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +171,7 @@ class ActPost : AppCompatActivity(),
|
|||
attachmentUploader = AttachmentUploader(this, handler)
|
||||
attachmentPicker = AttachmentPicker(this, this)
|
||||
density = resources.displayMetrics.density
|
||||
arMushroom.register(this, log)
|
||||
arMushroom.register(this)
|
||||
|
||||
progressChannel = Channel(capacity = Channel.CONFLATED)
|
||||
launchMain {
|
||||
|
@ -288,15 +283,6 @@ class ActPost : AppCompatActivity(),
|
|||
openBrowser(span.linkInfo.url)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
attachmentPicker.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
override fun onPickAttachment(uri: Uri, mimeType: String?) {
|
||||
addAttachment(uri, mimeType)
|
||||
}
|
||||
|
@ -315,8 +301,13 @@ class ActPost : AppCompatActivity(),
|
|||
onPostAttachmentCompleteImpl(pa)
|
||||
}
|
||||
|
||||
override fun onPickCustomThumbnail(src: GetContentResultEntry) {
|
||||
onPickCustomThumbnailImpl(src)
|
||||
override fun resumeCustomThumbnailTarget(id: String?): PostAttachment? {
|
||||
id?: return null
|
||||
return attachmentList.find{ it.attachment?.id?.toString() == id }
|
||||
}
|
||||
|
||||
override fun onPickCustomThumbnail(pa: PostAttachment,src: GetContentResultEntry) {
|
||||
onPickCustomThumbnailImpl(pa,src)
|
||||
}
|
||||
|
||||
fun initUI() {
|
||||
|
|
|
@ -202,13 +202,14 @@ fun ActPost.performAttachmentClick(idx: Int) {
|
|||
}
|
||||
if (account?.isMastodon == true) {
|
||||
when (pa.attachment?.type) {
|
||||
TootAttachmentType.Audio, TootAttachmentType.GIFV, TootAttachmentType.Video ->
|
||||
a.addAction(getString(R.string.custom_thumbnail)) {
|
||||
openCustomThumbnail(pa)
|
||||
}
|
||||
|
||||
else -> {
|
||||
TootAttachmentType.Audio,
|
||||
TootAttachmentType.GIFV,
|
||||
TootAttachmentType.Video,
|
||||
-> a.addAction(getString(R.string.custom_thumbnail)) {
|
||||
attachmentPicker.openCustomThumbnail(pa)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,19 +310,9 @@ fun ActPost.editAttachmentDescription(pa: PostAttachment) {
|
|||
})
|
||||
}
|
||||
|
||||
fun ActPost.openCustomThumbnail(pa: PostAttachment) {
|
||||
paThumbnailTarget = pa
|
||||
attachmentPicker.openCustomThumbnail()
|
||||
}
|
||||
|
||||
fun ActPost.onPickCustomThumbnailImpl(src: GetContentResultEntry) {
|
||||
val account = this.account
|
||||
val pa = paThumbnailTarget
|
||||
when {
|
||||
account == null ->
|
||||
showToast(false, R.string.account_select_please)
|
||||
pa == null || !attachmentList.contains(pa) ->
|
||||
showToast(true, "lost attachment information")
|
||||
fun ActPost.onPickCustomThumbnailImpl(pa: PostAttachment, src: GetContentResultEntry) {
|
||||
when (val account = this.account) {
|
||||
null -> showToast(false, R.string.account_select_please)
|
||||
else -> launchMain {
|
||||
val result = attachmentUploader.uploadCustomThumbnail(account, src, pa)
|
||||
result?.error?.let { showToast(true, it) }
|
||||
|
|
|
@ -3,21 +3,16 @@ package jp.juggler.subwaytooter.util
|
|||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.kJson
|
||||
import jp.juggler.util.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.util.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class AttachmentPicker(
|
||||
val activity: AppCompatActivity,
|
||||
|
@ -25,14 +20,15 @@ class AttachmentPicker(
|
|||
) {
|
||||
companion object {
|
||||
private val log = LogCategory("AttachmentPicker")
|
||||
private val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
private const val PERMISSION_REQUEST_CODE = 1
|
||||
}
|
||||
|
||||
// callback after media selected
|
||||
interface Callback {
|
||||
fun onPickAttachment(uri: Uri, mimeType: String? = null)
|
||||
fun onPickCustomThumbnail(src: GetContentResultEntry)
|
||||
fun onPickCustomThumbnail(pa:PostAttachment,src: GetContentResultEntry)
|
||||
fun resumeCustomThumbnailTarget(id:String?): PostAttachment?
|
||||
}
|
||||
|
||||
// actions after permission granted
|
||||
|
@ -44,7 +40,7 @@ class AttachmentPicker(
|
|||
@Serializable(with = UriSerializer::class)
|
||||
var uriCameraImage: Uri? = null,
|
||||
|
||||
var afterPermission: AfterPermission = AfterPermission.Attachment,
|
||||
var customThumbnailTargetId: String? = null,
|
||||
)
|
||||
|
||||
private var states = States()
|
||||
|
@ -52,49 +48,66 @@ class AttachmentPicker(
|
|||
////////////////////////////////////////////////////////////////////////
|
||||
// activity result handlers
|
||||
|
||||
private val arAttachmentChooser = activity.activityResultHandler { ar ->
|
||||
if (ar?.resultCode == AppCompatActivity.RESULT_OK) {
|
||||
ar.data?.handleGetContentResult(contentResolver)?.pickAll()
|
||||
}
|
||||
private val arAttachmentChooser = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.handleGetContentResult(activity.contentResolver)?.pickAll()
|
||||
}
|
||||
|
||||
private val arCamera = activity.activityResultHandler { ar ->
|
||||
if (ar?.resultCode == AppCompatActivity.RESULT_OK) {
|
||||
// 画像のURL
|
||||
when (val uri = ar.data?.data ?: states.uriCameraImage) {
|
||||
null -> showToast(false, "missing image uri")
|
||||
else -> callback.onPickAttachment(uri)
|
||||
}
|
||||
} else {
|
||||
private val arCamera = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) {
|
||||
// 失敗したら DBからデータを削除
|
||||
states.uriCameraImage?.let { uri ->
|
||||
contentResolver.delete(uri, null, null)
|
||||
activity.contentResolver.delete(uri, null, null)
|
||||
states.uriCameraImage = null
|
||||
}
|
||||
} else {
|
||||
// 画像のURL
|
||||
when (val uri = r.data?.data ?: states.uriCameraImage) {
|
||||
null -> activity.showToast(false, "missing image uri")
|
||||
else -> callback.onPickAttachment(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val arCapture = activity.activityResultHandler { ar ->
|
||||
if (ar?.resultCode == AppCompatActivity.RESULT_OK) {
|
||||
ar.data?.data?.let { callback.onPickAttachment(it) }
|
||||
}
|
||||
private val arCapture = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data?.data?.let { callback.onPickAttachment(it) }
|
||||
}
|
||||
|
||||
private val arCustomThumbnail = activity.activityResultHandler { ar ->
|
||||
if (ar?.resultCode == AppCompatActivity.RESULT_OK) {
|
||||
ar.data
|
||||
?.handleGetContentResult(contentResolver)
|
||||
?.firstOrNull()
|
||||
?.let { callback.onPickCustomThumbnail(it) }
|
||||
}
|
||||
private val arCustomThumbnail = ActivityResultHandler(log) { r ->
|
||||
if (r.isNotOk) return@ActivityResultHandler
|
||||
r.data
|
||||
?.handleGetContentResult(activity.contentResolver)
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
callback.resumeCustomThumbnailTarget(states.customThumbnailTargetId)?.let { pa->
|
||||
callback.onPickCustomThumbnail(pa,it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val prPickAttachment = PermissionRequester(
|
||||
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
deniedId = R.string.missing_permission_to_access_media,
|
||||
) { openPicker() }
|
||||
|
||||
private val prPickCustomThumbnail = PermissionRequester(
|
||||
permissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
deniedId = R.string.missing_permission_to_access_media,
|
||||
) {
|
||||
callback.resumeCustomThumbnailTarget(states.customThumbnailTargetId)
|
||||
?.let{ openCustomThumbnail(it) }
|
||||
}
|
||||
|
||||
init {
|
||||
// must register all ARHs before onStart
|
||||
arAttachmentChooser.register(activity, log)
|
||||
arCamera.register(activity, log)
|
||||
arCapture.register(activity, log)
|
||||
arCustomThumbnail.register(activity, log)
|
||||
prPickAttachment.register(activity)
|
||||
prPickCustomThumbnail.register(activity)
|
||||
arAttachmentChooser.register(activity)
|
||||
arCamera.register(activity)
|
||||
arCapture.register(activity)
|
||||
arCustomThumbnail.register(activity)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -107,53 +120,19 @@ class AttachmentPicker(
|
|||
fun encodeState(): String {
|
||||
val encoded = kJson.encodeToString(states)
|
||||
val decoded = kJson.decodeFromString<States>(encoded)
|
||||
log.d("encodeState: ${decoded.uriCameraImage},${decoded.afterPermission},$encoded")
|
||||
log.d("encodeState: ${decoded.uriCameraImage},$encoded")
|
||||
return encoded
|
||||
}
|
||||
|
||||
fun restoreState(encoded: String) {
|
||||
states = kJson.decodeFromString(encoded)
|
||||
log.d("restoreState: ${states.uriCameraImage},${states.afterPermission},$encoded")
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// permission check
|
||||
// (current implementation does not auto restart actions after got permission
|
||||
|
||||
// returns true if permission granted, false if not granted, (may request permissions)
|
||||
private fun checkPermission(afterPermission: AfterPermission): Boolean {
|
||||
states.afterPermission = afterPermission
|
||||
if (permissions.all {
|
||||
ContextCompat.checkSelfPermission(activity, it) == PackageManager.PERMISSION_GRANTED
|
||||
}) return true
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE)
|
||||
} else {
|
||||
activity.showToast(true, R.string.missing_permission_to_access_media)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
|
||||
if (requestCode != PERMISSION_REQUEST_CODE) return
|
||||
|
||||
if ((permissions.indices).any { grantResults.elementAtOrNull(it) != PackageManager.PERMISSION_GRANTED }) {
|
||||
activity.showToast(true, R.string.missing_permission_to_access_media)
|
||||
return
|
||||
}
|
||||
|
||||
when (states.afterPermission) {
|
||||
AfterPermission.Attachment -> openPicker()
|
||||
AfterPermission.CustomThumbnail -> openCustomThumbnail()
|
||||
}
|
||||
log.d("restoreState: ${states.uriCameraImage},$encoded")
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fun openPicker() {
|
||||
if (!checkPermission(AfterPermission.Attachment)) return
|
||||
if (!prPickAttachment.checkOrLaunch()) return
|
||||
|
||||
// permissionCheck = ContextCompat.checkSelfPermission( this, Manifest.permission.CAMERA );
|
||||
// if( permissionCheck != PackageManager.PERMISSION_GRANTED ){
|
||||
|
@ -209,8 +188,10 @@ class AttachmentPicker(
|
|||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
||||
}
|
||||
|
||||
val newUri = activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
|
||||
.also { states.uriCameraImage = it }
|
||||
val newUri =
|
||||
activity.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
values)
|
||||
.also { states.uriCameraImage = it }
|
||||
|
||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, newUri)
|
||||
|
@ -238,13 +219,16 @@ class AttachmentPicker(
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Mastodon's custom thumbnail
|
||||
|
||||
fun openCustomThumbnail() {
|
||||
if (!checkPermission(AfterPermission.CustomThumbnail)) return
|
||||
fun openCustomThumbnail(pa: PostAttachment) {
|
||||
states.customThumbnailTargetId = pa.attachment?.id?.toString()
|
||||
if (!prPickCustomThumbnail.checkOrLaunch()) return
|
||||
|
||||
// SAFのIntentで開く
|
||||
try {
|
||||
arCustomThumbnail.launch(
|
||||
intentGetContent(false, activity.getString(R.string.pick_images), arrayOf("image/*"))
|
||||
intentGetContent(false,
|
||||
activity.getString(R.string.pick_images),
|
||||
arrayOf("image/*"))
|
||||
)
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package jp.juggler.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
|
||||
/**
|
||||
* API 33 でget() が deprecatedになる?
|
||||
*/
|
||||
fun Bundle.getRaw(key: String) = get(key)
|
||||
|
||||
fun Intent.decodeRingtonePickerResult() =
|
||||
extras?.getRaw(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as? Uri
|
||||
|
||||
fun Bundle.intOrNull(key: String) =
|
||||
when (val v = getRaw(key)) {
|
||||
null -> null
|
||||
is Number -> v.toInt()
|
||||
is String -> v.toIntOrNull()
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun Bundle.longOrNull(key: String) =
|
||||
when (val v = getRaw(key)) {
|
||||
null -> null
|
||||
is Number -> v.toLong()
|
||||
is String -> v.toLongOrNull()
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun Intent.intOrNull(key: String) = extras?.intOrNull(key)
|
||||
fun Intent.longOrNull(key: String) = extras?.longOrNull(key)
|
|
@ -0,0 +1,162 @@
|
|||
package jp.juggler.util
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import jp.juggler.subwaytooter.R
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/**
|
||||
* ActivityResultLauncherを使ってパーミッション要求とその結果の処理を行う
|
||||
*/
|
||||
class PermissionRequester(
|
||||
/**
|
||||
* 必要なパーミッションのリスト
|
||||
*/
|
||||
val permissions: List<String>,
|
||||
|
||||
/**
|
||||
* 要求が拒否された場合に表示するメッセージのID
|
||||
*/
|
||||
@StringRes val deniedId: Int,
|
||||
|
||||
/**
|
||||
* なぜ権限が必要なのか説明するメッセージのID。
|
||||
* デフォルトは0で、この場合はメッセージを出さない。
|
||||
*/
|
||||
@StringRes val rationalId: Int = 0,
|
||||
|
||||
/**
|
||||
* 権限が与えられた際に処理を再開するラムダ
|
||||
* - ラムダの引数にこのPermissionRequester自身が渡される
|
||||
*/
|
||||
val onGrant: (PermissionRequester) -> Unit,
|
||||
) : ActivityResultCallback<Map<String, Boolean>> {
|
||||
companion object {
|
||||
private val log = LogCategory("PermissionRequester")
|
||||
}
|
||||
|
||||
private var launcher: ActivityResultLauncher<Array<String>>? = null
|
||||
|
||||
private var getContext: (() -> Context?)? = null
|
||||
|
||||
private val activity
|
||||
get() = getContext?.invoke() as? FragmentActivity
|
||||
|
||||
// ActivityのonCreate()から呼び出す
|
||||
fun register(activity: FragmentActivity) {
|
||||
getContext = { activity }
|
||||
launcher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions(),
|
||||
this,
|
||||
)
|
||||
}
|
||||
|
||||
// FragmentのonCreate()から呼び出す
|
||||
fun register(fragment: Fragment) {
|
||||
getContext = { fragment.activity ?: fragment.context }
|
||||
launcher = fragment.registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions(),
|
||||
this,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 実行時権限が全て揃っているならtrueを返す
|
||||
* そうでなければ権限の要求を行い、falseを返す
|
||||
*/
|
||||
fun checkOrLaunch(): Boolean {
|
||||
val activity = activity ?: error("missing activity.")
|
||||
val listNotGranted = permissions.filter {
|
||||
PackageManager.PERMISSION_GRANTED !=
|
||||
ContextCompat.checkSelfPermission(activity, it)
|
||||
}
|
||||
if (listNotGranted.isEmpty()) return true
|
||||
|
||||
launchMain {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
activity.showToast(true, deniedId)
|
||||
return@launchMain
|
||||
}
|
||||
|
||||
val shouldShowRational = listNotGranted.any {
|
||||
shouldShowRequestPermissionRationale(activity, it)
|
||||
}
|
||||
if (shouldShowRational && rationalId != 0) {
|
||||
suspendCancellableCoroutine { cont ->
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(rationalId)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (cont.isActive) cont.resumeWith(Result.success(Unit))
|
||||
}
|
||||
.setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||
}
|
||||
.create()
|
||||
.also { dialog -> cont.invokeOnCancellation { dialog.dismissSafe() } }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
launcher!!.launch(listNotGranted.toTypedArray())
|
||||
} catch (ex: Throwable) {
|
||||
if (ex !is CancellationException) {
|
||||
activity.showToast(ex, "can't request permissions.")
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 権限要求の結果を処理する
|
||||
* @param result 「パーミッション名」と「それが許可されているなら真」のマップ
|
||||
*/
|
||||
override fun onActivityResult(result: Map<String, Boolean>?) {
|
||||
try {
|
||||
result ?: error("missing result.")
|
||||
val listNotGranted = result.entries.filter { !it.value }.map { it.key }
|
||||
if (listNotGranted.isEmpty()) {
|
||||
// すべて許可されている
|
||||
onGrant(this)
|
||||
return
|
||||
}
|
||||
// 許可されなかった。
|
||||
val activity = activity ?: error("missing activity.")
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(deniedId)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.setting) { _, _ ->
|
||||
openAppSetting(activity)
|
||||
}
|
||||
.show()
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex, "can't handle result.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun openAppSetting(activity: FragmentActivity) {
|
||||
try {
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
"package:${activity.packageName}".toUri()
|
||||
).let { activity.startActivity(it) }
|
||||
} catch (ex: Throwable) {
|
||||
activity.showToast(ex, "openAppSetting failed.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
|
@ -19,15 +20,14 @@ import android.util.SparseArray
|
|||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import jp.juggler.subwaytooter.R
|
||||
import java.util.*
|
||||
|
||||
object UiUtils {
|
||||
|
||||
|
@ -95,7 +95,7 @@ fun createRoundDrawable(
|
|||
radius: Float,
|
||||
fillColor: Int? = null,
|
||||
strokeColor: Int? = null,
|
||||
strokeWidth: Int = 4
|
||||
strokeWidth: Int = 4,
|
||||
) =
|
||||
GradientDrawable().apply {
|
||||
cornerRadius = radius
|
||||
|
@ -113,7 +113,7 @@ fun getAdaptiveRippleDrawableRound(
|
|||
context: Context,
|
||||
normalColor: Int,
|
||||
pressedColor: Int,
|
||||
roundNormal: Boolean = false
|
||||
roundNormal: Boolean = false,
|
||||
): Drawable {
|
||||
val dp6 = context.resources.displayMetrics.density * 6f
|
||||
return if (roundNormal) {
|
||||
|
@ -137,7 +137,7 @@ fun getAdaptiveRippleDrawableRound(
|
|||
|
||||
private class ColorFilterCacheValue(
|
||||
val filter: ColorFilter,
|
||||
var lastUsed: Long
|
||||
var lastUsed: Long,
|
||||
)
|
||||
|
||||
private val colorFilterCache = SparseArray<ColorFilterCacheValue>()
|
||||
|
@ -174,16 +174,16 @@ private fun createColorFilter(rgb: Int): ColorFilter {
|
|||
private class ColoredDrawableCacheKey(
|
||||
val drawableId: Int,
|
||||
val rgb: Int,
|
||||
val alpha: Int
|
||||
val alpha: Int,
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || (
|
||||
other is ColoredDrawableCacheKey &&
|
||||
drawableId == other.drawableId &&
|
||||
rgb == other.rgb &&
|
||||
alpha == other.alpha
|
||||
)
|
||||
other is ColoredDrawableCacheKey &&
|
||||
drawableId == other.drawableId &&
|
||||
rgb == other.rgb &&
|
||||
alpha == other.alpha
|
||||
)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
@ -193,7 +193,7 @@ private class ColoredDrawableCacheKey(
|
|||
|
||||
private class ColoredDrawableCacheValue(
|
||||
val drawable: Drawable,
|
||||
var lastUsed: Long
|
||||
var lastUsed: Long,
|
||||
)
|
||||
|
||||
private val coloredDrawableCache = HashMap<ColoredDrawableCacheKey, ColoredDrawableCacheValue>()
|
||||
|
@ -203,7 +203,7 @@ fun createColoredDrawable(
|
|||
context: Context,
|
||||
drawableId: Int,
|
||||
color: Int,
|
||||
alphaMultiplier: Float
|
||||
alphaMultiplier: Float,
|
||||
): Drawable {
|
||||
val rgb = (color and 0xffffff) or Color.BLACK
|
||||
val alpha = if (alphaMultiplier >= 1f) {
|
||||
|
@ -248,7 +248,7 @@ fun setIconDrawableId(
|
|||
imageView: ImageView,
|
||||
drawableId: Int,
|
||||
color: Int? = null,
|
||||
alphaMultiplier: Float
|
||||
alphaMultiplier: Float,
|
||||
) {
|
||||
if (color == null) {
|
||||
// ImageViewにアイコンを設定する。デフォルトの色
|
||||
|
@ -310,14 +310,14 @@ fun DialogInterface.dismissSafe() {
|
|||
}
|
||||
|
||||
class CustomTextWatcher(
|
||||
val callback: () -> Unit
|
||||
val callback: () -> Unit,
|
||||
) : TextWatcher {
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
after: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -356,34 +356,36 @@ var View.isEnabledAlpha: Boolean
|
|||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
class ActivityResultHandler<A : ComponentActivity>(
|
||||
val callback: A.(ActivityResult?) -> Unit
|
||||
class ActivityResultHandler(
|
||||
private val log: LogCategory,
|
||||
private val callback: (ActivityResult) -> Unit,
|
||||
) {
|
||||
private lateinit var log: LogCategory
|
||||
private lateinit var context: Context
|
||||
private lateinit var launcher: ActivityResultLauncher<Intent>
|
||||
private var launcher: ActivityResultLauncher<Intent>? = null
|
||||
private var getContext: (() -> Context?)? = null
|
||||
|
||||
private val context
|
||||
get() = getContext?.invoke()
|
||||
|
||||
// startForActivityResultの代わりに呼び出す
|
||||
fun launch(intent: Intent, options: ActivityOptionsCompat? = null) = try {
|
||||
launcher.launch(intent, options)
|
||||
(launcher ?: error("ActivityResultHandler not registered."))
|
||||
.launch(intent, options)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "launch failed")
|
||||
context.showToast(ex, "activity launch failed.")
|
||||
context?.showToast(ex, "activity launch failed.")
|
||||
}
|
||||
|
||||
// onCreate時に呼び出す
|
||||
fun register(a: A, log: LogCategory) {
|
||||
this.log = log
|
||||
this.context = a.applicationContext
|
||||
fun register(a: FragmentActivity) {
|
||||
getContext = { a.applicationContext }
|
||||
this.launcher = a.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { callback(a, it) }
|
||||
) { callback(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun <A : ComponentActivity> A.activityResultHandler(callback: A.(ActivityResult?) -> Unit) =
|
||||
ActivityResultHandler(callback)
|
||||
|
||||
val AppCompatActivity.isLiveActivity: Boolean
|
||||
get() = !(isFinishing || isDestroyed)
|
||||
|
||||
val ActivityResult.isNotOk
|
||||
get() = Activity.RESULT_OK != resultCode
|
||||
|
|
Loading…
Reference in New Issue