Browse Source
- Created HistoryFragment with RecyclerView architecture for performance - Implemented HistoryAdapter with expandable/collapsible Pokemon detection cards - Added navigation integration between main app and history screen - Connected to StorageInterface for detection result data retrieval - Fixed MainActivity to extend FragmentActivity for fragment compatibility - Added ServiceLocator synchronous storage access method - Implemented delete functionality with proper state management - Added empty state handling with informative messaging - Enhanced build.gradle with navigation-compose dependency Features: - Scrollable detection history with smooth expand/collapse animations - Compact view: Pokemon name, timestamp, processing time, status icons - Expanded view: Complete Pokemon data organized in sections - Delete buttons for result management - Performance optimized for large lists with view recycling - Proper error handling and lifecycle management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>feature/pgh-1-results-display-history
6 changed files with 761 additions and 9 deletions
@ -0,0 +1,443 @@ |
|||
package com.quillstudios.pokegoalshelper.ui |
|||
|
|||
import android.animation.ObjectAnimator |
|||
import android.content.Context |
|||
import android.graphics.drawable.GradientDrawable |
|||
import android.util.TypedValue |
|||
import android.view.View |
|||
import android.view.ViewGroup |
|||
import android.widget.* |
|||
import androidx.core.content.ContextCompat |
|||
import androidx.recyclerview.widget.RecyclerView |
|||
import com.quillstudios.pokegoalshelper.models.DetectionResult |
|||
import com.quillstudios.pokegoalshelper.utils.PGHLog |
|||
import java.time.format.DateTimeFormatter |
|||
|
|||
/** |
|||
* RecyclerView adapter for Pokemon detection history with expandable cards. |
|||
* |
|||
* Features: |
|||
* - Expandable/collapsible cards |
|||
* - Compact and detailed views |
|||
* - Delete functionality |
|||
* - Smooth animations |
|||
* - Performance optimized for large lists |
|||
*/ |
|||
class HistoryAdapter( |
|||
private val onItemClick: (DetectionResult, Int) -> Unit, |
|||
private val onDeleteClick: (DetectionResult, Int) -> Unit |
|||
) : RecyclerView.Adapter<HistoryAdapter.HistoryViewHolder>() |
|||
{ |
|||
companion object |
|||
{ |
|||
private const val TAG = "HistoryAdapter" |
|||
} |
|||
|
|||
data class HistoryItem( |
|||
val result: DetectionResult, |
|||
val isExpanded: Boolean = false |
|||
) |
|||
|
|||
private val items = mutableListOf<HistoryItem>() |
|||
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder |
|||
{ |
|||
val cardView = createCardView(parent.context) |
|||
return HistoryViewHolder(cardView) |
|||
} |
|||
|
|||
override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) |
|||
{ |
|||
val item = items[position] |
|||
holder.bind(item, position) |
|||
} |
|||
|
|||
override fun getItemCount(): Int = items.size |
|||
|
|||
fun updateResults(results: List<DetectionResult>) |
|||
{ |
|||
items.clear() |
|||
items.addAll(results.map { HistoryItem(it, false) }) |
|||
notifyDataSetChanged() |
|||
PGHLog.d(TAG, "Updated adapter with ${results.size} items") |
|||
} |
|||
|
|||
fun removeItem(position: Int) |
|||
{ |
|||
if (position in 0 until items.size) { |
|||
items.removeAt(position) |
|||
notifyItemRemoved(position) |
|||
PGHLog.d(TAG, "Removed item at position $position") |
|||
} |
|||
} |
|||
|
|||
fun toggleExpansion(position: Int) |
|||
{ |
|||
if (position in 0 until items.size) { |
|||
val item = items[position] |
|||
items[position] = item.copy(isExpanded = !item.isExpanded) |
|||
notifyItemChanged(position) |
|||
PGHLog.d(TAG, "Toggled expansion for position $position: ${items[position].isExpanded}") |
|||
} |
|||
} |
|||
|
|||
private fun createCardView(context: Context): View |
|||
{ |
|||
// Create main card container |
|||
val cardContainer = LinearLayout(context).apply { |
|||
orientation = LinearLayout.VERTICAL |
|||
background = createCardBackground(context) |
|||
setPadding(dpToPx(context, 16), dpToPx(context, 12), dpToPx(context, 16), dpToPx(context, 12)) |
|||
|
|||
layoutParams = RecyclerView.LayoutParams( |
|||
RecyclerView.LayoutParams.MATCH_PARENT, |
|||
RecyclerView.LayoutParams.WRAP_CONTENT |
|||
).apply { |
|||
setMargins(dpToPx(context, 8), dpToPx(context, 4), dpToPx(context, 8), dpToPx(context, 4)) |
|||
} |
|||
} |
|||
|
|||
// Collapsed content (always visible) |
|||
val collapsedContent = createCollapsedContent(context) |
|||
collapsedContent.tag = "collapsed_content" |
|||
|
|||
// Expanded content (initially hidden) |
|||
val expandedContent = createExpandedContent(context) |
|||
expandedContent.tag = "expanded_content" |
|||
expandedContent.visibility = View.GONE |
|||
|
|||
cardContainer.addView(collapsedContent) |
|||
cardContainer.addView(expandedContent) |
|||
|
|||
return cardContainer |
|||
} |
|||
|
|||
private fun createCollapsedContent(context: Context): View |
|||
{ |
|||
return LinearLayout(context).apply { |
|||
orientation = LinearLayout.HORIZONTAL |
|||
gravity = android.view.Gravity.CENTER_VERTICAL |
|||
|
|||
// Status icon |
|||
val statusIcon = ImageView(context).apply { |
|||
tag = "status_icon" |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
dpToPx(context, 24), |
|||
dpToPx(context, 24) |
|||
).apply { |
|||
setMargins(0, 0, dpToPx(context, 12), 0) |
|||
} |
|||
} |
|||
|
|||
// Main info container |
|||
val infoContainer = LinearLayout(context).apply { |
|||
orientation = LinearLayout.VERTICAL |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
0, |
|||
ViewGroup.LayoutParams.WRAP_CONTENT, |
|||
1f |
|||
) |
|||
|
|||
// Pokemon name/species |
|||
val titleText = TextView(context).apply { |
|||
tag = "title_text" |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.white)) |
|||
typeface = android.graphics.Typeface.DEFAULT_BOLD |
|||
} |
|||
|
|||
// Timestamp and confidence |
|||
val subtitleText = TextView(context).apply { |
|||
tag = "subtitle_text" |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) |
|||
} |
|||
|
|||
addView(titleText) |
|||
addView(subtitleText) |
|||
} |
|||
|
|||
// Expand chevron |
|||
val chevronIcon = TextView(context).apply { |
|||
tag = "chevron_icon" |
|||
text = "▼" |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
ViewGroup.LayoutParams.WRAP_CONTENT, |
|||
ViewGroup.LayoutParams.WRAP_CONTENT |
|||
).apply { |
|||
setMargins(dpToPx(context, 8), 0, 0, 0) |
|||
} |
|||
} |
|||
|
|||
addView(statusIcon) |
|||
addView(infoContainer) |
|||
addView(chevronIcon) |
|||
} |
|||
} |
|||
|
|||
private fun createExpandedContent(context: Context): View |
|||
{ |
|||
return ScrollView(context).apply { |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
LinearLayout.LayoutParams.MATCH_PARENT, |
|||
dpToPx(context, 300) // Max height for expanded content |
|||
) |
|||
|
|||
val contentContainer = LinearLayout(context).apply { |
|||
orientation = LinearLayout.VERTICAL |
|||
setPadding(0, dpToPx(context, 8), 0, 0) |
|||
tag = "expanded_container" |
|||
} |
|||
|
|||
addView(contentContainer) |
|||
} |
|||
} |
|||
|
|||
private fun createCardBackground(context: Context): GradientDrawable |
|||
{ |
|||
return GradientDrawable().apply { |
|||
setColor(ContextCompat.getColor(context, android.R.color.black)) |
|||
alpha = (0.8f * 255).toInt() |
|||
cornerRadius = dpToPx(context, 8).toFloat() |
|||
setStroke(1, ContextCompat.getColor(context, android.R.color.darker_gray)) |
|||
} |
|||
} |
|||
|
|||
private fun dpToPx(context: Context, dp: Int): Int |
|||
{ |
|||
return TypedValue.applyDimension( |
|||
TypedValue.COMPLEX_UNIT_DIP, |
|||
dp.toFloat(), |
|||
context.resources.displayMetrics |
|||
).toInt() |
|||
} |
|||
|
|||
inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) |
|||
{ |
|||
private val cardContainer = itemView as LinearLayout |
|||
private val collapsedContent = itemView.findViewWithTag<LinearLayout>("collapsed_content") |
|||
private val expandedContent = itemView.findViewWithTag<ScrollView>("expanded_content") |
|||
private val expandedContainer = expandedContent.findViewWithTag<LinearLayout>("expanded_container") |
|||
|
|||
// Collapsed content views |
|||
private val statusIcon = collapsedContent.findViewWithTag<ImageView>("status_icon") |
|||
private val titleText = collapsedContent.findViewWithTag<TextView>("title_text") |
|||
private val subtitleText = collapsedContent.findViewWithTag<TextView>("subtitle_text") |
|||
private val chevronIcon = collapsedContent.findViewWithTag<TextView>("chevron_icon") |
|||
|
|||
fun bind(item: HistoryItem, position: Int) |
|||
{ |
|||
val result = item.result |
|||
val context = itemView.context |
|||
|
|||
// Update collapsed content |
|||
updateCollapsedContent(result, context) |
|||
|
|||
// Update expanded content if needed |
|||
if (item.isExpanded) { |
|||
updateExpandedContent(result, context) |
|||
showExpandedContent() |
|||
} else { |
|||
hideExpandedContent() |
|||
} |
|||
|
|||
// Set click listeners |
|||
collapsedContent.setOnClickListener { |
|||
onItemClick(result, position) |
|||
} |
|||
|
|||
// Add delete button in expanded state |
|||
if (item.isExpanded) { |
|||
addDeleteButton(result, position, context) |
|||
} |
|||
} |
|||
|
|||
private fun updateCollapsedContent(result: DetectionResult, context: Context) |
|||
{ |
|||
// Status icon |
|||
statusIcon.setImageResource( |
|||
if (result.success) android.R.drawable.ic_menu_myplaces |
|||
else android.R.drawable.ic_dialog_alert |
|||
) |
|||
statusIcon.setColorFilter( |
|||
ContextCompat.getColor( |
|||
context, |
|||
if (result.success) android.R.color.holo_green_light |
|||
else android.R.color.holo_red_light |
|||
) |
|||
) |
|||
|
|||
// Title (Pokemon name or status) |
|||
titleText.text = when { |
|||
result.success && result.pokemonInfo?.species != null -> { |
|||
result.pokemonInfo.species + |
|||
(result.pokemonInfo.nationalDexNumber?.let { " (#$it)" } ?: "") |
|||
} |
|||
result.success -> "Pokemon Detected" |
|||
else -> "Detection Failed" |
|||
} |
|||
|
|||
// Subtitle (timestamp and processing time) |
|||
val formatter = DateTimeFormatter.ofPattern("MMM dd, HH:mm") |
|||
subtitleText.text = "${result.timestamp.format(formatter)} • ${result.processingTimeMs}ms" |
|||
|
|||
// Chevron rotation based on expansion state |
|||
val rotation = if (expandedContent.visibility == View.VISIBLE) 180f else 0f |
|||
chevronIcon.rotation = rotation |
|||
} |
|||
|
|||
private fun updateExpandedContent(result: DetectionResult, context: Context) |
|||
{ |
|||
expandedContainer.removeAllViews() |
|||
|
|||
if (result.success && result.pokemonInfo != null) { |
|||
addPokemonInfoViews(result.pokemonInfo, context) |
|||
} else { |
|||
addErrorInfoViews(result, context) |
|||
} |
|||
|
|||
// Technical info |
|||
addTechnicalInfoViews(result, context) |
|||
} |
|||
|
|||
private fun addPokemonInfoViews(pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo, context: Context) |
|||
{ |
|||
// Basic info section |
|||
addSectionHeader("Basic Info", context) |
|||
pokemonInfo.level?.let { addInfoRow("Level", it.toString(), context) } |
|||
pokemonInfo.gender?.let { addInfoRow("Gender", it, context) } |
|||
pokemonInfo.nature?.let { addInfoRow("Nature", it, context) } |
|||
|
|||
// Types section |
|||
if (pokemonInfo.primaryType != null || pokemonInfo.secondaryType != null) { |
|||
addSectionHeader("Types", context) |
|||
val typeText = when { |
|||
pokemonInfo.primaryType != null && pokemonInfo.secondaryType != null -> |
|||
"${pokemonInfo.primaryType} / ${pokemonInfo.secondaryType}" |
|||
pokemonInfo.primaryType != null -> pokemonInfo.primaryType |
|||
else -> "Unknown" |
|||
} |
|||
addInfoRow("Type", typeText, context) |
|||
pokemonInfo.teraType?.let { addInfoRow("Tera Type", it, context) } |
|||
} |
|||
|
|||
// Special properties |
|||
if (pokemonInfo.isShiny || pokemonInfo.isAlpha || pokemonInfo.isFavorited) { |
|||
addSectionHeader("Properties", context) |
|||
if (pokemonInfo.isShiny) addInfoRow("Shiny", "✨ Yes", context) |
|||
if (pokemonInfo.isAlpha) addInfoRow("Alpha", "🅰 Yes", context) |
|||
if (pokemonInfo.isFavorited) addInfoRow("Favorited", "⭐ Yes", context) |
|||
} |
|||
} |
|||
|
|||
private fun addErrorInfoViews(result: DetectionResult, context: Context) |
|||
{ |
|||
addSectionHeader("Error Details", context) |
|||
addInfoRow("Status", if (result.success) "No Pokemon found" else "Detection failed", context) |
|||
result.errorMessage?.let { addInfoRow("Error", it, context) } |
|||
} |
|||
|
|||
private fun addTechnicalInfoViews(result: DetectionResult, context: Context) |
|||
{ |
|||
addSectionHeader("Technical Info", context) |
|||
addInfoRow("Processing Time", "${result.processingTimeMs}ms", context) |
|||
addInfoRow("Detections Found", result.detections.size.toString(), context) |
|||
addInfoRow("Timestamp", result.timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), context) |
|||
} |
|||
|
|||
private fun addSectionHeader(title: String, context: Context) |
|||
{ |
|||
val header = TextView(context).apply { |
|||
text = title |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light)) |
|||
typeface = android.graphics.Typeface.DEFAULT_BOLD |
|||
setPadding(0, dpToPx(context, 8), 0, dpToPx(context, 4)) |
|||
} |
|||
expandedContainer.addView(header) |
|||
} |
|||
|
|||
private fun addInfoRow(label: String, value: String, context: Context) |
|||
{ |
|||
val row = LinearLayout(context).apply { |
|||
orientation = LinearLayout.HORIZONTAL |
|||
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) |
|||
} |
|||
|
|||
val labelView = TextView(context).apply { |
|||
text = "$label:" |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
dpToPx(context, 100), |
|||
ViewGroup.LayoutParams.WRAP_CONTENT |
|||
) |
|||
} |
|||
|
|||
val valueView = TextView(context).apply { |
|||
text = value |
|||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.white)) |
|||
layoutParams = LinearLayout.LayoutParams( |
|||
0, |
|||
ViewGroup.LayoutParams.WRAP_CONTENT, |
|||
1f |
|||
) |
|||
} |
|||
|
|||
row.addView(labelView) |
|||
row.addView(valueView) |
|||
expandedContainer.addView(row) |
|||
} |
|||
|
|||
private fun addDeleteButton(result: DetectionResult, position: Int, context: Context) |
|||
{ |
|||
val deleteButton = Button(context).apply { |
|||
text = "Delete" |
|||
setTextColor(ContextCompat.getColor(context, android.R.color.white)) |
|||
background = GradientDrawable().apply { |
|||
setColor(ContextCompat.getColor(context, android.R.color.holo_red_light)) |
|||
cornerRadius = dpToPx(context, 4).toFloat() |
|||
} |
|||
setPadding(dpToPx(context, 16), dpToPx(context, 8), dpToPx(context, 16), dpToPx(context, 8)) |
|||
|
|||
setOnClickListener { |
|||
onDeleteClick(result, position) |
|||
} |
|||
|
|||
layoutParams = LinearLayout.LayoutParams( |
|||
ViewGroup.LayoutParams.WRAP_CONTENT, |
|||
ViewGroup.LayoutParams.WRAP_CONTENT |
|||
).apply { |
|||
setMargins(0, dpToPx(context, 8), 0, 0) |
|||
} |
|||
} |
|||
|
|||
expandedContainer.addView(deleteButton) |
|||
} |
|||
|
|||
private fun showExpandedContent() |
|||
{ |
|||
expandedContent.visibility = View.VISIBLE |
|||
|
|||
// Animate chevron rotation |
|||
ObjectAnimator.ofFloat(chevronIcon, "rotation", 0f, 180f).apply { |
|||
duration = 200L |
|||
start() |
|||
} |
|||
} |
|||
|
|||
private fun hideExpandedContent() |
|||
{ |
|||
expandedContent.visibility = View.GONE |
|||
|
|||
// Animate chevron rotation |
|||
ObjectAnimator.ofFloat(chevronIcon, "rotation", 180f, 0f).apply { |
|||
duration = 200L |
|||
start() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,193 @@ |
|||
package com.quillstudios.pokegoalshelper.ui |
|||
|
|||
import android.os.Bundle |
|||
import android.view.LayoutInflater |
|||
import android.view.View |
|||
import android.view.ViewGroup |
|||
import androidx.fragment.app.Fragment |
|||
import androidx.lifecycle.lifecycleScope |
|||
import androidx.recyclerview.widget.LinearLayoutManager |
|||
import androidx.recyclerview.widget.RecyclerView |
|||
import com.quillstudios.pokegoalshelper.models.DetectionResult |
|||
import com.quillstudios.pokegoalshelper.storage.StorageInterface |
|||
import com.quillstudios.pokegoalshelper.di.ServiceLocator |
|||
import com.quillstudios.pokegoalshelper.utils.PGHLog |
|||
import kotlinx.coroutines.launch |
|||
|
|||
/** |
|||
* History Fragment displaying scrollable list of Pokemon detection results. |
|||
* |
|||
* Features: |
|||
* - RecyclerView with expandable cards |
|||
* - Empty state handling |
|||
* - Delete and refresh functionality |
|||
* - Performance optimized for large lists |
|||
*/ |
|||
class HistoryFragment : Fragment() |
|||
{ |
|||
companion object |
|||
{ |
|||
private const val TAG = "HistoryFragment" |
|||
} |
|||
|
|||
private lateinit var recyclerView: RecyclerView |
|||
private lateinit var historyAdapter: HistoryAdapter |
|||
private lateinit var emptyStateView: View |
|||
private val storage: StorageInterface by lazy { ServiceLocator.getStorageServiceSync() } |
|||
|
|||
override fun onCreateView( |
|||
inflater: LayoutInflater, |
|||
container: ViewGroup?, |
|||
savedInstanceState: Bundle? |
|||
): View? |
|||
{ |
|||
// Create the root view programmatically since we don't have XML layouts |
|||
return createHistoryView() |
|||
} |
|||
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) |
|||
{ |
|||
super.onViewCreated(view, savedInstanceState) |
|||
setupRecyclerView() |
|||
loadHistory() |
|||
} |
|||
|
|||
private fun createHistoryView(): View |
|||
{ |
|||
val context = requireContext() |
|||
|
|||
// Create main container |
|||
val container = androidx.constraintlayout.widget.ConstraintLayout(context).apply { |
|||
layoutParams = ViewGroup.LayoutParams( |
|||
ViewGroup.LayoutParams.MATCH_PARENT, |
|||
ViewGroup.LayoutParams.MATCH_PARENT |
|||
) |
|||
} |
|||
|
|||
// Create RecyclerView |
|||
recyclerView = RecyclerView(context).apply { |
|||
id = View.generateViewId() |
|||
layoutParams = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams( |
|||
0, 0 |
|||
).apply { |
|||
topToTop = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
bottomToBottom = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
startToStart = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
endToEnd = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
} |
|||
} |
|||
|
|||
// Create empty state view |
|||
emptyStateView = android.widget.TextView(context).apply { |
|||
id = View.generateViewId() |
|||
text = "No Pokemon detections yet\\n\\nStart analyzing Pokemon Home screens to see results here!" |
|||
textAlignment = View.TEXT_ALIGNMENT_CENTER |
|||
setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16f) |
|||
setPadding(32, 32, 32, 32) |
|||
visibility = View.GONE |
|||
|
|||
layoutParams = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams( |
|||
androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.WRAP_CONTENT, |
|||
androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.WRAP_CONTENT |
|||
).apply { |
|||
topToTop = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
bottomToBottom = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
startToStart = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
endToEnd = androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID |
|||
} |
|||
} |
|||
|
|||
container.addView(recyclerView) |
|||
container.addView(emptyStateView) |
|||
|
|||
return container |
|||
} |
|||
|
|||
private fun setupRecyclerView() |
|||
{ |
|||
historyAdapter = HistoryAdapter( |
|||
onItemClick = { result, position -> toggleItemExpansion(result, position) }, |
|||
onDeleteClick = { result, position -> deleteResult(result, position) } |
|||
) |
|||
|
|||
recyclerView.apply { |
|||
layoutManager = LinearLayoutManager(requireContext()) |
|||
adapter = historyAdapter |
|||
|
|||
// Add item decoration for spacing |
|||
addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration( |
|||
requireContext(), |
|||
androidx.recyclerview.widget.DividerItemDecoration.VERTICAL |
|||
)) |
|||
} |
|||
|
|||
PGHLog.d(TAG, "RecyclerView setup complete") |
|||
} |
|||
|
|||
private fun loadHistory() |
|||
{ |
|||
lifecycleScope.launch { |
|||
try { |
|||
PGHLog.d(TAG, "Loading detection history...") |
|||
val results = storage.getDetectionResults() |
|||
|
|||
if (results.isEmpty()) { |
|||
showEmptyState(true) |
|||
PGHLog.d(TAG, "No results found - showing empty state") |
|||
} else { |
|||
showEmptyState(false) |
|||
historyAdapter.updateResults(results) |
|||
PGHLog.d(TAG, "Loaded ${results.size} detection results") |
|||
} |
|||
} catch (e: Exception) { |
|||
PGHLog.e(TAG, "Error loading history", e) |
|||
showEmptyState(true) |
|||
} |
|||
} |
|||
} |
|||
|
|||
fun refreshHistory() |
|||
{ |
|||
PGHLog.d(TAG, "Refreshing history...") |
|||
loadHistory() |
|||
} |
|||
|
|||
private fun deleteResult(result: DetectionResult, position: Int) |
|||
{ |
|||
lifecycleScope.launch { |
|||
try { |
|||
val success = storage.deleteDetectionResult(result.id) |
|||
if (success) { |
|||
historyAdapter.removeItem(position) |
|||
PGHLog.d(TAG, "Deleted result: ${result.id}") |
|||
|
|||
// Check if list is now empty |
|||
if (historyAdapter.itemCount == 0) { |
|||
showEmptyState(true) |
|||
} |
|||
} else { |
|||
PGHLog.w(TAG, "Failed to delete result: ${result.id}") |
|||
} |
|||
} catch (e: Exception) { |
|||
PGHLog.e(TAG, "Error deleting result", e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun toggleItemExpansion(result: DetectionResult, position: Int) |
|||
{ |
|||
historyAdapter.toggleExpansion(position) |
|||
PGHLog.d(TAG, "Toggled expansion for item at position $position") |
|||
} |
|||
|
|||
private fun showEmptyState(show: Boolean) |
|||
{ |
|||
if (show) { |
|||
recyclerView.visibility = View.GONE |
|||
emptyStateView.visibility = View.VISIBLE |
|||
} else { |
|||
recyclerView.visibility = View.VISIBLE |
|||
emptyStateView.visibility = View.GONE |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue