Профиль, редактирование без аватара
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package core.di
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import core.network.AuthInterceptor
|
||||
import core.network.DynamicBaseUrlInterceptor
|
||||
import core.network.ServerConfig
|
||||
@@ -25,7 +26,7 @@ object NetworkModule {
|
||||
dynamicBaseUrlInterceptor: DynamicBaseUrlInterceptor
|
||||
): OkHttpClient {
|
||||
val logging = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.HEADERS
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
return OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
@@ -40,10 +41,13 @@ object NetworkModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
|
||||
val gson = com.google.gson.GsonBuilder()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://api.placeholder.com/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,15 @@ import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
||||
@Singleton
|
||||
class AuthInterceptor @Inject constructor(
|
||||
private val tokenManager: TokenManager
|
||||
private val tokenManager: TokenManager,
|
||||
private val navigationManager: core.utils.NavigationManager,
|
||||
private val authRepositoryProvider: javax.inject.Provider<auth.domain.repository.AuthRepository>
|
||||
) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val token = tokenManager.getToken()
|
||||
@@ -17,6 +22,17 @@ class AuthInterceptor @Inject constructor(
|
||||
addHeader("Authorization", "Bearer $it")
|
||||
}
|
||||
}.build()
|
||||
return chain.proceed(request)
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (response.code == 401) {
|
||||
// Global logout
|
||||
kotlinx.coroutines.GlobalScope.launch(kotlinx.coroutines.Dispatchers.Main) {
|
||||
authRepositoryProvider.get().logout()
|
||||
navigationManager.logout()
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,18 @@ class ActiveChatTracker @Inject constructor() {
|
||||
private val _currentChatId = MutableStateFlow<String?>(null)
|
||||
val currentChatId: StateFlow<String?> = _currentChatId.asStateFlow()
|
||||
|
||||
private val _totalUnreadCount = MutableStateFlow(0)
|
||||
val totalUnreadCount: StateFlow<Int> = _totalUnreadCount.asStateFlow()
|
||||
|
||||
fun setChatId(chatId: String?) {
|
||||
_currentChatId.value = chatId
|
||||
}
|
||||
|
||||
fun setTotalUnreadCount(count: Int) {
|
||||
_totalUnreadCount.value = count
|
||||
}
|
||||
|
||||
fun incrementUnreadCount() {
|
||||
_totalUnreadCount.value += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ class ForkFirebaseMessagingService : FirebaseMessagingService() {
|
||||
@Inject
|
||||
lateinit var authApi: AuthApi
|
||||
|
||||
@Inject
|
||||
lateinit var activeChatTracker: ActiveChatTracker
|
||||
|
||||
private val job = SupervisorJob()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + job)
|
||||
|
||||
@@ -48,11 +51,11 @@ class ForkFirebaseMessagingService : FirebaseMessagingService() {
|
||||
val chatId = message.data["chatId"]
|
||||
val messageId = message.data["id"] ?: message.messageId
|
||||
|
||||
NotificationHelper.showNotification(this, title, body, type, chatId, messageId?.hashCode())
|
||||
NotificationHelper.showNotification(this, title, body, type, chatId, messageId?.hashCode(), activeChatTracker.totalUnreadCount.value)
|
||||
}
|
||||
|
||||
private fun showNotification(title: String?, body: String?, type: String?) {
|
||||
NotificationHelper.showNotification(this, title, body, type)
|
||||
NotificationHelper.showNotification(this, title, body, type, totalCount = activeChatTracker.totalUnreadCount.value)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.core.app.NotificationCompat
|
||||
object NotificationHelper {
|
||||
private const val CHANNEL_ID = "fork_notifications_channel"
|
||||
|
||||
fun showNotification(context: Context, title: String?, body: String?, type: String?, chatId: String? = null, notificationId: Int? = null) {
|
||||
fun showNotification(context: Context, title: String?, body: String?, type: String?, chatId: String? = null, notificationId: Int? = null, totalCount: Int = 0) {
|
||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val finalNotificationId = notificationId ?: (System.currentTimeMillis() % Int.MAX_VALUE).toInt()
|
||||
@@ -48,6 +48,7 @@ object NotificationHelper {
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
.setNumber(totalCount)
|
||||
.setContentIntent(pendingIntent)
|
||||
|
||||
notificationManager.notify(finalNotificationId, notificationBuilder.build())
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import core.presentation.theme.SoftSquareShape
|
||||
|
||||
@Composable
|
||||
@@ -45,10 +46,14 @@ fun AppAvatar(
|
||||
|
||||
val avatarColor = Color(0xFF4AA6F3) // Premium Telegram Blue (Web)
|
||||
|
||||
val cornerRadius = remember(size) {
|
||||
if (size <= 32.dp) (size.value * 0.25).dp else 12.dp
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
.clip(SoftSquareShape)
|
||||
.clip(RoundedCornerShape(cornerRadius))
|
||||
.background(avatarColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
@@ -130,17 +130,11 @@ private fun RowScope.ProfileNavItem(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(26.dp)
|
||||
.clip(CircleShape)
|
||||
) {
|
||||
AppAvatar(
|
||||
url = avatarUrl,
|
||||
name = username,
|
||||
size = 26.dp
|
||||
)
|
||||
}
|
||||
AppAvatar(
|
||||
url = avatarUrl,
|
||||
name = username,
|
||||
size = 26.dp
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.profile_tab),
|
||||
color = contentColor,
|
||||
|
||||
@@ -9,11 +9,12 @@ data class LinkMetadata(
|
||||
|
||||
object LinkParser {
|
||||
private val URL_PATTERN = Regex(
|
||||
"(?:^|[\\s])((https?://)[\\w-]+(\\.[\\w-]+)+\\.?(:\\d+)?(/[\\w- ./?%&=]*)?)",
|
||||
"(?:^|[\\s])(https?://[^\\s\\n\\r\"]+)",
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
|
||||
fun findLinks(text: String): List<String> {
|
||||
if (text.isEmpty()) return emptyList()
|
||||
return URL_PATTERN.findAll(text).map { it.groupValues[1].trim() }.toList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
sealed class NavEvent {
|
||||
data class OpenChat(val chatId: String) : NavEvent()
|
||||
object Logout : NavEvent()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@@ -16,4 +17,8 @@ class NavigationManager @Inject constructor() {
|
||||
fun navigateToChat(chatId: String) {
|
||||
_events.tryEmit(NavEvent.OpenChat(chatId))
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
_events.tryEmit(NavEvent.Logout)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user