更新代码
This commit is contained in:
@@ -120,7 +120,8 @@ interface MomentService {
|
||||
author: Int? = null,
|
||||
timelineId: Int? = null,
|
||||
contentSearch: String? = null,
|
||||
trend: Boolean? = false
|
||||
trend: Boolean? = false,
|
||||
favoriteUserId: Int? = null
|
||||
): ListContainer<MomentEntity>
|
||||
|
||||
/**
|
||||
|
||||
@@ -92,10 +92,12 @@ data class RegisterMessageChannelRequestBody(
|
||||
@SerializedName("identifier")
|
||||
val identifier: String,
|
||||
)
|
||||
|
||||
data class ResetPasswordRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
|
||||
interface RiderProAPI {
|
||||
@POST("register")
|
||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||
@@ -120,6 +122,7 @@ interface RiderProAPI {
|
||||
@Query("contentSearch") contentSearch: String? = null,
|
||||
@Query("postUser") postUser: Int? = null,
|
||||
@Query("trend") trend: String? = null,
|
||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||
): Response<ListContainer<Moment>>
|
||||
|
||||
@Multipart
|
||||
|
||||
@@ -25,7 +25,8 @@ class MomentPagingSource(
|
||||
private val author: Int? = null,
|
||||
private val timelineId: Int? = null,
|
||||
private val contentSearch: String? = null,
|
||||
private val trend: Boolean? = false
|
||||
private val trend: Boolean? = false,
|
||||
private val favoriteUserId: Int? = null
|
||||
) : PagingSource<Int, MomentEntity>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
|
||||
return try {
|
||||
@@ -35,7 +36,8 @@ class MomentPagingSource(
|
||||
author = author,
|
||||
timelineId = timelineId,
|
||||
contentSearch = contentSearch,
|
||||
trend = trend
|
||||
trend = trend,
|
||||
favoriteUserId = favoriteUserId
|
||||
)
|
||||
|
||||
LoadResult.Page(
|
||||
@@ -62,14 +64,16 @@ class MomentRemoteDataSource(
|
||||
author: Int?,
|
||||
timelineId: Int?,
|
||||
contentSearch: String?,
|
||||
trend: Boolean?
|
||||
trend: Boolean?,
|
||||
favoriteUserId: Int?
|
||||
): ListContainer<MomentEntity> {
|
||||
return momentService.getMoments(
|
||||
pageNumber = pageNumber,
|
||||
author = author,
|
||||
timelineId = timelineId,
|
||||
contentSearch = contentSearch,
|
||||
trend = trend
|
||||
trend = trend,
|
||||
favoriteUserId = favoriteUserId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -82,14 +86,16 @@ class MomentServiceImpl() : MomentService {
|
||||
author: Int?,
|
||||
timelineId: Int?,
|
||||
contentSearch: String?,
|
||||
trend: Boolean?
|
||||
trend: Boolean?,
|
||||
favoriteUserId: Int?
|
||||
): ListContainer<MomentEntity> {
|
||||
return momentBackend.fetchMomentItems(
|
||||
pageNumber = pageNumber,
|
||||
author = author,
|
||||
timelineId = timelineId,
|
||||
contentSearch = contentSearch,
|
||||
trend = trend
|
||||
trend = trend,
|
||||
favoriteUserId = favoriteUserId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -136,7 +142,8 @@ class MomentBackend {
|
||||
author: Int? = null,
|
||||
timelineId: Int?,
|
||||
contentSearch: String?,
|
||||
trend: Boolean?
|
||||
trend: Boolean?,
|
||||
favoriteUserId: Int? = null
|
||||
): ListContainer<MomentEntity> {
|
||||
val resp = ApiClient.api.getPosts(
|
||||
pageSize = DataBatchSize,
|
||||
@@ -144,7 +151,8 @@ class MomentBackend {
|
||||
timelineId = timelineId,
|
||||
authorId = author,
|
||||
contentSearch = contentSearch,
|
||||
trend = if (trend == true) "true" else ""
|
||||
trend = if (trend == true) "true" else "",
|
||||
favouriteUserId = favoriteUserId
|
||||
)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get moments")
|
||||
return ListContainer(
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.ui.account.AccountEditScreen2
|
||||
import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
||||
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
||||
import com.aiosman.riderpro.ui.favourite.FavouriteListPage
|
||||
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen
|
||||
import com.aiosman.riderpro.ui.follower.FollowerListScreen
|
||||
import com.aiosman.riderpro.ui.follower.FollowerNoticeScreen
|
||||
@@ -82,6 +83,7 @@ sealed class NavigationRoute(
|
||||
data object FollowerList : NavigationRoute("FollowerList/{id}")
|
||||
data object FollowingList : NavigationRoute("FollowingList/{id}")
|
||||
data object ResetPassword : NavigationRoute("ResetPassword")
|
||||
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||
}
|
||||
|
||||
|
||||
@@ -271,7 +273,13 @@ fun NavigationController(
|
||||
ResetPasswordScreen()
|
||||
}
|
||||
}
|
||||
|
||||
composable(route = NavigationRoute.FavouriteList.route) {
|
||||
CompositionLocalProvider(
|
||||
LocalAnimatedContentScope provides this,
|
||||
) {
|
||||
FavouriteListPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontVariation.width
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aiosman.riderpro.R
|
||||
@@ -29,6 +30,7 @@ data class MenuItem(
|
||||
val action: () -> Unit
|
||||
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun DropdownMenu(
|
||||
expanded: Boolean = false,
|
||||
@@ -47,8 +49,8 @@ fun DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = Modifier
|
||||
.apply {
|
||||
width?.let { width(width.dp) }
|
||||
.let {
|
||||
if (width != null) it.width(width.dp) else it
|
||||
}
|
||||
.background(Color.White)
|
||||
) {
|
||||
|
||||
@@ -61,31 +61,6 @@ fun CustomAsyncImage(
|
||||
val localContext = LocalContext.current
|
||||
|
||||
val imageLoader = getImageLoader(context ?: localContext)
|
||||
// val blurBitmap = remember(blurHash) {
|
||||
// blurHash?.let {
|
||||
// BlurHashDecoder.decode(
|
||||
// blurHash = it,
|
||||
// width = DEFAULT_HASHED_BITMAP_WIDTH,
|
||||
// height = DEFAULT_HASHED_BITMAP_HEIGHT
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// var bitmap by remember(imageUrl) { mutableStateOf<Bitmap?>(null) }
|
||||
//
|
||||
// LaunchedEffect(imageUrl) {
|
||||
// if (bitmap == null) {
|
||||
// val request = ImageRequest.Builder(context)
|
||||
// .data(imageUrl)
|
||||
// .crossfade(3000)
|
||||
// .build()
|
||||
//
|
||||
// val result = withContext(Dispatchers.IO) {
|
||||
// (imageLoader.execute(request) as? SuccessResult)?.drawable?.toBitmap()
|
||||
// }
|
||||
// bitmap = result
|
||||
// }
|
||||
// }
|
||||
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context ?: localContext)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.aiosman.riderpro.ui.favourite
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun FavouriteListPage() {
|
||||
val model = FavouriteListViewModel
|
||||
var dataFlow = model.favouriteMomentsFlow
|
||||
var moments = dataFlow.collectAsLazyPagingItems()
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val state = rememberPullRefreshState(FavouriteListViewModel.isLoading, onRefresh = {
|
||||
model.refreshPager(force = true)
|
||||
})
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f).pullRefresh(state)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||
) {
|
||||
NoticeScreenHeader("Favourite")
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(moments.itemCount) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.padding(2.dp)
|
||||
|
||||
.noRippleClickable {
|
||||
navController.navigate(
|
||||
NavigationRoute.Post.route.replace(
|
||||
"{id}",
|
||||
momentItem.id.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = momentItem.images[0].thumbnail,
|
||||
contentDescription = "",
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
context = context
|
||||
)
|
||||
if (momentItem.images.size > 1) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp, end = 8.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(R.drawable.rider_pro_picture_more),
|
||||
contentDescription = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
FavouriteListViewModel.isLoading,
|
||||
state,
|
||||
Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.aiosman.riderpro.ui.favourite
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.aiosman.riderpro.data.MomentService
|
||||
import com.aiosman.riderpro.entity.MomentEntity
|
||||
import com.aiosman.riderpro.entity.MomentPagingSource
|
||||
import com.aiosman.riderpro.entity.MomentRemoteDataSource
|
||||
import com.aiosman.riderpro.entity.MomentServiceImpl
|
||||
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel.accountService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object FavouriteListViewModel:ViewModel() {
|
||||
private val momentService: MomentService = MomentServiceImpl()
|
||||
private val _favouriteMomentsFlow =
|
||||
MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
val favouriteMomentsFlow = _favouriteMomentsFlow.asStateFlow()
|
||||
var isLoading by mutableStateOf(false)
|
||||
init {
|
||||
refreshPager()
|
||||
}
|
||||
fun refreshPager(force:Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
if (force) {
|
||||
isLoading = true
|
||||
}
|
||||
val profile = accountService.getMyAccountProfile()
|
||||
isLoading = false
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
favoriteUserId = profile.id
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_favouriteMomentsFlow.value = it
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,79 @@
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||
import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.riderpro.utils.File.saveImageToGallery
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.launch
|
||||
import net.engawapg.lib.zoomable.rememberZoomState
|
||||
import net.engawapg.lib.zoomable.zoomable
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class,
|
||||
ExperimentalMaterial3Api::class
|
||||
)
|
||||
@Composable
|
||||
fun ImageViewer() {
|
||||
val model = ImageViewerViewModel
|
||||
val images = model.imageList
|
||||
val pagerState = rememberPagerState(pageCount = { images.size }, initialPage = model.initialIndex)
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val pagerState =
|
||||
rememberPagerState(pageCount = { images.size }, initialPage = model.initialIndex)
|
||||
val navController = LocalNavController.current
|
||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||
val animatedVisibilityScope = LocalAnimatedContentScope.current
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
}
|
||||
val navigationBarPaddings =
|
||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 16.dp
|
||||
val scope = rememberCoroutineScope()
|
||||
val showRawImageStates = remember { mutableStateListOf(*Array(images.size) { false }) }
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
var isDownloading by remember { mutableStateOf(false) }
|
||||
StatusBarMaskLayout(
|
||||
modifier = Modifier.background(Color.Black),
|
||||
) {
|
||||
@@ -50,15 +87,11 @@ fun ImageViewer() {
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { page ->
|
||||
val zoomState = rememberZoomState()
|
||||
with(sharedTransitionScope) {
|
||||
CustomAsyncImage(
|
||||
context,
|
||||
images[page].url,
|
||||
if (showRawImageStates[page]) images[page].url else images[page].thumbnail,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.sharedElement(
|
||||
rememberSharedContentState(key = images[page]),
|
||||
animatedVisibilityScope = animatedVisibilityScope
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zoomable(
|
||||
zoomState = zoomState,
|
||||
@@ -69,8 +102,85 @@ fun ImageViewer() {
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black
|
||||
),
|
||||
)
|
||||
)
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = navigationBarPaddings),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.noRippleClickable {
|
||||
if (isDownloading) {
|
||||
return@noRippleClickable
|
||||
}
|
||||
isDownloading = true
|
||||
scope.launch {
|
||||
saveImageToGallery(context, images[pagerState.currentPage].url)
|
||||
isDownloading = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
if (isDownloading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(32.dp),
|
||||
color = Color.White
|
||||
)
|
||||
}else{
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_download),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
"Download",
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
if (!showRawImageStates[pagerState.currentPage]) {
|
||||
Spacer(modifier = Modifier.width(32.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.noRippleClickable {
|
||||
showRawImageStates[pagerState.currentPage] = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_raw),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
"Original",
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -75,6 +76,7 @@ import com.aiosman.riderpro.entity.MomentEntity
|
||||
import com.aiosman.riderpro.exp.formatPostTime2
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.riderpro.ui.composables.MenuItem
|
||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.riderpro.ui.post.PostViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -118,6 +120,7 @@ fun ProfilePage() {
|
||||
})
|
||||
.pullRefresh(state)
|
||||
) {
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
@@ -166,9 +169,14 @@ fun ProfilePage() {
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(
|
||||
top = statusBarPaddingValues.calculateTopPadding(),
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
start = 8.dp,
|
||||
end = 8.dp
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(16.dp).clip(RoundedCornerShape(8.dp)).shadow(
|
||||
elevation = 20.dp
|
||||
).background(Color.White.copy(alpha = 0.7f))
|
||||
){
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||
@@ -176,26 +184,19 @@ fun ProfilePage() {
|
||||
modifier = Modifier.noRippleClickable {
|
||||
expanded = true
|
||||
},
|
||||
tint = Color.White
|
||||
tint = Color.Black
|
||||
)
|
||||
MaterialTheme(
|
||||
shapes = MaterialTheme.shapes.copy(
|
||||
extraSmall = RoundedCornerShape(
|
||||
16.dp
|
||||
)
|
||||
)
|
||||
) {
|
||||
DropdownMenu(
|
||||
}
|
||||
|
||||
com.aiosman.riderpro.ui.composables.DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier
|
||||
.width(250.dp)
|
||||
.background(Color.White)
|
||||
width = 300,
|
||||
menuItems = listOf(
|
||||
MenuItem(
|
||||
stringResource(R.string.logout),
|
||||
R.mipmap.rider_pro_logout
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 14.dp, horizontal = 24.dp)
|
||||
.noRippleClickable {
|
||||
expanded = false
|
||||
scope.launch {
|
||||
model.logout()
|
||||
@@ -205,44 +206,28 @@ fun ProfilePage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Row {
|
||||
Text(
|
||||
stringResource(R.string.logout),
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_logout),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 14.dp, horizontal = 24.dp)
|
||||
.noRippleClickable {
|
||||
},
|
||||
MenuItem(
|
||||
stringResource(R.string.change_password),
|
||||
R.mipmap.rider_pro_change_password
|
||||
) {
|
||||
expanded = false
|
||||
scope.launch {
|
||||
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||
}
|
||||
}) {
|
||||
Row {
|
||||
Text(
|
||||
stringResource(R.string.change_password),
|
||||
fontWeight = FontWeight.W500
|
||||
},
|
||||
MenuItem(
|
||||
"Favourite",
|
||||
R.drawable.rider_pro_favourite
|
||||
) {
|
||||
expanded = false
|
||||
scope.launch {
|
||||
navController.navigate(NavigationRoute.FavouriteList.route)
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_change_password),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
@@ -125,7 +125,7 @@ fun ActionPostNoticeItem(
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
Box(
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
92
app/src/main/java/com/aiosman/riderpro/utils/File.kt
Normal file
92
app/src/main/java/com/aiosman/riderpro/utils/File.kt
Normal file
@@ -0,0 +1,92 @@
|
||||
package com.aiosman.riderpro.utils
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.widget.Toast
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import com.aiosman.riderpro.utils.Utils.getImageLoader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.OutputStream
|
||||
|
||||
object File {
|
||||
suspend fun saveImageToGallery(context: Context, url: String) {
|
||||
val loader = getImageLoader(context)
|
||||
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(url)
|
||||
.allowHardware(false) // Disable hardware bitmaps.
|
||||
.build()
|
||||
|
||||
val result = (loader.execute(request) as SuccessResult).drawable
|
||||
val bitmap = (result as BitmapDrawable).bitmap
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Images.Media.DISPLAY_NAME, "image_${System.currentTimeMillis()}.jpg")
|
||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
||||
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
|
||||
}
|
||||
|
||||
val uri = context.contentResolver.insert(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
)
|
||||
uri?.let {
|
||||
val outputStream: OutputStream? = context.contentResolver.openOutputStream(it)
|
||||
outputStream.use { stream ->
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream!!)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Image saved to gallery", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveImageToMediaStore(context: Context, displayName: String, bitmap: Bitmap): Uri? {
|
||||
val imageCollections = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
} else {
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
|
||||
val imageDetails = ContentValues().apply {
|
||||
put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
|
||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.Images.Media.IS_PENDING, 1)
|
||||
}
|
||||
}
|
||||
|
||||
val resolver = context.applicationContext.contentResolver
|
||||
val imageContentUri = resolver.insert(imageCollections, imageDetails) ?: return null
|
||||
|
||||
return try {
|
||||
resolver.openOutputStream(imageContentUri, "w").use { os ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os!!)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
imageDetails.clear()
|
||||
imageDetails.put(MediaStore.Images.Media.IS_PENDING, 0)
|
||||
resolver.update(imageContentUri, imageDetails, null, null)
|
||||
}
|
||||
|
||||
imageContentUri
|
||||
} catch (e: FileNotFoundException) {
|
||||
// Some legacy devices won't create directory for the Uri if dir not exist, resulting in
|
||||
// a FileNotFoundException. To resolve this issue, we should use the File API to save the
|
||||
// image, which allows us to create the directory ourselves.
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
5
app/src/main/res/drawable/rider_pro_delete.xml
Normal file
5
app/src/main/res/drawable/rider_pro_delete.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/rider_pro_download.xml
Normal file
5
app/src/main/res/drawable/rider_pro_download.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16.59,9H15V4c0,-0.55 -0.45,-1 -1,-1h-4c-0.55,0 -1,0.45 -1,1v5H7.41c-0.89,0 -1.34,1.08 -0.71,1.71l4.59,4.59c0.39,0.39 1.02,0.39 1.41,0l4.59,-4.59c0.63,-0.63 0.19,-1.71 -0.7,-1.71zM5,19c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H6c-0.55,0 -1,0.45 -1,1z"/>
|
||||
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/rider_pro_raw.xml
Normal file
9
app/src/main/res/drawable/rider_pro_raw.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6.5,9H3v6h1.5v-2h1.1l0.9,2H8l-0.9,-2.1C7.6,12.6 8,12.1 8,11.5v-1C8,9.7 7.3,9 6.5,9zM6.5,11.5h-2v-1h2V11.5z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10.25,9l-1.5,6h1.5l0.38,-1.5h1.75l0.37,1.5h1.5l-1.5,-6H10.25zM11,12l0.25,-1h0.5L12,12H11z"/>
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.98,9l-0.74,3l-0.74,-3l-1.52,0l-0.74,3l-0.74,-3l-1.5,0l1.5,6l1.48,0l0.76,-3.04l0.76,3.04l1.48,0l1.5,-6z"/>
|
||||
|
||||
</vector>
|
||||
Reference in New Issue
Block a user