update AndroidX, use ActivityResultContracts (#2170)
* update AndroidX, use ActivityResultContracts * make allowMultiple setable in PickMediaFiles * add license headers to PickMediaFiles
This commit is contained in:
parent
d2cdaae129
commit
ca5c455881
4 changed files with 165 additions and 143 deletions
|
@ -85,7 +85,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.lifecycleVersion = "2.2.0"
|
ext.lifecycleVersion = "2.3.1"
|
||||||
ext.roomVersion = '2.3.0'
|
ext.roomVersion = '2.3.0'
|
||||||
ext.retrofitVersion = '2.9.0'
|
ext.retrofitVersion = '2.9.0'
|
||||||
ext.okhttpVersion = '4.9.1'
|
ext.okhttpVersion = '4.9.1'
|
||||||
|
@ -97,16 +97,16 @@ ext.materialdrawerVersion = '8.2.0'
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.3.2"
|
implementation "androidx.core:core-ktx:1.5.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
implementation "androidx.appcompat:appcompat:1.3.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
implementation "androidx.fragment:fragment-ktx:1.3.3"
|
||||||
implementation "androidx.browser:browser:1.3.0"
|
implementation "androidx.browser:browser:1.3.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||||
implementation "androidx.exifinterface:exifinterface:1.3.2"
|
implementation "androidx.exifinterface:exifinterface:1.3.2"
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||||
implementation "androidx.sharetarget:sharetarget:1.0.0"
|
implementation "androidx.sharetarget:sharetarget:1.1.0"
|
||||||
implementation "androidx.emoji:emoji:1.1.0"
|
implementation "androidx.emoji:emoji:1.1.0"
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
|
@ -116,7 +116,7 @@ dependencies {
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||||
implementation "androidx.work:work-runtime:2.4.0"
|
implementation "androidx.work:work-runtime:2.5.0"
|
||||||
implementation "androidx.room:room-runtime:$roomVersion"
|
implementation "androidx.room:room-runtime:$roomVersion"
|
||||||
implementation "androidx.room:room-rxjava3:$roomVersion"
|
implementation "androidx.room:room-rxjava3:$roomVersion"
|
||||||
kapt "androidx.room:room-compiler:$roomVersion"
|
kapt "androidx.room:room-compiler:$roomVersion"
|
||||||
|
|
|
@ -199,6 +199,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requesters.containsKey(requestCode)) {
|
if (requesters.containsKey(requestCode)) {
|
||||||
PermissionRequester requester = requesters.remove(requestCode);
|
PermissionRequester requester = requesters.remove(requestCode);
|
||||||
requester.onRequestPermissionsResult(permissions, grantResults);
|
requester.onRequestPermissionsResult(permissions, grantResults);
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package com.keylesspalace.tusky.components.compose
|
package com.keylesspalace.tusky.components.compose
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -28,13 +27,13 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
@ -114,6 +113,21 @@ class ComposeActivity : BaseActivity(),
|
||||||
private val maxUploadMediaNumber = 4
|
private val maxUploadMediaNumber = 4
|
||||||
private var mediaCount = 0
|
private var mediaCount = 0
|
||||||
|
|
||||||
|
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
||||||
|
if (success) {
|
||||||
|
pickMedia(photoUploadUri!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val pickMediaFile = registerForActivityResult(PickMediaFiles()) { uris ->
|
||||||
|
if (mediaCount + uris.size > maxUploadMediaNumber) {
|
||||||
|
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
uris.forEach { uri ->
|
||||||
|
pickMedia(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -605,7 +619,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
||||||
} else {
|
} else {
|
||||||
initiateMediaPicking()
|
pickMediaFile.launch(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -755,20 +769,20 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
grantResults: IntArray) {
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
|
||||||
if (requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
if (requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
initiateMediaPicking()
|
pickMediaFile.launch(true)
|
||||||
} else {
|
} else {
|
||||||
val bar = Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission,
|
Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission,
|
||||||
Snackbar.LENGTH_SHORT).apply {
|
Snackbar.LENGTH_SHORT).apply {
|
||||||
|
setAction(R.string.action_retry) { onMediaPick() }
|
||||||
}
|
|
||||||
bar.setAction(R.string.action_retry) { onMediaPick() }
|
|
||||||
//necessary so snackbar is shown over everything
|
//necessary so snackbar is shown over everything
|
||||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||||
bar.show()
|
show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -776,11 +790,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
private fun initiateCameraApp() {
|
private fun initiateCameraApp() {
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
|
||||||
// We don't need to ask for permission in this case, because the used calls require
|
|
||||||
// android.permission.WRITE_EXTERNAL_STORAGE only on SDKs *older* than Kitkat, which was
|
|
||||||
// way before permission dialogues have been introduced.
|
|
||||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
val photoFile: File = try {
|
val photoFile: File = try {
|
||||||
createNewImageFile(this)
|
createNewImageFile(this)
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
|
@ -792,20 +801,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
photoUploadUri = FileProvider.getUriForFile(this,
|
photoUploadUri = FileProvider.getUriForFile(this,
|
||||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||||
photoFile)
|
photoFile)
|
||||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUploadUri)
|
takePicture.launch(photoUploadUri)
|
||||||
startActivityForResult(intent, MEDIA_TAKE_PHOTO_RESULT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initiateMediaPicking() {
|
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
|
|
||||||
val mimeTypes = arrayOf("image/*", "video/*", "audio/*")
|
|
||||||
intent.type = "*/*"
|
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
||||||
startActivityForResult(intent, MEDIA_PICK_RESULT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
||||||
|
@ -828,31 +824,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
viewModel.removeMediaFromQueue(item)
|
viewModel.removeMediaFromQueue(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, intent)
|
|
||||||
if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) {
|
|
||||||
if (intent.data != null) {
|
|
||||||
// Single media, upload it and done.
|
|
||||||
pickMedia(intent.data!!)
|
|
||||||
} else if (intent.clipData != null) {
|
|
||||||
val clipData = intent.clipData!!
|
|
||||||
val count = clipData.itemCount
|
|
||||||
if (mediaCount + count > maxUploadMediaNumber) {
|
|
||||||
// check if exist media + upcoming media > 4, then prob error message.
|
|
||||||
Toast.makeText(this, resources.getQuantityString(R.plurals.error_upload_max_media_reached, maxUploadMediaNumber, maxUploadMediaNumber), Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
// if not grater then 4, upload all multiple media.
|
|
||||||
for (i in 0 until count) {
|
|
||||||
val imageUri = clipData.getItemAt(i).getUri()
|
|
||||||
pickMedia(imageUri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) {
|
|
||||||
pickMedia(photoUploadUri!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) {
|
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) {
|
||||||
withLifecycleContext {
|
withLifecycleContext {
|
||||||
viewModel.pickMedia(uri).observe { exceptionOrItem ->
|
viewModel.pickMedia(uri).observe { exceptionOrItem ->
|
||||||
|
@ -1034,8 +1005,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ComposeActivity" // logging tag
|
private const val TAG = "ComposeActivity" // logging tag
|
||||||
private const val MEDIA_PICK_RESULT = 1
|
|
||||||
private const val MEDIA_TAKE_PHOTO_RESULT = 2
|
|
||||||
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||||
|
|
||||||
internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS"
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* Copyright 2021 Tusky Contributors
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky.util
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
|
||||||
|
class PickMediaFiles : ActivityResultContract<Boolean, List<Uri>>() {
|
||||||
|
override fun createIntent(context: Context, allowMultiple: Boolean): Intent {
|
||||||
|
return Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
.setType("*/*")
|
||||||
|
.apply {
|
||||||
|
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*", "audio/*"))
|
||||||
|
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val intentData = intent?.data
|
||||||
|
val clipData = intent?.clipData
|
||||||
|
if (intentData != null) {
|
||||||
|
// Single media, upload it and done.
|
||||||
|
return listOf(intentData)
|
||||||
|
} else if (clipData != null) {
|
||||||
|
val result: MutableList<Uri> = mutableListOf()
|
||||||
|
for (i in 0 until clipData.itemCount) {
|
||||||
|
result.add(clipData.getItemAt(i).uri)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue