add push notif firebase

This commit is contained in:
shaulascr
2025-05-14 21:40:19 +07:00
parent ce8a6b24b4
commit afd2819192
13 changed files with 234 additions and 121 deletions

View File

@ -8,10 +8,10 @@
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
<option name="appId" value="com.alya.ecommerce_serang" />
<option name="mobileSdkAppId" value="1:284675201257:android:2755670e3dbb1b48683878" />
<option name="projectId" value="ecommerce-serang" />
<option name="projectNumber" value="284675201257" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />

2
.idea/kotlinc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="2.1.0" />
</component>
</project>

View File

@ -6,6 +6,7 @@ plugins {
id("androidx.navigation.safeargs")
id("kotlin-parcelize")
alias(libs.plugins.dagger.hilt) // Use alias from catalog
id("com.google.gms.google-services")
}
val localProperties = Properties().apply {
@ -22,8 +23,8 @@ android {
defaultConfig {
applicationId = "com.alya.ecommerce_serang"
minSdk = 21
targetSdk = 34
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
@ -119,6 +120,11 @@ dependencies {
implementation("io.socket:socket.io-client:2.1.0") // or latest version
//fcm token
implementation(platform("com.google.firebase:firebase-bom:33.13.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-messaging-ktx")
}

29
app/google-services.json Normal file
View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "284675201257",
"project_id": "ecommerce-serang",
"storage_bucket": "ecommerce-serang.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:284675201257:android:2755670e3dbb1b48683878",
"android_client_info": {
"package_name": "com.alya.ecommerce_serang"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB-nWHsVbdV4PPIH06JZSStIVXjv9Qc4iU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -61,6 +61,7 @@
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<activity
android:name=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity"
android:exported="false"/>
@ -148,6 +149,25 @@
<activity
android:name=".ui.MainActivity"
android:exported="false" />
<service
android:name=".ui.notif.fcm.FCMService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/outline_notifications_24" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/blue_500" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="fcm_default_channel" />
</application>
</manifest>

View File

@ -1,18 +1,48 @@
package com.alya.ecommerce_serang.app
import android.app.Application
import android.content.Context
import android.util.Log
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application(){
// override fun onCreate() {
// super.onCreate()
//
// val sessionManager = SessionManager(this)
// if (sessionManager.getUserId() != null) {
// val serviceIntent = Intent(this, SimpleWebSocketService::class.java)
// startService(serviceIntent)
// }
// }
private val TAG = "AppSerang"
override fun onCreate() {
super.onCreate()
// Initialize Firebase
FirebaseApp.initializeApp(this)
// Request FCM token at app startup
retrieveFCMToken()
}
private fun retrieveFCMToken() {
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
return@addOnCompleteListener
}
val token = task.result
Log.d(TAG, "FCM token retrieved: $token")
// Save token locally
val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
// Send to your server
sendTokenToServer(token)
}
}
private fun sendTokenToServer(token: String) {
// TODO: Implement your API call
Log.d(TAG, "Would send token to server: $token")
}
}

View File

@ -20,6 +20,7 @@ import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
import com.alya.ecommerce_serang.data.api.dto.StoreAddressResponse
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
@ -81,6 +82,15 @@ interface ApiService {
@Body registerRequest: RegisterRequest
): Response<RegisterResponse>
@GET("checkstore")
suspend fun checkStore (): Response<CheckStoreResponse>
@Multipart
@POST("registerstore")
suspend fun registerStore(
): Response<>
@POST("otp")
suspend fun getOTP(
@Body otpRequest: OtpRequest

View File

@ -131,102 +131,6 @@ class UserRepository(private val apiService: ApiService) {
}
}
// suspend fun sendChatMessage(
// storeId: Int,
// message: String,
// productId: Int,
// imageFile: File? = null
// ): Result<SendChatResponse> {
// return try {
// // Create multipart request builder
// val requestBodyBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
//
// // Add text fields
// requestBodyBuilder.addFormDataPart("store_id", storeId.toString())
// requestBodyBuilder.addFormDataPart("message", message)
// requestBodyBuilder.addFormDataPart("product_id", productId.toString())
//
// // Add image if it exists
// if (imageFile != null && imageFile.exists()) {
// val requestFile = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
// requestBodyBuilder.addFormDataPart("chatimg", imageFile.name, requestFile)
// }
//
// // Build the final request body
// val requestBody = requestBodyBuilder.build()
//
// // Make the API call using a custom endpoint that takes a plain MultipartBody
// val response = apiService.sendChatLineWithBody(requestBody)
//
// if (response.isSuccessful) {
// response.body()?.let {
// Result.Success(it)
// } ?: Result.Error(Exception("Send chat response is empty"))
// } else {
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
// Log.e("ChatRepository", "HTTP Error: ${response.code()}, Body: $errorBody")
// Result.Error(Exception("API Error: ${response.code()} - $errorBody"))
// }
// } catch (e: Exception) {
// Log.e("ChatRepository", "Exception sending message", e)
// e.printStackTrace()
// Result.Error(e)
// }
// }
//
// /**
// * Updates the status of a message (sent, delivered, read)
// *
// * @param messageId The ID of the message to update
// * @param status The new status to set
// * @return Result containing the updated message details or error
// */
// suspend fun updateMessageStatus(
// messageId: Int,
// status: String
// ): Result<UpdateChatResponse> {
// return try {
// val requestBody = UpdateChatRequest(
// id = messageId,
// status = status
// )
//
// val response = apiService.updateChatStatus(requestBody)
//
// if (response.isSuccessful) {
// response.body()?.let {
// Result.Success(it)
// } ?: Result.Error(Exception("Update status response is empty"))
// } else {
// Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
// }
// } catch (e: Exception) {
// Result.Error(e)
// }
// }
//
// /**
// * Gets the chat history for a specific chat room
// *
// * @param chatRoomId The ID of the chat room
// * @return Result containing the list of chat messages or error
// */
// suspend fun getChatHistory(chatRoomId: Int): Result<ChatHistoryResponse> {
// return try {
// val response = apiService.getChatDetail(chatRoomId)
//
// if (response.isSuccessful) {
// response.body()?.let {
// Result.Success(it)
// } ?: Result.Error(Exception("Chat history response is empty"))
// } else {
// Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
// }
// } catch (e: Exception) {
// Result.Error(e)
// }
// }
companion object{
private const val TAG = "UserRepository"
}

View File

@ -0,0 +1,81 @@
package com.alya.ecommerce_serang.ui.notif.fcm
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import com.alya.ecommerce_serang.R
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class FCMService : FirebaseMessagingService() {
private val TAG = "FCMService"
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d(TAG, "Refreshed FCM token: $token")
// Store the token locally
storeTokenLocally(token)
// Send token to your server
sendTokenToServer(token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d(TAG, "From: ${remoteMessage.from}")
// Handle data payload
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
// Process data payload if needed
}
// Handle notification payload
remoteMessage.notification?.let {
Log.d(TAG, "Message notification: ${it.title} / ${it.body}")
showNotification(it.title, it.body)
}
}
private fun storeTokenLocally(token: String) {
val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
}
private fun sendTokenToServer(token: String) {
// TODO: Implement API call to your server to send the token
// This is a placeholder - you'll need to replace with actual API call to your server
Log.d(TAG, "Token would be sent to server: $token")
}
private fun showNotification(title: String?, body: String?) {
val channelId = "fcm_default_channel"
// Create notification channel for Android O and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"FCM Notifications",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
// Build notification
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.outline_notifications_24) // Make sure this resource exists
.setContentTitle(title ?: "New Message")
.setContentText(body ?: "You have a new notification")
.setAutoCancel(true)
// Show notification
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationId = System.currentTimeMillis().toInt()
notificationManager.notify(notificationId, notificationBuilder.build())
}
}

View File

@ -0,0 +1,29 @@
package com.alya.ecommerce_serang.ui.notif.fcm
import android.content.Context
import android.util.Log
import com.google.firebase.messaging.FirebaseMessaging
object FCMTokenManager {
private const val TAG = "FCMTokenManager"
fun getToken(callback: (String?) -> Unit) {
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
callback(null)
return@addOnCompleteListener
}
val token = task.result
Log.d(TAG, "FCM token retrieved: $token")
callback(token)
}
}
fun getStoredToken(context: Context): String? {
val sharedPreferences = context.getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
return sharedPreferences.getString("FCM_TOKEN", null)
}
}

View File

@ -7,6 +7,7 @@ import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
@ -19,12 +20,10 @@ import com.alya.ecommerce_serang.ui.profile.mystore.product.ProductActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsActivity
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsListFragment
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.bumptech.glide.Glide
import kotlin.getValue
class MyStoreActivity : AppCompatActivity() {
private lateinit var binding: ActivityMyStoreBinding
@ -68,7 +67,7 @@ class MyStoreActivity : AppCompatActivity() {
binding.tvStoreType.text = store.storeType
if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
val imageUrl = "$BASE_URL${store.storeImage}"
Log.d("MyStoreActivity", "Loading store image from: $imageUrl")
Glide.with(this)

View File

@ -1,7 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
classpath ("com.google.gms:google-services:4.4.2")
}
}

View File

@ -1,13 +1,13 @@
[versions]
agp = "8.9.2"
glide = "4.16.0"
hiltAndroid = "2.48" # Updated from 2.44 for better compatibility
hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility
hiltLifecycleViewmodel = "1.0.0-alpha03"
hiltCompiler = "2.48" # Added for consistency
ksp = "1.9.0-1.0.13"
kotlin = "1.9.0"
hiltCompiler = "2.56.2" # Added for consistency
ksp = "2.1.0-1.0.28"
kotlin = "2.1.0"
coreKtx = "1.10.1"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
@ -19,7 +19,7 @@ constraintlayout = "2.1.4"
legacySupportV4 = "1.0.0"
lifecycleLivedataKtx = "2.8.7"
lifecycleViewmodelKtx = "2.8.7"
fragmentKtx = "1.5.6"
fragmentKtx = "1.7.0"
navigationFragmentKtx = "2.8.5"
navigationUiKtx = "2.8.5"
playServicesLocation = "21.3.0"