From 98a9216515db517a1086e24b6fbeed1f305efce3 Mon Sep 17 00:00:00 2001 From: MaximOksiuta <63787095+MaximOksiuta@users.noreply.github.com> Date: Sun, 23 Nov 2025 15:22:48 +0300 Subject: [PATCH] fcm token sending --- .../moscow2025/data/dto/FcmTokenDTO.kt | 10 ++++++ .../repImplementations/FCMRepositoryImpl.kt | 29 ++++++++++++++++ .../domain/interfaces/FCMRepository.kt | 6 ++++ .../usecase/auth/SendFCMTokenUseCase.kt | 9 +++++ .../moscow2025/presentation/MainActivity.kt | 33 +++++++++---------- .../presentation/navigation/TTasksApp.kt | 4 ++- .../presentation/navigation/TTasksNavHost.kt | 4 ++- .../presentation/screens/main/MainScreen.kt | 12 +++++-- 8 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/prodhack/moscow2025/data/dto/FcmTokenDTO.kt create mode 100644 app/src/main/java/com/prodhack/moscow2025/data/repImplementations/FCMRepositoryImpl.kt create mode 100644 app/src/main/java/com/prodhack/moscow2025/domain/interfaces/FCMRepository.kt create mode 100644 app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/SendFCMTokenUseCase.kt diff --git a/app/src/main/java/com/prodhack/moscow2025/data/dto/FcmTokenDTO.kt b/app/src/main/java/com/prodhack/moscow2025/data/dto/FcmTokenDTO.kt new file mode 100644 index 0000000..1bb0ad7 --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/data/dto/FcmTokenDTO.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/prodhack/moscow2025/data/repImplementations/FCMRepositoryImpl.kt b/app/src/main/java/com/prodhack/moscow2025/data/repImplementations/FCMRepositoryImpl.kt new file mode 100644 index 0000000..0af7651 --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/data/repImplementations/FCMRepositoryImpl.kt @@ -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 { + method = HttpMethod.Post + url("/notifications/register_device") + setBody(FcmTokenDTO(token)) + contentType(ContentType.Application.Json) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/prodhack/moscow2025/domain/interfaces/FCMRepository.kt b/app/src/main/java/com/prodhack/moscow2025/domain/interfaces/FCMRepository.kt new file mode 100644 index 0000000..bf5c671 --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/domain/interfaces/FCMRepository.kt @@ -0,0 +1,6 @@ +package com.prodhack.moscow2025.domain.interfaces + +interface FCMRepository { + + suspend fun sendFCMToken(token: String) +} diff --git a/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/SendFCMTokenUseCase.kt b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/SendFCMTokenUseCase.kt new file mode 100644 index 0000000..2b212ef --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/SendFCMTokenUseCase.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/MainActivity.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/MainActivity.kt index 2acf616..bef8b6a 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/MainActivity.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/MainActivity.kt @@ -14,12 +14,15 @@ import androidx.compose.runtime.getValue import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat +import androidx.lifecycle.lifecycleScope import com.google.firebase.messaging.FirebaseMessaging 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.presentation.navigation.AppDestination import com.prodhack.moscow2025.presentation.navigation.TTasksApp import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.koin.android.ext.android.inject import kotlin.getValue @@ -32,6 +35,7 @@ class MainActivity : ComponentActivity() { private val checkSessionUseCase: CheckSessionUseCase by inject() + private val sendFCMTokenUseCase: SendFCMTokenUseCase by inject() private val sessionDestinationState = MutableStateFlow(null) override fun onCreate(savedInstanceState: Bundle?) { @@ -65,20 +69,13 @@ class MainActivity : ComponentActivity() { setContent { val sessionDestination by sessionDestinationState.collectAsState() - TTasksApp(sessionDestination = sessionDestination, context = this) - LaunchedEffect(Unit) { - requestPermissions( - arrayOf(Manifest.permission.ACCESS_NOTIFICATION_POLICY), 123 - ) - FirebaseMessaging.getInstance().token - .addOnCompleteListener { task -> - if (task.isSuccessful) { - val token = task.result - } - } - - checkAndRequestNotificationPermission() - } + TTasksApp( + sessionDestination = sessionDestination, + context = this, + requestNotifyPermissions = { + checkAndRequestNotificationPermission() + } + ) } } @@ -89,12 +86,10 @@ class MainActivity : ComponentActivity() { this, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED -> { - // Разрешение уже есть, получаем токен getFCMToken() } else -> { - // Запрашиваем разрешение requestPermissions( arrayOf(Manifest.permission.POST_NOTIFICATIONS), 123 @@ -102,17 +97,19 @@ class MainActivity : ComponentActivity() { } } } else { - // Для версий ниже Android 13 разрешение не требуется getFCMToken() } } - private fun getFCMToken() { + fun getFCMToken() { FirebaseMessaging.getInstance().token .addOnCompleteListener { task -> if (task.isSuccessful) { val token = task.result Log.d("TOKEN", token) + lifecycleScope.launch { + sendFCMTokenUseCase(token) + } } else { Log.e("TOKEN", "Failed to get token", task.exception) } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt index 8e7629a..6bc4e94 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt @@ -26,6 +26,7 @@ import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle fun TTasksApp( appState: TTasksAppState = rememberTTasksAppState(), context: Context, + requestNotifyPermissions: () -> Unit, sessionDestination: AppDestination? = null ) { MoscowHackatonTemplateTheme { @@ -99,7 +100,8 @@ fun TTasksApp( modifier = Modifier.padding(padding), sessionDestination = sessionDestination, snackbarHostState = snackbarHostState, - context = context + context = context, + requestNotifyPermissions = requestNotifyPermissions ) } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt index a6f14c1..f2b3d57 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt @@ -28,6 +28,7 @@ fun TTasksNavHost( modifier: Modifier = Modifier, sessionDestination: AppDestination? = null, context: Context, + requestNotifyPermissions: () -> Unit, snackbarHostState: SnackbarHostState ) { val startDestination = sessionDestination?.route ?: AppDestination.Login.route @@ -100,7 +101,8 @@ fun TTasksNavHost( }) }, openCreateResume = { navController.navigate(AppDestination.ResumeCreation.route) - } + }, + requestNotifyPermissions = requestNotifyPermissions ) } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt index d4a4a89..1d9acf4 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/main/MainScreen.kt @@ -1,5 +1,6 @@ package com.prodhack.moscow2025.presentation.screens.main +import android.Manifest import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -21,6 +22,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -30,6 +32,7 @@ import androidx.compose.ui.unit.sp import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems +import com.google.firebase.messaging.FirebaseMessaging import com.prodhack.moscow2025.R import com.prodhack.moscow2025.presentation.components.standart.BigButton import com.prodhack.moscow2025.presentation.components.standart.TopLogo @@ -45,9 +48,14 @@ fun ErrorCollectorScope.MainScreen( modifier: Modifier = Modifier, openResumeDetails: (String) -> Unit, openCreateResume: () -> Unit, + requestNotifyPermissions: () -> Unit, viewModel: MainScreenViewModel = koinViewModel() ) { - Box (modifier = modifier){ + LaunchedEffect(Unit) { + requestNotifyPermissions() + } + + Box(modifier = modifier) { val items = viewModel.resumeList.collectAsLazyPagingItems() MainScreenContent( @@ -162,7 +170,7 @@ private fun MainScreenContent( } item { - Spacer(modifier = Modifier.height(Paddings.large*4.5f)) + Spacer(modifier = Modifier.height(Paddings.large * 4.5f)) } } }