diff --git a/app/MainActivity.kt b/app/MainActivity.kt new file mode 100644 index 0000000..a90c857 --- /dev/null +++ b/app/MainActivity.kt @@ -0,0 +1,71 @@ +package ru.nsu.concertmate + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.content.ContextCompat +import ru.nsu.concertmate.ui.screens.FavoriteCitiesScreen +import ru.nsu.concertmate.ui.theme.ConcertMateTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + enableEdgeToEdge() + + // Запрос разрешения на уведомления для Android 13 и выше + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestNotificationPermission() + } + + setContent { + FavoriteCitiesScreen() + } + } + + private fun requestNotificationPermission() { + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + Log.d("Permission", "Разрешение на уведомления предоставлено") + } else { + Log.d("Permission", "Разрешение на уведомления отклонено") + } + } + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } +} + +@Composable +fun Greeting(name: String, modifier: Modifier = Modifier) { + Text( + text = "Hello $name!", + modifier = modifier + ) +} + +@Preview(showBackground = true) +@Composable +fun GreetingPreview() { + ConcertMateTheme { + Greeting("Android") + } +} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e250eb0..caa33c8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.google.gms.google.services) } android { @@ -61,6 +62,8 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.landscapist) implementation(libs.landscapist.coil3.android) + implementation(libs.firebase.auth) + implementation(libs.firebase.messaging.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..8c12f46 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "320206274480", + "project_id": "concert-mate-ba47b", + "storage_bucket": "concert-mate-ba47b.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:320206274480:android:6c650272877b1d9e472075", + "android_client_info": { + "package_name": "ru.nsu.concertmate" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCVBbqQn3ESdsCJOn6BRj8f5Xj7VNvK01k" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e1c19e..aec8010 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,11 @@ + + + + + + tools:targetApi="31"> + + + - - - - - - + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/java/ru/nsu/concertmate/MainActivity.kt b/app/src/main/java/ru/nsu/concertmate/MainActivity.kt index 8577da4..a90c857 100644 --- a/app/src/main/java/ru/nsu/concertmate/MainActivity.kt +++ b/app/src/main/java/ru/nsu/concertmate/MainActivity.kt @@ -1,17 +1,71 @@ package ru.nsu.concertmate +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import ru.nsu.concertmate.ui.screens.ConcertInfoScreen +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.content.ContextCompat +import ru.nsu.concertmate.ui.screens.FavoriteCitiesScreen +import ru.nsu.concertmate.ui.theme.ConcertMateTheme + class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + + // Запрос разрешения на уведомления для Android 13 и выше + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestNotificationPermission() + } + setContent { - ConcertInfoScreen() + FavoriteCitiesScreen() } } + + private fun requestNotificationPermission() { + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + Log.d("Permission", "Разрешение на уведомления предоставлено") + } else { + Log.d("Permission", "Разрешение на уведомления отклонено") + } + } + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } +} + +@Composable +fun Greeting(name: String, modifier: Modifier = Modifier) { + Text( + text = "Hello $name!", + modifier = modifier + ) +} + +@Preview(showBackground = true) +@Composable +fun GreetingPreview() { + ConcertMateTheme { + Greeting("Android") + } } diff --git a/app/src/main/java/ru/nsu/concertmate/MyFirebaseMessagingService.kt b/app/src/main/java/ru/nsu/concertmate/MyFirebaseMessagingService.kt new file mode 100644 index 0000000..9549aad --- /dev/null +++ b/app/src/main/java/ru/nsu/concertmate/MyFirebaseMessagingService.kt @@ -0,0 +1,111 @@ +package ru.nsu.concertmate + +import android.Manifest +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + +class MyFirebaseMessagingService : FirebaseMessagingService() { + + companion object { + private const val TAG = "MyFirebaseMessaging" + private const val CHANNEL_ID = "FCM_Channel" + private const val CHANNEL_NAME = "Default Channel" + private const val CHANNEL_DESCRIPTION = "Firebase Cloud Messaging Notifications" + } + + override fun onNewToken(token: String) { + super.onNewToken(token) + Log.d(TAG, "New token: $token") + + // Сохраняем токен с помощью Preferences + Preferences.setAccessToken(applicationContext, token) + + // Отправляем токен на сервер + sendTokenToServer(token) + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + super.onMessageReceived(remoteMessage) + + // Обрабатываем уведомление + remoteMessage.notification?.let { notification -> + Log.d(TAG, "Notification received: ${notification.title}, ${notification.body}") + showNotification(notification.title, notification.body) + } + + // Обрабатываем пользовательские данные + if (remoteMessage.data.isNotEmpty()) { + Log.d(TAG, "Data payload: ${remoteMessage.data}") + handleDataPayload(remoteMessage.data) + } + } + + private fun sendTokenToServer(token: String) { + // Пример API вызова или обновления на сервере + Log.d(TAG, "Token sent to server: $token") + } + + private fun handleDataPayload(data: Map) { + // Обрабатываем пользовательские данные + Log.d(TAG, "Handling custom data payload: $data") + } + + private fun showNotification(title: String?, message: String?) { + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + // Создаём канал уведомлений для Android 8.0 и выше + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = CHANNEL_DESCRIPTION + } + notificationManager.createNotificationChannel(channel) + } + + // Создаём интент для открытия MainActivity + val intent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + // Строим уведомление + val notification = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(title ?: "New Notification") + .setContentText(message ?: "You have a new message.") + .setSmallIcon(R.drawable.ic_launcher_foreground) // Замените на иконку вашего приложения + .setColor(ContextCompat.getColor(this, R.color.teal_200)) // Необязательный цвет + .setAutoCancel(true) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .build() + + // Показываем уведомление + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + return + } + NotificationManagerCompat.from(this).notify(System.currentTimeMillis().toInt(), notification) + } +} diff --git a/app/src/main/java/ru/nsu/concertmate/Preferences.kt b/app/src/main/java/ru/nsu/concertmate/Preferences.kt new file mode 100644 index 0000000..210293f --- /dev/null +++ b/app/src/main/java/ru/nsu/concertmate/Preferences.kt @@ -0,0 +1,24 @@ +package ru.nsu.concertmate + +import android.content.Context +import android.content.SharedPreferences + +object Preferences { + + private const val PREF_NAME = "MySharedPref" + private const val ACCESS_TOKEN_KEY = "ACCESSTOKEN" + + // Сохранить токен + fun setAccessToken(context: Context, token: String?) { + val sharedPreferences: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putString(ACCESS_TOKEN_KEY, token) + editor.apply() + } + + // Извлечь токен + fun getAccessToken(context: Context): String? { + val sharedPreferences: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + return sharedPreferences.getString(ACCESS_TOKEN_KEY, null) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index f74b04b..932a7ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.google.gms.google.services) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 482d682..dd4b048 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,9 @@ lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.9.3" composeBom = "2024.04.01" uiTextGoogleFonts = "1.7.6" +googleGmsGoogleServices = "4.4.2" +firebaseAuth = "23.1.0" +firebaseMessagingKtx = "24.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -30,8 +33,11 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-material3 = { group = "androidx.compose.material3", name = "material3" } landscapist = { module = "com.github.skydoves:landscapist", version.ref = "landscapist" } landscapist-coil3-android = { module = "com.github.skydoves:landscapist-coil3-android", version.ref = "landscapistCoil3Android" } +firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebaseAuth" } +firebase-messaging-ktx = { group = "com.google.firebase", name = "firebase-messaging-ktx", version.ref = "firebaseMessagingKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +google-gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleGmsGoogleServices" }