flutter_nerve
A sensory-reactive UI engine for Flutter. Make your app feel the real world — react to battery level, network quality, motion, ambient light, and sound with zero boilerplate.
Platform Support
| Sensor | Android | iOS | Web | Desktop |
|---|---|---|---|---|
| Battery | ✅ | ✅ | ⚠️ partial | ✅ |
| Network | ✅ | ✅ | ✅ | ✅ |
| Motion (accelerometer) | ✅ | ✅ | ❌ | ❌ |
| Ambient Light | ✅ | ⚠️ entitlement | ❌ | ❌ |
| Sound (microphone) | ✅ | ✅ | ❌ | ❌ |
Quick Start
1. Add the dependency
dependencies:
flutter_nerve: ^0.2.0
2. Wrap your app
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return NerveRoot(
enableMicrophone: true, // requests mic permission automatically
child: MaterialApp(home: MyHomePage()),
);
}
}
3. Read sensor data anywhere
// Inside any descendant widget:
final state = NerveProvider.of(context);
print(state.batteryLevel); // 0.0 – 1.0
print(state.networkQuality); // NetworkQuality.good / fair / poor / none
print(state.tiltX); // -1.0 – 1.0
print(state.soundLevel); // 0.0 – 1.0 (null if unavailable)
print(state.ambientLight); // lux (null if unavailable)
Reactive Widgets
NerveBuilder
Rebuilds on every sensor update — like AnimatedBuilder but for device senses:
NerveBuilder(
builder: (context, state, child) {
return Opacity(
opacity: state.batteryLevel,
child: child,
);
},
child: const BatteryIcon(),
)
ReactiveContainer
A container whose color, scale, and blur are driven by sensor state:
ReactiveContainer(
colorMapper: (s) => Color.lerp(Colors.red, Colors.green, s.batteryLevel)!,
scaleMapper: (s) => 1.0 + MotionSense.magnitude(s.tiltX, s.tiltY) * 0.15,
child: const FlutterLogo(size: 100),
)
NervePulse
Pulses (scales) a child widget in sync with ambient sound level:
NervePulse(
maxScale: 1.3,
pulseOpacity: true,
child: const Icon(Icons.mic, size: 64),
)
NerveGlow
Animates a glowing border driven by tilt magnitude or a custom mapper:
NerveGlow(
color: Colors.purpleAccent,
maxBlurRadius: 24,
child: const FlutterLogo(size: 80),
)
NerveBatteryIcon
Custom-painted animated battery icon — color-coded, ⚡ when charging:
NerveBatteryIcon(size: 48)
NerveScaffold
Full-screen scaffold with sensor-reactive gradient background and motion-parallax header:
NerveScaffold(
parallaxDepth: 20,
showNetworkBanner: true,
body: MyContent(),
)
NerveMonitor
Floating, draggable debug overlay showing all live sensor readings. Tap to collapse to a 🧠 icon. Use inside kDebugMode to strip from production:
NerveMonitor(
startCollapsed: true, // starts as a small icon
child: MyScaffoldBody(),
)
NerveShake
Detects shake gestures via accelerometer spikes. Fires onShake with cooldown:
NerveShake(
threshold: 0.65, // 0.0–1.0, how strong a shake is required
cooldown: Duration(milliseconds: 600),
onShake: () => setState(() => _count++),
child: myWidget,
)
NerveSpring
Applies a physics-spring offset to its child driven by tilt, creating a floating parallax feel:
NerveSpring(
depth: 20, // max pixel offset at full tilt
child: const FlutterLogo(size: 100),
)
NerveConnectivity
Pre-built animated banner that slides in when network is poor or gone:
NerveConnectivity(
child: MyContent(),
// Optional: supply your own banners
noBanner: MyNoBannerWidget(),
poorBanner: MyPoorBannerWidget(),
)
Testing with NerveFakes
Test any widget without real hardware using pre-set sensor controllers:
testWidgets('shows red theme on low battery', (tester) async {
final ctrl = NerveFakes.withBattery(0.05);
await tester.pumpWidget(
NerveRoot(controller: ctrl, child: MyApp()),
);
expect(find.text('Low Battery!'), findsOneWidget);
ctrl.dispose();
});
// Other factories:
NerveFakes.withNetwork(NetworkQuality.none);
NerveFakes.withMotion(0.5, -0.3);
NerveFakes.withLight(200.0);
NerveFakes.withSound(0.8);
NerveFakes.withAllSenses(batteryLevel: 0.1, charging: false, soundLevel: 0.9);
Adaptive Theme
NerveTheme.resolve() returns a ThemeData that automatically adapts to sensor readings:
| Condition | Effect |
|---|---|
| Battery < 15% | Greyscale palette |
| No network | Red tint |
| Poor network | Orange warning tint |
| Sound level > 70% | Vibrant cyan palette |
| Tilted > 30% | Hue shift |
NerveBuilder(
builder: (context, state, _) {
return MaterialApp(
theme: NerveTheme.resolve(context, state),
home: MyHomePage(),
);
},
)
Custom Sense Adapters
Inject your own sensor streams via the NerveController constructor for testing or custom data sources:
final controller = NerveController(
battery: MyCustomBatterySense(),
sound: SoundSense.withStream(myAudioStream),
);
NerveRoot(controller: controller, child: MyApp())
Permissions
Add the following to your app (not the package itself):
Android (android/app/src/main/AndroidManifest.xml):
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
iOS (ios/Runner/Info.plist):
<key>NSMicrophoneUsageDescription</key>
<string>Used to detect ambient sound level for reactive UI effects.</string>
License
MIT — see LICENSE.