更新代码
This commit is contained in:
@@ -120,7 +120,8 @@ interface MomentService {
|
|||||||
author: Int? = null,
|
author: Int? = null,
|
||||||
timelineId: Int? = null,
|
timelineId: Int? = null,
|
||||||
contentSearch: String? = null,
|
contentSearch: String? = null,
|
||||||
trend: Boolean? = false
|
trend: Boolean? = false,
|
||||||
|
favoriteUserId: Int? = null
|
||||||
): ListContainer<MomentEntity>
|
): ListContainer<MomentEntity>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -92,10 +92,12 @@ data class RegisterMessageChannelRequestBody(
|
|||||||
@SerializedName("identifier")
|
@SerializedName("identifier")
|
||||||
val identifier: String,
|
val identifier: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ResetPasswordRequestBody(
|
data class ResetPasswordRequestBody(
|
||||||
@SerializedName("username")
|
@SerializedName("username")
|
||||||
val username: String,
|
val username: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface RiderProAPI {
|
interface RiderProAPI {
|
||||||
@POST("register")
|
@POST("register")
|
||||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||||
@@ -120,6 +122,7 @@ interface RiderProAPI {
|
|||||||
@Query("contentSearch") contentSearch: String? = null,
|
@Query("contentSearch") contentSearch: String? = null,
|
||||||
@Query("postUser") postUser: Int? = null,
|
@Query("postUser") postUser: Int? = null,
|
||||||
@Query("trend") trend: String? = null,
|
@Query("trend") trend: String? = null,
|
||||||
|
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||||
): Response<ListContainer<Moment>>
|
): Response<ListContainer<Moment>>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class MomentPagingSource(
|
|||||||
private val author: Int? = null,
|
private val author: Int? = null,
|
||||||
private val timelineId: Int? = null,
|
private val timelineId: Int? = null,
|
||||||
private val contentSearch: String? = null,
|
private val contentSearch: String? = null,
|
||||||
private val trend: Boolean? = false
|
private val trend: Boolean? = false,
|
||||||
|
private val favoriteUserId: Int? = null
|
||||||
) : PagingSource<Int, MomentEntity>() {
|
) : PagingSource<Int, MomentEntity>() {
|
||||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
|
||||||
return try {
|
return try {
|
||||||
@@ -35,7 +36,8 @@ class MomentPagingSource(
|
|||||||
author = author,
|
author = author,
|
||||||
timelineId = timelineId,
|
timelineId = timelineId,
|
||||||
contentSearch = contentSearch,
|
contentSearch = contentSearch,
|
||||||
trend = trend
|
trend = trend,
|
||||||
|
favoriteUserId = favoriteUserId
|
||||||
)
|
)
|
||||||
|
|
||||||
LoadResult.Page(
|
LoadResult.Page(
|
||||||
@@ -62,14 +64,16 @@ class MomentRemoteDataSource(
|
|||||||
author: Int?,
|
author: Int?,
|
||||||
timelineId: Int?,
|
timelineId: Int?,
|
||||||
contentSearch: String?,
|
contentSearch: String?,
|
||||||
trend: Boolean?
|
trend: Boolean?,
|
||||||
|
favoriteUserId: Int?
|
||||||
): ListContainer<MomentEntity> {
|
): ListContainer<MomentEntity> {
|
||||||
return momentService.getMoments(
|
return momentService.getMoments(
|
||||||
pageNumber = pageNumber,
|
pageNumber = pageNumber,
|
||||||
author = author,
|
author = author,
|
||||||
timelineId = timelineId,
|
timelineId = timelineId,
|
||||||
contentSearch = contentSearch,
|
contentSearch = contentSearch,
|
||||||
trend = trend
|
trend = trend,
|
||||||
|
favoriteUserId = favoriteUserId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,14 +86,16 @@ class MomentServiceImpl() : MomentService {
|
|||||||
author: Int?,
|
author: Int?,
|
||||||
timelineId: Int?,
|
timelineId: Int?,
|
||||||
contentSearch: String?,
|
contentSearch: String?,
|
||||||
trend: Boolean?
|
trend: Boolean?,
|
||||||
|
favoriteUserId: Int?
|
||||||
): ListContainer<MomentEntity> {
|
): ListContainer<MomentEntity> {
|
||||||
return momentBackend.fetchMomentItems(
|
return momentBackend.fetchMomentItems(
|
||||||
pageNumber = pageNumber,
|
pageNumber = pageNumber,
|
||||||
author = author,
|
author = author,
|
||||||
timelineId = timelineId,
|
timelineId = timelineId,
|
||||||
contentSearch = contentSearch,
|
contentSearch = contentSearch,
|
||||||
trend = trend
|
trend = trend,
|
||||||
|
favoriteUserId = favoriteUserId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +142,8 @@ class MomentBackend {
|
|||||||
author: Int? = null,
|
author: Int? = null,
|
||||||
timelineId: Int?,
|
timelineId: Int?,
|
||||||
contentSearch: String?,
|
contentSearch: String?,
|
||||||
trend: Boolean?
|
trend: Boolean?,
|
||||||
|
favoriteUserId: Int? = null
|
||||||
): ListContainer<MomentEntity> {
|
): ListContainer<MomentEntity> {
|
||||||
val resp = ApiClient.api.getPosts(
|
val resp = ApiClient.api.getPosts(
|
||||||
pageSize = DataBatchSize,
|
pageSize = DataBatchSize,
|
||||||
@@ -144,7 +151,8 @@ class MomentBackend {
|
|||||||
timelineId = timelineId,
|
timelineId = timelineId,
|
||||||
authorId = author,
|
authorId = author,
|
||||||
contentSearch = contentSearch,
|
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")
|
val body = resp.body() ?: throw ServiceException("Failed to get moments")
|
||||||
return ListContainer(
|
return ListContainer(
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import com.aiosman.riderpro.LocalSharedTransitionScope
|
|||||||
import com.aiosman.riderpro.ui.account.AccountEditScreen2
|
import com.aiosman.riderpro.ui.account.AccountEditScreen2
|
||||||
import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
||||||
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
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.favourite.FavouriteNoticeScreen
|
||||||
import com.aiosman.riderpro.ui.follower.FollowerListScreen
|
import com.aiosman.riderpro.ui.follower.FollowerListScreen
|
||||||
import com.aiosman.riderpro.ui.follower.FollowerNoticeScreen
|
import com.aiosman.riderpro.ui.follower.FollowerNoticeScreen
|
||||||
@@ -82,6 +83,7 @@ sealed class NavigationRoute(
|
|||||||
data object FollowerList : NavigationRoute("FollowerList/{id}")
|
data object FollowerList : NavigationRoute("FollowerList/{id}")
|
||||||
data object FollowingList : NavigationRoute("FollowingList/{id}")
|
data object FollowingList : NavigationRoute("FollowingList/{id}")
|
||||||
data object ResetPassword : NavigationRoute("ResetPassword")
|
data object ResetPassword : NavigationRoute("ResetPassword")
|
||||||
|
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -271,7 +273,13 @@ fun NavigationController(
|
|||||||
ResetPasswordScreen()
|
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.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.aiosman.riderpro.R
|
import com.aiosman.riderpro.R
|
||||||
@@ -29,6 +30,7 @@ data class MenuItem(
|
|||||||
val action: () -> Unit
|
val action: () -> Unit
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DropdownMenu(
|
fun DropdownMenu(
|
||||||
expanded: Boolean = false,
|
expanded: Boolean = false,
|
||||||
@@ -47,8 +49,8 @@ fun DropdownMenu(
|
|||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.apply {
|
.let {
|
||||||
width?.let { width(width.dp) }
|
if (width != null) it.width(width.dp) else it
|
||||||
}
|
}
|
||||||
.background(Color.White)
|
.background(Color.White)
|
||||||
) {
|
) {
|
||||||
@@ -67,7 +69,7 @@ fun DropdownMenu(
|
|||||||
if (width != null) {
|
if (width != null) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -61,31 +61,6 @@ fun CustomAsyncImage(
|
|||||||
val localContext = LocalContext.current
|
val localContext = LocalContext.current
|
||||||
|
|
||||||
val imageLoader = getImageLoader(context ?: localContext)
|
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(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(context ?: localContext)
|
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.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
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.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.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.aiosman.riderpro.LocalAnimatedContentScope
|
import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||||
|
import com.aiosman.riderpro.R
|
||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
|
||||||
import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel
|
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 com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.engawapg.lib.zoomable.rememberZoomState
|
import net.engawapg.lib.zoomable.rememberZoomState
|
||||||
import net.engawapg.lib.zoomable.zoomable
|
import net.engawapg.lib.zoomable.zoomable
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class,
|
||||||
|
ExperimentalMaterial3Api::class
|
||||||
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ImageViewer() {
|
fun ImageViewer() {
|
||||||
val model = ImageViewerViewModel
|
val model = ImageViewerViewModel
|
||||||
val images = model.imageList
|
val images = model.imageList
|
||||||
val pagerState = rememberPagerState(pageCount = { images.size }, initialPage = model.initialIndex)
|
val pagerState =
|
||||||
val systemUiController = rememberSystemUiController()
|
rememberPagerState(pageCount = { images.size }, initialPage = model.initialIndex)
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
|
||||||
val animatedVisibilityScope = LocalAnimatedContentScope.current
|
|
||||||
val context = LocalContext.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(
|
StatusBarMaskLayout(
|
||||||
modifier = Modifier.background(Color.Black),
|
modifier = Modifier.background(Color.Black),
|
||||||
) {
|
) {
|
||||||
@@ -50,27 +87,100 @@ fun ImageViewer() {
|
|||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) { page ->
|
) { page ->
|
||||||
val zoomState = rememberZoomState()
|
val zoomState = rememberZoomState()
|
||||||
with(sharedTransitionScope) {
|
CustomAsyncImage(
|
||||||
CustomAsyncImage(
|
context,
|
||||||
context,
|
if (showRawImageStates[page]) images[page].url else images[page].thumbnail,
|
||||||
images[page].url,
|
contentDescription = null,
|
||||||
contentDescription = null,
|
modifier = Modifier
|
||||||
modifier = Modifier.sharedElement(
|
.fillMaxSize()
|
||||||
rememberSharedContentState(key = images[page]),
|
.zoomable(
|
||||||
animatedVisibilityScope = animatedVisibilityScope
|
zoomState = zoomState,
|
||||||
)
|
onTap = {
|
||||||
.fillMaxSize()
|
navController.popBackStack()
|
||||||
.zoomable(
|
}
|
||||||
zoomState = zoomState,
|
),
|
||||||
onTap = {
|
contentScale = ContentScale.Fit,
|
||||||
navController.popBackStack()
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Transparent,
|
||||||
|
Color.Black
|
||||||
),
|
),
|
||||||
contentScale = ContentScale.Fit,
|
)
|
||||||
)
|
)
|
||||||
|
.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.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.icons.Icons
|
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.exp.formatPostTime2
|
||||||
import com.aiosman.riderpro.ui.NavigationRoute
|
import com.aiosman.riderpro.ui.NavigationRoute
|
||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
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.modifiers.noRippleClickable
|
||||||
import com.aiosman.riderpro.ui.post.PostViewModel
|
import com.aiosman.riderpro.ui.post.PostViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -118,6 +120,7 @@ fun ProfilePage() {
|
|||||||
})
|
})
|
||||||
.pullRefresh(state)
|
.pullRefresh(state)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@@ -166,83 +169,65 @@ fun ProfilePage() {
|
|||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(
|
.padding(
|
||||||
top = statusBarPaddingValues.calculateTopPadding(),
|
top = statusBarPaddingValues.calculateTopPadding(),
|
||||||
start = 16.dp,
|
start = 8.dp,
|
||||||
end = 16.dp
|
end = 8.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Box(
|
||||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
modifier = Modifier.padding(16.dp).clip(RoundedCornerShape(8.dp)).shadow(
|
||||||
contentDescription = "",
|
elevation = 20.dp
|
||||||
modifier = Modifier.noRippleClickable {
|
).background(Color.White.copy(alpha = 0.7f))
|
||||||
expanded = true
|
){
|
||||||
},
|
Icon(
|
||||||
tint = Color.White
|
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||||
)
|
contentDescription = "",
|
||||||
MaterialTheme(
|
modifier = Modifier.noRippleClickable {
|
||||||
shapes = MaterialTheme.shapes.copy(
|
expanded = true
|
||||||
extraSmall = RoundedCornerShape(
|
},
|
||||||
16.dp
|
tint = Color.Black
|
||||||
)
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = expanded,
|
|
||||||
onDismissRequest = { expanded = false },
|
|
||||||
modifier = Modifier
|
|
||||||
.width(250.dp)
|
|
||||||
.background(Color.White)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 14.dp, horizontal = 24.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
expanded = false
|
|
||||||
scope.launch {
|
|
||||||
model.logout()
|
|
||||||
navController.navigate(NavigationRoute.Login.route) {
|
|
||||||
popUpTo(NavigationRoute.Index.route) {
|
|
||||||
inclusive = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
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 {
|
|
||||||
expanded = false
|
|
||||||
scope.launch {
|
|
||||||
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Row {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.change_password),
|
|
||||||
fontWeight = FontWeight.W500
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.rider_pro_change_password),
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
com.aiosman.riderpro.ui.composables.DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
width = 300,
|
||||||
|
menuItems = listOf(
|
||||||
|
MenuItem(
|
||||||
|
stringResource(R.string.logout),
|
||||||
|
R.mipmap.rider_pro_logout
|
||||||
|
) {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
model.logout()
|
||||||
|
navController.navigate(NavigationRoute.Login.route) {
|
||||||
|
popUpTo(NavigationRoute.Index.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuItem(
|
||||||
|
stringResource(R.string.change_password),
|
||||||
|
R.mipmap.rider_pro_change_password
|
||||||
|
) {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MenuItem(
|
||||||
|
"Favourite",
|
||||||
|
R.drawable.rider_pro_favourite
|
||||||
|
) {
|
||||||
|
expanded = false
|
||||||
|
scope.launch {
|
||||||
|
navController.navigate(NavigationRoute.FavouriteList.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ fun ActionPostNoticeItem(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
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