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