feat: 选图
This commit is contained in:
parent
f552e48c77
commit
d63d7a5a13
|
|
@ -54,6 +54,9 @@ import com.google.accompanist.navigation.material.ModalBottomSheetLayout
|
||||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||||
import com.huanchengfly.tieba.post.arch.BaseComposeActivity
|
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.services.NotifyJobService
|
||||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||||
import com.huanchengfly.tieba.post.ui.page.NavGraphs
|
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.ClientUtils
|
||||||
import com.huanchengfly.tieba.post.utils.JobServiceUtil
|
import com.huanchengfly.tieba.post.utils.JobServiceUtil
|
||||||
import com.huanchengfly.tieba.post.utils.PermissionUtils
|
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.TiebaUtil
|
||||||
import com.huanchengfly.tieba.post.utils.isIgnoringBatteryOptimizations
|
import com.huanchengfly.tieba.post.utils.isIgnoringBatteryOptimizations
|
||||||
import com.huanchengfly.tieba.post.utils.newIntentFilter
|
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.requestIgnoreBatteryOptimizations
|
||||||
import com.huanchengfly.tieba.post.utils.requestPermission
|
import com.huanchengfly.tieba.post.utils.requestPermission
|
||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||||
|
|
@ -124,6 +129,11 @@ class MainActivityV2 : BaseComposeActivity() {
|
||||||
private val notificationCountFlow: MutableSharedFlow<Int> =
|
private val notificationCountFlow: MutableSharedFlow<Int> =
|
||||||
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
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 {
|
private val devicePostureFlow: StateFlow<DevicePosture> by lazy {
|
||||||
WindowInfoTracker.getOrCreate(this)
|
WindowInfoTracker.getOrCreate(this)
|
||||||
.windowLayoutInfo(this)
|
.windowLayoutInfo(this)
|
||||||
|
|
@ -251,6 +261,11 @@ class MainActivityV2 : BaseComposeActivity() {
|
||||||
okSignAlertDialogState.show()
|
okSignAlertDialogState.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onGlobalEvent<GlobalEvent.StartSelectImages> {
|
||||||
|
pickMediasLauncher.launch(
|
||||||
|
PickMediasRequest(it.id, it.maxCount, it.mediaType)
|
||||||
|
)
|
||||||
|
}
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalNotificationCountFlow provides notificationCountFlow,
|
LocalNotificationCountFlow provides notificationCountFlow,
|
||||||
LocalDevicePosture provides devicePostureFlow.collectAsState(),
|
LocalDevicePosture provides devicePostureFlow.collectAsState(),
|
||||||
|
|
|
||||||
|
|
@ -99,9 +99,9 @@ class ReplyActivity : BaseActivity(), View.OnClickListener,
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val pickMediasLauncher = registerPickMediasLauncher {
|
val pickMediasLauncher = registerPickMediasLauncher { (_, uris) ->
|
||||||
val photoInfoBeans = insertPhotoAdapter.getFileList().toMutableList()
|
val photoInfoBeans = insertPhotoAdapter.getFileList().toMutableList()
|
||||||
for (uri in it) {
|
for (uri in uris) {
|
||||||
photoInfoBeans.add(PhotoInfoBean(this, uri))
|
photoInfoBeans.add(PhotoInfoBean(this, uri))
|
||||||
}
|
}
|
||||||
insertPhotoAdapter.setFileList(photoInfoBeans)
|
insertPhotoAdapter.setFileList(photoInfoBeans)
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,9 @@ class TranslucentThemeActivity : BaseActivity(), View.OnClickListener, OnSeekBar
|
||||||
@BindView(R.id.color_theme)
|
@BindView(R.id.color_theme)
|
||||||
lateinit var colorTheme: ViewGroup
|
lateinit var colorTheme: ViewGroup
|
||||||
|
|
||||||
private val selectImageLauncher = registerPickMediasLauncher {
|
private val selectImageLauncher = registerPickMediasLauncher { (_, uris) ->
|
||||||
if (it.isNotEmpty()) {
|
if (uris.isNotEmpty()) {
|
||||||
val sourceUri = it[0]
|
val sourceUri = uris[0]
|
||||||
launchUCrop(sourceUri)
|
launchUCrop(sourceUri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,14 @@ class InsertPhotoAdapter(private val mContext: Context) : RecyclerView.Adapter<M
|
||||||
holder.setItemOnClickListener {
|
holder.setItemOnClickListener {
|
||||||
if (mContext is AppCompatActivity && mContext is PickMediasLauncherProvider) {
|
if (mContext is AppCompatActivity && mContext is PickMediasLauncherProvider) {
|
||||||
askPermission {
|
askPermission {
|
||||||
mContext.getPickMediasLauncher().launch(PickMediasRequest().apply {
|
mContext.getPickMediasLauncher()
|
||||||
maxItems = 10 - fileList.size
|
.launch(
|
||||||
mediaType = PickMediasRequest.ImageOnly
|
PickMediasRequest(
|
||||||
})
|
"",
|
||||||
|
10 - fileList.size,
|
||||||
|
mediaType = PickMediasRequest.ImageOnly
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,60 @@
|
||||||
package com.huanchengfly.tieba.post.arch
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.cancellable
|
import kotlinx.coroutines.flow.cancellable
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
sealed interface GlobalEvent {
|
sealed interface GlobalEvent {
|
||||||
object AccountSwitched : 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) {
|
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
|
noinline listener: suspend (Event) -> Unit
|
||||||
): Job {
|
) {
|
||||||
return launch {
|
DisposableEffect(listener) {
|
||||||
GlobalEventFlow
|
val job = coroutineScope.launch {
|
||||||
.filterIsInstance<Event>()
|
GlobalEventFlow
|
||||||
.cancellable()
|
.filterIsInstance<Event>()
|
||||||
.collect {
|
.cancellable()
|
||||||
launch {
|
.collect {
|
||||||
listener(it)
|
launch {
|
||||||
|
listener(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDispose {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,9 +69,9 @@ class EditProfileActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pickMediasLauncher =
|
private val pickMediasLauncher =
|
||||||
registerPickMediasLauncher {
|
registerPickMediasLauncher { (_, uris) ->
|
||||||
if (it.isNotEmpty()) {
|
if (uris.isNotEmpty()) {
|
||||||
val sourceUri = it[0]
|
val sourceUri = uris[0]
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.asFile()
|
.asFile()
|
||||||
.load(sourceUri)
|
.load(sourceUri)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.huanchengfly.tieba.post.utils
|
package com.huanchengfly.tieba.post.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -16,9 +17,9 @@ import com.zhihu.matisse.MimeType
|
||||||
import com.zhihu.matisse.engine.impl.GlideEngine
|
import com.zhihu.matisse.engine.impl.GlideEngine
|
||||||
import com.zhihu.matisse.ui.MatisseActivity
|
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(
|
return registerForActivityResult(
|
||||||
PickMediasContract()
|
PickMediasContract
|
||||||
) {
|
) {
|
||||||
callback(it)
|
callback(it)
|
||||||
}
|
}
|
||||||
|
|
@ -55,15 +56,17 @@ private fun Intent.getClipDataUris(): List<Uri> {
|
||||||
return ArrayList(resultSet)
|
return ArrayList(resultSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
private fun getMaxItems() = if (isPhotoPickerAvailable()) {
|
private fun getMaxItems() = if (isPhotoPickerAvailable()) {
|
||||||
MediaStore.getPickImagesMaxLimit()
|
MediaStore.getPickImagesMaxLimit()
|
||||||
} else {
|
} else {
|
||||||
Integer.MAX_VALUE
|
Integer.MAX_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
class PickMediasRequest(
|
data class PickMediasRequest(
|
||||||
var maxItems: Int = 1,
|
val id: String = "",
|
||||||
var mediaType: MediaType = ImageAndVideo
|
val maxItems: Int = 1,
|
||||||
|
val mediaType: MediaType = ImageAndVideo
|
||||||
) {
|
) {
|
||||||
sealed interface MediaType
|
sealed interface MediaType
|
||||||
|
|
||||||
|
|
@ -73,7 +76,7 @@ class PickMediasRequest(
|
||||||
|
|
||||||
object ImageAndVideo : MediaType
|
object ImageAndVideo : MediaType
|
||||||
|
|
||||||
class SingleMimeType(val mimeType: String) : MediaType
|
data class SingleMimeType(val mimeType: String) : MediaType
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun getMimeType(input: MediaType): String? {
|
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 {
|
override fun createIntent(context: Context, input: PickMediasRequest): Intent {
|
||||||
|
curRequestId = input.id
|
||||||
if (isPhotoPickerAvailable()) {
|
if (isPhotoPickerAvailable()) {
|
||||||
return Intent(MediaStore.ACTION_PICK_IMAGES).apply {
|
return Intent(MediaStore.ACTION_PICK_IMAGES).apply {
|
||||||
type = PickMediasRequest.getMimeType(input.mediaType)
|
type = PickMediasRequest.getMimeType(input.mediaType)
|
||||||
|
|
@ -112,13 +126,15 @@ class PickMediasContract : ActivityResultContract<PickMediasRequest, List<Uri>>(
|
||||||
return Intent(context, MatisseActivity::class.java)
|
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) {
|
if (resultCode != Activity.RESULT_OK || intent == null) {
|
||||||
return emptyList()
|
return PickMediasResult(id, emptyList())
|
||||||
}
|
}
|
||||||
if (isPhotoPickerAvailable()) {
|
if (isPhotoPickerAvailable()) {
|
||||||
return intent.getClipDataUris()
|
return PickMediasResult(id, intent.getClipDataUris())
|
||||||
}
|
}
|
||||||
return Matisse.obtainResult(intent)
|
return PickMediasResult(id, Matisse.obtainResult(intent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue