diff --git a/.github/instructions/copilot-agent.instructions.md.new b/.github/instructions/copilot-agent.instructions.md.new new file mode 100644 index 0000000..e69de29 diff --git a/.github/instructions/flutter.instructions.md.new b/.github/instructions/flutter.instructions.md.new new file mode 100644 index 0000000..e69de29 diff --git a/app/lib/presentation/ui/base/route_observer.dart b/app/lib/presentation/ui/base/route_observer.dart new file mode 100644 index 0000000..a453e93 --- /dev/null +++ b/app/lib/presentation/ui/base/route_observer.dart @@ -0,0 +1,3 @@ +import 'package:flutter/material.dart'; + +final RouteObserver routeObserver = RouteObserver(); diff --git a/app/lib/presentation/ui/base/tracked_page.dart b/app/lib/presentation/ui/base/tracked_page.dart new file mode 100644 index 0000000..e65a000 --- /dev/null +++ b/app/lib/presentation/ui/base/tracked_page.dart @@ -0,0 +1,109 @@ +import 'package:app/main/init.dart'; +import 'package:app/presentation/ui/base/route_observer.dart'; +import 'package:common/analytics/abstract/analytics_client.dart'; +import 'package:flutter/material.dart'; + +/// A base class for pages that are tracked for analytics purposes. +/// This class can be extended to implement specific tracking logic +/// for different pages in the application. +/// class HomePage extends TrackedPage { +/// const HomePage({super.key}); +/// @override +/// String get trackingName => "home_page"; +/// @override +/// Map? get trackingProperties => { +/// 'userType': 'guest', +/// 'origin': 'splash_screen', +/// }; +/// @override +/// Widget buildPage(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: const Text("Home")), +/// body: const Center(child: Text("Welcome")), +/// ); +/// } +///} +abstract class TrackedPage extends StatefulWidget { + const TrackedPage({super.key}); + + /// The name used for tracking this page. + String get trackingName; + + /// Automatic Events + /// Override when needed + bool get trackOnCreate => true; + bool get trackOnEnter => true; + bool get trackOnExit => true; + bool get trackOnDispose => false; + + /// Extra properties for tracking. + Map? get trackingProperties => null; + + /// Build normal + Widget buildPage(BuildContext context); + + @override + State createState() => _TrackedPageState(); +} + +class _TrackedPageState extends State with RouteAware { + ModalRoute? _route; + + AnalyticsClient get analytics => getIt(); + + void _track(String phase) { + analytics.trackEvent('${widget.trackingName}_$phase', + properties: widget.trackingProperties); + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.trackOnCreate) _track("create"); + }); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _route = ModalRoute.of(context); + if (_route is PageRoute) { + routeObserver.subscribe(this, _route as PageRoute); + } + } + + @override + void dispose() { + if (widget.trackOnDispose) _track("dispose"); + if (_route is PageRoute) { + routeObserver.unsubscribe(this); + } + super.dispose(); + } + + @override + void didPush() { + if (widget.trackOnEnter) _track("enter"); + } + + @override + void didPopNext() { + if (widget.trackOnEnter) _track("enter"); + } + + @override + void didPushNext() { + if (widget.trackOnExit) _track("exit"); + } + + @override + void didPop() { + if (widget.trackOnExit) _track("exit"); + } + + @override + Widget build(BuildContext context) { + return widget.buildPage(context); + } +} diff --git a/modules/common/lib/analytics/abstract/analytics_client.dart b/modules/common/lib/analytics/abstract/analytics_client.dart new file mode 100644 index 0000000..878a9d8 --- /dev/null +++ b/modules/common/lib/analytics/abstract/analytics_client.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +/// Additional method to track custom events with a specific type. +/// Follow the naming convention for event types. +/// Future trackInitLoginFlow() => trackEvent('init_login', properties: {...}); +/// Future trackErrorLogin() => trackEvent('error_login', properties: {...}); + +abstract class AnalyticsClient { + /// Tracks an event with a function call and a name. + /// This is useful for tracking events that are triggered by specific actions. + /// Example usage: + /// trackFunction(() => loginWithEmailPassword(email, password), 'login_triggered', properties: {email: email}); + Future trackFunction( + FutureOr Function() fn, + String name, { + Map? properties, + }); + + Future trackEvent(String name, {Map? properties}); + + Future setUserId(String? userId); + + Future setUserProperties(Map properties); + + Future setUserProperty(String name, String value); + + Future reset(); + + Future trackAppCreated(); + + Future trackAppUpdated(); + + Future trackAppDeleted(); +} diff --git a/modules/common/lib/analytics/concrete/firebase_analytics.dart b/modules/common/lib/analytics/concrete/firebase_analytics.dart new file mode 100644 index 0000000..eda689a --- /dev/null +++ b/modules/common/lib/analytics/concrete/firebase_analytics.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:common/analytics/abstract/analytics_client.dart'; + +class FirebaseAnalytics implements AnalyticsClient { + @override + Future reset() { + // TODO: implement reset + throw UnimplementedError(); + } + + @override + Future setUserId(String? userId) { + // TODO: implement setUserId + throw UnimplementedError(); + } + + @override + Future setUserProperties(Map properties) { + // TODO: implement setUserProperties + throw UnimplementedError(); + } + + @override + Future setUserProperty(String name, String value) { + // TODO: implement setUserProperty + throw UnimplementedError(); + } + + @override + Future trackAppCreated() { + // TODO: implement trackAppCreated + throw UnimplementedError(); + } + + @override + Future trackAppDeleted() { + // TODO: implement trackAppDeleted + throw UnimplementedError(); + } + + @override + Future trackAppUpdated() { + // TODO: implement trackAppUpdated + throw UnimplementedError(); + } + + @override + Future trackEvent(String name, {Map? properties}) { + // TODO: implement trackEvent + throw UnimplementedError(); + } + + @override + Future trackFunction( + FutureOr Function() fn, + String name, { + Map? properties, + }) => + Future.value(fn()).then((_) => trackEvent(name, properties: properties)); +} diff --git a/modules/common/lib/analytics/setup_analytics.dart b/modules/common/lib/analytics/setup_analytics.dart new file mode 100644 index 0000000..1689793 --- /dev/null +++ b/modules/common/lib/analytics/setup_analytics.dart @@ -0,0 +1,7 @@ + +class SetupAnalytics { + static void initialize() { + // Initialize analytics services here + print("Analytics services initialized."); + } +}