update AndroidX, use ActivityResultContracts (#2170)

* update AndroidX, use ActivityResultContracts

* make allowMultiple setable in PickMediaFiles

* add license headers to PickMediaFiles
This commit is contained in:
Konrad Pozniak 2021-05-22 17:50:08 +02:00 committed by GitHub
parent d2cdaae129
commit ca5c455881
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 143 deletions

View file

@ -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"

View file

@ -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);

View file

@ -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"

View file

@ -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()
}
}