diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListNotifResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListNotifResponse.kt new file mode 100644 index 0000000..bd4a0c0 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListNotifResponse.kt @@ -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, + + @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 +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreNotifResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreNotifResponse.kt new file mode 100644 index 0000000..a16c055 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreNotifResponse.kt @@ -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, + + @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 +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt index 195ef18..ce83b01 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt @@ -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 + + @GET("notification") + suspend fun getNotif( + ): Response + + @GET("mystore/notification") + suspend fun getNotifStore( + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt index 2c0416e..2f2177b 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt @@ -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> { + 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> { + 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" } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt index d2040c4..fa65ac8 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotifViewModel.kt @@ -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.Loading) - val userProfile: StateFlow> = _userProfile.asStateFlow() + private val _notifList = MutableLiveData>>() + val notifList: LiveData>> = _notifList - init { - fetchUserProfile() - } + private val _checkStore = MutableLiveData() + val checkStore: LiveData = _checkStore - // Fetch user profile to get necessary data - fun fetchUserProfile() { + private val _notifStoreList = MutableLiveData>>() + val notifStoreList: LiveData>> = _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 + } + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt index 1e22402..cef7ec9 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/NotificationActivity.kt @@ -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, - 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") + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt new file mode 100644 index 0000000..e60aa1c --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt @@ -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(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?) { + 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() { + 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/StoreNotificationAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/StoreNotificationAdapter.kt new file mode 100644 index 0000000..657a813 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/StoreNotificationAdapter.kt @@ -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(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?) { + 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() { + 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" + + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index e821fd0..d75af86 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -8,22 +8,158 @@ android:layout_height="match_parent" tools:context=".ui.notif.NotificationActivity"> -