Skip to content

Commit 3a5f37c

Browse files
fix: Behaviour change in the Snowflake animation
fix: Guard against zero framesCount to avoid modulo by zero in SnowFlakeAnimation fix: blicking illusion back fix: added input validation fix: fixed the interchanges animations fix: animation and picture swapped with correct demo animation style: format animation_badge_provider fix: fixed animation preview and usage fix: shows selected animation in preview of saved badges fix: animation transfers to the badge succesfully as animation fix: test fail fix: failed flutter test fixed
1 parent f401ac4 commit 3a5f37c

15 files changed

+435
-166
lines changed
-34.5 KB
Loading
-70 KB
Loading
118 KB
Loading

lib/bademagic_module/models/messages.dart

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,25 @@ class Message {
77
final bool marquee;
88
final Speed speed;
99
final Mode mode;
10+
final int? animationIndex; // 👈 NEW
1011

1112
Message({
1213
required this.text,
1314
this.flash = false,
1415
this.marquee = false,
15-
this.speed = Speed.one, // Default speed
16-
this.mode = Mode.left, // Default mode
16+
this.speed = Speed.one,
17+
this.mode = Mode.left,
18+
this.animationIndex, // 👈 NEW
1719
});
1820

1921
// Convert Message object to JSON
2022
Map<String, dynamic> toJson() => {
2123
'text': text,
2224
'flash': flash,
2325
'marquee': marquee,
24-
'speed': speed.hexValue, // Use hexValue for serialization
25-
'mode': mode.hexValue, // Use hexValue for serialization
26+
'speed': speed.hexValue,
27+
'mode': mode.hexValue,
28+
if (animationIndex != null) 'animationIndex': animationIndex, // 👈 NEW
2629
};
2730

2831
// Convert JSON to Message object
@@ -52,10 +55,9 @@ class Message {
5255
text: List<String>.from(textList),
5356
flash: (json['flash'] as bool?) ?? false,
5457
marquee: (json['marquee'] as bool?) ?? false,
55-
speed: Speed.fromHex(
56-
json['speed'] as String), // Using helper method for safety
57-
mode: Mode.fromHex(
58-
json['mode'] as String), // Using helper method for safety
58+
speed: Speed.fromHex(json['speed'] as String),
59+
mode: Mode.fromHex(json['mode'] as String),
60+
animationIndex: json['animationIndex'] as int?, // 👈 NEW
5961
);
6062
}
6163
}

lib/bademagic_module/models/mode.dart

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,27 @@ enum Mode {
44
up('0x02'),
55
down('0x03'),
66
fixed('0x04'),
7-
snowflake('0x05'),
8-
picture('0x06'),
9-
animation('0x07'),
7+
animation('0x05'),
8+
snowflake('0x06'),
9+
picture('0x07'),
1010
laser('0x08');
1111

1212
final String hexValue;
1313
const Mode(this.hexValue);
1414

15-
//method to get the integer value of the mode
1615
static int getIntValue(Mode mode) {
17-
String hexValue = mode.hexValue.substring(3, 4);
18-
int intValue = int.parse(hexValue, radix: 10);
19-
return intValue;
16+
return int.parse(mode.hexValue.substring(2), radix: 16);
2017
}
2118

22-
// Helper method to safely parse hex value
2319
static Mode fromHex(String hexValue) {
2420
return Mode.values.firstWhere(
2521
(mode) => mode.hexValue == hexValue,
26-
orElse: () => Mode.left, // Default to Mode.left if no match
22+
orElse: () => Mode.left,
2723
);
2824
}
25+
26+
static Mode fromInt(int value) {
27+
final hex = value.toRadixString(16).padLeft(2, '0');
28+
return fromHex('0x$hex');
29+
}
2930
}

lib/badge_animation/ani_animation.dart

Lines changed: 144 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,167 @@ import 'package:badgemagic/badge_animation/animation_abstract.dart';
22

33
class AniAnimation extends BadgeAnimation {
44
@override
5-
void processAnimation(int badgeHeight, int badgeWidth, int animationIndex,
6-
List<List<bool>> processGrid, List<List<bool>> canvas) {
7-
int newWidth = processGrid[0].length;
8-
int newHeight = processGrid.length;
9-
int verticalOffset = (badgeHeight - newHeight) ~/ 2;
10-
int displayWidth = newWidth > badgeWidth ? badgeWidth : newWidth;
11-
int horizontalOffset = (badgeWidth - displayWidth) ~/ 2;
12-
var totalAnimationLength = badgeWidth;
13-
int frame = animationIndex % totalAnimationLength;
14-
var firstHalf = frame < badgeWidth ~/ 2;
15-
var secondHalf = frame >= badgeWidth ~/ 2;
5+
void processAnimation(
6+
int badgeHeight,
7+
int badgeWidth,
8+
int animationIndex,
9+
List<List<bool>> processGrid,
10+
List<List<bool>> canvas,
11+
) {
12+
if (processGrid.isEmpty ||
13+
processGrid.any((row) => row.length != processGrid[0].length)) {
14+
throw ArgumentError(
15+
'processGrid must be a non-empty rectangular 2D list',
16+
);
17+
}
18+
19+
int newGridHeight = processGrid.length;
20+
int newGridWidth = processGrid[0].length;
21+
22+
if (canvas.length < badgeHeight ||
23+
canvas.any((row) => row.length < badgeWidth)) {
24+
throw ArgumentError(
25+
'canvas must have at least $badgeHeight rows and $badgeWidth columns',
26+
);
27+
}
1628

29+
// Clear canvas first
1730
for (int i = 0; i < badgeHeight; i++) {
1831
for (int j = 0; j < badgeWidth; j++) {
19-
bool lineShow = false;
20-
bool bitmapShowcenter = false;
21-
bool bitmapShowOut = false;
32+
canvas[i][j] = false;
33+
}
34+
}
2235

23-
int sourceRow = i - verticalOffset;
24-
int sourceCol = j - horizontalOffset;
36+
// Find word boundaries by detecting spaces (multiple empty columns)
37+
List<Map<String, int>> words =
38+
_findWords(processGrid, newGridHeight, newGridWidth);
39+
40+
if (words.isEmpty) {
41+
// If no words found, show the entire grid
42+
_displaySegment(processGrid, canvas, 0, newGridWidth, badgeHeight,
43+
badgeWidth, newGridHeight);
44+
return;
45+
}
2546

26-
bool isWithinNewGrid = sourceRow >= 0 &&
27-
sourceRow < newHeight &&
28-
sourceCol >= 0 &&
29-
sourceCol < displayWidth;
47+
// Determine which word to show based on animation index
48+
int frameDisplayDuration =
49+
30; // Adjust this to control how long each word is shown
50+
int currentWordIndex =
51+
(animationIndex ~/ frameDisplayDuration) % words.length;
3052

31-
int leftCenterCol = badgeWidth ~/ 2 - 1;
32-
int rightCenterCol = badgeWidth ~/ 2;
53+
// Get start and end positions for current word
54+
int wordStart = words[currentWordIndex]['start']!;
55+
int wordEnd = words[currentWordIndex]['end']!;
3356

34-
int maxDistance = leftCenterCol;
57+
// Display the current word
58+
_displaySegment(processGrid, canvas, wordStart, wordEnd, badgeHeight,
59+
badgeWidth, newGridHeight);
60+
}
3561

36-
int currentAnimationIndex = animationIndex % (maxDistance + 1);
62+
List<Map<String, int>> _findWords(
63+
List<List<bool>> grid, int height, int width) {
64+
List<Map<String, int>> words = [];
65+
int minSpaceWidth =
66+
3; // Minimum number of empty columns to consider as word separator
3767

38-
int leftColPos = leftCenterCol - currentAnimationIndex;
39-
int rightColPos = rightCenterCol + currentAnimationIndex;
68+
int wordStart = -1;
69+
int emptyColumnCount = 0;
4070

41-
if (leftColPos < 0) leftColPos += badgeWidth;
42-
if (rightColPos >= badgeWidth) rightColPos -= badgeWidth;
71+
for (int col = 0; col < width; col++) {
72+
bool hasPixel = false;
4373

44-
if (j == leftColPos || j == rightColPos) {
45-
lineShow = true;
46-
} else {
47-
lineShow = false;
74+
// Check if this column has any pixels
75+
for (int row = 0; row < height; row++) {
76+
if (grid[row][col]) {
77+
hasPixel = true;
78+
break;
4879
}
80+
}
4981

50-
if (firstHalf) {
51-
if (isWithinNewGrid && j > leftColPos && j < rightColPos) {
52-
bitmapShowcenter = processGrid[sourceRow][sourceCol];
53-
}
82+
if (hasPixel) {
83+
// This column has content
84+
if (wordStart == -1) {
85+
// Start of a new word
86+
wordStart = col;
5487
}
55-
if (secondHalf) {
56-
if (isWithinNewGrid && (j < leftColPos || j > rightColPos)) {
57-
bitmapShowOut = processGrid[sourceRow][sourceCol];
58-
}
88+
emptyColumnCount = 0;
89+
} else {
90+
// This column is empty
91+
emptyColumnCount++;
92+
93+
// If we have enough empty columns and we were in a word, end the word
94+
if (emptyColumnCount >= minSpaceWidth && wordStart != -1) {
95+
words.add({
96+
'start': wordStart,
97+
'end': col - emptyColumnCount + 1,
98+
});
99+
wordStart = -1;
59100
}
101+
}
102+
}
103+
104+
// Handle the last word if it extends to the end
105+
if (wordStart != -1) {
106+
words.add({
107+
'start': wordStart,
108+
'end': width,
109+
});
110+
}
60111

61-
canvas[i][j] = (lineShow || bitmapShowOut || bitmapShowcenter);
112+
return words;
113+
}
114+
115+
void _displaySegment(
116+
List<List<bool>> source,
117+
List<List<bool>> canvas,
118+
int startCol,
119+
int endCol,
120+
int badgeHeight,
121+
int badgeWidth,
122+
int sourceHeight) {
123+
// Create temporary frame for the word
124+
List<List<bool>> tempFrame = List.generate(
125+
badgeHeight,
126+
(_) => List.filled(badgeWidth, false),
127+
);
128+
129+
// Copy the word segment
130+
int wordWidth = endCol - startCol;
131+
for (int i = 0; i < badgeHeight && i < sourceHeight; i++) {
132+
for (int j = 0; j < wordWidth && j < badgeWidth; j++) {
133+
int sourceCol = startCol + j;
134+
if (sourceCol < source[i].length) {
135+
tempFrame[i][j] = source[i][sourceCol];
136+
}
137+
}
138+
}
139+
140+
// Remove leading empty columns to left-align the word
141+
int shiftLeftBy =
142+
_countLeadingEmptyCols(tempFrame, badgeHeight, badgeWidth);
143+
144+
// Apply the shift and copy to canvas
145+
for (int i = 0; i < badgeHeight; i++) {
146+
for (int j = 0; j < badgeWidth; j++) {
147+
int sourceCol = j + shiftLeftBy;
148+
canvas[i][j] = sourceCol < badgeWidth ? tempFrame[i][sourceCol] : false;
149+
}
150+
}
151+
}
152+
153+
int _countLeadingEmptyCols(List<List<bool>> frame, int height, int width) {
154+
for (int col = 0; col < width; col++) {
155+
bool isColEmpty = true;
156+
for (int row = 0; row < height; row++) {
157+
if (frame[row][col]) {
158+
isColEmpty = false;
159+
break;
160+
}
161+
}
162+
if (!isColEmpty) {
163+
return col;
62164
}
63165
}
166+
return 0;
64167
}
65168
}

lib/badge_animation/ani_picture.dart

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,60 @@ class PictureAnimation extends BadgeAnimation {
55
void processAnimation(int badgeHeight, int badgeWidth, int animationIndex,
66
List<List<bool>> processGrid, List<List<bool>> canvas) {
77
int newWidth = processGrid[0].length;
8-
int totalAnimationLength = badgeHeight * 16;
8+
int newHeight = processGrid.length;
9+
int verticalOffset = (badgeHeight - newHeight) ~/ 2;
10+
int displayWidth = newWidth > badgeWidth ? badgeWidth : newWidth;
11+
int horizontalOffset = (badgeWidth - displayWidth) ~/ 2;
12+
var totalAnimationLength = badgeWidth;
913
int frame = animationIndex % totalAnimationLength;
14+
var firstHalf = frame < badgeWidth ~/ 2;
15+
var secondHalf = frame >= badgeWidth ~/ 2;
1016

11-
int horizontalOffset = (badgeWidth - newWidth) ~/ 2;
17+
for (int i = 0; i < badgeHeight; i++) {
18+
for (int j = 0; j < badgeWidth; j++) {
19+
bool lineShow = false;
20+
bool bitmapShowcenter = false;
21+
bool bitmapShowOut = false;
1222

13-
bool phase1 = frame < badgeHeight * 4;
14-
bool phase2 = frame >= badgeHeight * 4 && frame < badgeHeight * 8;
23+
int sourceRow = i - verticalOffset;
24+
int sourceCol = j - horizontalOffset;
1525

16-
if (phase1) {
17-
for (int row = badgeHeight - 1; row >= 0; row--) {
18-
int fallPosition = frame - (badgeHeight - 1 - row) * 2;
19-
int stoppingPosition = row;
20-
fallPosition =
21-
fallPosition >= stoppingPosition ? stoppingPosition : fallPosition;
26+
bool isWithinNewGrid = sourceRow >= 0 &&
27+
sourceRow < newHeight &&
28+
sourceCol >= 0 &&
29+
sourceCol < displayWidth;
2230

23-
if (fallPosition >= 0 && fallPosition < badgeHeight) {
24-
for (int col = 0; col < badgeWidth; col++) {
25-
int sourceCol = col - horizontalOffset;
26-
bool isWithinNewGrid = sourceCol >= 0 && sourceCol < newWidth;
27-
if (isWithinNewGrid) {
28-
canvas[fallPosition][col] = processGrid[row][sourceCol];
29-
}
30-
}
31-
}
32-
}
33-
} else if (phase2) {
34-
for (int row = badgeHeight - 1; row >= 0; row--) {
35-
int fallOutStartFrame = (badgeHeight - 1 - row) * 2;
36-
int fallOutPosition =
37-
row + (frame - badgeHeight * 4 - fallOutStartFrame);
31+
int leftCenterCol = badgeWidth ~/ 2 - 1;
32+
int rightCenterCol = badgeWidth ~/ 2;
3833

39-
if (fallOutPosition < row) {
40-
for (int col = 0; col < badgeWidth; col++) {
41-
int sourceCol = col - horizontalOffset;
42-
bool isWithinNewGrid = sourceCol >= 0 && sourceCol < newWidth;
43-
if (isWithinNewGrid) {
44-
canvas[row][col] = processGrid[row][sourceCol];
45-
}
46-
}
34+
int maxDistance = leftCenterCol;
35+
36+
int currentAnimationIndex = animationIndex % (maxDistance + 1);
37+
38+
int leftColPos = leftCenterCol - currentAnimationIndex;
39+
int rightColPos = rightCenterCol + currentAnimationIndex;
40+
41+
if (leftColPos < 0) leftColPos += badgeWidth;
42+
if (rightColPos >= badgeWidth) rightColPos -= badgeWidth;
43+
44+
if (j == leftColPos || j == rightColPos) {
45+
lineShow = true;
46+
} else {
47+
lineShow = false;
4748
}
4849

49-
if (fallOutPosition >= row && fallOutPosition < badgeHeight) {
50-
for (int col = 0; col < badgeWidth; col++) {
51-
canvas[row][col] = false;
50+
if (firstHalf) {
51+
if (isWithinNewGrid && j > leftColPos && j < rightColPos) {
52+
bitmapShowcenter = processGrid[sourceRow][sourceCol];
5253
}
53-
54-
for (int col = 0; col < badgeWidth; col++) {
55-
int sourceCol = col - horizontalOffset;
56-
bool isWithinNewGrid = sourceCol >= 0 && sourceCol < newWidth;
57-
if (isWithinNewGrid && fallOutPosition < badgeHeight) {
58-
canvas[fallOutPosition][col] = processGrid[row][sourceCol];
59-
}
54+
}
55+
if (secondHalf) {
56+
if (isWithinNewGrid && (j < leftColPos || j > rightColPos)) {
57+
bitmapShowOut = processGrid[sourceRow][sourceCol];
6058
}
6159
}
60+
61+
canvas[i][j] = (lineShow || bitmapShowOut || bitmapShowcenter);
6262
}
6363
}
6464
}

0 commit comments

Comments
 (0)