xpunge 0.1.4 copy "xpunge: ^0.1.4" to clipboard
xpunge: ^0.1.4 copied to clipboard

On-device NSFW/explicit content detection SDK for Android and iOS. No images leave the device.

example/lib/main.dart

import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:xpunge/xpunge.dart';

void main() {
  runApp(const XPungeExampleApp());
}

class XPungeExampleApp extends StatelessWidget {
  const XPungeExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'xPunge Example',
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _initialized = false;
  bool _loading = false;
  String _status = 'Not initialized';
  Uint8List? _imageBytes;
  int _imageWidth = 0;
  int _imageHeight = 0;
  List<Detection> _detections = [];

  // Replace with a real key from https://xpunge.com/dashboard
  static const _testApiKey =
      'xp1.eyJ2IjoxLCJ0aWVyIjoiaW5kaWUiLCJwa2ciOiIiLCJleHAiOjB9.mmZOQZwsYXzsoyau8mW2hw';

  @override
  void initState() {
    super.initState();
    _initSdk();
  }

  Future<void> _initSdk() async {
    setState(() => _status = 'Initializing…');
    try {
      await XPunge.initialize(_testApiKey);
      setState(() {
        _initialized = true;
        _status = 'Ready — tier: ${XPunge.tier.name}';
      });
    } on XPungeException catch (e) {
      setState(() => _status = 'Init failed: $e');
    }
  }

  Future<void> _pickAndAnalyze() async {
    if (!_initialized) return;
    final picker = ImagePicker();
    final picked = await picker.pickImage(source: ImageSource.gallery);
    if (picked == null) return;

    final bytes = await picked.readAsBytes();
    final codec = await ui.instantiateImageCodec(bytes);
    final frame = await codec.getNextFrame();
    final imgW = frame.image.width;
    final imgH = frame.image.height;
    frame.image.dispose();
    setState(() {
      _loading = true;
      _imageBytes = bytes;
      _imageWidth = imgW;
      _imageHeight = imgH;
      _detections = [];
      _status = 'Analyzing…';
    });

    try {
      final detections = await XPunge.analyzeXFile(picked);
      setState(() {
        _detections = detections;
        _status = detections.isEmpty
            ? 'No explicit content detected'
            : '${detections.length} detection(s)';
      });
    } on XPungeException catch (e) {
      setState(() => _status = 'Detection failed: $e');
    } finally {
      setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('xPunge Demo')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(_status, style: Theme.of(context).textTheme.titleMedium),
          ),
          if (_imageBytes != null)
            Expanded(
              child: Stack(
                fit: StackFit.expand,
                children: [
                  Image.memory(_imageBytes!, fit: BoxFit.contain),
                  if (XPunge.tier == XPungeTier.paid)
                    CustomPaint(
                      painter: _BoundingBoxPainter(
                        _detections,
                        _imageWidth,
                        _imageHeight,
                      ),
                    ),
                ],
              ),
            ),
          if (_detections.isNotEmpty)
            SizedBox(
              height: 160,
              child: ListView.builder(
                itemCount: _detections.length,
                itemBuilder: (context, i) {
                  final d = _detections[i];
                  return ListTile(
                    title: Text(d.label),
                    subtitle: Text(
                      'confidence: ${(d.confidence * 100).toStringAsFixed(1)}%'
                      '${XPunge.tier == XPungeTier.paid ? '  box: ${d.boundingBox}' : '  (upgrade for bounding boxes)'}',
                    ),
                  );
                },
              ),
            ),
          const SizedBox(height: 16),
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _loading ? null : _pickAndAnalyze,
        label: const Text('Pick Image'),
        icon: const Icon(Icons.photo_library),
      ),
    );
  }
}

class _BoundingBoxPainter extends CustomPainter {
  final List<Detection> detections;
  final int imageWidth;
  final int imageHeight;
  _BoundingBoxPainter(this.detections, this.imageWidth, this.imageHeight);

  @override
  void paint(Canvas canvas, Size size) {
    if (imageWidth == 0 || imageHeight == 0) return;

    // Compute the rect occupied by the image inside the widget (BoxFit.contain).
    final scale = (size.width / imageWidth).clamp(0.0, size.height / imageHeight);
    final renderedW = imageWidth * scale;
    final renderedH = imageHeight * scale;
    final offsetX = (size.width - renderedW) / 2;
    final offsetY = (size.height - renderedH) / 2;

    final paint = Paint()
      ..color = const Color(0xFFFF3B30)
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    for (final d in detections) {
      final box = d.boundingBox;
      final rect = Rect.fromLTWH(
        offsetX + box.left * renderedW,
        offsetY + box.top * renderedH,
        box.width * renderedW,
        box.height * renderedH,
      );
      canvas.drawRect(rect, paint);
      final tp = TextPainter(
        text: TextSpan(
          text: '${d.label} ${(d.confidence * 100).toStringAsFixed(0)}%',
          style: const TextStyle(color: Color(0xFFFF3B30), fontSize: 12),
        ),
        textDirection: TextDirection.ltr,
      )..layout();
      tp.paint(canvas, Offset(rect.left, rect.top - 14));
    }
  }

  @override
  bool shouldRepaint(_BoundingBoxPainter old) =>
      old.detections != detections ||
      old.imageWidth != imageWidth ||
      old.imageHeight != imageHeight;
}
1
likes
140
points
102
downloads

Documentation

API reference

Publisher

verified publishermarkatlarge.com

Weekly Downloads

On-device NSFW/explicit content detection SDK for Android and iOS. No images leave the device.

Homepage

License

unknown (license)

Dependencies

cross_file, flutter, shared_preferences

More

Packages that depend on xpunge

Packages that implement xpunge