add notif but not yet in background service

This commit is contained in:
shaulascr
2025-04-23 17:12:45 +07:00
parent dfece2bfdf
commit 2bc4bda536
15 changed files with 698 additions and 28 deletions

View File

@ -2,10 +2,10 @@ import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id("kotlin-kapt")
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
}

View File

@ -6,12 +6,17 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application
android:name=".app.App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@ -23,6 +28,18 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<!-- <provider-->
<!-- android:name="androidx.startup.InitializationProvider"-->
<!-- android:authorities="${applicationId}.androidx-startup"-->
<!-- tools:node="remove" />-->
<service
android:name=".ui.notif.SimpleWebSocketService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<activity
android:name=".ui.notif.NotificationActivity"
android:exported="false" />
<activity
android:name=".ui.order.detail.AddEvidencePaymentActivity"
android:exported="false" />

View File

@ -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)
// }
// }
}

View File

@ -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)
}
}

View File

@ -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<out String>,
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()
}
}
}

View File

@ -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)
}
}

View File

@ -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<UserProfile?>>(Result.Loading)
val userProfile: StateFlow<Result<UserProfile?>> = _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
}
}

View File

@ -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<out String>,
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
}
}
}
}

View File

@ -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()
}
}

View File

@ -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}")
}
}
}

View File

@ -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()
}
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8L11,8v6l4.75,2.85 0.75,-1.23 -4,-2.37L12.5,8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.notif.NotificationActivity">
<Button
android:id="@+id/simple_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="simple notificaton"/>
<Button
android:id="@+id/update_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="update notificaton"/>
<Button
android:id="@+id/cancel_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="cancel notificaton"/>
</LinearLayout>

View File

@ -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")
}
}

View File

@ -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" }