flutter_callouts 5.0.0 copy "flutter_callouts: ^5.0.0" to clipboard
flutter_callouts: ^5.0.0 copied to clipboard

Point stuff out for your users => Target, Transform, Configure Callouts, Record to JSON in Dev, and Play in Producton

example/lib/main.dart

// import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_callouts/flutter_callouts.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  fca.logger.d('Running example app');
  fca.loggerNs.i('Info message');
  fca.loggerNs.w('Just a warning!');
  fca.logger.e('Error! Something bad happened', error: 'Test Error');
  fca.loggerNs.t({'key': 5, 'value': 'something'});

  await fca.initLocalStorage();

  runApp(const MaterialApp(
    title: 'flutter_callouts demo',
    home: CounterDemoPage(),
  ));
}

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

  @override
  State<CounterDemoPage> createState() => _CounterDemoPageState();
}

/// it's important to add the mixin, because callouts are animated
class _CounterDemoPageState extends State<CounterDemoPage>
    with TickerProviderStateMixin {
  late GlobalKey fabGK;
  late GlobalKey countGK;

  NamedScrollController namedSC = NamedScrollController('main', Axis.vertical);

  late List<Alignment> alignments;

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

    /// target's key
    fabGK = GlobalKey();
    countGK = GlobalKey();

    alignments = [
      Alignment.topCenter, Alignment.topRight,
      Alignment.centerRight, Alignment.bottomRight,
      Alignment.bottomCenter,Alignment.bottomLeft,
      Alignment.centerLeft, Alignment.topLeft,
      Alignment.center,
    ];

    fca.afterNextBuildDo(() {
      fca.showOverlay(
        calloutConfig: basicCalloutConfig(namedSC),
        calloutContent: const Padding(
          padding: EdgeInsets.all(8.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('Tap this floating action button to increment the counter.'),
            ],
          ),
        ),
        targetGkF: () => fabGK,
      );
      fca.afterMsDelayDo(
        800,
        () => _showToast(Alignment.topCenter),
      );
    });
  }

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

  @override
  void didChangeDependencies() {
    fca.initWithContext(context);
    super.didChangeDependencies();
  }

  void _showToast(Alignment gravity,
          {int showForMs = 0, VoidCallback? onDismissedF}) =>
      fca.showToast(
        removeAfterMs: showForMs,
        calloutConfig: CalloutConfig(
          cId: 'main-toast',
          gravity: gravity,
          initialCalloutW: 500,
          initialCalloutH: 90,
          fillColor: Colors.black26,
          showCloseButton: true,
          borderThickness: 5,
          borderRadius: 16,
          borderColor: Colors.yellow,
          elevation: 10,
          scrollControllerName: namedSC.name,
          onDismissedF: () => onDismissedF?.call(),
          // allowCalloutToScroll: false,
        ),
        calloutContent: Center(
          child: Text(
            'gravity: ${gravity.toString()}',
            textScaler: const TextScaler.linear(2),
            style: const TextStyle(color: Colors.white),
          ),
        ),
      );

  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      create: (_) => CounterBloc(),
      child: CounterView(namedSC, fabGK, countGK, basicCalloutConfig(namedSC), alignments),
    );
  }
}

class CounterView extends StatelessWidget {
  final NamedScrollController namedSC;
  final GlobalKey fabGK;
  final GlobalKey countGK;
  final CalloutConfig cc;
  final List<Alignment> alignments;

  const CounterView(this.namedSC, this.fabGK, this.countGK, this.cc, this.alignments, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, state) {
            return NotificationListener<SizeChangedLayoutNotification>(
              onNotification: (SizeChangedLayoutNotification notification) {
                fca.afterMsDelayDo(300, () {
                  fca.refreshAll();
                });
                return true;
              },
              child: SizeChangedLayoutNotifier(
                child: Scaffold(
                  body: Center(
                    child: SingleChildScrollView(
                      controller: namedSC,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          SizedBox(
                            height: MediaQuery.of(context).size.height - 200,
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Text(
                                  key: countGK,
                                  '$state',
                                  style: Theme.of(context)
                                      .textTheme
                                      .headlineMedium,
                                ),
                              ],
                            ),
                          ),
                          SizedBox(
                            width: double.infinity,
                            height: 100,
                            child: Align(
                              alignment: Alignment.centerRight,
                              child: Padding(
                                padding: const EdgeInsets.all(18.0),
                                child: FloatingActionButton(
                                  key: fabGK,
                                  onPressed: () {
                                    context
                                        .read<CounterBloc>()
                                        .add(CounterIncrementPressed());
                                    // point out the number using a callout
                                    fca.dismissAll();
                                    int index = state%alignments.length;
                                    Alignment ca = alignments[index];
                                    Alignment ta = -ca;
                                    fca.showOverlay(
                                      calloutConfig: basicCalloutConfig(namedSC)
                                        ..initialCalloutAlignment =
                                            ca
                                        ..initialTargetAlignment =
                                            ta
                                        ..calloutW = 200
                                        ..calloutH = 80,
                                      calloutContent:  Padding(
                                        padding: const EdgeInsets.all(8.0),
                                        child: const Text(
                                          'You have pushed the +\nbutton this many times:',
                                        ),
                                      ),
                                      targetGkF: () => countGK,
                                      removeAfterMs: 2000,
                                    );
                                  },
                                  tooltip: 'Increment',
                                  child: const Icon(Icons.add),
                                ),
                              ),
                            ),
                          ),
                          Container(
                            height: 1000,
                            width: double.infinity,
                            color: Colors.blue[50],
                            child: const Padding(
                              padding: EdgeInsets.all(8.0),
                              child: Text(
                                  'Scroll to see that the yellow callout is Scroll-aware  --  '
                                  'Resize the window to see the pointer refreshing --  '
                                  'The yellow callout is draggable'),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

sealed class CounterEvent {}

final class CounterIncrementPressed extends CounterEvent {}

final class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends HydratedBloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterIncrementPressed>((event, emit) => emit(state + 1));
    on<CounterDecrementPressed>((event, emit) => emit(state - 1));
  }

  @override
  int fromJson(Map<String, dynamic> json) => json['value'] as int;

  @override
  Map<String, int> toJson(int state) => {'value': state};
}

/// the CalloutConfig object is where you configure the callout and its pointer
/// All params are shown, and many are commented out for this example callout
CalloutConfig basicCalloutConfig(NamedScrollController nsc) =>
    CalloutConfig(
      cId: 'basic',
      // -- initial pos and animation ---------------------------------
      initialTargetAlignment: Alignment.topLeft,
      initialCalloutAlignment: Alignment.bottomRight,
      // initialCalloutPos:
      finalSeparation: 100,
      // fromDelta: 0.0,
      // toDelta : 0.0,
      // initialAnimatedPositionDurationMs:
      // -- optional barrier (when opacity > 0) ----------------------
      // barrier: CalloutBarrier(
      //   opacity: .5,
      //   onTappedF: () {
      //     Callout.dismiss("basic");
      //   },
      // ),
      // -- callout appearance ----------------------------------------
      // suppliedCalloutW: 280, // if not supplied, callout content widget gets measured
      // suppliedCalloutH: 200, // if not supplied, callout content widget gets measured
      // borderRadius: 12,
      borderThickness: 3,
      fillColor: Colors.yellow[700],
      // elevation: 10,
      // frameTarget: true,
      // -- optional close button and got it button -------------------
      // showGotitButton: true,
      // showCloseButton: true,
      // closeButtonColor:
      // closeButtonPos:
      // gotitAxis:
      // -- pointer -------------------------------------------------
      // arrowColor: Colors.green,
      arrowType: ArrowType.THIN,
      animate: true,
      // lineLabel: Text('line label'),
      // fromDelta: -20,
      // toDelta: -20,
      // lengthDeltaPc: ,
      // contentTranslateX: ,
      // contentTranslateY:
      // targetTranslateX:
      // targetTranslateY:
      // scaleTarget:
      // -- resizing -------------------------------------------------
      // resizeableH: true,
      // resizeableV: true,
      // -- dragging -------------------------------------------------
      // draggable: false,
      // draggableColor: Colors.green,
      // dragHandleHeight: ,
      scrollControllerName: nsc.name,
      followScroll: false,
      barrier: CalloutBarrierConfig(
        cutoutPadding: 30,
        excludeTargetFromBarrier: true,
        closeOnTapped: true,
        color: Colors.grey,
        opacity: .5,
      ),
    );
2
likes
0
points
414
downloads

Publisher

verified publisherbiancashouse.com

Weekly Downloads

Point stuff out for your users => Target, Transform, Configure Callouts, Record to JSON in Dev, and Play in Producton

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

device_info_plus, flutter, gap, gradient_borders, hydrated_bloc, image_network, intl, logger, package_info_plus, path_provider, pointer_interceptor, timeago, universal_platform, vector_math

More

Packages that depend on flutter_callouts