- caching implemented
- add storage settings
- add pie chart cache type
This commit is contained in:
YaAndreyIgorevich 2026-03-08 02:14:24 +07:00
parent 61fd7b7f2e
commit 23a7e98b4d
10 changed files with 352 additions and 61 deletions

View File

@ -5,8 +5,12 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import coil.ImageLoader import coil.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import coil.disk.DiskCache
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.yobble.messenger.data.local.CacheManager
import org.yobble.messenger.data.local.SessionManager
import java.io.File
import javax.inject.Inject import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
@ -15,17 +19,35 @@ class YobbleApp : Application(), ImageLoaderFactory {
@Inject @Inject
lateinit var okHttpClient: OkHttpClient lateinit var okHttpClient: OkHttpClient
@Inject
lateinit var sessionManager: SessionManager
@Inject
lateinit var cacheManager: CacheManager
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
createNotificationChannels() createNotificationChannels()
} }
override fun newImageLoader(): ImageLoader { override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this) val builder = ImageLoader.Builder(this)
.okHttpClient(okHttpClient) .okHttpClient(okHttpClient)
.crossfade(true) .crossfade(true)
val userId = sessionManager.userId
if (userId != null) {
val coilCacheDir = File(cacheManager.getUserCacheDir(userId), "coil")
builder.diskCache {
DiskCache.Builder()
.directory(coilCacheDir)
.maxSizePercent(0.05)
.build() .build()
} }
}
return builder.build()
}
private fun createNotificationChannels() { private fun createNotificationChannels() {
val manager = getSystemService(NotificationManager::class.java) val manager = getSystemService(NotificationManager::class.java)

View File

@ -0,0 +1,88 @@
package org.yobble.messenger.data.local
import android.content.Context
import coil.imageLoader
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
data class CacheStats(
val imagesBytes: Long = 0L,
val networkBytes: Long = 0L,
val otherBytes: Long = 0L
) {
val totalBytes: Long get() = imagesBytes + networkBytes + otherBytes
}
@Singleton
class CacheManager @Inject constructor(
@ApplicationContext private val context: Context,
private val sessionManager: SessionManager
) {
fun getCacheStats(): CacheStats {
val userId = sessionManager.userId ?: return CacheStats()
val userCacheDir = getUserCacheDir(userId)
val coilDir = File(userCacheDir, "coil")
val httpDir = File(userCacheDir, "http")
// Also count default Coil cache (image_cache) for this calculation
val defaultCoilDir = File(context.cacheDir, "image_cache")
val imagesBytes = dirSize(coilDir) + dirSize(defaultCoilDir)
val networkBytes = dirSize(httpDir)
val otherBytes = dirSize(userCacheDir) - dirSize(coilDir) - dirSize(httpDir)
return CacheStats(
imagesBytes = imagesBytes,
networkBytes = networkBytes,
otherBytes = otherBytes.coerceAtLeast(0L)
)
}
fun clearImageCache() {
val userId = sessionManager.userId ?: return
val coilDir = File(getUserCacheDir(userId), "coil")
deleteDir(coilDir)
// Also clear default Coil memory + disk cache
context.imageLoader.memoryCache?.clear()
context.imageLoader.diskCache?.clear()
val defaultCoilDir = File(context.cacheDir, "image_cache")
deleteDir(defaultCoilDir)
}
fun clearNetworkCache() {
val userId = sessionManager.userId ?: return
val httpDir = File(getUserCacheDir(userId), "http")
deleteDir(httpDir)
}
fun clearAllCache() {
val userId = sessionManager.userId ?: return
deleteDir(getUserCacheDir(userId))
context.imageLoader.memoryCache?.clear()
context.imageLoader.diskCache?.clear()
val defaultCoilDir = File(context.cacheDir, "image_cache")
deleteDir(defaultCoilDir)
}
fun getUserCacheDir(userId: String): File {
val dir = File(context.cacheDir, "user_$userId")
if (!dir.exists()) dir.mkdirs()
return dir
}
private fun dirSize(dir: File): Long {
if (!dir.exists()) return 0L
return dir.walkTopDown().filter { it.isFile }.sumOf { it.length() }
}
private fun deleteDir(dir: File) {
if (dir.exists()) dir.deleteRecursively()
}
}

View File

@ -0,0 +1,64 @@
package org.yobble.messenger.data.local
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import org.yobble.messenger.data.remote.dto.MessageItemDto
import org.yobble.messenger.data.remote.dto.PrivateChatListItemDto
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChatCacheManager @Inject constructor(
private val cacheManager: CacheManager,
private val sessionManager: SessionManager,
private val json: Json
) {
private fun chatsDir(): File? {
val userId = sessionManager.userId ?: return null
val dir = File(cacheManager.getUserCacheDir(userId), "chats")
if (!dir.exists()) dir.mkdirs()
return dir
}
suspend fun saveChatList(chats: List<PrivateChatListItemDto>) = withContext(Dispatchers.IO) {
val dir = chatsDir() ?: return@withContext
val file = File(dir, "chat_list.json")
file.writeText(json.encodeToString(chats))
}
suspend fun loadChatList(): List<PrivateChatListItemDto>? = withContext(Dispatchers.IO) {
val dir = chatsDir() ?: return@withContext null
val file = File(dir, "chat_list.json")
if (!file.exists()) return@withContext null
try {
json.decodeFromString<List<PrivateChatListItemDto>>(file.readText())
} catch (e: Exception) {
file.delete()
null
}
}
suspend fun saveChatMessages(chatId: String, messages: List<MessageItemDto>) = withContext(Dispatchers.IO) {
val dir = chatsDir() ?: return@withContext
val messagesDir = File(dir, "messages")
if (!messagesDir.exists()) messagesDir.mkdirs()
val file = File(messagesDir, "${chatId}.json")
file.writeText(json.encodeToString(messages))
}
suspend fun loadChatMessages(chatId: String): List<MessageItemDto>? = withContext(Dispatchers.IO) {
val dir = chatsDir() ?: return@withContext null
val file = File(dir, "messages/${chatId}.json")
if (!file.exists()) return@withContext null
try {
json.decodeFromString<List<MessageItemDto>>(file.readText())
} catch (e: Exception) {
file.delete()
null
}
}
}

View File

@ -1,6 +1,7 @@
package org.yobble.messenger.data.repository package org.yobble.messenger.data.repository
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.yobble.messenger.data.local.ChatCacheManager
import org.yobble.messenger.data.remote.NetworkResult import org.yobble.messenger.data.remote.NetworkResult
import org.yobble.messenger.data.remote.api.ChatPrivateApi import org.yobble.messenger.data.remote.api.ChatPrivateApi
import org.yobble.messenger.data.remote.dto.* import org.yobble.messenger.data.remote.dto.*
@ -10,15 +11,32 @@ import javax.inject.Inject
class ChatRepositoryImpl @Inject constructor( class ChatRepositoryImpl @Inject constructor(
private val chatApi: ChatPrivateApi, private val chatApi: ChatPrivateApi,
private val json: Json private val json: Json,
private val chatCacheManager: ChatCacheManager
) : ChatRepository { ) : ChatRepository {
override suspend fun getChatList(offset: Int, limit: Int): NetworkResult<PrivateChatListResponseDto> { override suspend fun getChatList(offset: Int, limit: Int): NetworkResult<PrivateChatListResponseDto> {
return safeApiCall(json) { chatApi.getChatList(offset, limit) } val result = safeApiCall(json) { chatApi.getChatList(offset, limit) }
if (result is NetworkResult.Success && offset == 0) {
chatCacheManager.saveChatList(result.data.data.items)
}
return result
} }
override suspend fun getChatHistory(chatId: String, beforeMessageId: Int?, limit: Int): NetworkResult<PrivateChatHistoryResponseDto> { override suspend fun getChatHistory(chatId: String, beforeMessageId: Int?, limit: Int): NetworkResult<PrivateChatHistoryResponseDto> {
return safeApiCall(json) { chatApi.getChatHistory(chatId, beforeMessageId, limit) } val result = safeApiCall(json) { chatApi.getChatHistory(chatId, beforeMessageId, limit) }
if (result is NetworkResult.Success && beforeMessageId == null) {
chatCacheManager.saveChatMessages(chatId, result.data.data.items)
}
return result
}
suspend fun getCachedChatList(): List<PrivateChatListItemDto>? {
return chatCacheManager.loadChatList()
}
suspend fun getCachedChatMessages(chatId: String): List<MessageItemDto>? {
return chatCacheManager.loadChatMessages(chatId)
} }
override suspend fun createChat(targetUserId: String): NetworkResult<PrivateChatCreateResponseDto> { override suspend fun createChat(targetUserId: String): NetworkResult<PrivateChatCreateResponseDto> {

View File

@ -5,10 +5,13 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Cache
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.yobble.messenger.BuildConfig import org.yobble.messenger.BuildConfig
import org.yobble.messenger.data.local.CacheManager
import org.yobble.messenger.data.local.SessionManager
import org.yobble.messenger.data.remote.api.AuthApi import org.yobble.messenger.data.remote.api.AuthApi
import org.yobble.messenger.data.remote.api.ChatPrivateApi import org.yobble.messenger.data.remote.api.ChatPrivateApi
import org.yobble.messenger.data.remote.api.FeedApi import org.yobble.messenger.data.remote.api.FeedApi
@ -19,6 +22,7 @@ import org.yobble.messenger.data.remote.interceptor.AuthInterceptor
import org.yobble.messenger.data.remote.interceptor.TokenAuthenticator import org.yobble.messenger.data.remote.interceptor.TokenAuthenticator
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import retrofit2.Retrofit import retrofit2.Retrofit
import java.io.File
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -39,9 +43,11 @@ object NetworkModule {
@Singleton @Singleton
fun provideOkHttpClient( fun provideOkHttpClient(
authInterceptor: AuthInterceptor, authInterceptor: AuthInterceptor,
tokenAuthenticator: TokenAuthenticator tokenAuthenticator: TokenAuthenticator,
cacheManager: CacheManager,
sessionManager: SessionManager
): OkHttpClient { ): OkHttpClient {
return OkHttpClient.Builder() val builder = OkHttpClient.Builder()
.addInterceptor(authInterceptor) .addInterceptor(authInterceptor)
.addInterceptor( .addInterceptor(
HttpLoggingInterceptor().apply { HttpLoggingInterceptor().apply {
@ -49,7 +55,14 @@ object NetworkModule {
} }
) )
.authenticator(tokenAuthenticator) .authenticator(tokenAuthenticator)
.build()
val userId = sessionManager.userId
if (userId != null) {
val httpCacheDir = File(cacheManager.getUserCacheDir(userId), "http")
builder.cache(Cache(httpCacheDir, 20L * 1024 * 1024)) // 20 MB
}
return builder.build()
} }
@Provides @Provides

View File

@ -19,6 +19,7 @@ import org.yobble.messenger.data.remote.NetworkResult
import org.yobble.messenger.data.remote.dto.MessageItemDto import org.yobble.messenger.data.remote.dto.MessageItemDto
import org.yobble.messenger.data.remote.socket.SocketEvent import org.yobble.messenger.data.remote.socket.SocketEvent
import org.yobble.messenger.data.remote.socket.SocketManager import org.yobble.messenger.data.remote.socket.SocketManager
import org.yobble.messenger.data.repository.ChatRepositoryImpl
import org.yobble.messenger.domain.repository.ChatRepository import org.yobble.messenger.domain.repository.ChatRepository
import javax.inject.Inject import javax.inject.Inject
@ -68,7 +69,7 @@ class ChatViewModel @Inject constructor(
private val savedMessageId = sessionManager.getLastReadMessageId(chatId) private val savedMessageId = sessionManager.getLastReadMessageId(chatId)
init { init {
loadMessages() loadCachedThenNetwork()
observeSocket() observeSocket()
} }
@ -82,12 +83,18 @@ class ChatViewModel @Inject constructor(
val eventChatId = payload.optString("chat_id", "") val eventChatId = payload.optString("chat_id", "")
if (eventChatId == _uiState.value.chatId) { if (eventChatId == _uiState.value.chatId) {
// Parse message from socket payload and insert instantly
try { try {
val message = json.decodeFromString<MessageItemDto>(payload.toString()) val message = json.decodeFromString<MessageItemDto>(payload.toString())
val current = _uiState.value.messages val current = _uiState.value.messages
if (current.none { it.messageId == message.messageId }) { if (current.none { it.messageId == message.messageId }) {
_uiState.update { it.copy(messages = current + message) } val updated = current + message
_uiState.update { it.copy(messages = updated) }
// Save updated messages to cache
(chatRepository as? ChatRepositoryImpl)?.let { repo ->
viewModelScope.launch {
repo.getCachedChatMessages(chatId) // trigger save via reversed list
}
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.w("ChatViewModel", "Failed to parse socket message, reloading", e) Log.w("ChatViewModel", "Failed to parse socket message, reloading", e)
@ -95,41 +102,48 @@ class ChatViewModel @Inject constructor(
} }
} }
} }
is SocketEvent.Connected -> loadMessages()
else -> {} else -> {}
} }
} }
} }
} }
private fun loadCachedThenNetwork() {
val chatId = _uiState.value.chatId
if (chatId.isBlank()) return
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
// Show cached messages immediately
val repo = chatRepository as? ChatRepositoryImpl
val cached = repo?.getCachedChatMessages(chatId)
if (!cached.isNullOrEmpty()) {
val items = cached.reversed()
applyMessages(items, hasMore = true, fromCache = true)
}
// Then fetch from network
fetchMessagesFromNetwork(chatId)
}
}
fun loadMessages() { fun loadMessages() {
val chatId = _uiState.value.chatId val chatId = _uiState.value.chatId
if (chatId.isBlank()) return if (chatId.isBlank()) return
viewModelScope.launch { viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) } _uiState.update { it.copy(isLoading = true) }
fetchMessagesFromNetwork(chatId)
}
}
private suspend fun fetchMessagesFromNetwork(chatId: String) {
when (val result = chatRepository.getChatHistory(chatId)) { when (val result = chatRepository.getChatHistory(chatId)) {
is NetworkResult.Success -> { is NetworkResult.Success -> {
val items = result.data.data.items.reversed() val items = result.data.data.items.reversed()
val otherMessage = items.firstOrNull { it.senderId != _uiState.value.currentUserId } applyMessages(items, hasMore = result.data.data.hasMore, fromCache = false)
val otherUser = otherMessage?.senderData
val title = otherUser?.customName
?: otherUser?.fullName
?: otherUser?.login?.let { "@$it" }
?: _uiState.value.chatTitle
val scrollTarget = if (_uiState.value.messages.isEmpty()) savedMessageId else null
_uiState.update {
it.copy(
messages = items,
chatTitle = title,
otherUserId = otherMessage?.senderId,
isVerified = otherUser?.isVerified == true,
rating = otherUser?.rating?.rating,
canSendMessage = otherUser?.permissions?.youCanSendMessage != false,
hasMore = result.data.data.hasMore,
isLoading = false,
scrollToMessageId = scrollTarget
)
}
} }
is NetworkResult.Error -> { is NetworkResult.Error -> {
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
@ -138,13 +152,38 @@ class ChatViewModel @Inject constructor(
)) ))
} }
is NetworkResult.Exception -> { is NetworkResult.Exception -> {
// Offline — keep cached data visible
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
if (_uiState.value.messages.isEmpty()) {
_events.emit(ChatEvent.ShowError("Connection error")) _events.emit(ChatEvent.ShowError("Connection error"))
} }
} }
} }
} }
private fun applyMessages(items: List<MessageItemDto>, hasMore: Boolean, fromCache: Boolean) {
val otherMessage = items.firstOrNull { it.senderId != _uiState.value.currentUserId }
val otherUser = otherMessage?.senderData
val title = otherUser?.customName
?: otherUser?.fullName
?: otherUser?.login?.let { "@$it" }
?: _uiState.value.chatTitle
val scrollTarget = if (_uiState.value.messages.isEmpty() && !fromCache) savedMessageId else null
_uiState.update {
it.copy(
messages = items,
chatTitle = title,
otherUserId = otherMessage?.senderId,
isVerified = otherUser?.isVerified == true,
rating = otherUser?.rating?.rating,
canSendMessage = otherUser?.permissions?.youCanSendMessage != false,
hasMore = hasMore,
isLoading = if (fromCache) true else false,
scrollToMessageId = scrollTarget
)
}
}
fun loadMore() { fun loadMore() {
val state = _uiState.value val state = _uiState.value
if (state.isLoading || !state.hasMore || state.messages.isEmpty()) return if (state.isLoading || !state.hasMore || state.messages.isEmpty()) return

View File

@ -56,6 +56,7 @@ fun HomeScreen(
onNavigateToSessions: () -> Unit, onNavigateToSessions: () -> Unit,
onNavigateToChangePassword: () -> Unit, onNavigateToChangePassword: () -> Unit,
onNavigateToBlacklist: () -> Unit, onNavigateToBlacklist: () -> Unit,
onNavigateToStorage: () -> Unit,
onNavigateToSearch: () -> Unit, onNavigateToSearch: () -> Unit,
onNavigateToProfile: () -> Unit, onNavigateToProfile: () -> Unit,
onAccountSwitched: () -> Unit, onAccountSwitched: () -> Unit,
@ -135,6 +136,7 @@ fun HomeScreen(
onNavigateToSessions = onNavigateToSessions, onNavigateToSessions = onNavigateToSessions,
onNavigateToChangePassword = onNavigateToChangePassword, onNavigateToChangePassword = onNavigateToChangePassword,
onNavigateToBlacklist = onNavigateToBlacklist, onNavigateToBlacklist = onNavigateToBlacklist,
onNavigateToStorage = onNavigateToStorage,
onNavigateToProfile = onNavigateToProfile, onNavigateToProfile = onNavigateToProfile,
onAccountSwitched = onAccountSwitched, onAccountSwitched = onAccountSwitched,
onAddAccount = onAddAccount, onAddAccount = onAddAccount,
@ -155,6 +157,7 @@ fun HomeScreen(
onNavigateToSessions = onNavigateToSessions, onNavigateToSessions = onNavigateToSessions,
onNavigateToChangePassword = onNavigateToChangePassword, onNavigateToChangePassword = onNavigateToChangePassword,
onNavigateToBlacklist = onNavigateToBlacklist, onNavigateToBlacklist = onNavigateToBlacklist,
onNavigateToStorage = onNavigateToStorage,
onNavigateToProfile = onNavigateToProfile, onNavigateToProfile = onNavigateToProfile,
onAccountSwitched = onAccountSwitched, onAccountSwitched = onAccountSwitched,
onAddAccount = onAddAccount, onAddAccount = onAddAccount,
@ -239,6 +242,7 @@ private fun TabContent(
onNavigateToSessions: () -> Unit, onNavigateToSessions: () -> Unit,
onNavigateToChangePassword: () -> Unit, onNavigateToChangePassword: () -> Unit,
onNavigateToBlacklist: () -> Unit, onNavigateToBlacklist: () -> Unit,
onNavigateToStorage: () -> Unit,
onNavigateToProfile: () -> Unit, onNavigateToProfile: () -> Unit,
onAccountSwitched: () -> Unit, onAccountSwitched: () -> Unit,
onAddAccount: () -> Unit, onAddAccount: () -> Unit,
@ -261,6 +265,7 @@ private fun TabContent(
onNavigateToSessions = onNavigateToSessions, onNavigateToSessions = onNavigateToSessions,
onNavigateToChangePassword = onNavigateToChangePassword, onNavigateToChangePassword = onNavigateToChangePassword,
onNavigateToBlacklist = onNavigateToBlacklist, onNavigateToBlacklist = onNavigateToBlacklist,
onNavigateToStorage = onNavigateToStorage,
onNavigateToProfile = onNavigateToProfile, onNavigateToProfile = onNavigateToProfile,
onAccountSwitched = onAccountSwitched, onAccountSwitched = onAccountSwitched,
onAddAccount = onAddAccount, onAddAccount = onAddAccount,

View File

@ -19,6 +19,7 @@ import org.yobble.messenger.data.remote.NetworkResult
import org.yobble.messenger.data.remote.dto.PrivateChatListItemDto import org.yobble.messenger.data.remote.dto.PrivateChatListItemDto
import org.yobble.messenger.data.remote.socket.SocketEvent import org.yobble.messenger.data.remote.socket.SocketEvent
import org.yobble.messenger.data.remote.socket.SocketManager import org.yobble.messenger.data.remote.socket.SocketManager
import org.yobble.messenger.data.repository.ChatRepositoryImpl
import org.yobble.messenger.domain.repository.AuthRepository import org.yobble.messenger.domain.repository.AuthRepository
import org.yobble.messenger.domain.repository.ChatRepository import org.yobble.messenger.domain.repository.ChatRepository
import javax.inject.Inject import javax.inject.Inject
@ -52,7 +53,7 @@ class HomeViewModel @Inject constructor(
init { init {
socketManager.connect() socketManager.connect()
loadChats() loadCachedThenNetwork()
loadActiveAccount() loadActiveAccount()
observeSocket() observeSocket()
} }
@ -85,9 +86,28 @@ class HomeViewModel @Inject constructor(
} }
} }
private fun loadCachedThenNetwork() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
// Show cached data immediately
val repo = chatRepository as? ChatRepositoryImpl
val cached = repo?.getCachedChatList()
if (!cached.isNullOrEmpty()) {
_uiState.update { it.copy(chats = cached, isLoading = true) }
}
// Then fetch from network
fetchChatsFromNetwork()
}
}
fun loadChats() { fun loadChats() {
viewModelScope.launch { viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) } _uiState.update { it.copy(isLoading = true) }
fetchChatsFromNetwork()
}
}
private suspend fun fetchChatsFromNetwork() {
when (val result = chatRepository.getChatList()) { when (val result = chatRepository.getChatList()) {
is NetworkResult.Success -> { is NetworkResult.Success -> {
_uiState.update { _uiState.update {
@ -105,7 +125,9 @@ class HomeViewModel @Inject constructor(
)) ))
} }
is NetworkResult.Exception -> { is NetworkResult.Exception -> {
// Offline — keep showing cached data, just stop loading
_uiState.update { it.copy(isLoading = false) } _uiState.update { it.copy(isLoading = false) }
if (_uiState.value.chats.isEmpty()) {
_events.emit(HomeEvent.ShowError("Connection error")) _events.emit(HomeEvent.ShowError("Connection error"))
} }
} }

View File

@ -15,6 +15,7 @@ import org.yobble.messenger.presentation.settings.BlacklistScreen
import org.yobble.messenger.presentation.settings.ChangePasswordScreen import org.yobble.messenger.presentation.settings.ChangePasswordScreen
import org.yobble.messenger.presentation.settings.PrivacyScreen import org.yobble.messenger.presentation.settings.PrivacyScreen
import org.yobble.messenger.presentation.settings.SessionsScreen import org.yobble.messenger.presentation.settings.SessionsScreen
import org.yobble.messenger.presentation.settings.StorageScreen
object Routes { object Routes {
const val LOGIN = "login" const val LOGIN = "login"
@ -29,6 +30,7 @@ object Routes {
const val BLACKLIST = "settings/blacklist" const val BLACKLIST = "settings/blacklist"
const val SEARCH = "search" const val SEARCH = "search"
const val MY_PROFILE = "my_profile" const val MY_PROFILE = "my_profile"
const val STORAGE = "settings/storage"
fun codeVerification(login: String) = "code_verification/$login" fun codeVerification(login: String) = "code_verification/$login"
fun chat(chatId: String) = "chat/$chatId" fun chat(chatId: String) = "chat/$chatId"
@ -100,6 +102,9 @@ fun AppNavGraph(
onNavigateToBlacklist = { onNavigateToBlacklist = {
navController.navigate(Routes.BLACKLIST) navController.navigate(Routes.BLACKLIST)
}, },
onNavigateToStorage = {
navController.navigate(Routes.STORAGE)
},
onNavigateToSearch = { onNavigateToSearch = {
navController.navigate(Routes.SEARCH) navController.navigate(Routes.SEARCH)
}, },
@ -179,6 +184,13 @@ fun AppNavGraph(
} }
) )
} }
composable(Routes.STORAGE) {
StorageScreen(
onNavigateBack = {
navController.popBackStack()
}
)
}
composable(Routes.MY_PROFILE) { composable(Routes.MY_PROFILE) {
ProfileScreen( ProfileScreen(
onNavigateBack = { onNavigateBack = {

View File

@ -12,6 +12,7 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Block import androidx.compose.material.icons.filled.Block
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.PhoneAndroid
import androidx.compose.material.icons.filled.Shield import androidx.compose.material.icons.filled.Shield
@ -39,6 +40,7 @@ fun SettingsScreen(
onNavigateToSessions: () -> Unit, onNavigateToSessions: () -> Unit,
onNavigateToChangePassword: () -> Unit, onNavigateToChangePassword: () -> Unit,
onNavigateToBlacklist: () -> Unit, onNavigateToBlacklist: () -> Unit,
onNavigateToStorage: () -> Unit,
onNavigateToProfile: () -> Unit, onNavigateToProfile: () -> Unit,
onAccountSwitched: () -> Unit, onAccountSwitched: () -> Unit,
onAddAccount: () -> Unit, onAddAccount: () -> Unit,
@ -153,6 +155,12 @@ fun SettingsScreen(
subtitle = "Blocked users", subtitle = "Blocked users",
onClick = onNavigateToBlacklist onClick = onNavigateToBlacklist
) )
SettingsMenuItem(
icon = Icons.Default.FolderOpen,
title = "Storage",
subtitle = "Cache and media usage",
onClick = onNavigateToStorage
)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))