Skip to content

Commit 8435342

Browse files
Merge branch 'development' into issue#1268
2 parents 6f2d625 + 18a099e commit 8435342

39 files changed

+4615
-1004
lines changed

.github/workflows/pull_request.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ on:
77
env:
88
ANDROID_EMULATOR_API: 34
99
ANDROID_EMULATOR_ARCH: x86_64
10-
IPHONE_DEVICE_MODEL: iPhone 16 Pro Max
11-
IPAD_DEVICE_MODEL: iPad Pro 13-inch (M4)
10+
IPHONE_DEVICE_MODEL: iPhone 16
11+
IPAD_DEVICE_MODEL: iPad (10th generation)
1212

1313
jobs:
1414
common:
@@ -48,7 +48,7 @@ jobs:
4848
- name: Set up Xcode
4949
uses: maxim-lobanov/[email protected]
5050
with:
51-
xcode-version: latest
51+
xcode-version: 16.4.0
5252

5353
- uses: actions/checkout@v4
5454

@@ -74,10 +74,13 @@ jobs:
7474
- name: Set up Xcode
7575
uses: maxim-lobanov/[email protected]
7676
with:
77-
xcode-version: latest
77+
xcode-version: 16.4.0
7878

7979
- uses: actions/checkout@v4
8080

81+
- name: List available simulators
82+
run: xcrun simctl list devices
83+
8184
- name: iOS Screenshot Workflow
8285
uses: ./.github/actions/screenshot-ios
8386
with:

.github/workflows/push.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ jobs:
171171
- name: Set up Xcode
172172
uses: maxim-lobanov/[email protected]
173173
with:
174-
xcode-version: latest
174+
xcode-version: latest-stable
175175

176176
- uses: actions/checkout@v4
177177

lib/bademagic_module/models/mode.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ enum Mode {
44
up('0x02'),
55
down('0x03'),
66
fixed('0x04'),
7-
animation('0x05'),
8-
snowflake('0x06'),
9-
picture('0x07'),
10-
laser('0x08');
7+
snowflake('0x05'),
8+
picture('0x06'),
9+
animation('0x07'),
10+
laser('0x08'),
11+
pacman('0x09'), // Added Pacman mode
12+
chevronleft('0x0A'), // Chevron left mode
13+
diamond('0x0B'), // Diamond animation mode
14+
feet('0x0C'), // Feet animation mode
15+
brokenhearts('0x0D'), // Broken Hearts animation mode
16+
cupid('0x0E'); // Cupid animation mode
1117

1218
final String hexValue;
1319
const Mode(this.hexValue);

lib/bademagic_module/models/speed.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ enum Speed {
1313

1414
// Static method to get int value of speed from the Enum Speed
1515
static int getIntValue(Speed speed) {
16-
String hexValue = speed.hexValue.substring(2, 3);
17-
int intValue = int.parse(hexValue, radix: 10);
18-
return intValue;
16+
// Map Speed.one (index 0) -> 1, Speed.two (index 1) -> 2, etc.
17+
return speed.index + 1;
1918
}
2019

2120
// Static method to get Speed from hex value
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'package:badgemagic/bademagic_module/utils/badge_text_storage.dart';
4+
import 'package:badgemagic/bademagic_module/utils/file_helper.dart';
5+
import 'package:badgemagic/bademagic_module/models/data.dart';
6+
import 'package:path_provider/path_provider.dart';
7+
8+
/// Helper class for loading and parsing badge data and original text from disk.
9+
/// This class centralizes logic-heavy operations for badge editing, keeping UI code clean and testable.
10+
class BadgeLoaderHelper {
11+
/// Loads badge data and original text from disk for editing.
12+
///
13+
/// [badgeFilename] is the filename of the badge JSON (with or without .json extension).
14+
/// Returns a tuple: (badgeText, Data object, savedData Map).
15+
/// Throws if the badge file is not found or cannot be parsed.
16+
static Future<(String, Data, Map<String, dynamic>?)> loadBadgeDataAndText(
17+
String badgeFilename) async {
18+
Map<String, dynamic>? savedData;
19+
String badgeText = "";
20+
Data? badgeData;
21+
try {
22+
// Load badge JSON from disk. This contains all badge settings and state.
23+
final directory = await getApplicationDocumentsDirectory();
24+
final filePath = '${directory.path}/$badgeFilename';
25+
final file = File(filePath);
26+
if (await file.exists()) {
27+
final jsonString = await file.readAsString();
28+
savedData = jsonDecode(jsonString) as Map<String, dynamic>;
29+
} else {
30+
// If the badge file doesn't exist, editing cannot proceed.
31+
throw Exception("Badge file not found: $filePath");
32+
}
33+
// Load the original badge text (the user's typed message) using BadgeTextStorage.
34+
// Always use .json extension for consistency with how text was saved.
35+
final textFilename = badgeFilename.endsWith('.json')
36+
? badgeFilename
37+
: '$badgeFilename.json';
38+
badgeText = await BadgeTextStorage.getOriginalText(textFilename);
39+
if (badgeText.isEmpty) {
40+
// Fallback to default text if original is missing (should rarely happen).
41+
badgeText = "Hello";
42+
}
43+
// Parse the JSON map into a strongly-typed Data object for downstream use.
44+
badgeData = FileHelper().jsonToData(savedData);
45+
return (badgeText, badgeData, savedData);
46+
} catch (e) {
47+
// Rethrow so UI can handle error and show a message.
48+
rethrow;
49+
}
50+
}
51+
52+
/// Parses the animation mode value from a badge message's mode field.
53+
///
54+
/// Accepts either an int or an enum-like string (e.g., 'BadgeMode.left').
55+
/// Returns the integer mode value for use with animationMap.
56+
///
57+
/// This logic is centralized here to allow easy extension if new modes are added.
58+
static int parseAnimationMode(dynamic mode) {
59+
int modeValue = 0;
60+
try {
61+
if (mode is int) {
62+
// If already an int, use directly.
63+
modeValue = mode;
64+
} else {
65+
// Otherwise, parse from string (e.g., 'BadgeMode.left').
66+
String modeString = mode.toString();
67+
if (modeString.contains('.')) {
68+
// Extract the enum name (right of the dot).
69+
String modeName = modeString.split('.').last;
70+
switch (modeName.toLowerCase()) {
71+
case 'left':
72+
modeValue = 0;
73+
break;
74+
case 'right':
75+
modeValue = 1;
76+
break;
77+
case 'up':
78+
modeValue = 2;
79+
break;
80+
case 'down':
81+
modeValue = 3;
82+
break;
83+
case 'fixed':
84+
modeValue = 4;
85+
break;
86+
case 'snowflake':
87+
modeValue = 5;
88+
break;
89+
case 'picture':
90+
modeValue = 6;
91+
break;
92+
case 'animation':
93+
modeValue = 7;
94+
break;
95+
default:
96+
modeValue = 0;
97+
}
98+
} else {
99+
// If not an enum string, try parsing as int.
100+
modeValue = int.tryParse(modeString) ?? 0;
101+
}
102+
}
103+
} catch (_) {
104+
// Defensive: fallback to 0 (left) if parsing fails.
105+
modeValue = 0;
106+
}
107+
return modeValue;
108+
}
109+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart';
5+
import 'package:path_provider/path_provider.dart';
6+
7+
/// A utility class to store and retrieve the original text of badges
8+
class BadgeTextStorage {
9+
static const String TEXT_STORAGE_FILENAME = 'badge_original_texts.json';
10+
11+
/// Save the original text for a badge
12+
static Future<void> saveOriginalText(
13+
String badgeFilename, String originalText) async {
14+
try {
15+
// Get the existing text storage or create a new one
16+
Map<String, String> textStorage = await _getTextStorage();
17+
18+
// Store the original text with the badge filename as the key
19+
textStorage[badgeFilename] = originalText;
20+
21+
// Save the updated storage
22+
await _saveTextStorage(textStorage);
23+
24+
logger.d('Saved original text for badge: $badgeFilename');
25+
} catch (e) {
26+
logger.e('Error saving original text: $e');
27+
}
28+
}
29+
30+
/// Get the original text for a badge
31+
static Future<String> getOriginalText(String badgeFilename) async {
32+
try {
33+
// Get the existing text storage
34+
Map<String, String> textStorage = await _getTextStorage();
35+
36+
// Return the original text if it exists, otherwise return empty string
37+
return textStorage[badgeFilename] ?? '';
38+
} catch (e) {
39+
logger.e('Error getting original text: $e');
40+
return '';
41+
}
42+
}
43+
44+
/// Move the original text mapping from oldFilename to newFilename
45+
static Future<void> moveOriginalText(
46+
String oldFilename, String newFilename) async {
47+
try {
48+
Map<String, String> textStorage = await _getTextStorage();
49+
if (textStorage.containsKey(oldFilename)) {
50+
textStorage[newFilename] = textStorage[oldFilename]!;
51+
textStorage.remove(oldFilename);
52+
await _saveTextStorage(textStorage);
53+
logger.d('Moved original text from: $oldFilename to $newFilename');
54+
}
55+
} catch (e) {
56+
logger.e('Error moving original text: $e');
57+
}
58+
}
59+
60+
/// Delete the original text for a badge
61+
static Future<void> deleteOriginalText(String badgeFilename) async {
62+
try {
63+
// Get the existing text storage
64+
Map<String, String> textStorage = await _getTextStorage();
65+
66+
// Remove the entry for the badge
67+
textStorage.remove(badgeFilename);
68+
69+
// Save the updated storage
70+
await _saveTextStorage(textStorage);
71+
72+
logger.d('Deleted original text for badge: $badgeFilename');
73+
} catch (e) {
74+
logger.e('Error deleting original text: $e');
75+
}
76+
}
77+
78+
/// Get the text storage file
79+
static Future<Map<String, String>> _getTextStorage() async {
80+
try {
81+
final directory = await getApplicationDocumentsDirectory();
82+
final file = File('${directory.path}/$TEXT_STORAGE_FILENAME');
83+
84+
// Create the file if it doesn't exist
85+
if (!await file.exists()) {
86+
await file.create();
87+
await file.writeAsString('{}');
88+
return {};
89+
}
90+
91+
// Read the file and parse the JSON
92+
final jsonString = await file.readAsString();
93+
if (jsonString.isEmpty) {
94+
return {};
95+
}
96+
97+
final Map<String, dynamic> jsonData = jsonDecode(jsonString);
98+
99+
// Convert dynamic values to String
100+
final Map<String, String> textStorage = {};
101+
jsonData.forEach((key, value) {
102+
textStorage[key] = value.toString();
103+
});
104+
105+
return textStorage;
106+
} catch (e) {
107+
logger.e('Error getting text storage: $e');
108+
return {};
109+
}
110+
}
111+
112+
/// Save the text storage to file
113+
static Future<void> _saveTextStorage(Map<String, String> textStorage) async {
114+
try {
115+
final directory = await getApplicationDocumentsDirectory();
116+
final file = File('${directory.path}/$TEXT_STORAGE_FILENAME');
117+
118+
// Convert the map to JSON and save it
119+
final jsonString = jsonEncode(textStorage);
120+
await file.writeAsString(jsonString);
121+
} catch (e) {
122+
logger.e('Error saving text storage: $e');
123+
}
124+
}
125+
}

lib/bademagic_module/utils/byte_array_utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ List<int> hexStringToByteArray(String hexString) {
2525
int secondDigit = int.parse(hexString[i + 1], radix: 16);
2626
data.add((firstDigit << 4) + secondDigit);
2727
}
28-
logger.d(data.length);
28+
2929
return data;
3030
}
3131

0 commit comments

Comments
 (0)