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
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 184 KiB |
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 460 KiB |
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 9.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 21 KiB |
|
@ -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>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">@color/tetris_pink</color>
|
||||
</resources>
|
|
@ -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>
|
||||
|
||||
|
|