fcm token sending

This commit is contained in:
MaximOksiuta
2025-11-23 15:22:48 +03:00
parent 28285be9da
commit 98a9216515
8 changed files with 85 additions and 22 deletions
@@ -0,0 +1,10 @@
package com.prodhack.moscow2025.data.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class FcmTokenDTO(
@SerialName("device_id")
val deviceId: String
)
@@ -0,0 +1,29 @@
package com.prodhack.moscow2025.data.repImplementations
import com.prodhack.moscow2025.data.base.BaseRepository
import com.prodhack.moscow2025.data.data_providers.api.ApiKtorClient
import com.prodhack.moscow2025.data.dto.FcmTokenDTO
import com.prodhack.moscow2025.domain.interfaces.FCMRepository
import io.ktor.client.request.setBody
import io.ktor.client.request.url
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.http.contentType
import org.koin.core.annotation.Single
@Single
class FCMRepositoryImpl(
val ktorClient: ApiKtorClient
) : FCMRepository, BaseRepository() {
override val defaultKtorClient = ktorClient.client
override suspend fun sendFCMToken(token: String) {
networkRequest<String> {
method = HttpMethod.Post
url("/notifications/register_device")
setBody(FcmTokenDTO(token))
contentType(ContentType.Application.Json)
}
}
}
@@ -0,0 +1,6 @@
package com.prodhack.moscow2025.domain.interfaces
interface FCMRepository {
suspend fun sendFCMToken(token: String)
}
@@ -0,0 +1,9 @@
package com.prodhack.moscow2025.domain.usecase.auth
import com.prodhack.moscow2025.domain.interfaces.FCMRepository
import org.koin.core.annotation.Single
@Single
class SendFCMTokenUseCase(private val fcmRepository: FCMRepository) {
suspend operator fun invoke(token: String) = fcmRepository.sendFCMToken(token = token)
}
@@ -14,12 +14,15 @@ import androidx.compose.runtime.getValue
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import com.prodhack.moscow2025.domain.usecase.auth.CheckSessionUseCase import com.prodhack.moscow2025.domain.usecase.auth.CheckSessionUseCase
import com.prodhack.moscow2025.domain.usecase.auth.SendFCMTokenUseCase
import com.prodhack.moscow2025.domain.usecase.auth.SessionState import com.prodhack.moscow2025.domain.usecase.auth.SessionState
import com.prodhack.moscow2025.presentation.navigation.AppDestination import com.prodhack.moscow2025.presentation.navigation.AppDestination
import com.prodhack.moscow2025.presentation.navigation.TTasksApp import com.prodhack.moscow2025.presentation.navigation.TTasksApp
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import kotlin.getValue import kotlin.getValue
@@ -32,6 +35,7 @@ class MainActivity : ComponentActivity() {
private val checkSessionUseCase: CheckSessionUseCase by inject() private val checkSessionUseCase: CheckSessionUseCase by inject()
private val sendFCMTokenUseCase: SendFCMTokenUseCase by inject()
private val sessionDestinationState = MutableStateFlow<AppDestination?>(null) private val sessionDestinationState = MutableStateFlow<AppDestination?>(null)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -65,20 +69,13 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
val sessionDestination by sessionDestinationState.collectAsState() val sessionDestination by sessionDestinationState.collectAsState()
TTasksApp(sessionDestination = sessionDestination, context = this) TTasksApp(
LaunchedEffect(Unit) { sessionDestination = sessionDestination,
requestPermissions( context = this,
arrayOf(Manifest.permission.ACCESS_NOTIFICATION_POLICY), 123 requestNotifyPermissions = {
)
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
}
}
checkAndRequestNotificationPermission() checkAndRequestNotificationPermission()
} }
)
} }
} }
@@ -89,12 +86,10 @@ class MainActivity : ComponentActivity() {
this, this,
Manifest.permission.POST_NOTIFICATIONS Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED -> { ) == PackageManager.PERMISSION_GRANTED -> {
// Разрешение уже есть, получаем токен
getFCMToken() getFCMToken()
} }
else -> { else -> {
// Запрашиваем разрешение
requestPermissions( requestPermissions(
arrayOf(Manifest.permission.POST_NOTIFICATIONS), arrayOf(Manifest.permission.POST_NOTIFICATIONS),
123 123
@@ -102,17 +97,19 @@ class MainActivity : ComponentActivity() {
} }
} }
} else { } else {
// Для версий ниже Android 13 разрешение не требуется
getFCMToken() getFCMToken()
} }
} }
private fun getFCMToken() { fun getFCMToken() {
FirebaseMessaging.getInstance().token FirebaseMessaging.getInstance().token
.addOnCompleteListener { task -> .addOnCompleteListener { task ->
if (task.isSuccessful) { if (task.isSuccessful) {
val token = task.result val token = task.result
Log.d("TOKEN", token) Log.d("TOKEN", token)
lifecycleScope.launch {
sendFCMTokenUseCase(token)
}
} else { } else {
Log.e("TOKEN", "Failed to get token", task.exception) Log.e("TOKEN", "Failed to get token", task.exception)
} }
@@ -26,6 +26,7 @@ import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle
fun TTasksApp( fun TTasksApp(
appState: TTasksAppState = rememberTTasksAppState(), appState: TTasksAppState = rememberTTasksAppState(),
context: Context, context: Context,
requestNotifyPermissions: () -> Unit,
sessionDestination: AppDestination? = null sessionDestination: AppDestination? = null
) { ) {
MoscowHackatonTemplateTheme { MoscowHackatonTemplateTheme {
@@ -99,7 +100,8 @@ fun TTasksApp(
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
sessionDestination = sessionDestination, sessionDestination = sessionDestination,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
context = context context = context,
requestNotifyPermissions = requestNotifyPermissions
) )
} }
} }
@@ -28,6 +28,7 @@ fun TTasksNavHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
sessionDestination: AppDestination? = null, sessionDestination: AppDestination? = null,
context: Context, context: Context,
requestNotifyPermissions: () -> Unit,
snackbarHostState: SnackbarHostState snackbarHostState: SnackbarHostState
) { ) {
val startDestination = sessionDestination?.route ?: AppDestination.Login.route val startDestination = sessionDestination?.route ?: AppDestination.Login.route
@@ -100,7 +101,8 @@ fun TTasksNavHost(
}) })
}, openCreateResume = { }, openCreateResume = {
navController.navigate(AppDestination.ResumeCreation.route) navController.navigate(AppDestination.ResumeCreation.route)
} },
requestNotifyPermissions = requestNotifyPermissions
) )
} }
@@ -1,5 +1,6 @@
package com.prodhack.moscow2025.presentation.screens.main package com.prodhack.moscow2025.presentation.screens.main
import android.Manifest
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -21,6 +22,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -30,6 +32,7 @@ import androidx.compose.ui.unit.sp
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.google.firebase.messaging.FirebaseMessaging
import com.prodhack.moscow2025.R import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.presentation.components.standart.BigButton import com.prodhack.moscow2025.presentation.components.standart.BigButton
import com.prodhack.moscow2025.presentation.components.standart.TopLogo import com.prodhack.moscow2025.presentation.components.standart.TopLogo
@@ -45,9 +48,14 @@ fun ErrorCollectorScope.MainScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
openResumeDetails: (String) -> Unit, openResumeDetails: (String) -> Unit,
openCreateResume: () -> Unit, openCreateResume: () -> Unit,
requestNotifyPermissions: () -> Unit,
viewModel: MainScreenViewModel = koinViewModel() viewModel: MainScreenViewModel = koinViewModel()
) { ) {
Box (modifier = modifier){ LaunchedEffect(Unit) {
requestNotifyPermissions()
}
Box(modifier = modifier) {
val items = viewModel.resumeList.collectAsLazyPagingItems() val items = viewModel.resumeList.collectAsLazyPagingItems()
MainScreenContent( MainScreenContent(
@@ -162,7 +170,7 @@ private fun MainScreenContent(
} }
item { item {
Spacer(modifier = Modifier.height(Paddings.large*4.5f)) Spacer(modifier = Modifier.height(Paddings.large * 4.5f))
} }
} }
} }