Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion android/app/src/main/kotlin/com/example/pegma/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package com.khlebobul.pegma

import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity()
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Force hardware acceleration for better rendering performance
window.setFlags(
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
}
Comment on lines +6 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for hardwareAccelerated in manifest
find android/app/src/main -name "AndroidManifest.xml" -exec rg -n "hardwareAccelerated" {} \;

Repository: khlebobul/pegma

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Read the manifest to see full context
find android/app/src/main -name "AndroidManifest.xml" -type f | head -1 | xargs cat

Repository: khlebobul/pegma

Length of output: 2253


🏁 Script executed:

#!/bin/bash
# Check build.gradle for target SDK version
find android/app -name "build.gradle*" -type f | xargs rg -A 2 "targetSdk|compileSdk" | head -20

Repository: khlebobul/pegma

Length of output: 270


Remove the redundant programmatic hardware acceleration setting.

Hardware acceleration is already enabled in the manifest (android:hardwareAccelerated="true" on line 13 of AndroidManifest.xml), which is the proper and authoritative way to configure it. Setting FLAG_HARDWARE_ACCELERATED programmatically in onCreate() is ineffective—the window is already created and the rendering pipeline established before this callback executes. Remove this code to avoid confusion.

🤖 Prompt for AI Agents
In android/app/src/main/kotlin/com/example/pegma/MainActivity.kt around lines 6
to 14, remove the programmatic hardware acceleration flag set in onCreate(): the
window.setFlags(...) block is redundant and ineffective because hardware
acceleration is already enabled via AndroidManifest.xml
(android:hardwareAccelerated="true"); delete the three lines that call
window.setFlags(...) and the accompanying comment so onCreate only calls
super.onCreate(savedInstanceState).

}
12 changes: 6 additions & 6 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = G4KB5U6326;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand All @@ -505,7 +505,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.8;
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.khlebobul.pegma;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand Down Expand Up @@ -683,7 +683,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = G4KB5U6326;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand All @@ -693,7 +693,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.8;
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.khlebobul.pegma;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand All @@ -709,7 +709,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = G4KB5U6326;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand All @@ -719,7 +719,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.8;
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.khlebobul.pegma;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
Expand Down
54 changes: 47 additions & 7 deletions lib/core/database/database_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
static Future<Database>? _initFuture;

DatabaseHelper._init();

Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('pegma.db');
// Prevent race conditions by reusing the same initialization future
_initFuture ??= _initDB('pegma.db');
_database = await _initFuture;
return _database!;
}
Comment on lines 12 to 18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Race condition guard is incomplete for close/reopen scenarios.

The _initFuture is never reset. If close() is called and the database is accessed again, _initFuture still holds the completed future pointing to the closed database instance. This will return a closed database on subsequent access.

Consider resetting both fields in close():

  Future<void> close() async {
    final db = await database;
    await db.close();
+   _database = null;
+   _initFuture = null;
  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/core/database/database_helper.dart around lines 12 to 18, the
race-condition guard is incomplete because _initFuture is never reset on close
causing reopened accesses to await a completed future that points to a closed
DB; update the close() implementation to await closing the existing Database (if
any), then set _database = null and _initFuture = null so subsequent callers
will reinitialize a fresh DB instance (ensure any close awaits complete before
nulling fields to avoid races).


Expand All @@ -20,7 +23,7 @@ class DatabaseHelper {

return await openDatabase(
path,
version: 4,
version: 5,
onCreate: _createDB,
onUpgrade: _upgradeDB,
);
Expand Down Expand Up @@ -49,6 +52,16 @@ class DatabaseHelper {
)
''');
}

if (oldVersion < 5) {
// Delete saved games for levels that were modified (10, 45, 48)
// This fixes incompatibility issues while preserving completed level progress
await db.delete(
'saved_games',
where: 'level_id IN (?, ?, ?)',
whereArgs: [10, 45, 48],
);
}
}

Future<void> _createDB(Database db, int version) async {
Expand Down Expand Up @@ -126,11 +139,26 @@ class DatabaseHelper {

if (result.isEmpty) return null;

final row = result.first;
return {
'board': jsonDecode(row['board_state'] as String) as List<dynamic>,
'moves_count': row['moves_count'] as int,
};
try {
final row = result.first;
final boardData = jsonDecode(row['board_state'] as String) as List<dynamic>;

// Validate board structure
if (boardData.isEmpty || boardData.first is! List) {
// Invalid board structure, delete corrupted save
await deleteSavedGameState(levelId);
return null;
}

return {
'board': boardData,
'moves_count': row['moves_count'] as int,
};
} catch (e) {
// If decoding fails, delete corrupted save and return null
await deleteSavedGameState(levelId);
return null;
}
}

Future<void> deleteSavedGameState(int levelId) async {
Expand All @@ -143,6 +171,18 @@ class DatabaseHelper {
await db.delete('saved_games');
}

/// Clear saved games for specific levels (useful after level modifications)
Future<void> clearSavedGamesForLevels(List<int> levelIds) async {
if (levelIds.isEmpty) return;
final db = await database;
final placeholders = List.filled(levelIds.length, '?').join(',');
await db.delete(
'saved_games',
where: 'level_id IN ($placeholders)',
whereArgs: levelIds,
);
}

Future<void> close() async {
final db = await database;
await db.close();
Expand Down
11 changes: 11 additions & 0 deletions lib/data/levels/level_51.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "1", "0", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["0", "0", "1", "1", "1", "0", "0"],
["0", "1", "0", "1", "0", "1", "0"],
["0", "0", "1", "0", "1", "0", "0"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_52.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["0", "0", "0", "1", "0", "0", "0"],
["0", "0", "1", "1", "1", "0", "0"],
["1", "1", "0", "0", "0", "1", "1"],
["-1", "-1", "1", "0", "1", "-1", "-1"],
["-1", "-1", "1", "0", "1", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_53.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["0", "0", "1", "1", "1", "0", "0"],
["0", "1", "1", "0", "1", "1", "0"],
["0", "0", "1", "0", "1", "0", "0"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_54.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["0", "0", "1", "1", "1", "0", "0"],
["0", "1", "0", "1", "0", "1", "0"],
["0", "1", "1", "0", "1", "1", "0"],
["-1", "-1", "1", "0", "1", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_55.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "1", "0", "-1", "-1"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["0", "0", "1", "0", "1", "0", "0"],
["0", "0", "1", "1", "1", "0", "0"],
["0", "0", "1", "0", "1", "0", "0"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_56.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"],
["0", "0", "0", "0", "1", "1", "1"],
["1", "1", "1", "1", "1", "1", "1"],
["0", "0", "0", "1", "1", "0", "0"],
["-1", "-1", "0", "1", "1", "-1", "-1"],
["-1", "-1", "0", "1", "1", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_57.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "1", "0", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["0", "0", "0", "0", "0", "0", "0"],
["0", "1", "1", "1", "1", "1", "0"],
["0", "1", "0", "0", "0", "1", "0"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["-1", "-1", "1", "0", "1", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_58.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"],
["0", "1", "1", "0", "1", "1", "0"],
["1", "1", "1", "1", "1", "1", "1"],
["0", "1", "1", "0", "1", "1", "0"],
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_59.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"],
["0", "1", "1", "0", "1", "1", "0"],
["0", "0", "1", "1", "1", "1", "0"],
["0", "0", "1", "0", "1", "0", "0"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["-1", "-1", "0", "1", "0", "-1", "-1"]
]
}
11 changes: 11 additions & 0 deletions lib/data/levels/level_60.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"board": [
["-1", "-1", "0", "0", "0", "-1", "-1"],
["-1", "-1", "0", "0", "0", "-1", "-1"],
["0", "1", "0", "1", "0", "1", "1"],
["0", "1", "1", "1", "1", "1", "0"],
["0", "0", "0", "1", "0", "0", "0"],
["-1", "-1", "1", "1", "1", "-1", "-1"],
["-1", "-1", "1", "0", "1", "-1", "-1"]
]
}
2 changes: 1 addition & 1 deletion lib/generated/intl/messages_it.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'it';

static String m0(String appName) =>
"il nome ${appName} è un gioco di parole che combina: \"peg\" (piolo, pedina) - l'elemento principale del gioco - e \"theorema\", associato al rigore e alla logica matematica.";
"il nome ${appName} è un gioco di parole che combina: \"peg\" (piolo, pedina) - lelemento principale del gioco - e \"theorema\", associato al rigore e alla logica matematica.";

final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
Expand Down
12 changes: 10 additions & 2 deletions lib/presentation/screens/game/game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ class _GameScreenState extends ConsumerState<GameScreen>
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_checkLevelStatusAndLoad();
// Use addPostFrameCallback to ensure proper initialization on Android
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_checkLevelStatusAndLoad();
}
});
}

Future<void> _checkLevelStatusAndLoad() async {
Expand Down Expand Up @@ -297,7 +302,10 @@ class _GameScreenState extends ConsumerState<GameScreen>
child: Center(
child: AspectRatio(
aspectRatio: 1,
child: GameBoard(levelId: widget.levelId),
child: GameBoard(
key: ValueKey('game_board_${widget.levelId}'),
levelId: widget.levelId,
),
),
),
),
Expand Down
14 changes: 9 additions & 5 deletions lib/presentation/widgets/game/game_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ class GameBoard extends ConsumerWidget {
return const Center(child: CircularProgressIndicator());
}

return Padding(
padding: const EdgeInsets.all(20.0),
child: AspectRatio(
aspectRatio: 1.0,
child: Column(
// RepaintBoundary isolates rendering to prevent Android release mode issues
return RepaintBoundary(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: AspectRatio(
aspectRatio: 1.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(gameState.board.length, (row) {
return Expanded(
Expand Down Expand Up @@ -55,6 +57,7 @@ class GameBoard extends ConsumerWidget {
}),
),
),
),
);
}

Expand Down Expand Up @@ -101,6 +104,7 @@ class GameBoard extends ConsumerWidget {

return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque, // Ensures tap detection works reliably on Android
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: pegma
description: "A free and cross-platform version of the classic Peg solitaire! Enjoy the timeless puzzle on your mobile device!"
publish_to: 'none'
version: 1.3.8+25
version: 1.4.0+26

environment:
sdk: ^3.8.1
Expand Down