feat: integrate Firebase Analytics, add Google Maps dependencies, and expose new GA4 metric API endpoints.
This commit is contained in:
@@ -169,10 +169,180 @@ app.MapGet("/api/metrics/general", async (
|
|||||||
Console.WriteLine($"Error: {ex}");
|
Console.WriteLine($"Error: {ex}");
|
||||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
.WithName("GetGeneralMetrics")
|
.WithName("GetGeneralMetrics")
|
||||||
.WithOpenApi();
|
.WithOpenApi();
|
||||||
|
|
||||||
|
// 3. Tech Overview (OS & App Version)
|
||||||
|
// GET /api/metrics/tech?from=x&to=y
|
||||||
|
app.MapGet("/api/metrics/tech", async (
|
||||||
|
[FromServices] AnalyticsDataService service,
|
||||||
|
[FromQuery] string? from,
|
||||||
|
[FromQuery] string? to) =>
|
||||||
|
{
|
||||||
|
var startDate = from ?? "30daysAgo";
|
||||||
|
var endDate = to ?? "today";
|
||||||
|
|
||||||
|
// Request for OS
|
||||||
|
var requestOS = new RunReportRequest
|
||||||
|
{
|
||||||
|
Property = $"properties/{ga4PropertyId}",
|
||||||
|
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||||
|
Dimensions = new List<Dimension> { new Dimension { Name = "operatingSystem" } },
|
||||||
|
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request for App Version
|
||||||
|
var requestVersion = new RunReportRequest
|
||||||
|
{
|
||||||
|
Property = $"properties/{ga4PropertyId}",
|
||||||
|
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||||
|
Dimensions = new List<Dimension> { new Dimension { Name = "appVersion" } },
|
||||||
|
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } } // use active users to see version adoption
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var responseOS = await service.Properties.RunReport(requestOS, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||||
|
var responseVersion = await service.Properties.RunReport(requestVersion, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||||
|
|
||||||
|
var osData = responseOS.Rows?.Select(row => new {
|
||||||
|
os = row.DimensionValues[0].Value,
|
||||||
|
users = int.Parse(row.MetricValues[0].Value)
|
||||||
|
}) ?? Enumerable.Empty<object>();
|
||||||
|
|
||||||
|
var versionData = responseVersion.Rows?.Select(row => new {
|
||||||
|
version = row.DimensionValues[0].Value,
|
||||||
|
users = int.Parse(row.MetricValues[0].Value)
|
||||||
|
}) ?? Enumerable.Empty<object>();
|
||||||
|
|
||||||
|
return Results.Ok(new { operatingSystems = osData, appVersions = versionData });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WithName("GetTechOverview")
|
||||||
|
.WithOpenApi();
|
||||||
|
|
||||||
|
// 4. Top Events
|
||||||
|
// GET /api/metrics/events?from=x&to=y
|
||||||
|
app.MapGet("/api/metrics/events", async (
|
||||||
|
[FromServices] AnalyticsDataService service,
|
||||||
|
[FromQuery] string? from,
|
||||||
|
[FromQuery] string? to) =>
|
||||||
|
{
|
||||||
|
var startDate = from ?? "30daysAgo";
|
||||||
|
var endDate = to ?? "today";
|
||||||
|
|
||||||
|
var request = new RunReportRequest
|
||||||
|
{
|
||||||
|
Property = $"properties/{ga4PropertyId}",
|
||||||
|
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||||
|
Dimensions = new List<Dimension> { new Dimension { Name = "eventName" } },
|
||||||
|
Metrics = new List<Metric> { new Metric { Name = "eventCount" }, new Metric { Name = "activeUsers" } },
|
||||||
|
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "eventCount" } } },
|
||||||
|
Limit = 20
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||||
|
var result = response.Rows?.Select(row => new {
|
||||||
|
eventName = row.DimensionValues[0].Value,
|
||||||
|
count = int.Parse(row.MetricValues[0].Value),
|
||||||
|
users = int.Parse(row.MetricValues[1].Value)
|
||||||
|
}) ?? Enumerable.Empty<object>();
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WithName("GetTopEvents")
|
||||||
|
.WithOpenApi();
|
||||||
|
|
||||||
|
// 5. Top Screens
|
||||||
|
// GET /api/metrics/screens?from=x&to=y
|
||||||
|
app.MapGet("/api/metrics/screens", async (
|
||||||
|
[FromServices] AnalyticsDataService service,
|
||||||
|
[FromQuery] string? from,
|
||||||
|
[FromQuery] string? to) =>
|
||||||
|
{
|
||||||
|
var startDate = from ?? "30daysAgo";
|
||||||
|
var endDate = to ?? "today";
|
||||||
|
|
||||||
|
var request = new RunReportRequest
|
||||||
|
{
|
||||||
|
Property = $"properties/{ga4PropertyId}",
|
||||||
|
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||||
|
// GA4 uses 'unifiedScreenName' or 'pagePath' depending on setup, usually 'unifiedScreenName' for apps
|
||||||
|
Dimensions = new List<Dimension> { new Dimension { Name = "unifiedScreenName" } },
|
||||||
|
Metrics = new List<Metric> { new Metric { Name = "screenPageViews" }, new Metric { Name = "activeUsers" } },
|
||||||
|
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "screenPageViews" } } },
|
||||||
|
Limit = 20
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||||
|
var result = response.Rows?.Select(row => new {
|
||||||
|
screenName = row.DimensionValues[0].Value,
|
||||||
|
views = int.Parse(row.MetricValues[0].Value),
|
||||||
|
users = int.Parse(row.MetricValues[1].Value)
|
||||||
|
}) ?? Enumerable.Empty<object>();
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WithName("GetTopScreens")
|
||||||
|
.WithOpenApi();
|
||||||
|
|
||||||
|
// 6. User Retention (New vs Returning)
|
||||||
|
// GET /api/metrics/retention?from=x&to=y
|
||||||
|
app.MapGet("/api/metrics/retention", async (
|
||||||
|
[FromServices] AnalyticsDataService service,
|
||||||
|
[FromQuery] string? from,
|
||||||
|
[FromQuery] string? to) =>
|
||||||
|
{
|
||||||
|
var startDate = from ?? "30daysAgo";
|
||||||
|
var endDate = to ?? "today";
|
||||||
|
|
||||||
|
var request = new RunReportRequest
|
||||||
|
{
|
||||||
|
Property = $"properties/{ga4PropertyId}",
|
||||||
|
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||||
|
Dimensions = new List<Dimension> { new Dimension { Name = "newVsReturning" } },
|
||||||
|
Metrics = new List<Metric> { new Metric { Name = "activeUsers" }, new Metric { Name = "sessions" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||||
|
var result = response.Rows?.Select(row => new {
|
||||||
|
type = row.DimensionValues[0].Value,
|
||||||
|
users = int.Parse(row.MetricValues[0].Value),
|
||||||
|
sessions = int.Parse(row.MetricValues[1].Value)
|
||||||
|
}) ?? Enumerable.Empty<object>();
|
||||||
|
|
||||||
|
return Results.Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WithName("GetUserRetention")
|
||||||
|
.WithOpenApi();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
// Helper to format GA4 YYYYMMDD to YYYY-MM-DD
|
// Helper to format GA4 YYYYMMDD to YYYY-MM-DD
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ import '../../repositories/auth_repository.dart';
|
|||||||
import 'auth_event.dart';
|
import 'auth_event.dart';
|
||||||
import 'auth_state.dart';
|
import 'auth_state.dart';
|
||||||
import '../../services/notification_service.dart';
|
import '../../services/notification_service.dart';
|
||||||
|
import '../../services/analytics_service.dart';
|
||||||
|
|
||||||
/// BLoC for managing authentication state and operations.
|
/// BLoC for managing authentication state and operations.
|
||||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
/// Repository for authentication operations.
|
/// Repository for authentication operations.
|
||||||
final AuthRepository _authRepository;
|
final AuthRepository _authRepository;
|
||||||
final NotificationService _notificationService;
|
final NotificationService _notificationService;
|
||||||
|
final AnalyticsService _analyticsService;
|
||||||
|
|
||||||
/// Creates an [AuthBloc] with the provided [authRepository].
|
/// Creates an [AuthBloc] with the provided [authRepository].
|
||||||
///
|
///
|
||||||
@@ -40,8 +42,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
AuthBloc({
|
AuthBloc({
|
||||||
required AuthRepository authRepository,
|
required AuthRepository authRepository,
|
||||||
NotificationService? notificationService,
|
NotificationService? notificationService,
|
||||||
|
AnalyticsService? analyticsService,
|
||||||
}) : _authRepository = authRepository,
|
}) : _authRepository = authRepository,
|
||||||
_notificationService = notificationService ?? NotificationService(),
|
_notificationService = notificationService ?? NotificationService(),
|
||||||
|
_analyticsService = analyticsService ?? AnalyticsService(),
|
||||||
super(AuthInitial()) {
|
super(AuthInitial()) {
|
||||||
on<AuthCheckRequested>(_onAuthCheckRequested);
|
on<AuthCheckRequested>(_onAuthCheckRequested);
|
||||||
on<AuthSignInRequested>(_onSignInRequested);
|
on<AuthSignInRequested>(_onSignInRequested);
|
||||||
@@ -76,6 +80,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Save FCM Token on auto-login
|
// Save FCM Token on auto-login
|
||||||
await _notificationService.saveTokenToFirestore(user.id!);
|
await _notificationService.saveTokenToFirestore(user.id!);
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(AuthUnauthenticated());
|
emit(AuthUnauthenticated());
|
||||||
@@ -107,6 +112,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Save FCM Token
|
// Save FCM Token
|
||||||
await NotificationService().saveTokenToFirestore(user.id!);
|
await NotificationService().saveTokenToFirestore(user.id!);
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'login',
|
||||||
|
parameters: {'method': 'email'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Invalid email or password'));
|
emit(const AuthError(message: 'Invalid email or password'));
|
||||||
@@ -138,6 +148,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Save FCM Token
|
// Save FCM Token
|
||||||
await NotificationService().saveTokenToFirestore(user.id!);
|
await NotificationService().saveTokenToFirestore(user.id!);
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'sign_up',
|
||||||
|
parameters: {'method': 'email'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Failed to create account'));
|
emit(const AuthError(message: 'Failed to create account'));
|
||||||
@@ -163,6 +178,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Save FCM Token
|
// Save FCM Token
|
||||||
await NotificationService().saveTokenToFirestore(user.id!);
|
await NotificationService().saveTokenToFirestore(user.id!);
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'login',
|
||||||
|
parameters: {'method': 'google'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(
|
emit(
|
||||||
@@ -191,6 +211,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'sign_up',
|
||||||
|
parameters: {'method': 'google'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Failed to create account with Google'));
|
emit(const AuthError(message: 'Failed to create account with Google'));
|
||||||
@@ -214,6 +239,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'sign_up',
|
||||||
|
parameters: {'method': 'apple'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(const AuthError(message: 'Failed to create account with Apple'));
|
emit(const AuthError(message: 'Failed to create account with Apple'));
|
||||||
@@ -239,6 +269,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Save FCM Token
|
// Save FCM Token
|
||||||
await NotificationService().saveTokenToFirestore(user.id!);
|
await NotificationService().saveTokenToFirestore(user.id!);
|
||||||
|
await _analyticsService.setUserId(user.id);
|
||||||
|
await _analyticsService.logEvent(
|
||||||
|
name: 'login',
|
||||||
|
parameters: {'method': 'apple'},
|
||||||
|
);
|
||||||
emit(AuthAuthenticated(user: user));
|
emit(AuthAuthenticated(user: user));
|
||||||
} else {
|
} else {
|
||||||
emit(
|
emit(
|
||||||
@@ -261,6 +296,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
) async {
|
) async {
|
||||||
await _authRepository.signOut();
|
await _authRepository.signOut();
|
||||||
|
await _analyticsService.setUserId(null); // Clear user ID
|
||||||
emit(AuthUnauthenticated());
|
emit(AuthUnauthenticated());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import 'package:travel_mate/services/expense_service.dart';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:travel_mate/services/notification_service.dart';
|
import 'package:travel_mate/services/notification_service.dart';
|
||||||
import 'package:travel_mate/services/map_navigation_service.dart';
|
import 'package:travel_mate/services/map_navigation_service.dart';
|
||||||
|
import 'package:travel_mate/services/analytics_service.dart';
|
||||||
import 'blocs/auth/auth_bloc.dart';
|
import 'blocs/auth/auth_bloc.dart';
|
||||||
import 'blocs/auth/auth_event.dart';
|
import 'blocs/auth/auth_event.dart';
|
||||||
import 'blocs/theme/theme_bloc.dart';
|
import 'blocs/theme/theme_bloc.dart';
|
||||||
@@ -146,6 +147,10 @@ class MyApp extends StatelessWidget {
|
|||||||
RepositoryProvider<MapNavigationService>(
|
RepositoryProvider<MapNavigationService>(
|
||||||
create: (context) => MapNavigationService(),
|
create: (context) => MapNavigationService(),
|
||||||
),
|
),
|
||||||
|
// Analysis service
|
||||||
|
RepositoryProvider<AnalyticsService>(
|
||||||
|
create: (context) => AnalyticsService(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -206,6 +211,9 @@ class MyApp extends StatelessWidget {
|
|||||||
title: 'Travel Mate',
|
title: 'Travel Mate',
|
||||||
navigatorKey: ErrorService.navigatorKey,
|
navigatorKey: ErrorService.navigatorKey,
|
||||||
themeMode: themeState.themeMode,
|
themeMode: themeState.themeMode,
|
||||||
|
navigatorObservers: [
|
||||||
|
context.read<AnalyticsService>().getAnalyticsObserver(),
|
||||||
|
],
|
||||||
// Light theme configuration
|
// Light theme configuration
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
|||||||
54
lib/services/analytics_service.dart
Normal file
54
lib/services/analytics_service.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Service wrapper for Google Analytics
|
||||||
|
class AnalyticsService {
|
||||||
|
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
|
||||||
|
|
||||||
|
FirebaseAnalyticsObserver getAnalyticsObserver() =>
|
||||||
|
FirebaseAnalyticsObserver(analytics: _analytics);
|
||||||
|
|
||||||
|
Future<void> logEvent({
|
||||||
|
required String name,
|
||||||
|
Map<String, Object>? parameters,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _analytics.logEvent(name: name, parameters: parameters);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error logging analytics event: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUserProperty({
|
||||||
|
required String name,
|
||||||
|
required String? value,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _analytics.setUserProperty(name: name, value: value);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error setting user property: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUserId(String? id) async {
|
||||||
|
try {
|
||||||
|
await _analytics.setUserId(id: id);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error setting user ID: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logScreenView({
|
||||||
|
required String screenName,
|
||||||
|
String? screenClass,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _analytics.logScreenView(
|
||||||
|
screenName: screenName,
|
||||||
|
screenClass: screenClass,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error logging screen view: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
pubspec.lock
36
pubspec.lock
@@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a"
|
sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.64"
|
version: "1.3.65"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -401,6 +401,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+4"
|
version: "0.9.3+4"
|
||||||
|
firebase_analytics:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_analytics
|
||||||
|
sha256: "8ca4832c7a6d145ce987fd07d6dfbb8c91d9058178342f20de6305fb77b1b40d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.1.0"
|
||||||
|
firebase_analytics_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_analytics_platform_interface
|
||||||
|
sha256: d00234716f415f89eb5c2cefb1238d7fd2f3120275d71414b84ae434dcdb7a19
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.5"
|
||||||
|
firebase_analytics_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_analytics_web
|
||||||
|
sha256: e42b294e51aedb4bd4b761a886c8d6b473c44b44aa4c0b47cab06b2c66ac3fba
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.1+1"
|
||||||
firebase_auth:
|
firebase_auth:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -429,10 +453,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c"
|
sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.3.0"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -445,10 +469,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398
|
sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.3.1"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ dependencies:
|
|||||||
firebase_messaging: ^16.0.4
|
firebase_messaging: ^16.0.4
|
||||||
flutter_local_notifications: ^19.5.0
|
flutter_local_notifications: ^19.5.0
|
||||||
package_info_plus: ^8.3.1
|
package_info_plus: ^8.3.1
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
=======
|
||||||
|
google_maps_flutter_platform_interface: ^2.14.1
|
||||||
|
google_maps_flutter_android: ^2.18.6
|
||||||
|
firebase_analytics: ^12.1.0
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
|
|||||||
Reference in New Issue
Block a user