diff --git a/compose-core/src/main/java/com/qmuiteam/compose/core/ui/QMUIIcon.kt b/compose-core/src/main/java/com/qmuiteam/compose/core/ui/QMUIIcon.kt index 87faf9a5..2d3ab93a 100644 --- a/compose-core/src/main/java/com/qmuiteam/compose/core/ui/QMUIIcon.kt +++ b/compose-core/src/main/java/com/qmuiteam/compose/core/ui/QMUIIcon.kt @@ -1,12 +1,23 @@ package com.qmuiteam.compose.core.ui +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp import com.qmuiteam.compose.core.R @Composable @@ -22,28 +33,73 @@ enum class CheckStatus { none, partial, checked } + @Composable fun QMUICheckBox( + size: Dp, status: CheckStatus = CheckStatus.none, isEnabled: Boolean = true, - tint: Color? + tint: Color?, + background: Color = Color.Transparent ) { - val icon = when (status) { - CheckStatus.checked -> R.drawable.ic_qmui_checkbox_checked - CheckStatus.partial -> R.drawable.ic_qmui_checkbox_partial - else -> R.drawable.ic_qmui_checkbox_normal + Box( + modifier = Modifier + .size(size) + .clip(CircleShape) + ) { + AnimatedVisibility( + visible = status == CheckStatus.none, + enter = fadeIn(), + exit = fadeOut() + ) { + QMUICheckBoxImage(R.drawable.ic_qmui_checkbox_normal, isEnabled, tint, background) + } + + AnimatedVisibility( + visible = status == CheckStatus.checked, + enter = fadeIn(), + exit = fadeOut() + ) { + QMUICheckBoxImage(R.drawable.ic_qmui_checkbox_checked, isEnabled, tint, background) + } + + AnimatedVisibility( + visible = status == CheckStatus.partial, + enter = fadeIn(), + exit = fadeOut() + ) { + QMUICheckBoxImage(R.drawable.ic_qmui_checkbox_partial, isEnabled, tint, background) + } } +} + +@Composable +private fun QMUICheckBoxImage( + resourceId: Int, + isEnabled: Boolean = true, + tint: Color?, + background: Color = Color.Transparent +){ Image( - painter = painterResource(id = icon), + painter = painterResource(id = resourceId), + contentScale = ContentScale.Fit, contentDescription = "", colorFilter = tint?.let { ColorFilter.tint(it) }, - modifier = Modifier.let { - if (isEnabled) { - it - } else { - it.alpha(0.5f) + modifier = Modifier + .fillMaxSize() + .let { + if (isEnabled) { + it + } else { + it.alpha(0.5f) + } + }.let { + if (background != Color.Transparent) { + it.background(background) + } else { + it + } } - } ) } diff --git a/photo/src/main/java/com/qmuiteam/photo/activity/QMUIPhotoPickerActivity.kt b/photo/src/main/java/com/qmuiteam/photo/activity/QMUIPhotoPickerActivity.kt index 070c88ed..e0164bf1 100644 --- a/photo/src/main/java/com/qmuiteam/photo/activity/QMUIPhotoPickerActivity.kt +++ b/photo/src/main/java/com/qmuiteam/photo/activity/QMUIPhotoPickerActivity.kt @@ -44,7 +44,10 @@ import com.qmuiteam.photo.compose.* import com.qmuiteam.photo.compose.picker.* import com.qmuiteam.photo.data.* import com.qmuiteam.photo.vm.* +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext const val QMUI_PHOTO_DEFAULT_PICK_LIMIT_COUNT = 9 internal const val QMUI_PHOTO_RESULT_URI_LIST = "qmui_photo_result_uri_list" @@ -161,8 +164,7 @@ open class QMUIPhotoPickerActivity : AppCompatActivity() { onStartCheckPermission() onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) { override fun handleOnBackPressed() { - val currentScene = viewModel.photoPickerSceneFlow.value - when (currentScene) { + when (val currentScene = viewModel.photoPickerSceneFlow.value) { is QMUIPhotoPickerEditScene -> { viewModel.updateScene(currentScene.prevScene) } @@ -233,30 +235,22 @@ open class QMUIPhotoPickerActivity : AppCompatActivity() { enter = fadeIn(), exit = fadeOut() ) { - PhotoPickerGridContent(viewModel, data, pickedItems) + PhotoPickerGridScene(viewModel, data, pickedItems) } AnimatedVisibility( visible = scene is QMUIPhotoPickerPreviewScene, enter = fadeIn() + scaleIn(initialScale = 0.8f), exit = fadeOut() + scaleOut(targetScale = 0.8f) ) { - val previewScheme = scene as? QMUIPhotoPickerPreviewScene - if (previewScheme != null) { - val list = remember(previewScheme) { - if (previewScheme.onlySelected) { - viewModel.getPickedVOList() - } else { - data.find { it.id == previewScheme.buckedId }?.list ?: emptyList() - } - } - PhotoPickerPreviewContent(viewModel, list, pickedItems, previewScheme.currentId) + val previewScene = scene as? QMUIPhotoPickerPreviewScene + if (previewScene != null) { + PhotoPickerPreviewScene(viewModel, previewScene, data, pickedItems) } - } } @Composable - protected open fun BoxScope.PhotoPickerGridContent( + protected open fun BoxScope.PhotoPickerGridScene( viewModel: QMUIPhotoPickerViewModel, data: List, pickedItems: List, @@ -370,6 +364,22 @@ open class QMUIPhotoPickerActivity : AppCompatActivity() { } } + @Composable + protected open fun BoxScope.PhotoPickerPreviewScene( + viewModel: QMUIPhotoPickerViewModel, + scene: QMUIPhotoPickerPreviewScene, + data: List, + pickedItems: List + ){ + val list = remember(scene) { + if (scene.onlySelected) { + viewModel.getPickedVOList() + } else { + data.find { it.id == scene.buckedId }?.list ?: emptyList() + } + } + PhotoPickerPreviewContent(viewModel, list, pickedItems, scene.currentId) + } @OptIn(ExperimentalPagerApi::class) @Composable @@ -401,22 +411,68 @@ open class QMUIPhotoPickerActivity : AppCompatActivity() { }) } - QMUIPhotoPickerPreview( - pagerState, - data, - loading = { Loading() }, - loadingFailed = {}, - ) { - isFullPageState = !isFullPageState - } + val scope = rememberCoroutineScope() - QMUITopBar( - title = "${pagerState.currentPage + 1}/${data.size}", - separatorHeight = 0.dp, - backgroundColor = QMUILocalPickerConfig.current.topBarBgColor, - leftItems = topBarLeftItems, - rightItems = topBarRightItems - ) + Box(modifier = Modifier.fillMaxSize()){ + QMUIPhotoPickerPreview( + pagerState, + data, + loading = { Loading() }, + loadingFailed = {}, + ) { + isFullPageState = !isFullPageState + } + + AnimatedVisibility( + visible = !isFullPageState, + enter = slideInVertically(initialOffsetY = {-it}), + exit = slideOutVertically(targetOffsetY = {-it}) + ) { + QMUITopBar( + title = "${pagerState.currentPage + 1}/${data.size}", + separatorHeight = 0.dp, + backgroundColor = QMUILocalPickerConfig.current.topBarBgColor, + leftItems = topBarLeftItems, + rightItems = topBarRightItems + ) + } + + AnimatedVisibility( + visible = !isFullPageState, + modifier = Modifier.align(Alignment.BottomCenter), + enter = slideInVertically(initialOffsetY = {it}), + exit = slideOutVertically(targetOffsetY = {it}) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + QMUIPhotoPickerPreviewPickedItems(data, pickedItems, data[pagerState.currentPage].model.id){ + scope.launch { + pagerState.scrollToPage(data.indexOf(it)) + } + } + + val isCurrentPicked = remember(data, pickedItems, pagerState.currentPage) { + pickedItems.indexOf(data[pagerState.currentPage].model.id) >= 0 + } + + QMUIPhotoPickerPreviewToolBar( + modifier = Modifier.fillMaxWidth(), + isCurrentPicked = isCurrentPicked, + enableOrigin = viewModel.enableOrigin, + isOriginOpenFlow = viewModel.isOriginOpenFlow, + onToggleOrigin = { + viewModel.toggleOrigin(it) + }, + onEdit = { + + }, + onToggleSelect = { + viewModel.togglePick(data[pagerState.currentPage]) + } + ) + + } + } + } } diff --git a/photo/src/main/java/com/qmuiteam/photo/compose/picker/Config.kt b/photo/src/main/java/com/qmuiteam/photo/compose/picker/Config.kt index b041834c..22ff95b9 100644 --- a/photo/src/main/java/com/qmuiteam/photo/compose/picker/Config.kt +++ b/photo/src/main/java/com/qmuiteam/photo/compose/picker/Config.kt @@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.StateFlow class QMUIPhotoPickerConfig( val commonTextButtonTextColor: Color = Color.White, val commonSeparatorColor: Color = Color.White.copy(alpha = 0.3f), - val commonCheckIconNormalTintColor: Color = Color.White, + val commonCheckIconNormalTintColor: Color = Color.White.copy(0.8f), val commonCheckIconCheckedTintColor: Color = qmuiPrimaryColor, val commonCheckIconCheckedTextColor: Color = Color.White.copy(alpha = 0.6f), diff --git a/photo/src/main/java/com/qmuiteam/photo/compose/picker/Preview.kt b/photo/src/main/java/com/qmuiteam/photo/compose/picker/Preview.kt index 56e70600..72505dd9 100644 --- a/photo/src/main/java/com/qmuiteam/photo/compose/picker/Preview.kt +++ b/photo/src/main/java/com/qmuiteam/photo/compose/picker/Preview.kt @@ -1,17 +1,21 @@ package com.qmuiteam.photo.compose.picker -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState -import com.qmuiteam.compose.core.ui.QMUITopBar -import com.qmuiteam.compose.core.ui.QMUITopBarBackIconItem import com.qmuiteam.photo.compose.QMUIGesturePhoto import com.qmuiteam.photo.data.PhotoLoadStatus import com.qmuiteam.photo.data.QMUIMediaPhotoVO @@ -88,4 +92,64 @@ private fun QMUIPhotoPickerPreviewItemContent( loadingFailed() } } +} + +@Composable +fun QMUIPhotoPickerPreviewPickedItems( + data: List, + pickedItems: List, + currentId: Long, + onClick:(QMUIMediaPhotoVO) -> Unit +) { + if (pickedItems.isNotEmpty()) { + val list = remember(data, pickedItems) { + data.filter { pickedItems.contains(it.model.id) } + } + LazyRow( + modifier = Modifier + .fillMaxWidth() + .height(60.dp) + .background(QMUILocalPickerConfig.current.toolBarBgColor), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = spacedBy(5.dp), + contentPadding = PaddingValues(horizontal = 5.dp) + ) { + items(list, { it.model.id }){ + QMUIPhotoPickerPreviewPickedItem(it, it.model.id == currentId, onClick) + } + } + } +} + +@Composable +private fun QMUIPhotoPickerPreviewPickedItem( + item: QMUIMediaPhotoVO, + isCurrent: Boolean, + onClick:(QMUIMediaPhotoVO) -> Unit +){ + val thumb = remember(item){ + item.photoProvider.thumbnail() + } + Box(modifier = Modifier + .size(40.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + onClick(item) + } + .let { + if(isCurrent){ + it.border(2.dp, QMUILocalPickerConfig.current.commonCheckIconCheckedTintColor) + } else { + it + } + } + ){ + thumb?.Compose( + contentScale = ContentScale.Crop, + isContainerDimenExactly = true, + onSuccess = null, + onError = null) + } } \ No newline at end of file diff --git a/photo/src/main/java/com/qmuiteam/photo/compose/picker/ToolBar.kt b/photo/src/main/java/com/qmuiteam/photo/compose/picker/ToolBar.kt index 5616451d..744040ad 100644 --- a/photo/src/main/java/com/qmuiteam/photo/compose/picker/ToolBar.kt +++ b/photo/src/main/java/com/qmuiteam/photo/compose/picker/ToolBar.kt @@ -12,68 +12,18 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.view.WindowInsetsCompat import com.qmuiteam.compose.core.ex.drawTopSeparator import com.qmuiteam.compose.core.provider.QMUILocalWindowInsets import com.qmuiteam.compose.core.provider.dp +import com.qmuiteam.compose.core.ui.CheckStatus import com.qmuiteam.compose.core.ui.PressWithAlphaBox +import com.qmuiteam.compose.core.ui.QMUICheckBox import kotlinx.coroutines.flow.StateFlow -@Composable -fun QMUIPhotoPickerGridPageToolBar( - modifier: Modifier = Modifier, - enableOrigin: Boolean, - pickedItems: List, - isOriginOpenFlow: StateFlow, - onToggleOrigin: (toOpen: Boolean) -> Unit, - onPreview: () -> Unit -) { - val insets = QMUILocalWindowInsets.current.getInsetsIgnoringVisibility( - WindowInsetsCompat.Type.navigationBars() - ).dp() - val config = QMUILocalPickerConfig.current - Box(modifier = modifier - .background(config.toolBarBgColor) - .padding(bottom = insets.bottom) - .height(44.dp) - .drawBehind { - drawTopSeparator(config.commonSeparatorColor) - } - ) { - PressWithAlphaBox( - enable = pickedItems.isNotEmpty(), - modifier = Modifier - .fillMaxHeight() - .padding(horizontal = 16.dp) - .align(Alignment.CenterStart), - onClick = { - onPreview() - } - ) { - Text( - "预览", - fontSize = 17.sp, - color = config.commonTextButtonTextColor, - modifier = Modifier.align(Alignment.Center) - ) - } - - if(enableOrigin){ - OriginOpenButton( - modifier = Modifier - .fillMaxHeight() - .padding(horizontal = 16.dp) - .align(Alignment.Center), - isOriginOpenFlow = isOriginOpenFlow, - onToggleOrigin = onToggleOrigin - ) - } - } -} - - @Composable fun OriginOpenButton( modifier: Modifier = Modifier, @@ -100,4 +50,156 @@ fun OriginOpenButton( color = QMUILocalPickerConfig.current.commonTextButtonTextColor ) } +} + +@Composable +fun PickCurrentCheckButton( + modifier: Modifier = Modifier, + isPicked: Boolean, + onPicked: (toPick: Boolean) -> Unit, +) { + val config = QMUILocalPickerConfig.current + Row( + modifier = modifier.clickable( + interactionSource = remember { + MutableInteractionSource() + }, + indication = null + ) { + onPicked.invoke(!isPicked) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Absolute.spacedBy(5.dp) + ) { + QMUICheckBox( + size = 18.dp, + status = if(isPicked) CheckStatus.checked else CheckStatus.none, + tint = if(isPicked) config.commonCheckIconCheckedTintColor else config.commonCheckIconNormalTintColor, + background = if(isPicked) config.commonCheckIconNormalTintColor else Color.Transparent, + ) + Text( + "选择", + fontSize = 17.sp, + color = QMUILocalPickerConfig.current.commonTextButtonTextColor + ) + } +} + +@Composable +fun CommonTextButton( + modifier: Modifier, + enable: Boolean, + text: String, + onClick: () -> Unit +){ + PressWithAlphaBox( + enable = enable, + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + .then(modifier), + onClick = { + onClick() + } + ) { + Text( + text, + fontSize = 17.sp, + color = QMUILocalPickerConfig.current.commonTextButtonTextColor, + modifier = Modifier.align(Alignment.Center) + ) + } +} + +@Composable +fun QMUIPhotoPickerGridPageToolBar( + modifier: Modifier = Modifier, + enableOrigin: Boolean, + pickedItems: List, + isOriginOpenFlow: StateFlow, + onToggleOrigin: (toOpen: Boolean) -> Unit, + onPreview: () -> Unit +) { + val insets = QMUILocalWindowInsets.current.getInsetsIgnoringVisibility( + WindowInsetsCompat.Type.navigationBars() + ).dp() + val config = QMUILocalPickerConfig.current + Box(modifier = modifier + .background(config.toolBarBgColor) + .padding(bottom = insets.bottom) + .height(44.dp) + .drawBehind { + drawTopSeparator(config.commonSeparatorColor) + } + ) { + CommonTextButton( + modifier = Modifier.align(Alignment.CenterStart), + enable = pickedItems.isNotEmpty(), + text = "预览", + onClick = onPreview + ) + + if(enableOrigin){ + OriginOpenButton( + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + .align(Alignment.Center), + isOriginOpenFlow = isOriginOpenFlow, + onToggleOrigin = onToggleOrigin + ) + } + } +} + + +@Composable +fun QMUIPhotoPickerPreviewToolBar( + modifier: Modifier = Modifier, + isCurrentPicked: Boolean, + enableOrigin: Boolean, + isOriginOpenFlow: StateFlow, + onToggleOrigin: (toOpen: Boolean) -> Unit, + onEdit: () -> Unit, + onToggleSelect: (toSelect: Boolean) -> Unit +){ + val insets = QMUILocalWindowInsets.current.getInsetsIgnoringVisibility( + WindowInsetsCompat.Type.navigationBars() + ).dp() + val config = QMUILocalPickerConfig.current + Box(modifier = modifier + .background(config.toolBarBgColor) + .padding(bottom = insets.bottom) + .height(44.dp) + .drawBehind { + drawTopSeparator(config.commonSeparatorColor) + } + ) { + CommonTextButton( + modifier = Modifier.align(Alignment.CenterStart), + enable = true, + text = "预览", + onClick = onEdit + ) + + if(enableOrigin){ + OriginOpenButton( + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + .align(Alignment.Center), + isOriginOpenFlow = isOriginOpenFlow, + onToggleOrigin = onToggleOrigin + ) + } + + PickCurrentCheckButton( + modifier = Modifier + .fillMaxHeight() + .padding(horizontal = 16.dp) + .align(Alignment.CenterEnd), + isPicked = isCurrentPicked, + onPicked = onToggleSelect + ) + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/qmuiteam/plugin/Dep.kt b/plugin/src/main/java/com/qmuiteam/plugin/Dep.kt index 315f45f9..e2ce082f 100644 --- a/plugin/src/main/java/com/qmuiteam/plugin/Dep.kt +++ b/plugin/src/main/java/com/qmuiteam/plugin/Dep.kt @@ -14,7 +14,7 @@ object Dep { object QMUI { const val group = "com.qmuiteam" const val qmuiVer = "2.0.1" - const val archVer = "2.0.1" + const val archVer = "2.0.1_f_16" const val typeVer = "0.0.14" const val composeCoreVer = "0.0.1" const val composeVer = "0.0.1"