feat: 选图

This commit is contained in:
HuanCheng65 2023-07-20 22:58:41 +08:00
parent f552e48c77
commit d63d7a5a13
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
7 changed files with 98 additions and 38 deletions

View File

@ -54,6 +54,9 @@ import com.google.accompanist.navigation.material.ModalBottomSheetLayout
import com.google.accompanist.systemuicontroller.SystemUiController
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
import com.huanchengfly.tieba.post.arch.BaseComposeActivity
import com.huanchengfly.tieba.post.arch.GlobalEvent
import com.huanchengfly.tieba.post.arch.emitGlobalEvent
import com.huanchengfly.tieba.post.arch.onGlobalEvent
import com.huanchengfly.tieba.post.services.NotifyJobService
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.NavGraphs
@ -69,9 +72,11 @@ import com.huanchengfly.tieba.post.utils.AccountUtil
import com.huanchengfly.tieba.post.utils.ClientUtils
import com.huanchengfly.tieba.post.utils.JobServiceUtil
import com.huanchengfly.tieba.post.utils.PermissionUtils
import com.huanchengfly.tieba.post.utils.PickMediasRequest
import com.huanchengfly.tieba.post.utils.TiebaUtil
import com.huanchengfly.tieba.post.utils.isIgnoringBatteryOptimizations
import com.huanchengfly.tieba.post.utils.newIntentFilter
import com.huanchengfly.tieba.post.utils.registerPickMediasLauncher
import com.huanchengfly.tieba.post.utils.requestIgnoreBatteryOptimizations
import com.huanchengfly.tieba.post.utils.requestPermission
import com.ramcosta.composedestinations.DestinationsNavHost
@ -124,6 +129,11 @@ class MainActivityV2 : BaseComposeActivity() {
private val notificationCountFlow: MutableSharedFlow<Int> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val pickMediasLauncher =
registerPickMediasLauncher {
emitGlobalEvent(GlobalEvent.SelectedImages(it.id, it.uris))
}
private val devicePostureFlow: StateFlow<DevicePosture> by lazy {
WindowInfoTracker.getOrCreate(this)
.windowLayoutInfo(this)
@ -251,6 +261,11 @@ class MainActivityV2 : BaseComposeActivity() {
okSignAlertDialogState.show()
}
}
onGlobalEvent<GlobalEvent.StartSelectImages> {
pickMediasLauncher.launch(
PickMediasRequest(it.id, it.maxCount, it.mediaType)
)
}
CompositionLocalProvider(
LocalNotificationCountFlow provides notificationCountFlow,
LocalDevicePosture provides devicePostureFlow.collectAsState(),

View File

@ -99,9 +99,9 @@ class ReplyActivity : BaseActivity(), View.OnClickListener,
get() = false
@JvmField
val pickMediasLauncher = registerPickMediasLauncher {
val pickMediasLauncher = registerPickMediasLauncher { (_, uris) ->
val photoInfoBeans = insertPhotoAdapter.getFileList().toMutableList()
for (uri in it) {
for (uri in uris) {
photoInfoBeans.add(PhotoInfoBean(this, uri))
}
insertPhotoAdapter.setFileList(photoInfoBeans)

View File

@ -97,9 +97,9 @@ class TranslucentThemeActivity : BaseActivity(), View.OnClickListener, OnSeekBar
@BindView(R.id.color_theme)
lateinit var colorTheme: ViewGroup
private val selectImageLauncher = registerPickMediasLauncher {
if (it.isNotEmpty()) {
val sourceUri = it[0]
private val selectImageLauncher = registerPickMediasLauncher { (_, uris) ->
if (uris.isNotEmpty()) {
val sourceUri = uris[0]
launchUCrop(sourceUri)
}
}

View File

@ -70,10 +70,14 @@ class InsertPhotoAdapter(private val mContext: Context) : RecyclerView.Adapter<M
holder.setItemOnClickListener {
if (mContext is AppCompatActivity && mContext is PickMediasLauncherProvider) {
askPermission {
mContext.getPickMediasLauncher().launch(PickMediasRequest().apply {
maxItems = 10 - fileList.size
mediaType = PickMediasRequest.ImageOnly
})
mContext.getPickMediasLauncher()
.launch(
PickMediasRequest(
"",
10 - fileList.size,
mediaType = PickMediasRequest.ImageOnly
)
)
}
}
}

View File

@ -1,35 +1,60 @@
package com.huanchengfly.tieba.post.arch
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import com.huanchengfly.tieba.post.utils.PickMediasRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
sealed interface GlobalEvent {
object AccountSwitched : GlobalEvent
data class StartSelectImages(
val id: String,
val maxCount: Int,
val mediaType: PickMediasRequest.MediaType
) : GlobalEvent
data class SelectedImages(
val id: String,
val images: List<Uri>
) : GlobalEvent
}
private val mutableGlobalEventFlow by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MutableSharedFlow<GlobalEvent>() }
private val globalEventChannel: Channel<GlobalEvent> = Channel()
val GlobalEventFlow by lazy { mutableGlobalEventFlow }
val GlobalEventFlow: Flow<GlobalEvent>
get() = globalEventChannel.receiveAsFlow()
fun emitGlobalEvent(event: GlobalEvent) {
mutableGlobalEventFlow.tryEmit(event)
globalEventChannel.trySend(event)
}
inline fun <reified Event : GlobalEvent> CoroutineScope.onGlobalEvent(
@Composable
inline fun <reified Event : GlobalEvent> onGlobalEvent(
coroutineScope: CoroutineScope = rememberCoroutineScope(),
noinline listener: suspend (Event) -> Unit
): Job {
return launch {
GlobalEventFlow
.filterIsInstance<Event>()
.cancellable()
.collect {
launch {
listener(it)
) {
DisposableEffect(listener) {
val job = coroutineScope.launch {
GlobalEventFlow
.filterIsInstance<Event>()
.cancellable()
.collect {
launch {
listener(it)
}
}
}
}
onDispose {
job.cancel()
}
}
}

View File

@ -69,9 +69,9 @@ class EditProfileActivity : BaseActivity() {
}
private val pickMediasLauncher =
registerPickMediasLauncher {
if (it.isNotEmpty()) {
val sourceUri = it[0]
registerPickMediasLauncher { (_, uris) ->
if (uris.isNotEmpty()) {
val sourceUri = uris[0]
Glide.with(this)
.asFile()
.load(sourceUri)

View File

@ -1,5 +1,6 @@
package com.huanchengfly.tieba.post.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
@ -16,9 +17,9 @@ import com.zhihu.matisse.MimeType
import com.zhihu.matisse.engine.impl.GlideEngine
import com.zhihu.matisse.ui.MatisseActivity
fun AppCompatActivity.registerPickMediasLauncher(callback: (List<Uri>) -> Unit): ActivityResultLauncher<PickMediasRequest> {
fun AppCompatActivity.registerPickMediasLauncher(callback: (PickMediasResult) -> Unit): ActivityResultLauncher<PickMediasRequest> {
return registerForActivityResult(
PickMediasContract()
PickMediasContract
) {
callback(it)
}
@ -55,15 +56,17 @@ private fun Intent.getClipDataUris(): List<Uri> {
return ArrayList(resultSet)
}
@SuppressLint("NewApi")
private fun getMaxItems() = if (isPhotoPickerAvailable()) {
MediaStore.getPickImagesMaxLimit()
} else {
Integer.MAX_VALUE
}
class PickMediasRequest(
var maxItems: Int = 1,
var mediaType: MediaType = ImageAndVideo
data class PickMediasRequest(
val id: String = "",
val maxItems: Int = 1,
val mediaType: MediaType = ImageAndVideo
) {
sealed interface MediaType
@ -73,7 +76,7 @@ class PickMediasRequest(
object ImageAndVideo : MediaType
class SingleMimeType(val mimeType: String) : MediaType
data class SingleMimeType(val mimeType: String) : MediaType
companion object {
internal fun getMimeType(input: MediaType): String? {
@ -87,8 +90,19 @@ class PickMediasRequest(
}
}
class PickMediasContract : ActivityResultContract<PickMediasRequest, List<Uri>>() {
data class PickMediasResult(
val id: String,
val uris: List<Uri>
)
object PickMediasContract : ActivityResultContract<PickMediasRequest, PickMediasResult>() {
private var curRequestId: String? = null
val hasCurrentRequest: Boolean
get() = curRequestId != null
override fun createIntent(context: Context, input: PickMediasRequest): Intent {
curRequestId = input.id
if (isPhotoPickerAvailable()) {
return Intent(MediaStore.ACTION_PICK_IMAGES).apply {
type = PickMediasRequest.getMimeType(input.mediaType)
@ -112,13 +126,15 @@ class PickMediasContract : ActivityResultContract<PickMediasRequest, List<Uri>>(
return Intent(context, MatisseActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> {
override fun parseResult(resultCode: Int, intent: Intent?): PickMediasResult {
val id = curRequestId + ""
curRequestId = null
if (resultCode != Activity.RESULT_OK || intent == null) {
return emptyList()
return PickMediasResult(id, emptyList())
}
if (isPhotoPickerAvailable()) {
return intent.getClipDataUris()
return PickMediasResult(id, intent.getClipDataUris())
}
return Matisse.obtainResult(intent)
return PickMediasResult(id, Matisse.obtainResult(intent))
}
}