Skip to content

feat: add badge scan mode selection and filtering support #1357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
46 changes: 33 additions & 13 deletions lib/bademagic_module/bluetooth/scan_state.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import 'dart:async';
import 'package:badgemagic/bademagic_module/bluetooth/connect_state.dart';
import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart';
import 'package:badgemagic/providers/BadgeScanProvider.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'base_ble_state.dart';

class ScanState extends NormalBleState {
final DataTransferManager manager;
final BadgeScanMode mode;
final List<String> allowedNames;

ScanState({required this.manager});
ScanState({
required this.manager,
required this.mode,
required this.allowedNames,
});

@override
Future<BleState?> processState() async {
Expand All @@ -25,21 +32,35 @@ class ScanState extends NormalBleState {
if (isCompleted || results.isEmpty) return;

try {
final normalizedAllowedNames = allowedNames
.map((e) => e.trim().toLowerCase())
.where((e) => e.isNotEmpty)
.toList();

final foundDevice = results.firstWhere(
(r) => r.advertisementData.serviceUuids.contains(
Guid("0000fee0-0000-1000-8000-00805f9b34fb"),
),
(result) {
final matchesUuid = result.advertisementData.serviceUuids
.contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb"));

final deviceName = result.device.name.trim().toLowerCase();
final matchesName = mode == BadgeScanMode.any ||
normalizedAllowedNames.contains(deviceName);

return matchesUuid && matchesName;
},
orElse: () => throw Exception("Matching device not found."),
);

isCompleted = true;
await FlutterBluePlus.stopScan();
FlutterBluePlus.stopScan();
toast.showToast('Device found. Connecting...');
nextStateCompleter.complete(
ConnectState(scanResult: foundDevice, manager: manager),
);
} catch (_) {
// Ignore and keep scanning

nextStateCompleter.complete(ConnectState(
scanResult: foundDevice,
manager: manager,
));
} catch (e) {
logger.w("No matching device found in this batch: $e");
}
},
onError: (e) {
Expand All @@ -54,12 +75,11 @@ class ScanState extends NormalBleState {
}
},
);

await FlutterBluePlus.startScan(
withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")],
removeIfGone: const Duration(seconds: 5),
removeIfGone: Duration(seconds: 5),
continuousUpdates: true,
timeout: const Duration(seconds: 15),
timeout: const Duration(seconds: 15), // Reduced scan timeout.
);

await Future.delayed(const Duration(seconds: 1));
Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:badgemagic/providers/BadgeScanProvider.dart';
import 'package:badgemagic/providers/getitlocator.dart';
import 'package:badgemagic/providers/imageprovider.dart';
import 'package:badgemagic/view/about_us_screen.dart';
Expand All @@ -23,6 +24,7 @@ void main() {
providers: [
ChangeNotifierProvider<InlineImageProvider>(
create: (context) => getIt<InlineImageProvider>()),
ChangeNotifierProvider(create: (_) => BadgeScanProvider()),
Copy link
Contributor

Choose a reason for hiding this comment

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

@samruddhi-Rahegaonkar try to avoid global providers if possible the BadgeScanProviders are only needed when there is ascanning or connecting to the badge happens rest of the files does not need it.

Copy link
Member Author

Choose a reason for hiding this comment

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

However, for now I've kept it global to simplify access across the current implementation and avoid extra wiring.Since it's a lightweight provider and doesn’t hold heavy state or long-lived connections, the impact should be minimal.

],
child: const MyApp(),
));
Expand Down
36 changes: 36 additions & 0 deletions lib/providers/BadgeScanProvider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
Copy link
Contributor

Choose a reason for hiding this comment

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

@samruddhi-Rahegaonkar If we are saving the added badge using provider then if the app closes and starts it will be gone is this how this functionality is spossed to be ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah! for now it should work like this if we need to maintain the persistant storage then we need to use shared preferences or any other alternative.


enum BadgeScanMode { any, specific }

class BadgeScanProvider with ChangeNotifier {
BadgeScanMode _mode = BadgeScanMode.any;
List<String> _badgeNames = ['LSLED', 'VBLAB'];

BadgeScanMode get mode => _mode;
List<String> get badgeNames => List.unmodifiable(_badgeNames);

void setMode(BadgeScanMode mode) {
_mode = mode;
notifyListeners();
}

void setBadgeNames(List<String> names) {
_badgeNames = names.where((name) => name.trim().isNotEmpty).toList();
notifyListeners();
}

void addBadgeName(String name) {
_badgeNames.add(name);
notifyListeners();
}

void removeBadgeNameAt(int index) {
_badgeNames.removeAt(index);
notifyListeners();
}

void updateBadgeName(int index, String newName) {
_badgeNames[index] = newName;
notifyListeners();
}
}
124 changes: 6 additions & 118 deletions lib/providers/animation_badge_provider.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import 'dart:async';
import 'package:badgemagic/providers/badge_message_provider.dart';
import 'package:badgemagic/providers/imageprovider.dart';
import 'package:badgemagic/providers/speed_dial_provider.dart';
import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart';
import 'package:badgemagic/bademagic_module/utils/converters.dart';
import 'package:badgemagic/badge_animation/ani_animation.dart';
Expand All @@ -13,10 +10,6 @@ import 'package:badgemagic/badge_animation/ani_picture.dart';
import 'package:badgemagic/badge_animation/ani_right.dart';
import 'package:badgemagic/badge_animation/ani_snowflake.dart';
import 'package:badgemagic/badge_animation/ani_up.dart';
import 'package:badgemagic/badge_animation/ani_pacman.dart';
import 'package:badgemagic/badge_animation/ani_chevron_left.dart';
import 'package:badgemagic/badge_animation/ani_diamond.dart';
import 'package:badgemagic/badge_animation/ani_broken_hearts.dart';
import 'package:badgemagic/badge_animation/animation_abstract.dart';
import 'package:badgemagic/badge_effect/badgeeffectabstract.dart';
import 'package:badgemagic/badge_effect/flash_effect.dart';
Expand All @@ -25,15 +18,6 @@ import 'package:badgemagic/badge_effect/marquee_effect.dart';
import 'package:badgemagic/constants.dart';
import 'package:flutter/material.dart';

import 'package:badgemagic/badge_animation/ani_cupid.dart';
import 'package:badgemagic/badge_animation/ani_feet.dart';
import 'package:badgemagic/badge_animation/ani_fish.dart';
import 'package:badgemagic/badge_animation/ani_diagonal.dart';

import 'package:badgemagic/badge_animation/ani_emergency.dart';
import 'package:badgemagic/badge_animation/ani_beating_hearts.dart';
import 'package:badgemagic/badge_animation/ani_fireworks.dart';

Map<int, BadgeAnimation?> animationMap = {
0: LeftAnimation(),
1: RightAnimation(),
Expand All @@ -44,17 +28,6 @@ Map<int, BadgeAnimation?> animationMap = {
6: SnowFlakeAnimation(),
7: PictureAnimation(),
8: LaserAnimation(),
9: PacmanClassicAnimation(), // Pacman
10: LeftChevronAnimation(), // Chevron left
11: DiamondAnimation(), // Diamond
12: BrokenHeartsAnimation(), // Broken Hearts
13: CupidAnimation(), // Cupid
14: FeetAnimation(), // Feet
15: FishAnimation(), // Fish
16: DiagonalAnimation(), // Diagonal
17: EmergencyAnimation(), // Emergency
18: BeatingHeartsAnimation(), // Beating Hearts
19: FireworksAnimation(), // Fireworks
};

Map<int, BadgeEffect> effectMap = {
Expand All @@ -81,33 +54,9 @@ class AnimationBadgeProvider extends ChangeNotifier {
//function to get the state of the cell
List<List<bool>> getPaintGrid() => _paintGrid;

// Helper: returns true if a special animation (custom) is selected
bool isSpecialAnimationSelected() {
int idx = getAnimationIndex() ?? 0;
// Add all special animation indices here (including Fireworks at 19):
return [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19].contains(idx);
}

// Call this to reset to text animation (LeftAnimation)
void resetToTextAnimation() {
setAnimationMode(LeftAnimation());
}

//function to calculate duration for the animation
void calculateDuration(int speed) {
int idx = getAnimationIndex() ?? 0;
int newSpeed;
if (idx == 9 || idx == 10 || idx == 11 || idx == 12) {
// Use slower mapping for custom animations
// (aniSpeedStrategy already uses the slower mapping if you want, or you can hardcode)
newSpeed = aniSpeedStrategy(speed - 1); // keep as is, or adjust if needed
} else {
// Use original (faster) mapping for text/standard animations
// For original: aniBaseSpeed = 200000us, minSpeed = 25000us (example)
const int originalBase = 200000;
const int minSpeed = 25000;
newSpeed = originalBase - ((speed - 1) * (originalBase - minSpeed) ~/ 8);
}
int newSpeed = aniSpeedStrategy(speed - 1);
if (newSpeed != _animationSpeed) {
_animationSpeed = newSpeed;
_timer?.cancel();
Expand Down Expand Up @@ -178,27 +127,18 @@ class AnimationBadgeProvider extends ChangeNotifier {
void startTimer() {
_timer =
Timer.periodic(Duration(microseconds: _animationSpeed), (Timer timer) {
// logger.i(
// "New Grid set to: ${getNewGrid().map((e) => e.map((e) => e ? 1 : 0).toList()).toList()}");
renderGrid(getNewGrid());
if (_currentAnimation is CupidAnimation) {
int frameLimit =
CupidAnimation.frameCount(_paintGrid[0].length, _paintGrid.length);
_animationIndex = (_animationIndex + 1) % frameLimit;
} else {
_animationIndex++;
}
_animationIndex++;
});
}

void setAnimationMode(BadgeAnimation? animation) {
// Always reset the animation index and set the new animation
_animationIndex = 0;
_currentAnimation = animation ?? LeftAnimation();
// Stop the timer if running
_timer?.cancel();
// Start the timer for the new animation
startTimer();
notifyListeners();
logger.i("Animation Mode set to: $_currentAnimation and timer restarted");
logger.i("Animation Mode set to: $_currentAnimation");
}

int? getAnimationIndex() {
Expand All @@ -218,8 +158,7 @@ class AnimationBadgeProvider extends ChangeNotifier {

void badgeAnimation(
String message, Converters converters, bool isInverted) async {
bool isSpecial = isSpecialAnimationSelected();
if (message.isEmpty && !isSpecial) {
if (message.isEmpty) {
stopAllAnimations();
List<List<bool>> emptyGrid =
List.generate(11, (i) => List.generate(44, (j) => false));
Expand Down Expand Up @@ -253,55 +192,4 @@ class AnimationBadgeProvider extends ChangeNotifier {
_paintGrid = canvas;
notifyListeners();
}

/// Handles animation transfer selection logic for the current animation index.
Future<void> handleAnimationTransfer({
required BadgeMessageProvider badgeData,
required InlineImageProvider inlineImageProvider,
required SpeedDialProvider speedDialProvider,
required bool flash,
required bool marquee,
required bool invert,
}) async {
final int aniIndex = getAnimationIndex() ?? 0;
final int selectedSpeed = speedDialProvider.getOuterValue();
if (aniIndex == 9) {
// Pacman
await transferPacmanAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 10) {
await transferChevronAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 11) {
await transferDiamondAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 12) {
await transferBrokenHeartsAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 13) {
await transferCupidAnimation(badgeData, selectedSpeed);
setAnimationMode(CupidAnimation());
_animationIndex = 0;
if (_timer == null || !_timer!.isActive) startTimer();
} else if (aniIndex == 14) {
await transferFeetAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 15) {
await transferFishAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 16) {
await transferDiagonalAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 17) {
await transferEmergencyAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 18) {
await transferBeatingHeartsAnimation(badgeData, selectedSpeed);
} else if (aniIndex == 19) {
await transferFireworksAnimation(badgeData, selectedSpeed);
} else {
await badgeData.checkAndTransfer(
inlineImageProvider.getController().text,
flash,
marquee,
invert,
selectedSpeed,
modeValueMap[aniIndex],
null,
false,
);
}
}
}
Loading
Loading