Skip to content

Commit 6873a21

Browse files
author
Gengar
committed
Release v0.9.9 Beta
1 parent 46fccc6 commit 6873a21

96 files changed

Lines changed: 13196 additions & 3323 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ app.*.map.json
5656
/android/gradlew.bat
5757
/android/**/GeneratedPluginRegistrant.java
5858
/android/.cxx/
59+
android/app/.settings/
5960

6061
# Signing
6162
*.jks

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
66

77
---
88

9+
## [0.9.9] Beta — 2026-02-25
10+
11+
### Added
12+
- **Custom Shelves** — create personal game collections with manual curation, filter rules (by system, region, language), or hybrid mode; supports reordering, renaming, and per-shelf sort modes
13+
- **Device Info Service** — adaptive memory tiering (low/standard/high RAM) that auto-tunes image cache sizes, grid cache extents, and cover preload pools for low-end handhelds
14+
- **Shelf Picker Dialog** — quick-add games to shelves from library and game detail screens
15+
- **System Selector Overlay** — filter library view by system with visual system badges
16+
17+
### Improved
18+
- **Settings screen refactored** — split into Preferences, System, and About tabs with extracted `DeviceInfoCard` widget (1048→604 lines)
19+
- **Download overlay refactored** — extracted 7 widgets to `lib/widgets/download/` (DownloadItemCard, CoverThumbnail, PulsingDot, LowSpaceWarning, StatusLabel, DownloadProgressBar, DownloadActionButton) (1477→793 lines)
20+
- **Shelf edit screen refactored** — extracted GameListOverlay, TextInputDialog to shared library widgets (1108→631 lines)
21+
- **RomM onboarding refactored** — extracted RommConnectView, RommSelectView, RommFolderView, RommActionButton (1405→122 lines); state classes moved to `onboarding_state.dart` (1713→1362 lines)
22+
- **Library screen refactored** — extracted ReorderableCardWrapper, LibraryEntry to dedicated widgets (1490→1386 lines)
23+
- **Dependency pinning** — all 16 remaining caret-range dependencies pinned to exact resolved versions for reproducible builds
24+
25+
### Internal
26+
- New models: `CustomShelf`, `ShelfFilterRule` with JSON serialization
27+
- New providers: `CustomShelvesNotifier` / `customShelvesProvider` for shelf CRUD
28+
- `DeviceInfoService` with `MemoryTier` classification
29+
- ~20 new test files covering download queue manager, unified game service, library sync, thumbnail service, custom shelves, database service, config storage, image cache, storage service, and widget tests
30+
31+
---
32+
933
## [0.9.8] Beta — 2026-02-23
1034

1135
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ A premium, console-native game manager for Android. Built for handhelds, perfect
2222
</p>
2323

2424
<p align="center">
25-
<img src="https://img.shields.io/badge/version-0.9.8_Beta-blue?style=flat-square" alt="Version" />
25+
<img src="https://img.shields.io/badge/version-0.9.9_Beta-blue?style=flat-square" alt="Version" />
2626
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License" />
2727
<img src="https://img.shields.io/badge/platform-Android-brightgreen?style=flat-square" alt="Platform" />
2828
<img src="https://img.shields.io/github/stars/averageconsumer/R-Shop?style=flat-square&color=yellow" alt="Stars" />

android/app/.settings/org.eclipse.buildship.core.prefs

Lines changed: 0 additions & 13 deletions
This file was deleted.

android/app/src/main/kotlin/com/example/r_shop/MainActivity.kt

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@ import android.os.StatFs
77
import android.util.Log
88
import android.view.KeyEvent
99
import io.flutter.embedding.android.FlutterActivity
10+
import io.flutter.plugin.common.EventChannel
1011
import io.flutter.plugin.common.MethodChannel
1112
import java.io.*
13+
import java.util.concurrent.Executors
1214
import java.util.zip.ZipInputStream
1315

1416
class MainActivity : FlutterActivity() {
1517
private val CHANNEL = "com.retro.rshop/zip"
1618
private val STORAGE_CHANNEL = "com.retro.rshop/storage"
19+
private val PROGRESS_CHANNEL = "com.retro.rshop/zip_progress"
1720
private val TAG = "MainActivity"
1821

22+
private val extractorPool = Executors.newFixedThreadPool(2)
23+
private val mainHandler = Handler(Looper.getMainLooper())
24+
25+
private var progressSink: EventChannel.EventSink? = null
26+
1927
override fun onCreate(savedInstanceState: Bundle?) {
2028
super.onCreate(savedInstanceState)
2129
Log.d(TAG, "MainActivity onCreate")
@@ -24,29 +32,40 @@ class MainActivity : FlutterActivity() {
2432
override fun configureFlutterEngine(flutterEngine: io.flutter.embedding.engine.FlutterEngine) {
2533
super.configureFlutterEngine(flutterEngine)
2634

35+
EventChannel(flutterEngine.dartExecutor.binaryMessenger, PROGRESS_CHANNEL).setStreamHandler(
36+
object : EventChannel.StreamHandler {
37+
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
38+
progressSink = events
39+
}
40+
override fun onCancel(arguments: Any?) {
41+
progressSink = null
42+
}
43+
}
44+
)
45+
2746
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
2847
when (call.method) {
2948
"extractZip" -> {
3049
val zipPath = call.argument<String>("zipPath")
3150
val targetPath = call.argument<String>("targetPath")
32-
51+
3352
if (zipPath == null || targetPath == null) {
3453
result.error("INVALID_ARGS", "zipPath and targetPath required", null)
3554
return@setMethodCallHandler
3655
}
3756

38-
Thread {
57+
extractorPool.execute {
3958
try {
4059
val extractedFiles = extractZip(zipPath, targetPath)
41-
Handler(Looper.getMainLooper()).post {
60+
mainHandler.post {
4261
result.success(extractedFiles)
4362
}
4463
} catch (e: Exception) {
45-
Handler(Looper.getMainLooper()).post {
64+
mainHandler.post {
4665
result.error("EXTRACT_ERROR", e.message, null)
4766
}
4867
}
49-
}.start()
68+
}
5069
}
5170
else -> result.notImplemented()
5271
}
@@ -69,11 +88,35 @@ class MainActivity : FlutterActivity() {
6988
result.error("STAT_ERROR", e.message, null)
7089
}
7190
}
91+
"getDeviceMemory" -> {
92+
try {
93+
val am = getSystemService(android.content.Context.ACTIVITY_SERVICE) as android.app.ActivityManager
94+
val memInfo = android.app.ActivityManager.MemoryInfo()
95+
am.getMemoryInfo(memInfo)
96+
result.success(mapOf("totalBytes" to memInfo.totalMem))
97+
} catch (e: Exception) {
98+
result.error("MEMORY_ERROR", e.message, null)
99+
}
100+
}
72101
else -> result.notImplemented()
73102
}
74103
}
75104
}
76105

106+
override fun onDestroy() {
107+
extractorPool.shutdown()
108+
super.onDestroy()
109+
}
110+
111+
private fun sendProgress(extractedBytes: Long, totalBytes: Long) {
112+
val sink = progressSink ?: return
113+
if (totalBytes <= 0) return
114+
val percent = (extractedBytes.toDouble() / totalBytes * 100).toInt().coerceIn(0, 100)
115+
mainHandler.post {
116+
sink.success(mapOf("extracted" to extractedBytes, "total" to totalBytes, "percent" to percent))
117+
}
118+
}
119+
77120
private fun extractZip(zipPath: String, targetPath: String): List<String> {
78121
val extractedFiles = mutableListOf<String>()
79122
val buffer = ByteArray(8192)
@@ -97,6 +140,7 @@ class MainActivity : FlutterActivity() {
97140
var entry = zis.nextEntry
98141

99142
val canonicalTarget = File(targetPath).canonicalPath
143+
var lastReportedPercent = -1
100144

101145
while (entry != null) {
102146
val file = File(targetPath, entry.name)
@@ -112,21 +156,30 @@ class MainActivity : FlutterActivity() {
112156
file.mkdirs()
113157
} else {
114158
file.parentFile?.mkdirs()
115-
159+
116160
FileOutputStream(file).use { fos ->
117161
var len: Int
118162
while (zis.read(buffer).also { len = it } > 0) {
119163
fos.write(buffer, 0, len)
120164
extractedBytes += len
121165
}
122166
}
123-
167+
124168
extractedFiles.add(entry.name)
169+
170+
// Report progress (throttle: only on percent change)
171+
if (totalBytes > 0) {
172+
val currentPercent = (extractedBytes.toDouble() / totalBytes * 100).toInt()
173+
if (currentPercent != lastReportedPercent) {
174+
lastReportedPercent = currentPercent
175+
sendProgress(extractedBytes, totalBytes)
176+
}
177+
}
125178
}
126-
179+
127180
entry = zis.nextEntry
128181
}
129-
182+
130183
zis.close()
131184
}
132185

lib/core/input/focus_sync_manager.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ class FocusSyncManager {
139139

140140
final gridWidth = MediaQuery.of(context).size.width - 48;
141141
final itemWidth = (gridWidth - (crossAxisCount - 1) * 16) / crossAxisCount;
142-
final itemHeight = itemWidth / getGridRatio();
142+
final ratio = getGridRatio();
143+
final itemHeight = ratio > 0 ? (itemWidth / ratio) : itemWidth;
143144
final rowHeight = itemHeight + 16;
144145

145146
final centerOffset =

0 commit comments

Comments
 (0)