Skip to main content

bugstweet 0.9.4 copy "bugstweet: ^0.9.4" to clipboard
bugstweet: ^0.9.4 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
0
points
537
downloads

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)
View/report issues

License

unknown (license)

Dependencies

yaml

More

Packages that depend on bugstweet