- Added a "News" tab to the main index screen. - Implemented API parameters for fetching news-specific posts: `imageTag`, `search`, `advancedSearch`, `newsFilter`, `onlyNews`, `newsSource`, `newsLanguage`, `newsCategory`, `requireImageCache`. - Updated `Moment` data class and `MomentEntity` to include news-related fields like `isNews`, `newsTitle`, `newsUrl`, etc. - Created `News.kt` composable and `NewsViewModel.kt` to display and manage news items. - Updated `MomentLoader` to include a `newsOnly` parameter for fetching only news items. - Added Japanese translations for new index tab strings: "Worldwide", "Dynamic", "Following", "Hot", and "News". - Adjusted tab count and layout based on guest/logged-in user status to accommodate the new "News" tab.
110 lines
3.8 KiB
Kotlin
110 lines
3.8 KiB
Kotlin
package com.aiosman.ravenow.utils
|
||
|
||
import android.content.Context
|
||
import android.graphics.Bitmap
|
||
import android.graphics.BitmapFactory
|
||
import android.net.Uri
|
||
import coil.ImageLoader
|
||
import coil.request.CachePolicy
|
||
import com.aiosman.ravenow.data.api.AuthInterceptor
|
||
import com.aiosman.ravenow.data.api.getSafeOkHttpClient
|
||
import java.io.File
|
||
import java.io.FileOutputStream
|
||
import java.util.Date
|
||
import java.util.Locale
|
||
import java.util.UUID
|
||
import java.util.concurrent.TimeUnit
|
||
|
||
object Utils {
|
||
// 全局共享的 ImageLoader,避免每次创建导致内存缓存不共享
|
||
private var sharedImageLoader: ImageLoader? = null
|
||
fun generateRandomString(length: Int): String {
|
||
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||
return (1..length)
|
||
.map { allowedChars.random() }
|
||
.joinToString("")
|
||
}
|
||
|
||
fun getImageLoader(context: Context): ImageLoader {
|
||
val appContext = context.applicationContext
|
||
val existing = sharedImageLoader
|
||
if (existing != null) return existing
|
||
|
||
val okHttpClient = getSafeOkHttpClient(authInterceptor = AuthInterceptor())
|
||
val loader = ImageLoader.Builder(appContext)
|
||
.okHttpClient(okHttpClient)
|
||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||
.diskCachePolicy(CachePolicy.ENABLED)
|
||
.build()
|
||
sharedImageLoader = loader
|
||
return loader
|
||
}
|
||
|
||
fun getTimeAgo(date: Date): String {
|
||
val now = Date()
|
||
val diffInMillis = now.time - date.time
|
||
|
||
val seconds = TimeUnit.MILLISECONDS.toSeconds(diffInMillis)
|
||
val minutes = TimeUnit.MILLISECONDS.toMinutes(diffInMillis)
|
||
val hours = TimeUnit.MILLISECONDS.toHours(diffInMillis)
|
||
val days = TimeUnit.MILLISECONDS.toDays(diffInMillis)
|
||
val years = days / 365
|
||
|
||
return when {
|
||
seconds < 60 -> "$seconds seconds ago"
|
||
minutes < 60 -> "$minutes minutes ago"
|
||
hours < 24 -> "$hours hours ago"
|
||
days < 365 -> "$days days ago"
|
||
else -> "$years years ago"
|
||
}
|
||
}
|
||
|
||
fun getCurrentLanguage(): String {
|
||
return Locale.getDefault().language
|
||
}
|
||
|
||
/**
|
||
* 获取当前系统语言的完整标签,包含国家/地区代码
|
||
* 返回格式:en, ja, zh-CN, zh-TW 等
|
||
*/
|
||
fun getCurrentLanguageTag(): String {
|
||
val locale = Locale.getDefault()
|
||
val language = locale.language
|
||
val country = locale.country
|
||
|
||
return when {
|
||
// 中文需要区分简体和繁体
|
||
language == "zh" && country.isNotEmpty() -> "$language-$country"
|
||
// 其他语言通常只需要语言代码
|
||
else -> language
|
||
}
|
||
}
|
||
|
||
fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File {
|
||
val inputStream = context.contentResolver.openInputStream(uri)
|
||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||
val (width, height) = originalBitmap.width to originalBitmap.height
|
||
|
||
val (newWidth, newHeight) = if (width > height) {
|
||
maxSize to (height * maxSize / width)
|
||
} else {
|
||
(width * maxSize / height) to maxSize
|
||
}
|
||
|
||
val scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true)
|
||
val uuidImageName = UUID.randomUUID().toString().let { "$it.jpg" }
|
||
val compressedFile = File(context.cacheDir, uuidImageName)
|
||
val outputStream = FileOutputStream(compressedFile)
|
||
if (quality > 100) {
|
||
throw IllegalArgumentException("Quality must be less than 100")
|
||
}
|
||
if (quality < 0) {
|
||
throw IllegalArgumentException("Quality must be greater than 0")
|
||
}
|
||
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
|
||
outputStream.flush()
|
||
outputStream.close()
|
||
|
||
return compressedFile
|
||
}
|
||
} |