mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
add notif but not yet in background service
This commit is contained in:
@ -2,10 +2,10 @@ import java.util.Properties
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.jetbrains.kotlin.android)
|
alias(libs.plugins.jetbrains.kotlin.android)
|
||||||
id("kotlin-kapt")
|
alias(libs.plugins.ksp) // Use KSP instead of kapt
|
||||||
id ("androidx.navigation.safeargs")
|
id("androidx.navigation.safeargs")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
// id("com.google.dagger.hilt.android")
|
alias(libs.plugins.dagger.hilt) // Use alias from catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
val localProperties = Properties().apply {
|
val localProperties = Properties().apply {
|
||||||
@ -96,11 +96,27 @@ dependencies {
|
|||||||
|
|
||||||
|
|
||||||
// implementation(libs.hilt.android)
|
// implementation(libs.hilt.android)
|
||||||
// kapt("com.google.dagger:hilt-compiler:2.48")
|
implementation(libs.hilt.android)
|
||||||
//
|
ksp(libs.hilt.compiler)
|
||||||
// // For ViewModel injection (if needed)
|
|
||||||
// implementation(libs.androidx.hilt.lifecycle.viewmodel)
|
// Androidx Hilt
|
||||||
// kapt("androidx.hilt:hilt-compiler:1.0.0")
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,12 +6,17 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<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_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_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.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
<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
|
<application
|
||||||
|
android:name=".app.App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -23,6 +28,18 @@
|
|||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
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
|
<activity
|
||||||
android:name=".ui.order.detail.AddEvidencePaymentActivity"
|
android:name=".ui.order.detail.AddEvidencePaymentActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
package com.alya.ecommerce_serang.app
|
package com.alya.ecommerce_serang.app
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
//@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class App : Application(){
|
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)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,13 @@
|
|||||||
package com.alya.ecommerce_serang.ui
|
package com.alya.ecommerce_serang.ui
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
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.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
|
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.notif.WebSocketManager
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
//@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var apiService: ApiService
|
private lateinit var apiService: ApiService
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
|
// private val viewModel: NotifViewModel by viewModels()
|
||||||
private val navController by lazy {
|
private val navController by lazy {
|
||||||
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
|
(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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@ -47,9 +65,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
windowInsets
|
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()
|
setupBottomNavigation()
|
||||||
observeDestinationChanges()
|
observeDestinationChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
if (isFinishing) {
|
||||||
|
webSocketManager.stopWebSocketConnection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupBottomNavigation() {
|
private fun setupBottomNavigation() {
|
||||||
@ -78,7 +113,40 @@ class MainActivity : AppCompatActivity() {
|
|||||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||||
binding.bottomNavigation.isVisible = when (destination.id) {
|
binding.bottomNavigation.isVisible = when (destination.id) {
|
||||||
R.id.homeFragment, R.id.chatFragment, R.id.profileFragment -> true
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
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.ui.product.DetailProductActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
|
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
|
||||||
@ -131,7 +132,8 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.searchContainer.btnNotification.setOnClickListener {
|
binding.searchContainer.btnNotification.setOnClickListener {
|
||||||
// Navigate to notifications
|
val intent = Intent(requireContext(), NotificationActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
class SessionManager(context: Context) {
|
class SessionManager(context: Context) {
|
||||||
private var sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
private var sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
@ -10,12 +11,14 @@ class SessionManager(context: Context) {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val PREFS_NAME = "app_prefs"
|
private const val PREFS_NAME = "app_prefs"
|
||||||
private const val USER_TOKEN = "user_token"
|
private const val USER_TOKEN = "user_token"
|
||||||
|
private const val USER_ID = "user_id" // New constant for storing user ID
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToken(token: String) {
|
fun saveToken(token: String) {
|
||||||
val editor = sharedPreferences.edit()
|
sharedPreferences.edit() {
|
||||||
editor.putString(USER_TOKEN, token)
|
putString(USER_TOKEN, token)
|
||||||
editor.apply()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToken(): String? {
|
fun getToken(): String? {
|
||||||
@ -24,9 +27,35 @@ class SessionManager(context: Context) {
|
|||||||
return token
|
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() {
|
fun clearToken() {
|
||||||
val editor = sharedPreferences.edit()
|
sharedPreferences.edit() {
|
||||||
editor.remove(USER_TOKEN)
|
remove(USER_TOKEN)
|
||||||
editor.apply()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//clear data when log out
|
||||||
|
fun clearAll() {
|
||||||
|
sharedPreferences.edit() {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
5
app/src/main/res/drawable/baseline_alarm_24.xml
Normal file
5
app/src/main/res/drawable/baseline_alarm_24.xml
Normal 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>
|
29
app/src/main/res/layout/activity_notification.xml
Normal file
29
app/src/main/res/layout/activity_notification.xml
Normal 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>
|
@ -2,7 +2,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
|
classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1")
|
||||||
// classpath("com.google.dagger:hilt-android-gradle-plugin:2.55")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.5.2"
|
agp = "8.9.2" # 8.7.2 is not an existing version, using latest stable 8.2.0
|
||||||
glide = "4.16.0"
|
hiltAndroid = "2.48" # Updated from 2.44 for better compatibility
|
||||||
hiltAndroid = "2.51"
|
hiltCompiler = "2.48" # Added for consistency
|
||||||
hiltLifecycleViewmodel = "1.0.0-alpha03"
|
ksp = "1.9.0-1.0.13"
|
||||||
kotlin = "1.9.0"
|
kotlin = "1.9.0"
|
||||||
|
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
@ -20,11 +20,10 @@ lifecycleViewmodelKtx = "2.8.7"
|
|||||||
fragmentKtx = "1.5.6"
|
fragmentKtx = "1.5.6"
|
||||||
navigationFragmentKtx = "2.8.5"
|
navigationFragmentKtx = "2.8.5"
|
||||||
navigationUiKtx = "2.8.5"
|
navigationUiKtx = "2.8.5"
|
||||||
recyclerview = "1.4.0"
|
recyclerview = "1.3.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
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" }
|
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
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-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" }
|
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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
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" }
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user