Add UI improvements & media management features: Add custom T-shaped Tetris icon, implement swipe-to-delete for scores with undo functionality, add long-press media removal with confirmation dialog, fix notification repetition when switching tabs, add visual feedback for interactive elements

This commit is contained in:
cmclark00 2025-03-20 14:49:49 -04:00
parent 239aaa5c32
commit d95e449c16
31 changed files with 425 additions and 235 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View file

@ -4,6 +4,10 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -17,11 +21,15 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.accidentalproductions.tetristats.databinding.FragmentHistoryBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
@ -93,6 +101,8 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
observeScores()
observeExportResult()
observeMediaSaveResult()
observeDeleteResult()
observeMediaRemoveResult()
}
private fun setupRecyclerView() {
@ -100,6 +110,9 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
binding.recyclerViewHistory.apply {
adapter = scoreAdapter
layoutManager = LinearLayoutManager(context)
// Add swipe to delete functionality
setupSwipeToDelete(this)
}
}
@ -133,15 +146,55 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
when (result) {
is MediaSaveResult.Success -> {
Toast.makeText(context, "Media attached successfully", Toast.LENGTH_SHORT).show()
viewModel.clearMediaSaveResult()
}
is MediaSaveResult.Error -> {
Toast.makeText(context, "Failed to attach media: ${result.message}", Toast.LENGTH_LONG).show()
viewModel.clearMediaSaveResult()
}
null -> { /* Ignore initial state */ }
}
}
}
private fun observeDeleteResult() {
viewModel.deleteResult.observe(viewLifecycleOwner) { result ->
result?.let {
when (result) {
is DeleteResult.Success -> {
showUndoSnackbar(result.message)
viewModel.clearDeleteResult()
}
is DeleteResult.UndoSuccess -> {
Toast.makeText(context, result.message, Toast.LENGTH_SHORT).show()
viewModel.clearDeleteResult()
}
is DeleteResult.Error -> {
Toast.makeText(context, result.message, Toast.LENGTH_LONG).show()
viewModel.clearDeleteResult()
}
}
}
}
}
private fun observeMediaRemoveResult() {
viewModel.mediaRemoveResult.observe(viewLifecycleOwner) { result ->
result?.let {
when (result) {
is MediaRemoveResult.Success -> {
Toast.makeText(context, "Media removed", Toast.LENGTH_SHORT).show()
viewModel.clearMediaRemoveResult()
}
is MediaRemoveResult.Error -> {
Toast.makeText(context, "Failed to remove media: ${result.message}", Toast.LENGTH_LONG).show()
viewModel.clearMediaRemoveResult()
}
}
}
}
}
private fun shareExportedFile(uri: Uri) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/csv"
@ -153,6 +206,16 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
startActivity(chooserIntent)
}
private fun showUndoSnackbar(message: String) {
view?.let { view ->
val snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG)
snackbar.setAction("UNDO") {
viewModel.undoDelete()
}
snackbar.show()
}
}
// ScoreAdapter.MediaAttachmentListener implementation
override fun onAddMediaClicked(scoreId: Long) {
currentScoreId = scoreId
@ -327,4 +390,101 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
super.onDestroyView()
_binding = null
}
// New ScoreAdapter.MediaAttachmentListener methods
override fun onRemoveMediaClicked(scoreId: Long) {
// Confirm media removal with a dialog
MaterialAlertDialogBuilder(requireContext())
.setTitle("Remove Media")
.setMessage("Are you sure you want to remove the attached media?")
.setPositiveButton("Remove") { _, _ ->
viewModel.removeMedia(scoreId)
}
.setNegativeButton("Cancel", null)
.show()
}
private fun setupSwipeToDelete(recyclerView: RecyclerView) {
val swipeCallback = object : ItemTouchHelper.SimpleCallback(
0, // No drag and drop
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // Swipe left or right
) {
private val deleteIcon = ContextCompat.getDrawable(requireContext(), android.R.drawable.ic_menu_delete)
private val background = ColorDrawable(Color.parseColor("#FF5252")) // Red background
private val backgroundPaint = Paint().apply { color = Color.parseColor("#FF5252") }
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 40f
textAlign = Paint.Align.CENTER
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false // We don't support moving items
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val score = scoreAdapter.currentList[position]
// Delete the score
viewModel.deleteScore(score.id)
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
val itemView = viewHolder.itemView
val itemHeight = itemView.bottom - itemView.top
// Draw background
if (dX > 0) { // Swiping to the right
background.setBounds(itemView.left, itemView.top, itemView.left + dX.toInt(), itemView.bottom)
} else { // Swiping to the left
background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom)
}
background.draw(c)
// Draw delete icon
deleteIcon?.let {
val iconMargin = (itemHeight - it.intrinsicHeight) / 2
val iconTop = itemView.top + (itemHeight - it.intrinsicHeight) / 2
val iconBottom = iconTop + it.intrinsicHeight
if (dX > 0) { // Swiping to the right
val iconLeft = itemView.left + iconMargin
val iconRight = iconLeft + it.intrinsicWidth
it.setBounds(iconLeft, iconTop, iconRight, iconBottom)
} else { // Swiping to the left
val iconRight = itemView.right - iconMargin
val iconLeft = iconRight - it.intrinsicWidth
it.setBounds(iconLeft, iconTop, iconRight, iconBottom)
}
DrawableCompat.setTint(it, Color.WHITE)
it.draw(c)
// Draw "Delete" text
val text = "Delete"
if (dX > 0) { // Swiping to the right
c.drawText(text, itemView.left + 120f, itemView.top + itemHeight / 2f + 15, textPaint)
} else { // Swiping to the left
c.drawText(text, itemView.right - 120f, itemView.top + itemHeight / 2f + 15, textPaint)
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
// Attach the swipe handler to the RecyclerView
ItemTouchHelper(swipeCallback).attachToRecyclerView(recyclerView)
}
}

View file

@ -43,6 +43,17 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
private val _mediaSaveResult = MutableLiveData<MediaSaveResult?>()
val mediaSaveResult: LiveData<MediaSaveResult?> = _mediaSaveResult
// For delete and undo functionality
private val _deleteResult = MutableLiveData<DeleteResult?>()
val deleteResult: LiveData<DeleteResult?> = _deleteResult
// Keep track of recently deleted score for undo
private var lastDeletedScore: Score? = null
// For media removal
private val _mediaRemoveResult = MutableLiveData<MediaRemoveResult?>()
val mediaRemoveResult: LiveData<MediaRemoveResult?> = _mediaRemoveResult
fun exportScoresToCsv() {
viewModelScope.launch {
try {
@ -195,6 +206,97 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
)
}
}
fun deleteScore(scoreId: Long) {
viewModelScope.launch {
try {
val score = getScoreById(scoreId)
if (score != null) {
// Store for potential undo
lastDeletedScore = score
// Delete from database
scoreDao.delete(score)
_deleteResult.postValue(DeleteResult.Success("Score deleted. Tap UNDO to restore."))
} else {
_deleteResult.postValue(DeleteResult.Error("Score not found"))
}
} catch (e: Exception) {
_deleteResult.postValue(DeleteResult.Error("Delete failed: ${e.message}"))
}
}
}
fun undoDelete() {
viewModelScope.launch {
try {
lastDeletedScore?.let { score ->
// Reinsert the score
scoreDao.insert(score)
_deleteResult.postValue(DeleteResult.UndoSuccess("Score restored"))
// Clear the stored score
lastDeletedScore = null
} ?: run {
_deleteResult.postValue(DeleteResult.Error("No score to restore"))
}
} catch (e: Exception) {
_deleteResult.postValue(DeleteResult.Error("Restore failed: ${e.message}"))
}
}
}
fun removeMedia(scoreId: Long) {
viewModelScope.launch {
try {
val score = getScoreById(scoreId)
if (score != null && score.mediaUri != null) {
// Store media URI for potential cleanup
val mediaUri = score.mediaUri
// Update score with null media
val updatedScore = score.copy(mediaUri = null)
updateScore(updatedScore)
// Try to delete the physical file if it's in our app storage
if (mediaUri.startsWith("content://") &&
mediaUri.contains(getApplication<Application>().packageName)) {
try {
val uriObj = Uri.parse(mediaUri)
val context = getApplication<Application>()
val contentResolver = context.contentResolver
contentResolver.delete(uriObj, null, null)
} catch (e: Exception) {
// Just log, don't fail the whole operation
e.printStackTrace()
}
}
_mediaRemoveResult.postValue(MediaRemoveResult.Success)
} else {
_mediaRemoveResult.postValue(MediaRemoveResult.Error("Score not found or no media attached"))
}
} catch (e: Exception) {
e.printStackTrace()
_mediaRemoveResult.postValue(MediaRemoveResult.Error(e.message ?: "Unknown error"))
}
}
}
// Methods to clear LiveData after consumption
fun clearDeleteResult() {
_deleteResult.value = null
}
fun clearMediaSaveResult() {
_mediaSaveResult.value = null
}
fun clearMediaRemoveResult() {
_mediaRemoveResult.value = null
}
}
sealed class ExportResult {
@ -207,6 +309,17 @@ sealed class MediaSaveResult {
data class Error(val message: String) : MediaSaveResult()
}
sealed class DeleteResult {
data class Success(val message: String) : DeleteResult()
data class UndoSuccess(val message: String) : DeleteResult()
data class Error(val message: String) : DeleteResult()
}
sealed class MediaRemoveResult {
object Success : MediaRemoveResult()
data class Error(val message: String) : MediaRemoveResult()
}
class HistoryViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(HistoryViewModel::class.java)) {

View file

@ -17,6 +17,7 @@ import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
ListAdapter<Score, ScoreAdapter.ScoreViewHolder>(ScoreDiffCallback()) {
@ -24,6 +25,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
interface MediaAttachmentListener {
fun onAddMediaClicked(scoreId: Long)
fun onMediaClicked(mediaUri: Uri, isVideo: Boolean)
fun onRemoveMediaClicked(scoreId: Long)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScoreViewHolder {
@ -79,7 +81,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
binding.layoutLinesCleared.visibility = View.GONE
}
// Handle media display
// Handle media display and options
setupMedia(score)
}
@ -88,6 +90,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
// We have media to display
binding.cardViewMedia.visibility = View.VISIBLE
binding.buttonAddMedia.visibility = View.GONE
binding.buttonRemoveMedia.visibility = View.GONE
val uri = score.mediaUri.toUri()
val isVideo = score.mediaUri.endsWith(".mp4", ignoreCase = true) ||
@ -111,6 +114,12 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
mediaListener?.onMediaClicked(uri, true)
}
// Add long press listener for media deletion
binding.videoContainer.setOnLongClickListener {
showDeleteMediaDialog(score.id)
true
}
retriever.release()
} catch (e: Exception) {
e.printStackTrace()
@ -128,6 +137,13 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
binding.imageViewMedia.setOnClickListener {
mediaListener?.onMediaClicked(uri, false)
}
// Add long press listener for media deletion
binding.imageViewMedia.setOnLongClickListener {
showDeleteMediaDialog(score.id)
true
}
} catch (e: Exception) {
e.printStackTrace()
binding.cardViewMedia.visibility = View.GONE
@ -138,6 +154,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
// No media, show the add button
binding.cardViewMedia.visibility = View.GONE
binding.buttonAddMedia.visibility = View.VISIBLE
binding.buttonRemoveMedia.visibility = View.GONE
// Set click listener to add media
binding.buttonAddMedia.setOnClickListener {
@ -145,6 +162,19 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
}
}
}
private fun showDeleteMediaDialog(scoreId: Long) {
val context = binding.root.context
// Create and show deletion popup
MaterialAlertDialogBuilder(context)
.setTitle("Remove Media")
.setMessage("Would you like to remove this media?")
.setPositiveButton("Remove") { _, _ ->
mediaListener?.onRemoveMediaClicked(scoreId)
}
.setNegativeButton("Cancel", null)
.show()
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

View file

@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -1,44 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Main T shape -->
<!-- Simplified T shape -->
<path
android:fillColor="@color/tetris_turquoise"
android:pathData="M30,30h48v16h-16v32h-16v-32h-16z"/>
android:fillColor="#7DD3DB"
android:pathData="M36,36h36v12h-12v24h-12v-24h-12z" />
<!-- Border lines -->
<!-- Dark outline -->
<path
android:strokeColor="@color/tetris_navy"
android:strokeWidth="2"
android:fillColor="#00000000"
android:pathData="M30,30h48v16h-16v32h-16v-32h-16z"/>
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
android:fillColor="#263238"
android:pathData="M36,36v12h12v24h12v-24h12v-12h-36zM37,37h34v10h-12v24h-10v-24h-12v-10z" />
</vector>

View file

@ -118,13 +118,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="Score media" />
android:contentDescription="Score media"
android:foreground="?attr/selectableItemBackground" />
<!-- Video thumbnail with play indicator -->
<FrameLayout
android:id="@+id/videoContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?attr/selectableItemBackground"
android:visibility="gone">
<VideoView
@ -148,16 +150,49 @@
android:alpha="0.7"
android:contentDescription="Play video" />
</FrameLayout>
<!-- Long press hint -->
<TextView
android:id="@+id/textHintLongPress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:text="Long press to remove"
android:textSize="10sp"
android:textStyle="italic"
android:background="#80000000"
android:textColor="#FFFFFF"
android:padding="4dp"
android:layout_margin="4dp" />
</androidx.cardview.widget.CardView>
<!-- Add media button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonAddMedia"
<!-- Media buttons layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Photo/Video"
app:icon="@android:drawable/ic_menu_camera"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
android:orientation="horizontal">
<!-- Add media button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonAddMedia"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Add Photo/Video"
app:icon="@android:drawable/ic_menu_camera"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
<!-- Remove media button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonRemoveMedia"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Remove Media"
android:visibility="gone"
app:icon="@android:drawable/ic_menu_close_clear_cancel"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
</LinearLayout>
</FrameLayout>
</LinearLayout>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

View file

@ -19,4 +19,5 @@
<color name="teal_700">#48AAAA</color>
<color name="black">#000000</color>
<color name="white">#FFFFFF</color>
<color name="ic_launcher_background">#F5C3C2</color>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">@color/tetris_pink</color>
</resources>

View file

@ -19,7 +19,7 @@
<!-- Splash screen -->
<item name="android:windowSplashScreenBackground">@color/tetris_pink</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/tetris_pink</item>
</style>