Fix: Adjust layout padding to respect display cutouts

This commit is contained in:
cmclark00 2025-04-01 11:47:10 -04:00
parent 501e5b37fc
commit 6044b0d93b
8 changed files with 176 additions and 84 deletions

145
README.md
View file

@ -1,122 +1,109 @@
# pixelmintdrop
A modern Tetris implementation for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design.
A modern falling block puzzle game for Android, featuring smooth animations, responsive controls, and a beautiful minimalist design.
## Features
### Core Gameplay
- Classic Tetris mechanics
- Familiar block-stacking gameplay
- 7-bag randomizer for piece distribution
- Ghost piece preview
- Hard drop and soft drop
- T-Spin detection and scoring
- High score system with top 5 scores
- Ghost piece preview for precise placement
- Hard drop and soft drop controls
- Advanced move detection (e.g., T-Spins) and scoring
- Persistent high score system (Top 5)
### Modern Android Features
- Optimized for Android 11+ (API 30+)
- Hardware-accelerated rendering
- High refresh rate support
- Haptic feedback for piece movement and line clears
- Dark theme support
- Responsive touch controls
- Edge-to-edge display support
- Smooth hardware-accelerated rendering
- High refresh rate support for fluid visuals
- Haptic feedback for tactile interaction (piece movement, line clears)
- Automatic Dark theme support
- Intuitive and responsive touch controls
- Full edge-to-edge display utilization
### Scoring System
The game features a comprehensive scoring system:
Pixelmint Drop features a comprehensive scoring system designed to reward skillful play:
#### Base Scores
- Single line: 100 points
- Double: 300 points
- Triple: 500 points
- Quad (4 lines): 1200 points
- Single line clear: 100 points
- Double line clear: 300 points
- Triple line clear: 500 points
- Quad line clear (4 lines): 1200 points
#### Multipliers
#### Multipliers & Bonuses
1. **Level Multiplier**
- Score is multiplied by current level
- Level increases every 10 lines cleared
- Maximum level is 20
1. **Level Multiplier**
- Score multiplied by the current level.
- Level increases every 10 lines cleared (Max Level: 20).
2. **Combo System**
- Combo counter increases with each line clear
- Resets if no lines are cleared
- Multipliers:
- 1 combo: 1.0x
- 2 combos: 1.5x
- 3 combos: 2.0x
- 4 combos: 2.5x
- 5+ combos: 3.0x
2. **Combo System**
- Consecutive line clears increase a combo multiplier.
- Resets if a piece is placed without clearing lines.
- Multipliers: 1x (1), 1.5x (2), 2.0x (3), 2.5x (4), 3.0x (5+ combos).
3. **Back-to-Back Quad**
- 50% bonus (1.5x) for consecutive Quad clears
- Resets if a non-Quad clear is performed
3. **Back-to-Back Quad Bonus**
- 50% bonus (1.5x) for clearing 4 lines consecutively.
- Resets on non-Quad clears.
4. **Perfect Clear**
- 2x for single line
- 3x for double
- 4x for triple
- 5x for Quad
- Awarded when clearing lines without leaving blocks
4. **Perfect Clear Bonus**
- Bonus multiplier for clearing lines leaving an empty board.
- Multipliers: 2x (Single), 3x (Double), 4x (Triple), 5x (Quad).
5. **All Clear**
- 2x multiplier when clearing all blocks
- Requires no blocks in grid and no pieces in play
5. **All Clear Bonus**
- 2x multiplier for clearing the entire grid of blocks.
6. **T-Spin Bonuses**
- Single: 2x
- Double: 4x
- Triple: 6x
6. **T-Spin Bonuses**
- Additional multipliers for executing T-Spin maneuvers.
- Single: 2x, Double: 4x, Triple: 6x.
### Controls
- Swipe left/right to move piece
- Swipe down quickly for hard drop
- Swipe down slowly for soft drop
- Single tap to rotate
- **Swipe left/right:** Move piece horizontally.
- **Swipe down quickly:** Hard drop (instant placement).
- **Swipe down slowly:** Soft drop (faster downward movement).
- **Single tap:** Rotate piece.
### Visual Effects
- Smooth piece movement animations
- Pulsing border glow during line clears
- Ghost piece preview
- Block glow effects
- Subtle grid lines
- Animated title screen with falling pieces
- Silky-smooth piece movement animations.
- Dynamic pulsing border glow during line clears.
- Clear ghost piece preview for accurate drops.
- Subtle block glow effects.
- Clean grid lines for better visibility.
- Engaging animated title screen featuring falling pieces.
## Technical Details
### Requirements
- Android 11 (API 30) or higher
- Hardware acceleration support
- 2GB RAM minimum
- Android 11 (API 30) or higher.
- Hardware acceleration support.
- Minimum 2GB RAM recommended.
### Performance Optimizations
- Hardware-accelerated rendering
- Efficient collision detection
- Optimized grid operations
- Smooth animations at 60fps
- Background thread for score calculations
- Leverages hardware-accelerated rendering via Android Canvas.
- Efficient collision detection algorithms.
- Optimized grid data structures and operations.
- Targets smooth 60fps animations.
- Score calculations performed on a background thread to maintain UI responsiveness.
### Architecture
- Written in Kotlin
- Uses Android Canvas for rendering
- Follows Material Design guidelines
- Implements high score persistence using SharedPreferences
- Developed entirely in Kotlin.
- Utilizes Android Canvas API for custom drawing.
- Adheres to Material Design principles for UI/UX.
- Persists high scores using SharedPreferences.
## Building from Source
1. Clone the repository:
```bash
git clone https://github.com/cmclark00/pixelmintdrop.git
```
2. Open the project in Android Studio
3. Build and run on your device or emulator
1. Clone the repository:
```bash
git clone https://github.com/cmclark00/pixelmintdrop.git
```
2. Open the project in Android Studio.
3. Build and run on a compatible Android device or emulator (API 30+).
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Contributions are welcome! Please feel free to submit a Pull Request with improvements or bug fixes.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
This project is licensed under the MIT License - see the `LICENSE` file for details.

View file

@ -11,6 +11,11 @@ import android.graphics.Color
import android.util.Log
import android.view.KeyEvent
import android.view.InputDevice
import android.graphics.Rect
import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
class HighScoresActivity : AppCompatActivity() {
private lateinit var binding: HighScoresBinding
@ -26,6 +31,20 @@ class HighScoresActivity : AppCompatActivity() {
binding = HighScoresBinding.inflate(layoutInflater)
setContentView(binding.root)
// Apply insets to the root view
val initialPadding = Rect(binding.root.paddingLeft, binding.root.paddingTop,
binding.root.paddingRight, binding.root.paddingBottom)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = initialPadding.left + insets.left,
top = initialPadding.top + insets.top,
right = initialPadding.right + insets.right,
bottom = initialPadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
highScoreManager = HighScoreManager(this)
progressionManager = PlayerProgressionManager(this)

View file

@ -45,6 +45,10 @@ import android.widget.ImageButton
import android.graphics.drawable.GradientDrawable
import android.widget.TextView
import android.widget.Switch
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.view.ViewGroup
import androidx.core.view.updatePadding
class MainActivity : AppCompatActivity(),
GamepadController.GamepadConnectionListener,
@ -135,6 +139,65 @@ class MainActivity : AppCompatActivity(),
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Store initial padding values before applying insets
val initialPausePadding = Rect(binding.pauseContainer.paddingLeft, binding.pauseContainer.paddingTop,
binding.pauseContainer.paddingRight, binding.pauseContainer.paddingBottom)
val initialGameOverPadding = Rect(binding.gameOverContainer.paddingLeft, binding.gameOverContainer.paddingTop,
binding.gameOverContainer.paddingRight, binding.gameOverContainer.paddingBottom)
val initialCustomizationPadding = Rect(binding.customizationContainer.paddingLeft, binding.customizationContainer.paddingTop,
binding.customizationContainer.paddingRight, binding.customizationContainer.paddingBottom)
val initialProgressionPadding = Rect(binding.progressionScreen.paddingLeft, binding.progressionScreen.paddingTop,
binding.progressionScreen.paddingRight, binding.progressionScreen.paddingBottom)
// Apply insets to the pause container
ViewCompat.setOnApplyWindowInsetsListener(binding.pauseContainer) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// Apply insets by adding to initial padding
view.updatePadding(
left = initialPausePadding.left + insets.left,
top = initialPausePadding.top + insets.top,
right = initialPausePadding.right + insets.right,
bottom = initialPausePadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
// Apply insets to the game over container
ViewCompat.setOnApplyWindowInsetsListener(binding.gameOverContainer) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = initialGameOverPadding.left + insets.left,
top = initialGameOverPadding.top + insets.top,
right = initialGameOverPadding.right + insets.right,
bottom = initialGameOverPadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
// Apply insets to the customization container
ViewCompat.setOnApplyWindowInsetsListener(binding.customizationContainer) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = initialCustomizationPadding.left + insets.left,
top = initialCustomizationPadding.top + insets.top,
right = initialCustomizationPadding.right + insets.right,
bottom = initialCustomizationPadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
// Apply insets to the progression screen
ViewCompat.setOnApplyWindowInsetsListener(binding.progressionScreen) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = initialProgressionPadding.left + insets.left,
top = initialProgressionPadding.top + insets.top,
right = initialProgressionPadding.right + insets.right,
bottom = initialProgressionPadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
// Disable Android back gesture to prevent accidental app exits
disableAndroidBackGesture()

View file

@ -1,12 +1,17 @@
package com.pixelmintdrop
import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import com.pixelmintdrop.databinding.ActivityStatsBinding
import com.pixelmintdrop.model.StatsManager
import com.pixelmintdrop.model.PlayerProgressionManager
import android.graphics.Color
import java.text.SimpleDateFormat
import java.util.*
@ -21,6 +26,20 @@ class StatsActivity : AppCompatActivity() {
binding = ActivityStatsBinding.inflate(layoutInflater)
setContentView(binding.root)
// Apply insets to the root view
val initialPadding = Rect(binding.root.paddingLeft, binding.root.paddingTop,
binding.root.paddingRight, binding.root.paddingBottom)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = initialPadding.left + insets.left,
top = initialPadding.top + insets.top,
right = initialPadding.right + insets.right,
bottom = initialPadding.bottom + insets.bottom
)
WindowInsetsCompat.CONSUMED
}
statsManager = StatsManager(this)
progressionManager = PlayerProgressionManager(this)

View file

@ -3,7 +3,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical">
android:orientation="vertical"
android:fitsSystemWindows="true">
<TextView
android:layout_width="wrap_content"

View file

@ -4,7 +4,8 @@
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
android:padding="16dp">
android:padding="16dp"
android:fitsSystemWindows="true">
<TextView
android:layout_width="wrap_content"

View file

@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
android:background="@color/black"
android:fitsSystemWindows="true">
<ScrollView
android:layout_width="wrap_content"

View file

@ -4,7 +4,8 @@
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
android:padding="16dp">
android:padding="16dp"
android:fitsSystemWindows="true">
<TextView
android:layout_width="wrap_content"