mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22: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.FcmTokenResponse
|
||||
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.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||
@ -454,4 +456,12 @@ interface ApiService {
|
||||
@GET("chat")
|
||||
suspend fun getChatList(
|
||||
): 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.ListStoreTypeResponse
|
||||
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.RegisterResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
|
||||
@ -345,6 +347,36 @@ class UserRepository(private val apiService: ApiService) {
|
||||
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{
|
||||
private const val TAG = "UserRepository"
|
||||
}
|
||||
|
@ -1,67 +1,136 @@
|
||||
package com.alya.ecommerce_serang.ui.notif
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.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()
|
||||
private val _notifList = MutableLiveData<Result<List<NotifItem>>>()
|
||||
val notifList: LiveData<Result<List<NotifItem>>> = _notifList
|
||||
|
||||
init {
|
||||
fetchUserProfile()
|
||||
}
|
||||
private val _checkStore = MutableLiveData<Boolean>()
|
||||
val checkStore: LiveData<Boolean> = _checkStore
|
||||
|
||||
// Fetch user profile to get necessary data
|
||||
fun fetchUserProfile() {
|
||||
private val _notifStoreList = MutableLiveData<Result<List<NotifstoreItem>>>()
|
||||
val notifStoreList: LiveData<Result<List<NotifstoreItem>>> = _notifStoreList
|
||||
|
||||
fun getNotifList() {
|
||||
Log.d(TAG, "getNotifList: Fetching personal notifications")
|
||||
viewModelScope.launch {
|
||||
_userProfile.value = Result.Loading
|
||||
val result = userRepository.fetchUserProfile()
|
||||
_userProfile.value = result
|
||||
try {
|
||||
Log.d(TAG, "getNotifList: Setting state to Loading")
|
||||
_notifList.value = Result.Loading
|
||||
|
||||
// If successful, save the user ID for WebSocket use
|
||||
if (result is Result.Success && result.data != null) {
|
||||
sessionManager.saveUserId(result.data.userId.toString())
|
||||
Log.d(TAG, "getNotifList: Calling repository to get notifications")
|
||||
val result = userRepository.getListNotif()
|
||||
|
||||
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 startWebSocketConnection() {
|
||||
webSocketManager.startWebSocketConnection()
|
||||
fun getNotifStoreList() {
|
||||
Log.d(TAG, "getNotifStoreList: Fetching store notifications")
|
||||
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 stopWebSocketConnection() {
|
||||
webSocketManager.stopWebSocketConnection()
|
||||
fun checkStoreUser() {
|
||||
Log.d(TAG, "checkStoreUser: Checking if user has a store")
|
||||
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)
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// No need to stop here - the service will manage its own lifecycle
|
||||
companion object {
|
||||
private const val TAG = "NotifViewModel" // Constant for logging tag
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,118 +1,324 @@
|
||||
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 android.util.Log
|
||||
import android.view.View
|
||||
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 androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.databinding.ActivityNotificationBinding
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint // Required for Hilt
|
||||
private const val TAG = "NotificationActivity"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotificationActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityNotificationBinding
|
||||
private val viewModel: NotifViewModel by viewModels()
|
||||
|
||||
// Permission request code
|
||||
private val NOTIFICATION_PERMISSION_CODE = 100
|
||||
private lateinit var personalAdapter: PersonalNotificationAdapter
|
||||
private lateinit var storeAdapter: StoreNotificationAdapter
|
||||
|
||||
private var hasStore = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "onCreate: Starting NotificationActivity")
|
||||
binding = ActivityNotificationBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
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
|
||||
setupToolbar()
|
||||
setupAdapters()
|
||||
setupTabLayout()
|
||||
setupSwipeRefresh()
|
||||
setupObservers()
|
||||
|
||||
// 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 -> {
|
||||
// Handle error - show message, etc.
|
||||
Toast.makeText(this@NotificationActivity,
|
||||
"Failed to load profile",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
Result.Loading -> {
|
||||
// Show loading indicator if needed
|
||||
showStoreNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
// viewModel.startWebSocketConnection()
|
||||
// Observe store notifications
|
||||
viewModel.notifStoreList.observe(this) { result ->
|
||||
Log.d(TAG, "notifStoreList observed: ${result.javaClass.simpleName}")
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
|
||||
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
|
||||
)
|
||||
if (binding.tabLayout.selectedTabPosition == 1) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val notifications = result.data
|
||||
Log.d(TAG, "Store notifications received: ${notifications?.size ?: 0}, hasStore=$hasStore")
|
||||
if (!hasStore) {
|
||||
showEmptyState("Anda belum memiliki toko", true)
|
||||
} else 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)
|
||||
// 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 store notifications", result.exception)
|
||||
showEmptyState("Gagal memuat notifikasi toko", false)
|
||||
}
|
||||
is Result.Loading -> {
|
||||
Log.d(TAG, "Loading store notifications")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle permission request result
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
private fun showPersonalNotifications() {
|
||||
Log.d(TAG, "showPersonalNotifications called")
|
||||
val result = viewModel.notifList.value
|
||||
|
||||
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()
|
||||
if (result is Result.Success) {
|
||||
val notifications = result.data
|
||||
Log.d(TAG, "showPersonalNotifications: Success with ${notifications?.size ?: 0} notifications")
|
||||
if (notifications.isNullOrEmpty()) {
|
||||
showEmptyState("Belum Ada Notifikasi", false)
|
||||
} 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
|
||||
hideEmptyState()
|
||||
if (binding.recyclerViewNotif.adapter != personalAdapter) {
|
||||
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"
|
||||
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"/>
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/update_notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="update notificaton"/>
|
||||
<ImageButton
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Back"
|
||||
android:src="@drawable/ic_back_24" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_notification"
|
||||
<TextView
|
||||
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_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>
|
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:src="@drawable/outline_notifications_24"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_cart"
|
||||
app:layout_constraintTop_toTopOf="@id/search"/>
|
||||
|
Reference in New Issue
Block a user