Skip to main content

bugstweet 0.9.7 copy "bugstweet: ^0.9.7" to clipboard
bugstweet: ^0.9.7 copied to clipboard

Testers report bugs from inside your Flutter app as GitHub issues an AI coding agent can fix. Tester builds only: store builds never contain it.

example/lib/main.dart

// The sample app: a tiny health tracker with two planted bugs.
//
// This is the "store build" entry point. It knows nothing about bugsTweet.
// See .bugstweet/tester.dart for the tester build.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() => runApp(const SampleApp());

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

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Sample Health',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      colorSchemeSeed: const Color(0xFFB93A63),
      scaffoldBackgroundColor: const Color(0xFFF7F5F2),
    ),
    darkTheme: ThemeData(colorSchemeSeed: const Color(0xFFB93A63), brightness: Brightness.dark),
    // English and Arabic (right to left).
    supportedLocales: const [Locale('en'), Locale('ar')],
    localizationsDelegates: GlobalMaterialLocalizations.delegates,
    routes: {
      '/': (_) => const HomeShell(),
      '/dose': (_) => const DoseDetailScreen(),
    },
  );
}

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

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _tab = 0;

  @override
  Widget build(BuildContext context) => Scaffold(
    body: SafeArea(child: _tab == 0 ? const TodayScreen() : const VitalsScreen()),
    bottomNavigationBar: NavigationBar(
      selectedIndex: _tab,
      onDestinationSelected: (i) => setState(() => _tab = i),
      destinations: const [
        NavigationDestination(icon: Icon(Icons.today_outlined), label: 'Today'),
        NavigationDestination(icon: Icon(Icons.favorite_outline), label: 'Vitals'),
      ],
    ),
  );
}

/// Whether today's Bisoprolol dose is marked as taken. Public so the tester
/// build can name it in reports (see .bugstweet/tester.dart).
bool bisoprololTaken = false;

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

  @override
  State<TodayScreen> createState() => _TodayScreenState();
}

class _TodayScreenState extends State<TodayScreen> {
  bool _taken = false;

  @override
  Widget build(BuildContext context) => ListView(
    padding: const EdgeInsets.all(16),
    children: [
      const ScreenHeader(title: 'Today'),
      NextDoseCard(
        taken: _taken,
        onTaken: () => setState(() => _taken = bisoprololTaken = true),
      ),
      const SizedBox(height: 16),
      const Text('ALL DOSES TODAY', style: TextStyle(fontSize: 11, letterSpacing: .8, color: Colors.black54)),
      const SizedBox(height: 6),
      Card(
        child: Column(
          children: [
            const DoseRow(time: '08:00', name: 'Aspirin 75 mg', status: 'Taken'),
            // Planted bug 2: a long medicine name that overflows its row.
            const DoseRow(time: '13:00', name: 'Atorvastatin calcium 20 mg film-coated tablet', status: 'Due'),
            DoseRow(time: '21:00', name: 'Bisoprolol 2.5 mg', status: _taken ? 'Taken' : 'Due'),
          ],
        ),
      ),
      const SizedBox(height: 16),
      const Text('THIS WEEK', style: TextStyle(fontSize: 11, letterSpacing: .8, color: Colors.black54)),
      const SizedBox(height: 6),
      // A long list, so the page scrolls (for the long shot).
      for (final day in const ['Wed', 'Thu', 'Fri', 'Sat', 'Sun', 'Mon', 'Tue'])
        Card(
          child: Column(
            children: [
              DoseRow(time: '$day 08:00', name: 'Aspirin 75 mg', status: 'Planned'),
              DoseRow(time: '$day 13:00', name: 'Atorvastatin 20 mg', status: 'Planned'),
              DoseRow(time: '$day 21:00', name: 'Bisoprolol 2.5 mg', status: 'Planned'),
            ],
          ),
        ),
    ],
  );
}

class ScreenHeader extends StatelessWidget {
  const ScreenHeader({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) => Padding(
    padding: const EdgeInsets.only(bottom: 12),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('TUESDAY, 14 OCTOBER', style: TextStyle(fontSize: 11, letterSpacing: .8, color: Colors.black54)),
        Text(title, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w700)),
      ],
    ),
  );
}

class NextDoseCard extends StatelessWidget {
  const NextDoseCard({super.key, required this.taken, required this.onTaken});

  final bool taken;
  final VoidCallback onTaken;

  @override
  Widget build(BuildContext context) => Card(
    child: Padding(
      padding: const EdgeInsets.all(14),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('NEXT DOSE', style: TextStyle(fontSize: 11, letterSpacing: .8, color: Colors.black54)),
          const SizedBox(height: 4),
          const Text('Bisoprolol 2.5 mg', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          // Planted bug 1: a dose time that does not exist (should be 21:00).
          const Text('Due at 25:00', style: TextStyle(color: Color(0xFFB93A63), fontWeight: FontWeight.w600)),
          const SizedBox(height: 10),
          if (taken)
            const Text('Marked as taken', style: TextStyle(color: Color(0xFF2F7D4F), fontWeight: FontWeight.w600))
          else
            FilledButton(onPressed: onTaken, child: const Text('Mark as taken')),
        ],
      ),
    ),
  );
}

class DoseRow extends StatelessWidget {
  const DoseRow({super.key, required this.time, required this.name, required this.status});

  final String time;
  final String name;
  final String status;

  @override
  Widget build(BuildContext context) => InkWell(
    onTap: () => Navigator.of(context).pushNamed('/dose', arguments: name),
    child: Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
      child: Row(
        children: [
          Text(time, style: const TextStyle(fontFamily: 'monospace', color: Colors.black54)),
          const SizedBox(width: 12),
          // No Expanded on purpose: the long name overflows the row.
          Text(name, softWrap: false),
          const Spacer(),
          Text(status, style: TextStyle(color: status == 'Taken' ? const Color(0xFF2F7D4F) : Colors.black54)),
        ],
      ),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    final name = ModalRoute.of(context)?.settings.arguments as String? ?? 'Dose';
    return Scaffold(
      appBar: AppBar(title: Text(name)),
      body: const Padding(
        padding: EdgeInsets.all(16),
        child: Text('Dose details would be here.'),
      ),
    );
  }
}

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

  @override
  State<VitalsScreen> createState() => _VitalsScreenState();
}

class _VitalsScreenState extends State<VitalsScreen> {
  String? _chartError;
  final _note = TextEditingController(text: 'Dizzy after the 9 pm dose');

  @override
  void dispose() {
    _note.dispose();
    super.dispose();
  }

  void _loadChart() {
    try {
      // Planted error: the chart data is missing for one day.
      throw StateError('HeartRateChart: no readings found for 13 Oct (value was null)');
    } on StateError catch (e, stack) {
      FlutterError.reportError(FlutterErrorDetails(exception: e, stack: stack, library: 'sample vitals'));
      setState(() => _chartError = "Couldn't load chart");
    }
  }

  @override
  Widget build(BuildContext context) => ListView(
    padding: const EdgeInsets.all(16),
    children: [
      const ScreenHeader(title: 'Vitals'),
      HeartRateCard(error: _chartError, onLoad: _loadChart),
      const SizedBox(height: 12),
      const Card(
        child: ListTile(title: Text('Blood pressure'), subtitle: Text('118/76 mmHg'), trailing: Text('See history')),
      ),
      const SizedBox(height: 12),
      // A text field: bugsTweet blurs it in screenshots automatically.
      TextField(
        controller: _note,
        decoration: const InputDecoration(labelText: 'Note for your doctor', border: OutlineInputBorder()),
      ),
    ],
  );
}

class HeartRateCard extends StatelessWidget {
  const HeartRateCard({super.key, required this.error, required this.onLoad});

  final String? error;
  final VoidCallback onLoad;

  @override
  Widget build(BuildContext context) => Card(
    child: Padding(
      padding: const EdgeInsets.all(14),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('RESTING HEART RATE', style: TextStyle(fontSize: 11, letterSpacing: .8, color: Colors.black54)),
          const Text('72 bpm', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w700)),
          const SizedBox(height: 8),
          Container(
            height: 80,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              border: Border.all(color: const Color(0xFFD9B9C4)),
              borderRadius: BorderRadius.circular(10),
            ),
            child: error != null
                ? Text(error!, style: const TextStyle(color: Color(0xFFB93A63)))
                : TextButton(onPressed: onLoad, child: const Text('Load chart')),
          ),
        ],
      ),
    ),
  );
}
0
likes
160
points
457
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Testers report bugs from inside your Flutter app as GitHub issues an AI coding agent can fix. Tester builds only: store builds never contain it.

Repository (GitHub)

License

MIT (license)

Dependencies

yaml

More

Packages that depend on bugstweet