更新图片缓存
This commit is contained in:
@@ -55,7 +55,7 @@ data class AccountProfile(
|
|||||||
followerCount = followerCount,
|
followerCount = followerCount,
|
||||||
followingCount = followingCount,
|
followingCount = followingCount,
|
||||||
nickName = nickname,
|
nickName = nickname,
|
||||||
avatar = ApiClient.BASE_SERVER + avatar + "?token=${AppStore.token}",
|
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||||
bio = "",
|
bio = "",
|
||||||
country = "Worldwide",
|
country = "Worldwide",
|
||||||
isFollowing = isFollowing
|
isFollowing = isFollowing
|
||||||
@@ -86,8 +86,8 @@ data class NoticePost(
|
|||||||
textContent = textContent,
|
textContent = textContent,
|
||||||
images = images.map {
|
images = images.map {
|
||||||
it.copy(
|
it.copy(
|
||||||
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
|
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||||
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}",
|
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
time = ApiClient.dateFromApiString(time)
|
time = ApiClient.dateFromApiString(time)
|
||||||
@@ -113,7 +113,7 @@ data class NoticeUser(
|
|||||||
return NoticeUserEntity(
|
return NoticeUserEntity(
|
||||||
id = id,
|
id = id,
|
||||||
nickName = nickName,
|
nickName = nickName,
|
||||||
avatar = ApiClient.BASE_SERVER + avatar + "?token=${AppStore.token}",
|
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ class FollowItemPagingSource(
|
|||||||
LoadResult.Page(
|
LoadResult.Page(
|
||||||
data = followListContainer.list.map {
|
data = followListContainer.list.map {
|
||||||
it.copy(
|
it.copy(
|
||||||
avatar = ApiClient.BASE_SERVER + it.avatar + "?token=${AppStore.token}",
|
avatar = "${ApiClient.BASE_SERVER}${it.avatar}",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
prevKey = if (currentPage == 1) null else currentPage - 1,
|
prevKey = if (currentPage == 1) null else currentPage - 1,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ data class Comment(
|
|||||||
likes = likeCount,
|
likes = likeCount,
|
||||||
replies = emptyList(),
|
replies = emptyList(),
|
||||||
postId = postId,
|
postId = postId,
|
||||||
avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}",
|
avatar = "${ApiClient.BASE_SERVER}${user.avatar}",
|
||||||
author = user.id,
|
author = user.id,
|
||||||
liked = isLiked,
|
liked = isLiked,
|
||||||
unread = isUnread,
|
unread = isUnread,
|
||||||
@@ -63,8 +63,8 @@ data class Comment(
|
|||||||
it.copy(
|
it.copy(
|
||||||
images = it.images.map {
|
images = it.images.map {
|
||||||
it.copy(
|
it.copy(
|
||||||
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
|
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||||
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}"
|
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ data class Moment(
|
|||||||
val time: String
|
val time: String
|
||||||
) {
|
) {
|
||||||
fun toMomentItem(): MomentEntity {
|
fun toMomentItem(): MomentEntity {
|
||||||
val avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}"
|
val avatar = "${ApiClient.BASE_SERVER}${user.avatar}"
|
||||||
return MomentEntity(
|
return MomentEntity(
|
||||||
id = id.toInt(),
|
id = id.toInt(),
|
||||||
avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}",
|
avatar = "${ApiClient.BASE_SERVER}${user.avatar}",
|
||||||
nickname = user.nickName,
|
nickname = user.nickName,
|
||||||
location = "Worldwide",
|
location = "Worldwide",
|
||||||
time = time,
|
time = time,
|
||||||
@@ -60,9 +60,10 @@ data class Moment(
|
|||||||
favoriteCount = favoriteCount.toInt(),
|
favoriteCount = favoriteCount.toInt(),
|
||||||
images = images.map {
|
images = images.map {
|
||||||
MomentImageEntity(
|
MomentImageEntity(
|
||||||
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
|
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||||
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}",
|
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
|
||||||
id = it.id
|
id = it.id,
|
||||||
|
blurHash = it.blurHash
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
authorId = user.id.toInt(),
|
authorId = user.id.toInt(),
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.aiosman.riderpro.model
|
package com.aiosman.riderpro.model
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
data class MomentImageEntity(
|
data class MomentImageEntity(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val url: String,
|
val url: String,
|
||||||
val thumbnail: String
|
val thumbnail: String,
|
||||||
|
val blurHash: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MomentEntity(
|
data class MomentEntity(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val avatar: String,
|
val avatar: String,
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.aiosman.riderpro.ui.composables
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.annotation.ExperimentalCoilApi
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.aiosman.riderpro.utils.BlurHashDecoder
|
||||||
|
import com.aiosman.riderpro.utils.Utils.getImageLoader
|
||||||
|
|
||||||
|
private const val DEFAULT_HASHED_BITMAP_WIDTH = 4
|
||||||
|
private const val DEFAULT_HASHED_BITMAP_HEIGHT = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used to load an image asynchronously and blur it using BlurHash.
|
||||||
|
* @param imageUrl The URL of the image to be loaded.
|
||||||
|
* @param modifier The modifier to be applied to the image.
|
||||||
|
* @param imageModifier The modifier to be applied to the image.
|
||||||
|
* @param contentDescription The content description to be applied to the image.
|
||||||
|
* @param contentScale The content scale to be applied to the image.
|
||||||
|
* @param isCrossFadeRequired Whether cross-fade is required or not.
|
||||||
|
* @param onImageLoadSuccess The callback to be called when the image is loaded successfully.
|
||||||
|
* @param onImageLoadFailure The callback to be called when the image is failed to load.
|
||||||
|
* @see AsyncImage
|
||||||
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
@ExperimentalCoilApi
|
||||||
|
@Composable
|
||||||
|
fun AsyncBlurImage(
|
||||||
|
imageUrl: String,
|
||||||
|
blurHash: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
imageModifier: Modifier? = null,
|
||||||
|
contentDescription: String? = null,
|
||||||
|
contentScale: ContentScale = ContentScale.Fit,
|
||||||
|
isCrossFadeRequired: Boolean = false,
|
||||||
|
onImageLoadSuccess: () -> Unit = {},
|
||||||
|
onImageLoadFailure: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val resources = context.resources
|
||||||
|
val imageLoader = getImageLoader(context)
|
||||||
|
|
||||||
|
val blurBitmap by remember(blurHash) {
|
||||||
|
mutableStateOf(
|
||||||
|
BlurHashDecoder.decode(
|
||||||
|
blurHash = blurHash,
|
||||||
|
width = DEFAULT_HASHED_BITMAP_WIDTH,
|
||||||
|
height = DEFAULT_HASHED_BITMAP_HEIGHT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
modifier = imageModifier ?: modifier,
|
||||||
|
model = ImageRequest.Builder(context)
|
||||||
|
.data(imageUrl)
|
||||||
|
.crossfade(isCrossFadeRequired)
|
||||||
|
.placeholder(
|
||||||
|
blurBitmap?.toDrawable(resources)
|
||||||
|
)
|
||||||
|
.fallback(blurBitmap?.toDrawable(resources))
|
||||||
|
.build(),
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
contentScale = contentScale,
|
||||||
|
onSuccess = { onImageLoadSuccess() },
|
||||||
|
onError = { onImageLoadFailure() },
|
||||||
|
imageLoader = imageLoader
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -59,7 +59,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
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
|
||||||
@@ -71,9 +70,9 @@ import com.aiosman.riderpro.ui.comment.CommentModalContent
|
|||||||
import com.aiosman.riderpro.ui.composables.AnimatedCounter
|
import com.aiosman.riderpro.ui.composables.AnimatedCounter
|
||||||
import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon
|
import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon
|
||||||
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
|
import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon
|
||||||
|
import com.aiosman.riderpro.ui.composables.AsyncBlurImage
|
||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.riderpro.ui.composables.RelPostCard
|
import com.aiosman.riderpro.ui.composables.RelPostCard
|
||||||
import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel
|
|
||||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||||
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
import com.aiosman.riderpro.ui.post.NewPostViewModel
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
@@ -353,6 +352,18 @@ fun PostImageView(
|
|||||||
) { page ->
|
) { page ->
|
||||||
val image = images[page]
|
val image = images[page]
|
||||||
with(sharedTransitionScope) {
|
with(sharedTransitionScope) {
|
||||||
|
// AsyncBlurImage(
|
||||||
|
// imageUrl = image.url,
|
||||||
|
// blurHash = image.blurHash ?: "",
|
||||||
|
// contentDescription = "Image",
|
||||||
|
// contentScale = ContentScale.Crop,
|
||||||
|
// modifier = Modifier
|
||||||
|
// .sharedElement(
|
||||||
|
// rememberSharedContentState(key = image),
|
||||||
|
// animatedVisibilityScope = animatedVisibilityScope
|
||||||
|
// )
|
||||||
|
// .fillMaxSize()
|
||||||
|
// )
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
image.thumbnail,
|
image.thumbnail,
|
||||||
|
|||||||
218
app/src/main/java/com/aiosman/riderpro/utils/BlurHashDecoder.kt
Normal file
218
app/src/main/java/com/aiosman/riderpro/utils/BlurHashDecoder.kt
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package com.aiosman.riderpro.utils
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
|
import androidx.collection.SparseArrayCompat
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.withSign
|
||||||
|
|
||||||
|
internal object BlurHashDecoder {
|
||||||
|
|
||||||
|
// cache Math.cos() calculations to improve performance.
|
||||||
|
// The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps
|
||||||
|
// the cache is enabled by default, it is recommended to disable it only when just a few images are displayed
|
||||||
|
private val cacheCosinesX = SparseArrayCompat<DoubleArray>()
|
||||||
|
private val cacheCosinesY = SparseArrayCompat<DoubleArray>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear calculations stored in memory cache.
|
||||||
|
* The cache is not big, but will increase when many image sizes are used,
|
||||||
|
* if the app needs memory it is recommended to clear it.
|
||||||
|
*/
|
||||||
|
private fun clearCache() {
|
||||||
|
cacheCosinesX.clear()
|
||||||
|
cacheCosinesY.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a blur hash into a new bitmap.
|
||||||
|
*
|
||||||
|
* @param useCache use in memory cache for the calculated math, reused by images with same size.
|
||||||
|
* if the cache does not exist yet it will be created and populated with new calculations.
|
||||||
|
* By default it is true.
|
||||||
|
*/
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
internal fun decode(
|
||||||
|
blurHash: String?,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
punch: Float = 1f,
|
||||||
|
useCache: Boolean = true
|
||||||
|
): Bitmap? {
|
||||||
|
if (blurHash == null || blurHash.length < 6) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val numCompEnc = decode83(blurHash, 0, 1)
|
||||||
|
val numCompX = (numCompEnc % 9) + 1
|
||||||
|
val numCompY = (numCompEnc / 9) + 1
|
||||||
|
if (blurHash.length != 4 + 2 * numCompX * numCompY) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val maxAcEnc = decode83(blurHash, 1, 2)
|
||||||
|
val maxAc = (maxAcEnc + 1) / 166f
|
||||||
|
val colors = Array(numCompX * numCompY) { i ->
|
||||||
|
if (i == 0) {
|
||||||
|
val colorEnc = decode83(blurHash, 2, 6)
|
||||||
|
decodeDc(colorEnc)
|
||||||
|
} else {
|
||||||
|
val from = 4 + i * 2
|
||||||
|
val colorEnc = decode83(blurHash, from, from + 2)
|
||||||
|
decodeAc(colorEnc, maxAc * punch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return composeBitmap(width, height, numCompX, numCompY, colors, useCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decode83(str: String, from: Int = 0, to: Int = str.length): Int {
|
||||||
|
var result = 0
|
||||||
|
for (i in from until to) {
|
||||||
|
val index = charMap[str[i]] ?: -1
|
||||||
|
if (index != -1) {
|
||||||
|
result = result * 83 + index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDc(colorEnc: Int): FloatArray {
|
||||||
|
val r = colorEnc shr 16
|
||||||
|
val g = (colorEnc shr 8) and 255
|
||||||
|
val b = colorEnc and 255
|
||||||
|
return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun srgbToLinear(colorEnc: Int): Float {
|
||||||
|
val v = colorEnc / 255f
|
||||||
|
return if (v <= 0.04045f) {
|
||||||
|
(v / 12.92f)
|
||||||
|
} else {
|
||||||
|
((v + 0.055f) / 1.055f).pow(2.4f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeAc(value: Int, maxAc: Float): FloatArray {
|
||||||
|
val r = value / (19 * 19)
|
||||||
|
val g = (value / 19) % 19
|
||||||
|
val b = value % 19
|
||||||
|
return floatArrayOf(
|
||||||
|
signedPow2((r - 9) / 9.0f) * maxAc,
|
||||||
|
signedPow2((g - 9) / 9.0f) * maxAc,
|
||||||
|
signedPow2((b - 9) / 9.0f) * maxAc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signedPow2(value: Float) = value.pow(2f).withSign(value)
|
||||||
|
|
||||||
|
private fun composeBitmap(
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
|
numCompX: Int,
|
||||||
|
numCompY: Int,
|
||||||
|
colors: Array<FloatArray>,
|
||||||
|
useCache: Boolean
|
||||||
|
): Bitmap {
|
||||||
|
// use an array for better performance when writing pixel colors
|
||||||
|
val imageArray = IntArray(width * height)
|
||||||
|
val calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX)
|
||||||
|
val cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX)
|
||||||
|
val calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY)
|
||||||
|
val cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY)
|
||||||
|
runBlocking {
|
||||||
|
CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
|
||||||
|
val tasks = ArrayList<Deferred<Unit>>()
|
||||||
|
tasks.add(
|
||||||
|
async {
|
||||||
|
for (y in 0 until height) {
|
||||||
|
for (x in 0 until width) {
|
||||||
|
var r = 0f
|
||||||
|
var g = 0f
|
||||||
|
var b = 0f
|
||||||
|
for (j in 0 until numCompY) {
|
||||||
|
for (i in 0 until numCompX) {
|
||||||
|
val cosX =
|
||||||
|
cosinesX.getCos(calculateCosX, i, numCompX, x, width)
|
||||||
|
val cosY =
|
||||||
|
cosinesY.getCos(calculateCosY, j, numCompY, y, height)
|
||||||
|
val basis = (cosX * cosY).toFloat()
|
||||||
|
val color = colors[j * numCompX + i]
|
||||||
|
r += color[0] * basis
|
||||||
|
g += color[1] * basis
|
||||||
|
b += color[2] * basis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageArray[x + width * y] =
|
||||||
|
Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@async
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tasks.forEach { it.await() }
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getArrayForCosinesY(calculate: Boolean, height: Int, numCompY: Int) = when {
|
||||||
|
calculate -> {
|
||||||
|
DoubleArray(height * numCompY).also {
|
||||||
|
cacheCosinesY.put(height * numCompY, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
cacheCosinesY.get(height * numCompY)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getArrayForCosinesX(calculate: Boolean, width: Int, numCompX: Int) = when {
|
||||||
|
calculate -> {
|
||||||
|
DoubleArray(width * numCompX).also {
|
||||||
|
cacheCosinesX.put(width * numCompX, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> cacheCosinesX.get(width * numCompX)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DoubleArray.getCos(
|
||||||
|
calculate: Boolean,
|
||||||
|
x: Int,
|
||||||
|
numComp: Int,
|
||||||
|
y: Int,
|
||||||
|
size: Int
|
||||||
|
): Double {
|
||||||
|
if (calculate) {
|
||||||
|
this[x + numComp * y] = cos(Math.PI * y * x / size)
|
||||||
|
}
|
||||||
|
return this[x + numComp * y]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun linearToSrgb(value: Float): Int {
|
||||||
|
val v = value.coerceIn(0f, 1f)
|
||||||
|
return if (v <= 0.0031308f) {
|
||||||
|
(v * 12.92f * 255f + 0.5f).toInt()
|
||||||
|
} else {
|
||||||
|
((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val charMap = listOf(
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||||
|
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||||
|
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',',
|
||||||
|
'-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~'
|
||||||
|
)
|
||||||
|
.mapIndexed { i, c -> c to i }
|
||||||
|
.toMap()
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.aiosman.riderpro.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import coil.disk.DiskCache
|
||||||
|
import coil.memory.MemoryCache
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import com.aiosman.riderpro.data.api.getUnsafeOkHttpClient
|
import com.aiosman.riderpro.data.api.getUnsafeOkHttpClient
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -19,11 +21,16 @@ object Utils {
|
|||||||
val okHttpClient = getUnsafeOkHttpClient()
|
val okHttpClient = getUnsafeOkHttpClient()
|
||||||
return ImageLoader.Builder(context)
|
return ImageLoader.Builder(context)
|
||||||
.okHttpClient(okHttpClient)
|
.okHttpClient(okHttpClient)
|
||||||
.diskCachePolicy(CachePolicy.ENABLED)
|
.memoryCache {
|
||||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
MemoryCache.Builder(context)
|
||||||
|
.maxSizePercent(0.25) // 设置内存缓存大小为可用内存的 25%
|
||||||
.components {
|
.build()
|
||||||
|
}
|
||||||
|
.diskCache {
|
||||||
|
DiskCache.Builder()
|
||||||
|
.directory(context.cacheDir.resolve("image_cache"))
|
||||||
|
.maxSizePercent(0.02) // 设置磁盘缓存大小为可用存储空间的 2%
|
||||||
|
.build()
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user