mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
add list notif
This commit is contained in:
@ -0,0 +1,33 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.auth
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ListNotifResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("notif")
|
||||||
|
val notif: List<NotifItem>,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class NotifItem(
|
||||||
|
|
||||||
|
@field:SerializedName("user_id")
|
||||||
|
val userId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("created_at")
|
||||||
|
val createdAt: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("title")
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("type")
|
||||||
|
val type: String
|
||||||
|
)
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.auth
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ListStoreNotifResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("notifstore")
|
||||||
|
val notifstore: List<NotifstoreItem>,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class NotifstoreItem(
|
||||||
|
|
||||||
|
@field:SerializedName("user_id")
|
||||||
|
val userId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("created_at")
|
||||||
|
val createdAt: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("title")
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("type")
|
||||||
|
val type: String
|
||||||
|
)
|
@ -26,6 +26,8 @@ import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
|
|||||||
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.ListNotifResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreNotifResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
@ -454,4 +456,12 @@ interface ApiService {
|
|||||||
@GET("chat")
|
@GET("chat")
|
||||||
suspend fun getChatList(
|
suspend fun getChatList(
|
||||||
): Response<ChatListResponse>
|
): Response<ChatListResponse>
|
||||||
|
|
||||||
|
@GET("notification")
|
||||||
|
suspend fun getNotif(
|
||||||
|
): Response<ListNotifResponse>
|
||||||
|
|
||||||
|
@GET("mystore/notification")
|
||||||
|
suspend fun getNotifStore(
|
||||||
|
): Response<ListStoreNotifResponse>
|
||||||
}
|
}
|
@ -13,6 +13,8 @@ import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
|
|||||||
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifstoreItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
|
||||||
@ -345,6 +347,36 @@ class UserRepository(private val apiService: ApiService) {
|
|||||||
return apiService.updateFcm(request)
|
return apiService.updateFcm(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getListNotif(): Result<List<NotifItem>> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getNotif()
|
||||||
|
|
||||||
|
if (response.isSuccessful){
|
||||||
|
val chat = response.body()?.notif ?: emptyList()
|
||||||
|
Result.Success(chat)
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Failed to fetch list notif. Code: ${response.code()}"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception){
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getListNotifStore(): Result<List<NotifstoreItem>> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getNotifStore()
|
||||||
|
|
||||||
|
if (response.isSuccessful){
|
||||||
|
val chat = response.body()?.notifstore ?: emptyList()
|
||||||
|
Result.Success(chat)
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Failed to fetch list notif. Code: ${response.code()}"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception){
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
companion object{
|
companion object{
|
||||||
private const val TAG = "UserRepository"
|
private const val TAG = "UserRepository"
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,136 @@
|
|||||||
package com.alya.ecommerce_serang.ui.notif
|
package com.alya.ecommerce_serang.ui.notif
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationCompat
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifstoreItem
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
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 kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class NotifViewModel @Inject constructor(
|
class NotifViewModel @Inject constructor(
|
||||||
private val notificationBuilder: NotificationCompat.Builder,
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val webSocketManager: WebSocketManager,
|
|
||||||
private val sessionManager: SessionManager
|
private val sessionManager: SessionManager
|
||||||
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _userProfile = MutableStateFlow<Result<UserProfile?>>(Result.Loading)
|
private val _notifList = MutableLiveData<Result<List<NotifItem>>>()
|
||||||
val userProfile: StateFlow<Result<UserProfile?>> = _userProfile.asStateFlow()
|
val notifList: LiveData<Result<List<NotifItem>>> = _notifList
|
||||||
|
|
||||||
init {
|
private val _checkStore = MutableLiveData<Boolean>()
|
||||||
fetchUserProfile()
|
val checkStore: LiveData<Boolean> = _checkStore
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch user profile to get necessary data
|
private val _notifStoreList = MutableLiveData<Result<List<NotifstoreItem>>>()
|
||||||
fun fetchUserProfile() {
|
val notifStoreList: LiveData<Result<List<NotifstoreItem>>> = _notifStoreList
|
||||||
|
|
||||||
|
fun getNotifList() {
|
||||||
|
Log.d(TAG, "getNotifList: Fetching personal notifications")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_userProfile.value = Result.Loading
|
try {
|
||||||
val result = userRepository.fetchUserProfile()
|
Log.d(TAG, "getNotifList: Setting state to Loading")
|
||||||
_userProfile.value = result
|
_notifList.value = Result.Loading
|
||||||
|
|
||||||
// If successful, save the user ID for WebSocket use
|
Log.d(TAG, "getNotifList: Calling repository to get notifications")
|
||||||
if (result is Result.Success && result.data != null) {
|
val result = userRepository.getListNotif()
|
||||||
sessionManager.saveUserId(result.data.userId.toString())
|
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d(TAG, "getNotifList: Success, received ${result.data?.size ?: 0} notifications")
|
||||||
|
if (result.data != null && result.data.isNotEmpty()) {
|
||||||
|
Log.d(TAG, "getNotifList: First notification - id: ${result.data[0].id}, title: ${result.data[0].title}")
|
||||||
|
if (result.data.size > 1) {
|
||||||
|
Log.d(TAG, "getNotifList: Last notification - id: ${result.data[result.data.size-1].id}, title: ${result.data[result.data.size-1].title}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "getNotifList: Error fetching notifications", result.exception)
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
Log.d(TAG, "getNotifList: State is Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifList.value = result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "getNotifList: Unexpected error", e)
|
||||||
|
_notifList.value = Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start WebSocket connection
|
fun getNotifStoreList() {
|
||||||
fun startWebSocketConnection() {
|
Log.d(TAG, "getNotifStoreList: Fetching store notifications")
|
||||||
webSocketManager.startWebSocketConnection()
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "getNotifStoreList: Setting state to Loading")
|
||||||
|
_notifStoreList.value = Result.Loading
|
||||||
|
|
||||||
|
Log.d(TAG, "getNotifStoreList: Calling repository to get store notifications")
|
||||||
|
val result = userRepository.getListNotifStore()
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d(TAG, "getNotifStoreList: Success, received ${result.data?.size ?: 0} store notifications")
|
||||||
|
if (result.data != null && result.data.isNotEmpty()) {
|
||||||
|
Log.d(TAG, "getNotifStoreList: First store notification - id: ${result.data[0].id}, title: ${result.data[0].title}")
|
||||||
|
if (result.data.size > 1) {
|
||||||
|
Log.d(TAG, "getNotifStoreList: Last store notification - id: ${result.data[result.data.size-1].id}, title: ${result.data[result.data.size-1].title}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "getNotifStoreList: Error fetching store notifications", result.exception)
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
Log.d(TAG, "getNotifStoreList: State is Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_notifStoreList.value = result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "getNotifStoreList: Unexpected error", e)
|
||||||
|
_notifStoreList.value = Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop WebSocket connection
|
fun checkStoreUser() {
|
||||||
fun stopWebSocketConnection() {
|
Log.d(TAG, "checkStoreUser: Checking if user has a store")
|
||||||
webSocketManager.stopWebSocketConnection()
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
// Call the repository function to check store
|
||||||
|
Log.d(TAG, "checkStoreUser: Calling repository to check store")
|
||||||
|
val response: HasStoreResponse = userRepository.checkStore()
|
||||||
|
|
||||||
|
// Log and store success message
|
||||||
|
Log.d(TAG, "checkStoreUser: Response received, hasStore=${response.hasStore}")
|
||||||
|
_checkStore.value = response.hasStore // Store the value for UI feedback
|
||||||
|
Log.d(TAG, "checkStoreUser: Updated _checkStore value to ${response.hasStore}")
|
||||||
|
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// Handle any errors and update state
|
||||||
|
Log.e(TAG, "checkStoreUser: Error checking store", exception)
|
||||||
|
_checkStore.value = false
|
||||||
|
Log.d(TAG, "checkStoreUser: Set _checkStore to false due to error")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call when ViewModel is cleared (e.g., app closing)
|
companion object {
|
||||||
override fun onCleared() {
|
private const val TAG = "NotifViewModel" // Constant for logging tag
|
||||||
super.onCleared()
|
|
||||||
// No need to stop here - the service will manage its own lifecycle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,118 +1,324 @@
|
|||||||
package com.alya.ecommerce_serang.ui.notif
|
package com.alya.ecommerce_serang.ui.notif
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
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.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityNotificationBinding
|
import com.alya.ecommerce_serang.databinding.ActivityNotificationBinding
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@AndroidEntryPoint // Required for Hilt
|
private const val TAG = "NotificationActivity"
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class NotificationActivity : AppCompatActivity() {
|
class NotificationActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityNotificationBinding
|
private lateinit var binding: ActivityNotificationBinding
|
||||||
private val viewModel: NotifViewModel by viewModels()
|
private val viewModel: NotifViewModel by viewModels()
|
||||||
|
|
||||||
// Permission request code
|
private lateinit var personalAdapter: PersonalNotificationAdapter
|
||||||
private val NOTIFICATION_PERMISSION_CODE = 100
|
private lateinit var storeAdapter: StoreNotificationAdapter
|
||||||
|
|
||||||
|
private var hasStore = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d(TAG, "onCreate: Starting NotificationActivity")
|
||||||
|
binding = ActivityNotificationBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
lifecycleScope.launch {
|
setupToolbar()
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
setupAdapters()
|
||||||
viewModel.userProfile.collect { result ->
|
setupTabLayout()
|
||||||
when (result) {
|
setupSwipeRefresh()
|
||||||
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
setupObservers()
|
||||||
// User profile loaded successfully
|
|
||||||
// Potentially do something with user profile
|
// Load initial data
|
||||||
|
Log.d(TAG, "onCreate: Checking if user has a store")
|
||||||
|
viewModel.checkStoreUser()
|
||||||
|
Log.d(TAG, "onCreate: Loading personal notifications")
|
||||||
|
viewModel.getNotifList()
|
||||||
|
|
||||||
|
// Show personal notifications by default
|
||||||
|
binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupToolbar() {
|
||||||
|
binding.btnBack.setOnClickListener {
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAdapters() {
|
||||||
|
Log.d(TAG, "setupAdapters: Creating adapters")
|
||||||
|
|
||||||
|
// Create LayoutManager explicitly
|
||||||
|
val layoutManager = LinearLayoutManager(this)
|
||||||
|
Log.d(TAG, "setupAdapters: Created LinearLayoutManager")
|
||||||
|
|
||||||
|
// Personal notifications adapter
|
||||||
|
personalAdapter = PersonalNotificationAdapter { notifItem ->
|
||||||
|
// Handle personal notification click
|
||||||
|
Log.d(TAG, "Personal notification clicked: id=${notifItem.id}, type=${notifItem.type}")
|
||||||
|
}
|
||||||
|
Log.d(TAG, "setupAdapters: Created personalAdapter")
|
||||||
|
|
||||||
|
// Store notifications adapter
|
||||||
|
storeAdapter = StoreNotificationAdapter { storeNotifItem ->
|
||||||
|
// Handle store notification click
|
||||||
|
Log.d(TAG, "Store notification clicked: id=${storeNotifItem.id}, type=${storeNotifItem.type}")
|
||||||
|
}
|
||||||
|
Log.d(TAG, "setupAdapters: Created storeAdapter")
|
||||||
|
|
||||||
|
// Configure RecyclerView with explicit steps
|
||||||
|
binding.recyclerViewNotif.setHasFixedSize(true)
|
||||||
|
binding.recyclerViewNotif.layoutManager = layoutManager
|
||||||
|
binding.recyclerViewNotif.adapter = personalAdapter
|
||||||
|
|
||||||
|
Log.d(TAG, "setupAdapters: RecyclerView configured with personalAdapter")
|
||||||
|
Log.d(TAG, "setupAdapters: RecyclerView visibility: ${binding.recyclerViewNotif.visibility == View.VISIBLE}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTabLayout() {
|
||||||
|
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
|
Log.d(TAG, "Tab selected: position ${tab.position}")
|
||||||
|
when (tab.position) {
|
||||||
|
0 -> {
|
||||||
|
Log.d(TAG, "Showing personal notifications tab")
|
||||||
|
binding.recyclerViewNotif.adapter = personalAdapter
|
||||||
|
showPersonalNotifications()
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
Log.d(TAG, "Showing store notifications tab, hasStore=$hasStore")
|
||||||
|
binding.recyclerViewNotif.adapter = storeAdapter
|
||||||
|
if (hasStore) {
|
||||||
|
viewModel.getNotifStoreList()
|
||||||
}
|
}
|
||||||
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
showStoreNotifications()
|
||||||
// Handle error - show message, etc.
|
}
|
||||||
Toast.makeText(this@NotificationActivity,
|
}
|
||||||
"Failed to load profile",
|
}
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||||
}
|
|
||||||
Result.Loading -> {
|
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||||
// Show loading indicator if needed
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSwipeRefresh() {
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
|
Log.d(TAG, "Swipe refresh triggered, current tab: ${binding.tabLayout.selectedTabPosition}")
|
||||||
|
when (binding.tabLayout.selectedTabPosition) {
|
||||||
|
0 -> viewModel.getNotifList()
|
||||||
|
1 -> {
|
||||||
|
if (hasStore) {
|
||||||
|
viewModel.getNotifStoreList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
// Observe checkStore to determine if user has a store
|
||||||
|
viewModel.checkStore.observe(this) { hasStoreValue ->
|
||||||
|
Log.d(TAG, "checkStore observed: $hasStoreValue")
|
||||||
|
// Update the local hasStore variable
|
||||||
|
hasStore = hasStoreValue
|
||||||
|
|
||||||
|
// If we're on the store tab, update UI based on hasStore value
|
||||||
|
if (binding.tabLayout.selectedTabPosition == 1) {
|
||||||
|
if (hasStore) {
|
||||||
|
Log.d(TAG, "User has store, loading store notifications")
|
||||||
|
viewModel.getNotifStoreList()
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "User doesn't have store, showing empty state")
|
||||||
|
showEmptyState("Anda belum memiliki toko", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe personal notifications
|
||||||
|
viewModel.notifList.observe(this) { result ->
|
||||||
|
Log.d(TAG, "notifList observed: ${result.javaClass.simpleName}")
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
|
||||||
|
if (binding.tabLayout.selectedTabPosition == 0) {
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
val notifications = result.data
|
||||||
|
Log.d(TAG, "Personal notifications received: ${notifications?.size ?: 0}")
|
||||||
|
if (notifications.isNullOrEmpty()) {
|
||||||
|
showEmptyState("Belum Ada Notifikasi", false)
|
||||||
|
} else {
|
||||||
|
hideEmptyState()
|
||||||
|
// Ensure adapter is attached
|
||||||
|
if (binding.recyclerViewNotif.adapter != personalAdapter) {
|
||||||
|
Log.d(TAG, "Re-attaching personalAdapter to RecyclerView")
|
||||||
|
binding.recyclerViewNotif.adapter = personalAdapter
|
||||||
|
}
|
||||||
|
personalAdapter.submitList(notifications)
|
||||||
|
// Force a layout pass
|
||||||
|
binding.recyclerViewNotif.post {
|
||||||
|
Log.d(TAG, "Forcing layout pass on RecyclerView")
|
||||||
|
binding.recyclerViewNotif.requestLayout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "Error loading personal notifications", result.exception)
|
||||||
|
showEmptyState("Gagal memuat notifikasi", false)
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
Log.d(TAG, "Loading personal notifications")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start WebSocket connection
|
// Observe store notifications
|
||||||
// viewModel.startWebSocketConnection()
|
viewModel.notifStoreList.observe(this) { result ->
|
||||||
|
Log.d(TAG, "notifStoreList observed: ${result.javaClass.simpleName}")
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
|
||||||
binding = ActivityNotificationBinding.inflate(layoutInflater)
|
if (binding.tabLayout.selectedTabPosition == 1) {
|
||||||
setContentView(binding.root)
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
// Check and request notification permission for Android 13+
|
val notifications = result.data
|
||||||
requestNotificationPermissionIfNeeded()
|
Log.d(TAG, "Store notifications received: ${notifications?.size ?: 0}, hasStore=$hasStore")
|
||||||
|
if (!hasStore) {
|
||||||
// Set up button click listeners
|
showEmptyState("Anda belum memiliki toko", true)
|
||||||
// setupButtonListeners()
|
} else if (notifications.isNullOrEmpty()) {
|
||||||
|
showEmptyState("Belum Ada Notifikasi Toko", false)
|
||||||
|
} else {
|
||||||
}
|
hideEmptyState()
|
||||||
|
// Ensure adapter is attached
|
||||||
// private fun setupButtonListeners() {
|
if (binding.recyclerViewNotif.adapter != storeAdapter) {
|
||||||
// binding.simpleNotification.setOnClickListener {
|
Log.d(TAG, "Re-attaching storeAdapter to RecyclerView")
|
||||||
// viewModel.showSimpleNotification()
|
binding.recyclerViewNotif.adapter = storeAdapter
|
||||||
// }
|
}
|
||||||
//
|
storeAdapter.submitList(notifications)
|
||||||
// binding.updateNotification.setOnClickListener {
|
// Force a layout pass
|
||||||
// viewModel.updateSimpleNotification()
|
binding.recyclerViewNotif.post {
|
||||||
// }
|
Log.d(TAG, "Forcing layout pass on RecyclerView")
|
||||||
//
|
binding.recyclerViewNotif.requestLayout()
|
||||||
// binding.cancelNotification.setOnClickListener {
|
}
|
||||||
// viewModel.cancelSimpleNotification()
|
}
|
||||||
// }
|
}
|
||||||
// }
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "Error loading store notifications", result.exception)
|
||||||
private fun requestNotificationPermissionIfNeeded() {
|
showEmptyState("Gagal memuat notifikasi toko", false)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
}
|
||||||
if (ContextCompat.checkSelfPermission(
|
is Result.Loading -> {
|
||||||
this,
|
Log.d(TAG, "Loading store notifications")
|
||||||
android.Manifest.permission.POST_NOTIFICATIONS
|
}
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
}
|
||||||
) {
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
this,
|
|
||||||
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
|
|
||||||
NOTIFICATION_PERMISSION_CODE
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle permission request result
|
private fun showPersonalNotifications() {
|
||||||
override fun onRequestPermissionsResult(
|
Log.d(TAG, "showPersonalNotifications called")
|
||||||
requestCode: Int,
|
val result = viewModel.notifList.value
|
||||||
permissions: Array<out String>,
|
|
||||||
grantResults: IntArray
|
|
||||||
) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
|
|
||||||
if (requestCode == NOTIFICATION_PERMISSION_CODE) {
|
if (result is Result.Success) {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
val notifications = result.data
|
||||||
// Permission granted
|
Log.d(TAG, "showPersonalNotifications: Success with ${notifications?.size ?: 0} notifications")
|
||||||
Toast.makeText(this, "Notification permission granted", Toast.LENGTH_SHORT).show()
|
if (notifications.isNullOrEmpty()) {
|
||||||
|
showEmptyState("Belum Ada Notifikasi", false)
|
||||||
} else {
|
} else {
|
||||||
// Permission denied
|
hideEmptyState()
|
||||||
Toast.makeText(this, "Notification permission denied", Toast.LENGTH_SHORT).show()
|
if (binding.recyclerViewNotif.adapter != personalAdapter) {
|
||||||
// You might want to show a dialog explaining why notifications are important
|
Log.d(TAG, "Re-attaching personalAdapter to RecyclerView")
|
||||||
|
binding.recyclerViewNotif.adapter = personalAdapter
|
||||||
|
}
|
||||||
|
personalAdapter.submitList(notifications)
|
||||||
|
// DEBUG: Debug the RecyclerView state
|
||||||
|
Log.d(TAG, "RecyclerView visibility: ${binding.recyclerViewNotif.visibility == View.VISIBLE}")
|
||||||
|
Log.d(TAG, "RecyclerView adapter item count: ${personalAdapter.itemCount}")
|
||||||
}
|
}
|
||||||
|
} else if (result is Result.Error) {
|
||||||
|
Log.e(TAG, "showPersonalNotifications: Error", result.exception)
|
||||||
|
showEmptyState("Gagal memuat notifikasi", false)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "showPersonalNotifications: No data yet, triggering fetch")
|
||||||
|
// If we don't have data yet, trigger a fetch
|
||||||
|
viewModel.getNotifList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showStoreNotifications() {
|
||||||
|
Log.d(TAG, "showStoreNotifications called, hasStore=$hasStore")
|
||||||
|
if (!hasStore) {
|
||||||
|
showEmptyState("Anda belum memiliki toko", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = viewModel.notifStoreList.value
|
||||||
|
|
||||||
|
if (result is Result.Success) {
|
||||||
|
val notifications = result.data
|
||||||
|
Log.d(TAG, "showStoreNotifications: Success with ${notifications?.size ?: 0} notifications")
|
||||||
|
if (notifications.isNullOrEmpty()) {
|
||||||
|
showEmptyState("Belum Ada Notifikasi Toko", false)
|
||||||
|
} else {
|
||||||
|
hideEmptyState()
|
||||||
|
// Ensure adapter is attached
|
||||||
|
if (binding.recyclerViewNotif.adapter != storeAdapter) {
|
||||||
|
Log.d(TAG, "Re-attaching storeAdapter to RecyclerView")
|
||||||
|
binding.recyclerViewNotif.adapter = storeAdapter
|
||||||
|
}
|
||||||
|
storeAdapter.submitList(notifications)
|
||||||
|
// DEBUG: Debug the RecyclerView state
|
||||||
|
Log.d(TAG, "RecyclerView visibility: ${binding.recyclerViewNotif.visibility == View.VISIBLE}")
|
||||||
|
Log.d(TAG, "RecyclerView adapter item count: ${storeAdapter.itemCount}")
|
||||||
|
}
|
||||||
|
} else if (result is Result.Error) {
|
||||||
|
Log.e(TAG, "showStoreNotifications: Error", result.exception)
|
||||||
|
showEmptyState("Gagal memuat notifikasi toko", false)
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "showStoreNotifications: No data yet, triggering fetch")
|
||||||
|
// If we don't have data yet, trigger a fetch
|
||||||
|
viewModel.getNotifStoreList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEmptyState(message: String, showCreateStoreButton: Boolean) {
|
||||||
|
Log.d(TAG, "showEmptyState: message='$message', showCreateStoreButton=$showCreateStoreButton")
|
||||||
|
binding.swipeRefreshLayout.visibility = View.GONE
|
||||||
|
binding.emptyStateLayout.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// Set empty state message
|
||||||
|
binding.tvEmptyTitle.text = message
|
||||||
|
|
||||||
|
// Show "Create Store" button and description if user doesn't have a store
|
||||||
|
if (showCreateStoreButton) {
|
||||||
|
binding.tvEmptyDesc.visibility = View.VISIBLE
|
||||||
|
binding.btnCreateStore.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// Set up create store button click listener
|
||||||
|
binding.btnCreateStore.setOnClickListener {
|
||||||
|
Log.d(TAG, "Create store button clicked")
|
||||||
|
// Navigate to create store screen
|
||||||
|
// Intent to CreateStoreActivity
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.tvEmptyDesc.visibility = View.GONE
|
||||||
|
binding.btnCreateStore.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideEmptyState() {
|
||||||
|
Log.d(TAG, "hideEmptyState called")
|
||||||
|
binding.swipeRefreshLayout.visibility = View.VISIBLE
|
||||||
|
binding.emptyStateLayout.visibility = View.GONE
|
||||||
|
|
||||||
|
// Ensure recycler view is visible
|
||||||
|
binding.recyclerViewNotif.visibility = View.VISIBLE
|
||||||
|
Log.d(TAG, "hideEmptyState: Set RecyclerView visibility to VISIBLE")
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.notif
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifItem
|
||||||
|
import com.alya.ecommerce_serang.databinding.ItemNotificationBinding
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
class PersonalNotificationAdapter(
|
||||||
|
private val onNotificationClick: (NotifItem) -> Unit
|
||||||
|
) : ListAdapter<NotifItem, PersonalNotificationAdapter.ViewHolder>(NotificationDiffCallback()) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
Log.d(TAG, "onCreateViewHolder: Creating ViewHolder")
|
||||||
|
val binding = ItemNotificationBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return ViewHolder(binding, onNotificationClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
Log.d(TAG, "onBindViewHolder: Binding notification at position $position, id=${item.id}")
|
||||||
|
holder.bind(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun submitList(list: List<NotifItem>?) {
|
||||||
|
Log.d(TAG, "submitList: Received list with ${list?.size ?: 0} items")
|
||||||
|
super.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(
|
||||||
|
private val binding: ItemNotificationBinding,
|
||||||
|
private val onNotificationClick: (NotifItem) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(notification: NotifItem) {
|
||||||
|
binding.apply {
|
||||||
|
tvNotificationType.text = notification.type
|
||||||
|
tvTitle.text = notification.title
|
||||||
|
tvDescription.text = notification.message
|
||||||
|
|
||||||
|
// Format the date to show just the time
|
||||||
|
formatTimeDisplay(notification.createdAt)
|
||||||
|
|
||||||
|
// Handle notification click
|
||||||
|
root.setOnClickListener {
|
||||||
|
Log.d(TAG, "ViewHolder: Notification clicked, id=${notification.id}")
|
||||||
|
onNotificationClick(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTimeDisplay(createdAt: String) {
|
||||||
|
try {
|
||||||
|
// Parse the date with the expected format from API
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
val outputFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||||
|
|
||||||
|
val date = inputFormat.parse(createdAt)
|
||||||
|
date?.let {
|
||||||
|
binding.tvTime.text = outputFormat.format(it)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// If date parsing fails, just display the raw value
|
||||||
|
Log.e(TAG, "formatTimeDisplay: Error parsing date", e)
|
||||||
|
binding.tvTime.text = createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NotificationDiffCallback : DiffUtil.ItemCallback<NotifItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: NotifItem, newItem: NotifItem): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: NotifItem, newItem: NotifItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PersonalNotifAdapter"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.notif
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.auth.NotifstoreItem
|
||||||
|
import com.alya.ecommerce_serang.databinding.ItemNotificationBinding
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
class StoreNotificationAdapter(
|
||||||
|
private val onNotificationClick: (NotifstoreItem) -> Unit
|
||||||
|
) : ListAdapter<NotifstoreItem, StoreNotificationAdapter.ViewHolder>(NotificationDiffCallback()) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
Log.d(TAG, "onCreateViewHolder: Creating ViewHolder")
|
||||||
|
val binding = ItemNotificationBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return ViewHolder(binding, onNotificationClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
Log.d(TAG, "onBindViewHolder: Binding store notification at position $position, id=${item.id}")
|
||||||
|
holder.bind(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun submitList(list: List<NotifstoreItem>?) {
|
||||||
|
Log.d(TAG, "submitList: Received list with ${list?.size ?: 0} items")
|
||||||
|
super.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(
|
||||||
|
private val binding: ItemNotificationBinding,
|
||||||
|
private val onNotificationClick: (NotifstoreItem) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(notification: NotifstoreItem) {
|
||||||
|
binding.apply {
|
||||||
|
tvNotificationType.text = notification.type
|
||||||
|
tvTitle.text = notification.title
|
||||||
|
tvDescription.text = notification.message
|
||||||
|
|
||||||
|
// Format the date to show just the time
|
||||||
|
formatTimeDisplay(notification.createdAt)
|
||||||
|
|
||||||
|
// Handle notification click
|
||||||
|
root.setOnClickListener {
|
||||||
|
Log.d(TAG, "ViewHolder: Store notification clicked, id=${notification.id}")
|
||||||
|
onNotificationClick(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTimeDisplay(createdAt: String) {
|
||||||
|
try {
|
||||||
|
// Parse the date with the expected format from API
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
val outputFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||||
|
|
||||||
|
val date = inputFormat.parse(createdAt)
|
||||||
|
date?.let {
|
||||||
|
binding.tvTime.text = outputFormat.format(it)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// If date parsing fails, just display the raw value
|
||||||
|
Log.e(TAG, "formatTimeDisplay: Error parsing date", e)
|
||||||
|
binding.tvTime.text = createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NotificationDiffCallback : DiffUtil.ItemCallback<NotifstoreItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: NotifstoreItem, newItem: NotifstoreItem): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: NotifstoreItem, newItem: NotifstoreItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
private const val TAG = "StoreNotifAdapter"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -8,22 +8,158 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.notif.NotificationActivity">
|
tools:context=".ui.notif.NotificationActivity">
|
||||||
|
|
||||||
<Button
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/simple_notification"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:text="simple notificaton"/>
|
android:background="@android:color/white"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<Button
|
<ImageButton
|
||||||
android:id="@+id/update_notification"
|
android:id="@+id/btnBack"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="update notificaton"/>
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Back"
|
||||||
|
android:src="@drawable/ic_back_24" />
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/cancel_notification"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:text="Notifikasi"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||||
|
app:tabIndicatorColor="@color/blue_500"
|
||||||
|
app:tabSelectedTextColor="@color/blue_500"
|
||||||
|
app:tabTextColor="@android:color/darker_gray">
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabItem
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Notifikasi Saya" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabItem
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Notifikasi Toko" />
|
||||||
|
</com.google.android.material.tabs.TabLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvMarkAllRead"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="cancel notificaton"/>
|
android:layout_marginEnd="16dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="Tandai Sudah Dibaca Semua"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tabLayout" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvNewest"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="Terbaru"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvMarkAllRead" />
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvNewest">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerViewNotif"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:listitem="@layout/item_notification"/>
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/emptyStateLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvNewest">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivEmptyState"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:src="@drawable/outline_notifications_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.4" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEmptyTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Belum Ada Notifikasi"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/ivEmptyState" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEmptyDesc"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Anda belum memiliki toko. Buat toko untuk menerima notifikasi toko."
|
||||||
|
android:textColor="@android:color/darker_gray"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvEmptyTitle" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCreateStore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@drawable/bg_button_filled"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="Buat Toko"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvEmptyDesc" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
59
app/src/main/res/layout/item_notification.xml
Normal file
59
app/src/main/res/layout/item_notification.xml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:layout_marginVertical="4dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvNotificationType"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Penjualan"
|
||||||
|
android:textColor="@android:color/darker_gray"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="09:30"
|
||||||
|
android:textColor="@android:color/darker_gray"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Buat Tagihan untuk Pesanan #12345678 Sekarang"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDescription"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Hai, Nama Toko, yuk buat tagihan untuk pesanan #12345678 sebelum tanggal 12-12-2024 pukul 09.30 WIB!"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
@ -30,7 +30,6 @@
|
|||||||
android:backgroundTint="@color/white"
|
android:backgroundTint="@color/white"
|
||||||
android:src="@drawable/outline_notifications_24"
|
android:src="@drawable/outline_notifications_24"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/search"
|
app:layout_constraintBottom_toBottomOf="@id/search"
|
||||||
app:layout_constraintEnd_toStartOf="@id/btn_cart"
|
app:layout_constraintEnd_toStartOf="@id/btn_cart"
|
||||||
app:layout_constraintTop_toTopOf="@id/search"/>
|
app:layout_constraintTop_toTopOf="@id/search"/>
|
||||||
|
Reference in New Issue
Block a user