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.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager 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.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -17,11 +21,15 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.accidentalproductions.tetristats.databinding.FragmentHistoryBinding import com.accidentalproductions.tetristats.databinding.FragmentHistoryBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -93,6 +101,8 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
observeScores() observeScores()
observeExportResult() observeExportResult()
observeMediaSaveResult() observeMediaSaveResult()
observeDeleteResult()
observeMediaRemoveResult()
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
@ -100,6 +110,9 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
binding.recyclerViewHistory.apply { binding.recyclerViewHistory.apply {
adapter = scoreAdapter adapter = scoreAdapter
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
// Add swipe to delete functionality
setupSwipeToDelete(this)
} }
} }
@ -133,15 +146,55 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
when (result) { when (result) {
is MediaSaveResult.Success -> { is MediaSaveResult.Success -> {
Toast.makeText(context, "Media attached successfully", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Media attached successfully", Toast.LENGTH_SHORT).show()
viewModel.clearMediaSaveResult()
} }
is MediaSaveResult.Error -> { is MediaSaveResult.Error -> {
Toast.makeText(context, "Failed to attach media: ${result.message}", Toast.LENGTH_LONG).show() Toast.makeText(context, "Failed to attach media: ${result.message}", Toast.LENGTH_LONG).show()
viewModel.clearMediaSaveResult()
} }
null -> { /* Ignore initial state */ } 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) { private fun shareExportedFile(uri: Uri) {
val shareIntent = Intent(Intent.ACTION_SEND).apply { val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/csv" type = "text/csv"
@ -153,6 +206,16 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
startActivity(chooserIntent) 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 // ScoreAdapter.MediaAttachmentListener implementation
override fun onAddMediaClicked(scoreId: Long) { override fun onAddMediaClicked(scoreId: Long) {
currentScoreId = scoreId currentScoreId = scoreId
@ -327,4 +390,101 @@ class HistoryFragment : Fragment(), ScoreAdapter.MediaAttachmentListener {
super.onDestroyView() super.onDestroyView()
_binding = null _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?>() private val _mediaSaveResult = MutableLiveData<MediaSaveResult?>()
val mediaSaveResult: LiveData<MediaSaveResult?> = _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() { fun exportScoresToCsv() {
viewModelScope.launch { viewModelScope.launch {
try { 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 { sealed class ExportResult {
@ -207,6 +309,17 @@ sealed class MediaSaveResult {
data class Error(val message: String) : 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 { class HistoryViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(HistoryViewModel::class.java)) { if (modelClass.isAssignableFrom(HistoryViewModel::class.java)) {

View file

@ -17,6 +17,7 @@ import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) : class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
ListAdapter<Score, ScoreAdapter.ScoreViewHolder>(ScoreDiffCallback()) { ListAdapter<Score, ScoreAdapter.ScoreViewHolder>(ScoreDiffCallback()) {
@ -24,6 +25,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
interface MediaAttachmentListener { interface MediaAttachmentListener {
fun onAddMediaClicked(scoreId: Long) fun onAddMediaClicked(scoreId: Long)
fun onMediaClicked(mediaUri: Uri, isVideo: Boolean) fun onMediaClicked(mediaUri: Uri, isVideo: Boolean)
fun onRemoveMediaClicked(scoreId: Long)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScoreViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScoreViewHolder {
@ -79,7 +81,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
binding.layoutLinesCleared.visibility = View.GONE binding.layoutLinesCleared.visibility = View.GONE
} }
// Handle media display // Handle media display and options
setupMedia(score) setupMedia(score)
} }
@ -88,6 +90,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
// We have media to display // We have media to display
binding.cardViewMedia.visibility = View.VISIBLE binding.cardViewMedia.visibility = View.VISIBLE
binding.buttonAddMedia.visibility = View.GONE binding.buttonAddMedia.visibility = View.GONE
binding.buttonRemoveMedia.visibility = View.GONE
val uri = score.mediaUri.toUri() val uri = score.mediaUri.toUri()
val isVideo = score.mediaUri.endsWith(".mp4", ignoreCase = true) || val isVideo = score.mediaUri.endsWith(".mp4", ignoreCase = true) ||
@ -111,6 +114,12 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
mediaListener?.onMediaClicked(uri, true) mediaListener?.onMediaClicked(uri, true)
} }
// Add long press listener for media deletion
binding.videoContainer.setOnLongClickListener {
showDeleteMediaDialog(score.id)
true
}
retriever.release() retriever.release()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -128,6 +137,13 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
binding.imageViewMedia.setOnClickListener { binding.imageViewMedia.setOnClickListener {
mediaListener?.onMediaClicked(uri, false) mediaListener?.onMediaClicked(uri, false)
} }
// Add long press listener for media deletion
binding.imageViewMedia.setOnLongClickListener {
showDeleteMediaDialog(score.id)
true
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
binding.cardViewMedia.visibility = View.GONE binding.cardViewMedia.visibility = View.GONE
@ -138,6 +154,7 @@ class ScoreAdapter(private val mediaListener: MediaAttachmentListener? = null) :
// No media, show the add button // No media, show the add button
binding.cardViewMedia.visibility = View.GONE binding.cardViewMedia.visibility = View.GONE
binding.buttonAddMedia.visibility = View.VISIBLE binding.buttonAddMedia.visibility = View.VISIBLE
binding.buttonRemoveMedia.visibility = View.GONE
// Set click listener to add media // Set click listener to add media
binding.buttonAddMedia.setOnClickListener { 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"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="108dp"
android:height="108dp" android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> xmlns:android="http://schemas.android.com/apk/res/android">
<path <path android:fillColor="#3DDC84"
android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z"/>
android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:pathData="M9,0L9,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M19,0L19,108" <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:pathData="M29,0L29,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M39,0L39,108" <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:pathData="M49,0L49,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M59,0L59,108" <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:pathData="M69,0L69,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M79,0L79,108" <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:pathData="M89,0L89,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M99,0L99,108" <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:pathData="M0,9L108,9" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,19L108,19" <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:pathData="M0,29L108,29" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
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" />
</vector> </vector>

View file

@ -1,44 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<!-- Main T shape --> <!-- Simplified T shape -->
<path <path
android:fillColor="@color/tetris_turquoise" android:fillColor="#7DD3DB"
android:pathData="M30,30h48v16h-16v32h-16v-32h-16z"/> android:pathData="M36,36h36v12h-12v24h-12v-24h-12z" />
<!-- Border lines --> <!-- Dark outline -->
<path <path
android:strokeColor="@color/tetris_navy" android:fillColor="#263238"
android:strokeWidth="2" android:pathData="M36,36v12h12v24h12v-24h12v-12h-36zM37,37h34v10h-12v24h-10v-24h-12v-10z" />
android:fillColor="#00000000" </vector>
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>

View file

@ -118,13 +118,15 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:contentDescription="Score media" /> android:contentDescription="Score media"
android:foreground="?attr/selectableItemBackground" />
<!-- Video thumbnail with play indicator --> <!-- Video thumbnail with play indicator -->
<FrameLayout <FrameLayout
android:id="@+id/videoContainer" android:id="@+id/videoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:foreground="?attr/selectableItemBackground"
android:visibility="gone"> android:visibility="gone">
<VideoView <VideoView
@ -148,16 +150,49 @@
android:alpha="0.7" android:alpha="0.7"
android:contentDescription="Play video" /> android:contentDescription="Play video" />
</FrameLayout> </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> </androidx.cardview.widget.CardView>
<!-- Add media button --> <!-- Media buttons layout -->
<com.google.android.material.button.MaterialButton <LinearLayout
android:id="@+id/buttonAddMedia"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Add Photo/Video" android:orientation="horizontal">
app:icon="@android:drawable/ic_menu_camera"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" /> <!-- 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> </FrameLayout>
</LinearLayout> </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="teal_700">#48AAAA</color>
<color name="black">#000000</color> <color name="black">#000000</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="ic_launcher_background">#F5C3C2</color>
</resources> </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 --> <!-- Splash screen -->
<item name="android:windowSplashScreenBackground">@color/tetris_pink</item> <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> <item name="android:windowSplashScreenIconBackgroundColor">@color/tetris_pink</item>
</style> </style>