gravix_effects 1.1.2
gravix_effects: ^1.1.2 copied to clipboard
Gravix Effects — real-time beauty filter, reshape, makeup and looks for Flutter livestreaming. Outputs: gravix_effects_webrtc, gravix_effects_agora.
example/lib/main.dart
// Gravix Effects — the smallest complete integration: the camera with effects, a row of looks and the ready-made
// beauty panel. No streaming here — to send the picture to viewers, see gravix_effects_webrtc/example (LiveKit /
// flutter_webrtc) or gravix_effects_agora/example (Agora).
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gravix_effects/beauty_ui.dart';
import 'package:gravix_effects/gravix_effects.dart';
void main() => runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: CameraScreen()));
class CameraScreen extends StatefulWidget {
const CameraScreen({super.key});
@override
State<CameraScreen> createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
final _beauty = GravixBeautyController.shared; // remembers the chosen look + sliders across camera restarts
GravixCamera? _cam;
List<GravixLook> _looks = const [];
String? _error;
@override
void initState() {
super.initState();
_start();
}
Future<void> _start() async {
if (!GravixCamera.isSupported) return setState(() => _error = 'Gravix Effects runs on Android only.');
try {
unawaited(GravixCamera.preload()); // loads the face model while the permission dialog is open
if (!await GravixCamera.requestPermission()) return setState(() => _error = 'Camera permission is needed.');
final cam = await GravixCamera.start(lens: GravixLens.front, fps: 30);
await _beauty.attach(cam);
final looks = [for (final name in await cam.builtinLooks()) await GravixLooks.builtin(name)];
if (!mounted) {
await cam.stop();
return;
}
setState(() {
_cam = cam;
_looks = looks;
});
} on GravixException catch (e) {
setState(() => _error = 'Camera error ${e.code}: ${e.message ?? ''}');
}
}
@override
void dispose() {
final cam = _cam;
if (cam != null) {
_beauty.detach(cam);
cam.stop();
}
super.dispose();
}
void _openBeautyPanel() => showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.transparent,
builder: (_) => GravixBeautyPanel(controller: _beauty),
);
@override
Widget build(BuildContext context) {
final cam = _cam;
return Scaffold(
backgroundColor: Colors.black,
body: Stack(fit: StackFit.expand, children: [
if (cam != null)
GravixPreview(cam) // mirrored for the front camera, fills the screen
else
Center(
child: _error == null
? const CircularProgressIndicator()
: Text(_error!, style: const TextStyle(color: Colors.white), textAlign: TextAlign.center),
),
if (cam != null)
Align(
alignment: Alignment.bottomCenter,
child: SafeArea(
child: Column(mainAxisSize: MainAxisSize.min, children: [
LooksRow(looks: _looks, controller: _beauty),
Padding(
padding: const EdgeInsets.all(16),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
FilledButton.tonalIcon(
onPressed: _openBeautyPanel, icon: const Icon(Icons.face_retouching_natural), label: const Text('Beauty')),
FilledButton.tonalIcon(onPressed: cam.switchCamera, icon: const Icon(Icons.cameraswitch), label: const Text('Flip')),
]),
),
]),
),
),
]),
);
}
}
/// The built-in looks as tappable thumbnails; the chosen one is outlined.
class LooksRow extends StatelessWidget {
const LooksRow({super.key, required this.looks, required this.controller});
final List<GravixLook> looks;
final GravixBeautyController controller;
@override
Widget build(BuildContext context) => ListenableBuilder(
listenable: controller,
builder: (context, _) => SizedBox(
height: 92,
child: ListView(scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), children: [
for (final look in looks)
GestureDetector(
onTap: () => controller.selectLookFile(look),
child: Container(
width: 68,
margin: const EdgeInsets.symmetric(horizontal: 4),
child: Column(children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: controller.isLook(look) ? Colors.white : Colors.transparent, width: 2),
image: look.thumbnail == null ? null : DecorationImage(image: MemoryImage(look.thumbnail!), fit: BoxFit.cover),
color: Colors.white24,
),
),
const SizedBox(height: 4),
Text(look.name, style: const TextStyle(color: Colors.white, fontSize: 11), overflow: TextOverflow.ellipsis),
]),
),
),
]),
),
);
}