marco.pms.mobile/lib/view/dashboard/analytics_screen.dart
Vaibhav Surve 99902e743c Flutter application
- Created generated_plugin_registrant.cc and generated_plugin_registrant.h to manage plugin registration.
- Added generated_plugins.cmake for plugin configuration in CMake.
- Implemented CMakeLists.txt for the Windows runner, defining build settings and dependencies.
- Created Runner.rc for application resources including versioning and icons.
- Developed flutter_window.cpp and flutter_window.h to manage the Flutter window lifecycle.
- Implemented main.cpp as the entry point for the Windows application.
- Added resource.h for resource definitions.
- Included app icon in resources.
- Created runner.exe.manifest for application settings.
- Developed utils.cpp and utils.h for console management and command line argument handling.
- Implemented win32_window.cpp and win32_window.h for high DPI-aware window management.
2025-04-17 12:30:38 +05:30

547 lines
22 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:get/get.dart';
import 'package:marco/controller/dashboard/analytics_controller.dart';
import 'package:marco/helpers/theme/app_theme.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/utils/my_shadow.dart';
import 'package:marco/helpers/utils/utils.dart';
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
import 'package:marco/helpers/widgets/my_card.dart';
import 'package:marco/helpers/widgets/my_container.dart';
import 'package:marco/helpers/widgets/my_flex.dart';
import 'package:marco/helpers/widgets/my_flex_item.dart';
import 'package:marco/helpers/widgets/my_list_extension.dart';
import 'package:marco/helpers/widgets/my_progress_bar.dart';
import 'package:marco/helpers/widgets/my_spacing.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/images.dart';
import 'package:marco/model/chart_model.dart';
import 'package:marco/view/layouts/layout.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class AnalyticsScreen extends StatefulWidget {
const AnalyticsScreen({super.key});
@override
State<AnalyticsScreen> createState() => _AnalyticsScreenState();
}
class _AnalyticsScreenState extends State<AnalyticsScreen> with UIMixin {
AnalyticsController controller = Get.put(AnalyticsController());
@override
Widget build(BuildContext context) {
return Layout(
child: GetBuilder(
init: controller,
tag: 'analytics_controller',
builder: (controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: MySpacing.x(flexSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyText.titleMedium("Analytics", fontSize: 18, fontWeight: 600),
MyBreadcrumb(
children: [
MyBreadcrumbItem(name: 'Dashboard'),
MyBreadcrumbItem(name: 'Analytics', active: true),
],
),
],
),
),
MySpacing.height(flexSpacing),
Padding(
padding: MySpacing.x(flexSpacing / 2),
child: MyFlex(children: [
MyFlexItem(sizes: 'lg-2.4 md-6 sm-6', child: stats("Pending", "1.245", "5.12%", LucideIcons.clock)),
MyFlexItem(sizes: 'lg-2.4 md-6 sm-6', child: stats("Paid", "92.342", "67.89%", LucideIcons.circle_check)),
MyFlexItem(sizes: 'lg-2.4 md-4 sm-4', child: stats("Rejected", "12.367", "3.56%", LucideIcons.circle_x)),
MyFlexItem(sizes: 'lg-2.4 md-4 sm-4', child: stats("In Progress", "5.125", "10.78%", LucideIcons.hourglass)),
MyFlexItem(sizes: 'lg-2.4 md-4 sm-4', child: stats("Canceled", "7.489", "4.45%", LucideIcons.trash)),
MyFlexItem(sizes: 'lg-6 md-6 sm-6', child: activityOnThePage()),
MyFlexItem(sizes: 'lg-6 md-6 sm-6', child: audienceOverview()),
MyFlexItem(sizes: 'lg-4 md-12', child: buildTrafficSources()),
MyFlexItem(sizes: 'lg-4 md-6 sm-6', child: buildMostActiveUser()),
MyFlexItem(sizes: 'lg-4 md-6 sm-6', child: buildVisitorsByCountry()),
MyFlexItem(child: buildVisitorByChannel()),
]),
)
],
);
},
),
);
}
Widget stats(String title, String subTitle, String percentage, IconData icon) {
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 0,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Column(
children: [
Padding(
padding: MySpacing.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodySmall(title, maxLines: 1),
MySpacing.height(4),
MyText.titleLarge(subTitle, maxLines: 1),
],
),
),
MyContainer(
color: contentTheme.secondary.withValues(alpha:0.2),
paddingAll: 12,
child: Icon(icon, size: 16, color: contentTheme.onBackground),
)
],
),
),
MyContainer(
color: contentTheme.background,
borderRadiusAll: 0,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Row(
children: [
Icon(LucideIcons.arrow_up_right, size: 16),
MySpacing.width(8),
MyText.labelMedium(percentage),
MySpacing.width(8),
Expanded(child: MyText.labelMedium("Last Month", muted: true, maxLines: 1)),
Expanded(child: InkWell(onTap: () {}, child: MyText.labelMedium("View More", fontWeight: 600, maxLines: 1))),
],
),
)
],
),
);
}
Widget activityOnThePage() {
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: MyText.bodyMedium("Activity on the pages", fontWeight: 600, overflow: TextOverflow.ellipsis),
),
PopupMenuButton(
onSelected: controller.onSelectedActivity,
clipBehavior: Clip.antiAliasWithSaveLayer,
itemBuilder: (BuildContext context) {
return ["Year", "Month", "Week", "Day", "Hours"].map((behavior) {
return PopupMenuItem(
value: behavior,
height: 32,
child: MyText.bodySmall(
behavior.toString(),
color: theme.colorScheme.onSurface,
fontWeight: 600,
),
);
}).toList();
},
color: theme.cardTheme.color,
child: MyContainer.bordered(
padding: MySpacing.xy(8, 4),
borderRadiusAll: 8,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MyText.bodySmall(
controller.selectActivity.toString(),
fontWeight: 600,
color: theme.colorScheme.onSurface,
),
MySpacing.width(4),
Icon(
LucideIcons.chevron_down,
size: 20,
color: theme.colorScheme.onSurface,
)
],
),
),
),
],
),
MySpacing.height(24),
SizedBox(
height: 305,
child: SfCartesianChart(
plotAreaBorderWidth: 0,
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
tooltipBehavior: controller.columnChartToolTip,
legend: Legend(isVisible: true, position: LegendPosition.bottom),
series: [
ColumnSeries<ChartSampleData, int>(
opacity: 0.9,
width: 0.6,
color: contentTheme.title,
dataSource: controller.columnChart,
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
xValueMapper: (ChartSampleData data, _) => data.x,
yValueMapper: (ChartSampleData data, _) => data.y,
dataLabelSettings: DataLabelSettings(isVisible: true)),
ColumnSeries<ChartSampleData, int>(
color: contentTheme.success,
dataSource: controller.columnChart,
borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
xValueMapper: (ChartSampleData data, _) => data.x,
yValueMapper: (ChartSampleData data, _) => data.yValue,
dataLabelSettings: DataLabelSettings(isVisible: true),
),
],
),
),
],
),
);
}
Widget audienceOverview() {
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium("Audience Overview", fontWeight: 600),
MySpacing.height(24),
SizedBox(
height: 318,
child: SfCartesianChart(tooltipBehavior: controller.audienceOverview, series: <CartesianSeries>[
BarSeries<ChartSampleData, dynamic>(
color: Colors.blue,
borderRadius: BorderRadius.horizontal(right: Radius.circular(8)),
dataSource: controller.audienceOverviewChart,
xValueMapper: (ChartSampleData data, _) => data.x,
yValueMapper: (ChartSampleData data, _) => data.y,
width: 0.6,
spacing: 0.3),
BarSeries<ChartSampleData, dynamic>(
borderRadius: BorderRadius.horizontal(right: Radius.circular(8)),
dataSource: controller.audienceOverviewChart,
color: Colors.teal,
xValueMapper: (ChartSampleData data, _) => data.x,
yValueMapper: (ChartSampleData data, _) => data.yValue,
width: 0.6,
spacing: 0.3)
]))
],
),
);
}
Widget buildTrafficSources() {
Widget buildData(String browser, session, double process) {
return Row(
children: [
Expanded(child: MyText.bodyMedium(browser, fontWeight: 600, overflow: TextOverflow.ellipsis)),
Expanded(
child: Row(
children: [
if (session >= 5000) Icon(LucideIcons.trending_up, size: 20, color: contentTheme.success),
if (session < 5000) Icon(LucideIcons.trending_down, size: 20, color: contentTheme.danger),
MySpacing.width(8),
Expanded(
child: MyText.bodyMedium("$session", fontWeight: 600, overflow: TextOverflow.ellipsis),
),
],
),
),
Expanded(
child: MyProgressBar(
progress: process,
height: 4,
radius: 4,
inactiveColor: theme.dividerColor,
activeColor: contentTheme.primary,
),
),
],
);
}
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: MySpacing.only(left: 23, top: 19),
child: MyText.titleMedium("Traffic Sources", fontWeight: 600),
),
MySpacing.height(24),
Divider(height: 0),
MySpacing.height(24),
Padding(
padding: MySpacing.only(left: 23),
child: Row(children: [
Expanded(child: MyText.bodyMedium("Browser", fontWeight: 600)),
Expanded(child: MyText.bodyMedium("Sessions", fontWeight: 600)),
Expanded(child: MyText.bodyMedium("Traffic", fontWeight: 600)),
]),
),
MySpacing.height(24),
Divider(height: 0),
Padding(
padding: MySpacing.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildData("Google Chrome", 12000, .30),
MySpacing.height(24),
buildData("Apple Safari", 7000, .25),
MySpacing.height(24),
buildData("Microsoft Edge", 4500, .15),
MySpacing.height(24),
buildData("Mozilla Firefox", 8000, .22),
MySpacing.height(24),
buildData("Opera Browser", 3000, .18),
MySpacing.height(24),
buildData("Brave Browser", 2000, .12),
MySpacing.height(24),
buildData("Vivaldi Browser", 1500, .08),
],
),
)
],
),
);
}
Widget buildMostActiveUser() {
Widget buildData(String image, name, emailID) {
return MyContainer.bordered(
child: Row(
children: [
MyContainer.rounded(
paddingAll: 0,
height: 44,
width: 44,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Image.asset(image, fit: BoxFit.cover),
),
MySpacing.width(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium(name, fontWeight: 600),
MySpacing.height(4),
MyText.labelMedium(
emailID,
fontWeight: 600,
xMuted: true,
overflow: TextOverflow.ellipsis,
),
],
),
)
],
),
);
}
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium("Most Active user", fontWeight: 600),
MySpacing.height(20),
SizedBox(
height: 372,
child: SingleChildScrollView(
child: Column(
children: [
buildData(Images.avatars[0], "John Doe", "john.doe@example.com"),
MySpacing.height(24),
buildData(Images.avatars[1], "Emily Smith", "emily.smith@example.com"),
MySpacing.height(24),
buildData(Images.avatars[2], "Michael Johnson", "michael.johnson@example.com"),
MySpacing.height(24),
buildData(Images.avatars[3], "Olivia Williams", "olivia.williams@example.com"),
],
),
),
),
],
));
}
Widget buildVisitorsByCountry() {
Widget buildData(String image, name, count) {
return Row(
children: [
MyContainer.rounded(
paddingAll: 0,
height: 41,
width: 41,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Image.asset(
image,
fit: BoxFit.cover,
),
),
MySpacing.width(12),
Expanded(
child: MyText.bodyMedium(name, fontWeight: 600, overflow: TextOverflow.ellipsis),
),
MyContainer(
borderRadiusAll: 8,
padding: MySpacing.xy(8, 8),
color: Colors.brown.withAlpha(36),
child: MyText.bodySmall(
numberFormatter(count),
fontWeight: 600,
color: Colors.brown,
),
)
],
);
}
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.titleMedium("Visitor by country's", fontWeight: 600),
MySpacing.height(24),
buildData('assets/country/united_states.png', "United State", "41560"),
MySpacing.height(24),
buildData('assets/country/argentina.png', "Argentina", "18400"),
MySpacing.height(24),
buildData('assets/country/germany.png', "Germany", "9000"),
MySpacing.height(24),
buildData('assets/country/mexico.png', "Mexico", "15325"),
MySpacing.height(24),
buildData('assets/country/russia.png', "Russia", "12222"),
MySpacing.height(24),
buildData('assets/country/canada.png', "Canada", "2040"),
],
),
);
}
Widget buildVisitorByChannel() {
return MyCard.bordered(
borderRadiusAll: 4,
border: Border.all(color: Colors.grey.withValues(alpha:.2)),
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
paddingAll: 24,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MyText.bodyMedium("Visitors By Channel", fontWeight: 600),
MySpacing.height(24),
if (controller.visitorByChannel.isNotEmpty)
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
sortAscending: true,
columnSpacing: 105,
onSelectAll: (_) => {},
headingRowColor: WidgetStatePropertyAll(contentTheme.primary.withAlpha(40)),
dataRowMaxHeight: 60,
showBottomBorder: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
border: TableBorder.all(borderRadius: BorderRadius.circular(4), style: BorderStyle.solid, width: .4, color: Colors.grey),
columns: [
DataColumn(label: MyText.labelLarge('S.No', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Channel', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Session', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Bounce Rate', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Session Duration', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Target Reached', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Page Per Session', color: contentTheme.primary)),
DataColumn(label: MyText.labelLarge('Action', color: contentTheme.primary)),
],
rows: controller.visitorByChannel
.mapIndexed((index, data) => DataRow(cells: [
DataCell(MyText.bodyMedium('${data.id}')),
DataCell(MyText.labelLarge(data.channel, overflow: TextOverflow.ellipsis, maxLines: 1)),
DataCell(MyText.bodySmall('${data.session}', fontWeight: 600)),
DataCell(MyText.bodySmall('${data.bounceRate}%', fontWeight: 600)),
DataCell(MyText.bodySmall('${Utils.getDateTimeStringFromDateTime(data.sessionDuration)}', fontWeight: 600)),
DataCell(
MyContainer(
borderRadiusAll: 4,
clipBehavior: Clip.antiAliasWithSaveLayer,
padding: MySpacing.xy(8, 8),
color: contentTheme.primary.withAlpha(32),
child: MyText.bodySmall('${data.targetReached}', fontWeight: 600, color: contentTheme.primary),
),
),
DataCell(MyText.bodyMedium('${data.pagePerSession}')),
DataCell(SizedBox(
width: 130,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MyContainer(
onTap: () => {},
padding: MySpacing.xy(8, 8),
color: contentTheme.primary.withAlpha(36),
child: Icon(LucideIcons.pencil, size: 14, color: contentTheme.primary),
),
MyContainer(
onTap: () => {},
padding: MySpacing.xy(8, 8),
color: contentTheme.success.withAlpha(36),
child: Icon(LucideIcons.pencil, size: 14, color: contentTheme.success),
),
MyContainer(
onTap: () => controller.removeData(index),
padding: MySpacing.xy(8, 8),
color: contentTheme.danger.withAlpha(36),
child: Icon(LucideIcons.trash_2, size: 14, color: contentTheme.danger),
),
],
),
)),
]))
.toList()),
),
],
),
);
}
}