diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d01aae7..8afd6da 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,10 +2,10 @@ import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
- id("kotlin-kapt")
- id ("androidx.navigation.safeargs")
+ alias(libs.plugins.ksp) // Use KSP instead of kapt
+ id("androidx.navigation.safeargs")
id("kotlin-parcelize")
-// id("com.google.dagger.hilt.android")
+ alias(libs.plugins.dagger.hilt) // Use alias from catalog
}
val localProperties = Properties().apply {
@@ -96,11 +96,27 @@ dependencies {
// implementation(libs.hilt.android)
-// kapt("com.google.dagger:hilt-compiler:2.48")
-//
-// // For ViewModel injection (if needed)
-// implementation(libs.androidx.hilt.lifecycle.viewmodel)
-// kapt("androidx.hilt:hilt-compiler:1.0.0")
+ implementation(libs.hilt.android)
+ ksp(libs.hilt.compiler)
+
+ // Androidx Hilt
+ implementation(libs.androidx.hilt.navigation.fragment)
+ implementation(libs.androidx.hilt.work)
+ ksp(libs.androidx.hilt.compiler)
+
+ implementation("androidx.work:work-runtime-ktx:2.8.1")
+ implementation("androidx.work:work-runtime:2.8.1")
+
+ implementation("io.ktor:ktor-client-android:3.0.1")
+ implementation("io.ktor:ktor-client-core:3.0.1")
+ implementation("io.ktor:ktor-client-websockets:3.0.1")
+ implementation("io.ktor:ktor-client-logging:3.0.1")
+ implementation("io.ktor:ktor-client-okhttp:3.0.1")
+ implementation("io.ktor:ktor-client-content-negotiation:3.0.1")
+ implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.1")
+
+ implementation("io.socket:socket.io-client:2.1.0") // or latest version
+
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4bae0a0..5af131b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,12 +6,17 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/app/App.kt b/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
index 31a11eb..11361c4 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
@@ -1,7 +1,18 @@
package com.alya.ecommerce_serang.app
import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
-//@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)
+// }
+// }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/di/NotificationModule.kt b/app/src/main/java/com/alya/ecommerce_serang/di/NotificationModule.kt
new file mode 100644
index 0000000..b982df2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/di/NotificationModule.kt
@@ -0,0 +1,89 @@
+package com.alya.ecommerce_serang.di
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.graphics.Color
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.alya.ecommerce_serang.data.repository.UserRepository
+import com.alya.ecommerce_serang.utils.SessionManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NotificationModule {
+
+ @Provides
+ @Singleton
+ fun provideContext(@ApplicationContext context: Context): Context {
+ return context
+ }
+
+ @Provides
+ @Singleton
+ fun provideSessionManager(@ApplicationContext context: Context): SessionManager {
+ return SessionManager(context)
+ }
+
+ @Provides
+ @Singleton
+ fun provideApiService(sessionManager: SessionManager): ApiService {
+ return ApiConfig.getApiService(sessionManager)
+ }
+
+ @Provides
+ @Singleton
+ fun provideUserRepository(apiService: ApiService): UserRepository {
+ return UserRepository(apiService)
+ }
+
+ @Singleton
+ @Provides
+ fun provideNotificationBuilder(
+ @ApplicationContext context: Context
+ ): NotificationCompat.Builder {
+ // Create a unique channel ID for your app
+ val channelId = "websocket_notifications"
+
+ // Ensure the notification channel exists
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ channelId,
+ "WebSocket Notifications",
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ description = "Notifications received via WebSocket"
+ enableLights(true)
+ lightColor = Color.BLUE
+ enableVibration(true)
+ vibrationPattern = longArrayOf(0, 1000, 500, 1000)
+ }
+
+ val notificationManager = context.getSystemService(NotificationManager::class.java)
+ notificationManager.createNotificationChannel(channel)
+ }
+
+ return NotificationCompat.Builder(context, channelId)
+ .setSmallIcon(R.drawable.baseline_alarm_24)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setAutoCancel(true)
+ }
+
+ @Singleton
+ @Provides
+ fun provideNotificationManager(
+ @ApplicationContext context: Context
+ ): NotificationManagerCompat {
+ return NotificationManagerCompat.from(context)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
index 0500df0..0c93bc6 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
@@ -1,8 +1,13 @@
package com.alya.ecommerce_serang.ui
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
+import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@@ -13,18 +18,31 @@ import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
+import com.alya.ecommerce_serang.ui.notif.WebSocketManager
import com.alya.ecommerce_serang.utils.SessionManager
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
-//@AndroidEntryPoint
+@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
+// private val viewModel: NotifViewModel by viewModels()
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
+
+ companion object{
+ private const val NOTIFICATION_PERMISSION_CODE = 100
+ }
+
+ @Inject
+ lateinit var webSocketManager: WebSocketManager
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -47,9 +65,26 @@ class MainActivity : AppCompatActivity() {
windowInsets
}
+ requestNotificationPermissionIfNeeded()
+
+ // Start WebSocket service through WebSocketManager after permission check
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
+ webSocketManager.startWebSocketConnection()
+ }
+ } else {
+ webSocketManager.startWebSocketConnection()
+ }
+
setupBottomNavigation()
observeDestinationChanges()
+ }
+ override fun onDestroy() {
+ super.onDestroy()
+ if (isFinishing) {
+ webSocketManager.stopWebSocketConnection()
+ }
}
private fun setupBottomNavigation() {
@@ -78,7 +113,40 @@ class MainActivity : AppCompatActivity() {
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.bottomNavigation.isVisible = when (destination.id) {
R.id.homeFragment, R.id.chatFragment, R.id.profileFragment -> true
- else -> false // Bottom Navigation tidak terlihat di layar lain
+ else -> false
+ }
+ }
+ }
+
+ private fun requestNotificationPermissionIfNeeded() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
+ NOTIFICATION_PERMISSION_CODE
+ )
+ }
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == NOTIFICATION_PERMISSION_CODE) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "Notification permission granted", Toast.LENGTH_SHORT).show()
+ webSocketManager.startWebSocketConnection()
+ } else {
+ Toast.makeText(this, "Notification permission denied", Toast.LENGTH_SHORT).show()
}
}
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
index 381ad69..c19d02e 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt
@@ -21,6 +21,7 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
+import com.alya.ecommerce_serang.ui.notif.NotificationActivity
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
@@ -131,7 +132,8 @@ class HomeFragment : Fragment() {
}
binding.searchContainer.btnNotification.setOnClickListener {
- // Navigate to notifications
+ val intent = Intent(requireContext(), NotificationActivity::class.java)
+ startActivity(intent)
}
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt
new file mode 100644
index 0000000..d2040c4
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt
@@ -0,0 +1,67 @@
+package com.alya.ecommerce_serang.ui.notif
+
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.dto.UserProfile
+import com.alya.ecommerce_serang.data.repository.Result
+import com.alya.ecommerce_serang.data.repository.UserRepository
+import com.alya.ecommerce_serang.utils.SessionManager
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class NotifViewModel @Inject constructor(
+ private val notificationBuilder: NotificationCompat.Builder,
+ private val notificationManager: NotificationManagerCompat,
+ @ApplicationContext private val context: Context,
+ private val userRepository: UserRepository,
+ private val webSocketManager: WebSocketManager,
+ private val sessionManager: SessionManager
+
+) : ViewModel() {
+
+ private val _userProfile = MutableStateFlow>(Result.Loading)
+ val userProfile: StateFlow> = _userProfile.asStateFlow()
+
+ init {
+ fetchUserProfile()
+ }
+
+ // Fetch user profile to get necessary data
+ fun fetchUserProfile() {
+ viewModelScope.launch {
+ _userProfile.value = Result.Loading
+ val result = userRepository.fetchUserProfile()
+ _userProfile.value = result
+
+ // If successful, save the user ID for WebSocket use
+ if (result is Result.Success && result.data != null) {
+ sessionManager.saveUserId(result.data.userId.toString())
+ }
+ }
+ }
+
+ // Start WebSocket connection
+ fun startWebSocketConnection() {
+ webSocketManager.startWebSocketConnection()
+ }
+
+ // Stop WebSocket connection
+ fun stopWebSocketConnection() {
+ webSocketManager.stopWebSocketConnection()
+ }
+
+ // Call when ViewModel is cleared (e.g., app closing)
+ override fun onCleared() {
+ super.onCleared()
+ // No need to stop here - the service will manage its own lifecycle
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt
new file mode 100644
index 0000000..1e22402
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt
@@ -0,0 +1,118 @@
+package com.alya.ecommerce_serang.ui.notif
+
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.alya.ecommerce_serang.data.repository.Result
+import com.alya.ecommerce_serang.databinding.ActivityNotificationBinding
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint // Required for Hilt
+class NotificationActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityNotificationBinding
+ private val viewModel: NotifViewModel by viewModels()
+
+ // Permission request code
+ private val NOTIFICATION_PERMISSION_CODE = 100
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.userProfile.collect { result ->
+ when (result) {
+ is com.alya.ecommerce_serang.data.repository.Result.Success -> {
+ // User profile loaded successfully
+ // Potentially do something with user profile
+ }
+ is com.alya.ecommerce_serang.data.repository.Result.Error -> {
+ // Handle error - show message, etc.
+ Toast.makeText(this@NotificationActivity,
+ "Failed to load profile",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ Result.Loading -> {
+ // Show loading indicator if needed
+ }
+ }
+ }
+ }
+ }
+
+ // Start WebSocket connection
+// viewModel.startWebSocketConnection()
+
+ binding = ActivityNotificationBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ // Check and request notification permission for Android 13+
+ requestNotificationPermissionIfNeeded()
+
+ // Set up button click listeners
+// setupButtonListeners()
+
+
+ }
+
+// private fun setupButtonListeners() {
+// binding.simpleNotification.setOnClickListener {
+// viewModel.showSimpleNotification()
+// }
+//
+// binding.updateNotification.setOnClickListener {
+// viewModel.updateSimpleNotification()
+// }
+//
+// binding.cancelNotification.setOnClickListener {
+// viewModel.cancelSimpleNotification()
+// }
+// }
+
+ private fun requestNotificationPermissionIfNeeded() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
+ NOTIFICATION_PERMISSION_CODE
+ )
+ }
+ }
+ }
+
+ // Handle permission request result
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == NOTIFICATION_PERMISSION_CODE) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission granted
+ Toast.makeText(this, "Notification permission granted", Toast.LENGTH_SHORT).show()
+ } else {
+ // Permission denied
+ Toast.makeText(this, "Notification permission denied", Toast.LENGTH_SHORT).show()
+ // You might want to show a dialog explaining why notifications are important
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/SimpleWebSocketService.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/SimpleWebSocketService.kt
new file mode 100644
index 0000000..49913e7
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/SimpleWebSocketService.kt
@@ -0,0 +1,162 @@
+package com.alya.ecommerce_serang.ui.notif
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.content.ContextCompat
+import com.alya.ecommerce_serang.BuildConfig
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.utils.SessionManager
+import dagger.hilt.android.AndroidEntryPoint
+import io.socket.client.IO
+import io.socket.client.Socket
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class SimpleWebSocketService : Service() {
+
+ companion object {
+ private const val TAG = "SocketIOService"
+ private const val NOTIFICATION_CHANNEL_ID = "websocket_service_channel"
+ private const val FOREGROUND_SERVICE_ID = 1001
+ }
+
+ @Inject
+ lateinit var notificationBuilder: NotificationCompat.Builder
+
+ @Inject
+ lateinit var notificationManager: NotificationManagerCompat
+
+ @Inject
+ lateinit var sessionManager: SessionManager
+
+ private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ private var socket: Socket? = null
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG, "Service created")
+ createNotificationChannel()
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+// .setSmallIcon(R.drawable.ic_notification) // Replace with your app's icon
+ .setPriority(NotificationCompat.PRIORITY_MIN) // Set the lowest priority
+ .setSound(null) // No sound
+ .setVibrate(longArrayOf(0L)) // No vibration
+ .setContentText("") // Empty text or minimal text
+ .setOngoing(true) // Keeps it ongoing
+ .build()
+
+ startForeground(1, notification)
+
+
+ startForeground(FOREGROUND_SERVICE_ID, notification)
+ serviceScope.launch { initSocket() }
+ return START_STICKY
+ }
+
+ private suspend fun initSocket() {
+ val userId = sessionManager.getUserId() ?: run {
+ Log.e(TAG, "User ID not available")
+ stopSelf()
+ return
+ }
+
+ val options = IO.Options().apply {
+ forceNew = true
+ reconnection = true
+ reconnectionDelay = 1000 // Retry every 1 second if disconnected
+ reconnectionAttempts = Int.MAX_VALUE
+ }
+
+ socket = IO.socket(BuildConfig.BASE_URL, options)
+ socket?.apply {
+ on(Socket.EVENT_CONNECT) {
+ Log.d(TAG, "Socket.IO connected")
+ emit("joinRoom", userId)
+ }
+
+ on("notification") { args ->
+ if (args.isNotEmpty()) {
+ val data = args[0] as? JSONObject
+ val title = data?.optString("title", "New Notification") ?: "Notification"
+ val message = data?.optString("message", "") ?: ""
+ showNotification(title, message)
+ }
+ }
+
+ on(Socket.EVENT_DISCONNECT) {
+ Log.d(TAG, "Socket.IO disconnected")
+ }
+
+ on(Socket.EVENT_CONNECT_ERROR) { args ->
+ Log.e(TAG, "Socket.IO connection error: ${args.firstOrNull()}")
+ }
+
+ connect()
+ }
+ }
+
+ private fun showNotification(title: String, message: String) {
+ val notification = notificationBuilder
+ .setContentTitle(title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setSmallIcon(R.drawable.baseline_alarm_24)
+ .build()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ android.Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ notificationManager.notify(System.currentTimeMillis().toInt(), notification)
+ } else {
+ Log.e(TAG, "Notification permission not granted")
+ }
+ } else {
+ notificationManager.notify(System.currentTimeMillis().toInt(), notification)
+ }
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ "WebSocket Service Channel",
+ NotificationManager.IMPORTANCE_LOW
+ ).apply {
+ description = "Channel for WebSocket Service"
+ }
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ manager.createNotificationChannel(channel)
+ }
+ }
+
+ override fun onDestroy() {
+ Log.d(TAG, "Service destroyed")
+ socket?.disconnect()
+ socket?.off()
+ serviceScope.cancel()
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/WebSocketManager.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/WebSocketManager.kt
new file mode 100644
index 0000000..f80f46b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/WebSocketManager.kt
@@ -0,0 +1,51 @@
+package com.alya.ecommerce_serang.ui.notif
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.util.Log
+import com.alya.ecommerce_serang.utils.SessionManager
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class WebSocketManager @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val sessionManager: SessionManager
+) {
+ companion object {
+ private const val TAG = "WebSocketManager"
+
+ }
+
+ fun startWebSocketConnection() {
+ try {
+ // Only start if we have a token
+ if (sessionManager.getToken().isNullOrEmpty()) {
+ Log.d(TAG, "No auth token available, not starting WebSocket service")
+ return
+ }
+
+ Log.d(TAG, "Starting WebSocket service")
+ val serviceIntent = Intent(context, SimpleWebSocketService::class.java)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(serviceIntent)
+ } else {
+ context.startService(serviceIntent)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error starting WebSocket service: ${e.message}")
+ }
+ }
+
+ fun stopWebSocketConnection() {
+ try {
+ Log.d(TAG, "Stopping WebSocket service")
+ context.stopService(Intent(context, SimpleWebSocketService::class.java))
+ } catch (e: Exception) {
+ Log.e(TAG, "Error stopping WebSocket service: ${e.message}")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/SessionManager.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/SessionManager.kt
index 6feaa5c..f04b37e 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/utils/SessionManager.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/utils/SessionManager.kt
@@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.utils
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
+import androidx.core.content.edit
class SessionManager(context: Context) {
private var sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
@@ -10,12 +11,14 @@ class SessionManager(context: Context) {
companion object {
private const val PREFS_NAME = "app_prefs"
private const val USER_TOKEN = "user_token"
+ private const val USER_ID = "user_id" // New constant for storing user ID
+
}
fun saveToken(token: String) {
- val editor = sharedPreferences.edit()
- editor.putString(USER_TOKEN, token)
- editor.apply()
+ sharedPreferences.edit() {
+ putString(USER_TOKEN, token)
+ }
}
fun getToken(): String? {
@@ -24,9 +27,35 @@ class SessionManager(context: Context) {
return token
}
+ fun saveUserId(userId: String) {
+ sharedPreferences.edit() {
+ putString(USER_ID, userId)
+ }
+ Log.d("SessionManager", "Saved user ID: $userId")
+ }
+
+ fun getUserId(): String? {
+ val userId = sharedPreferences.getString(USER_ID, null)
+ Log.d("SessionManager", "Retrieved user ID: $userId")
+ return userId
+ }
+
+ fun clearUserId() {
+ sharedPreferences.edit() {
+ remove(USER_ID)
+ }
+ }
+
fun clearToken() {
- val editor = sharedPreferences.edit()
- editor.remove(USER_TOKEN)
- editor.apply()
+ sharedPreferences.edit() {
+ remove(USER_TOKEN)
+ }
+ }
+
+ //clear data when log out
+ fun clearAll() {
+ sharedPreferences.edit() {
+ clear()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/baseline_alarm_24.xml b/app/src/main/res/drawable/baseline_alarm_24.xml
new file mode 100644
index 0000000..59acdcb
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_alarm_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml
new file mode 100644
index 0000000..e821fd0
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 1bd531d..0f35cfd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,7 +2,6 @@
buildscript {
dependencies {
classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
-// classpath("com.google.dagger:hilt-android-gradle-plugin:2.55")
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e967142..8b204e3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,8 +1,8 @@
[versions]
-agp = "8.5.2"
-glide = "4.16.0"
-hiltAndroid = "2.51"
-hiltLifecycleViewmodel = "1.0.0-alpha03"
+agp = "8.9.2" # 8.7.2 is not an existing version, using latest stable 8.2.0
+hiltAndroid = "2.48" # Updated from 2.44 for better compatibility
+hiltCompiler = "2.48" # Added for consistency
+ksp = "1.9.0-1.0.13"
kotlin = "1.9.0"
coreKtx = "1.10.1"
@@ -20,11 +20,10 @@ lifecycleViewmodelKtx = "2.8.7"
fragmentKtx = "1.5.6"
navigationFragmentKtx = "2.8.5"
navigationUiKtx = "2.8.5"
-recyclerview = "1.4.0"
+recyclerview = "1.3.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
-androidx-hilt-lifecycle-viewmodel = { module = "androidx.hilt:hilt-lifecycle-viewmodel", version.ref = "hiltLifecycleViewmodel" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -41,7 +40,15 @@ androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", ve
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
+hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltCompiler" }
+androidx-hilt-common = { module = "androidx.hilt:hilt-common", version = "1.0.0" }
+androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version = "1.0.0" }
+androidx-hilt-navigation-fragment = { module = "androidx.hilt:hilt-navigation-fragment", version = "1.0.0" }
+androidx-hilt-work = { module = "androidx.hilt:hilt-work", version = "1.0.0" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }