From 2f7e25ef236b0a0db859cd01a781903fa44aab4d Mon Sep 17 00:00:00 2001 From: Erick Namukolo Date: Thu, 31 Jul 2025 23:34:16 +0200 Subject: [PATCH 1/5] added session details page --- lib/features/overview/widgets/stat_card.dart | 71 +++++----- lib/features/sessions/model/session.dart | 10 ++ lib/features/sessions/model/session.g.dart | 10 ++ .../screens/session_details_screen.dart | 122 ++++++++++++++++++ .../sessions/widget/session_card.dart | 56 ++++---- lib/main.dart | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 8 ++ pubspec.yaml | 1 + 9 files changed, 225 insertions(+), 57 deletions(-) create mode 100644 lib/features/sessions/screens/session_details_screen.dart diff --git a/lib/features/overview/widgets/stat_card.dart b/lib/features/overview/widgets/stat_card.dart index ef371b7..8123047 100644 --- a/lib/features/overview/widgets/stat_card.dart +++ b/lib/features/overview/widgets/stat_card.dart @@ -3,6 +3,7 @@ import 'package:icons_plus/icons_plus.dart'; import 'package:pulse/features/overview/repo/overview_repo.dart'; import 'package:pulse/utils/text.dart'; import 'package:intl/intl.dart'; +import 'package:pulse/utils/utils.dart'; import '../../../utils/colors.dart'; import '../../../widgets/container_wrapper.dart'; @@ -15,6 +16,7 @@ class StatCard extends StatelessWidget { @override Widget build(BuildContext context) { + logger.i(stat); double percentage = OverviewRepo().getPercentage( current: stat.value['value'], previous: stat.value['prev']); return ContainerWrapper( @@ -28,44 +30,47 @@ class StatCard extends StatelessWidget { Text(NumberFormat.compact().format((stat.value as Map)['value']), style: kTitleTextStyle.copyWith( fontSize: kTitleTextStyle.fontSize! + 8)), - Container( - padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 4), - decoration: BoxDecoration( - color: percentage.isNegative - ? kErrorColor.withOpacity(.2) - : kSuccessColor.withOpacity(.2), - borderRadius: BorderRadius.circular(20.0), - border: Border.all( + if (stat.value['prev'] != -1) + Container( + padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 4), + decoration: BoxDecoration( color: percentage.isNegative - ? kErrorColor.withOpacity(.5) - : kSuccessColor.withOpacity(.5), - width: 2.0, + ? kErrorColor.withOpacity(.2) + : kSuccessColor.withOpacity(.2), + borderRadius: BorderRadius.circular(20.0), + border: Border.all( + color: percentage.isNegative + ? kErrorColor.withOpacity(.5) + : kSuccessColor.withOpacity(.5), + width: 2.0, + ), ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 10, - children: [ - Visibility( - visible: percentage != 0, - child: Icon( - percentage.isNegative - ? Bootstrap.arrow_down_left_circle_fill - : Bootstrap.arrow_up_right_circle_fill, - color: percentage.isNegative ? kErrorColor : kSuccessColor, - size: 18, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Visibility( + visible: percentage != 0, + child: Icon( + percentage.isNegative + ? Bootstrap.arrow_down_left_circle_fill + : Bootstrap.arrow_up_right_circle_fill, + color: + percentage.isNegative ? kErrorColor : kSuccessColor, + size: 18, + ), ), - ), - Text( - '${percentage.toStringAsFixed(1).replaceAll('-', '')} %', - style: kBodyTextStyle.copyWith( - color: percentage.isNegative ? kErrorColor : kSuccessColor, - fontWeight: FontWeight.w600, + Text( + '${percentage.toStringAsFixed(1).replaceAll('-', '')} %', + style: kBodyTextStyle.copyWith( + color: + percentage.isNegative ? kErrorColor : kSuccessColor, + fontWeight: FontWeight.w600, + ), ), - ), - ], + ], + ), ), - ), ], ), ); diff --git a/lib/features/sessions/model/session.dart b/lib/features/sessions/model/session.dart index ef11cfd..c2e910c 100644 --- a/lib/features/sessions/model/session.dart +++ b/lib/features/sessions/model/session.dart @@ -9,11 +9,16 @@ class Session { String device; String screen; String country; + String language; String region; String city; int visits; int views; + int? totaltime; + int? events; DateTime createdAt; + DateTime firstAt; + DateTime lastAt; Session({ required this.browser, @@ -27,6 +32,11 @@ class Session { required this.screen, required this.visits, required this.views, + required this.firstAt, + required this.lastAt, + required this.language, + this.events, + this.totaltime, }); factory Session.fromJson(Map json) => diff --git a/lib/features/sessions/model/session.g.dart b/lib/features/sessions/model/session.g.dart index e4a88d7..250bb53 100644 --- a/lib/features/sessions/model/session.g.dart +++ b/lib/features/sessions/model/session.g.dart @@ -18,6 +18,11 @@ Session _$SessionFromJson(Map json) => Session( screen: json['screen'] as String, visits: (json['visits'] as num).toInt(), views: (json['views'] as num).toInt(), + firstAt: DateTime.parse(json['firstAt'] as String), + lastAt: DateTime.parse(json['lastAt'] as String), + language: json['language'] as String, + events: (json['events'] as num?)?.toInt(), + totaltime: (json['totaltime'] as num?)?.toInt(), ); Map _$SessionToJson(Session instance) => { @@ -27,9 +32,14 @@ Map _$SessionToJson(Session instance) => { 'device': instance.device, 'screen': instance.screen, 'country': instance.country, + 'language': instance.language, 'region': instance.region, 'city': instance.city, 'visits': instance.visits, 'views': instance.views, + 'totaltime': instance.totaltime, + 'events': instance.events, 'createdAt': instance.createdAt.toIso8601String(), + 'firstAt': instance.firstAt.toIso8601String(), + 'lastAt': instance.lastAt.toIso8601String(), }; diff --git a/lib/features/sessions/screens/session_details_screen.dart b/lib/features/sessions/screens/session_details_screen.dart new file mode 100644 index 0000000..1d05940 --- /dev/null +++ b/lib/features/sessions/screens/session_details_screen.dart @@ -0,0 +1,122 @@ +import 'package:country_flags/country_flags.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_boring_avatars/flutter_boring_avatars.dart'; +import 'package:icons_launcher/cli_commands.dart'; +import 'package:intl/intl.dart'; +import 'package:pulse/features/overview/widgets/stat_card.dart'; +import 'package:pulse/features/sessions/model/session.dart'; +import 'package:pulse/utils/text.dart'; +import 'package:pulse/widgets/custom_appbar.dart'; +import 'package:country_codes/country_codes.dart'; + +class SessionDetailsScreen extends StatelessWidget { + final Session session; + const SessionDetailsScreen({super.key, required this.session}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(title: 'Session'), + body: Padding( + padding: EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 15, + children: [ + Hero( + tag: session.id, + child: Align( + alignment: Alignment.center, + child: Stack( + clipBehavior: Clip.none, + children: [ + SizedBox( + width: 100, + height: 100, + child: BoringAvatar( + name: session.id, + type: BoringAvatarType.beam, + shape: CircleBorder(), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: CountryFlag.fromCountryCode( + session.country, + shape: const RoundedRectangle(30), + height: 25, + width: 25, + ), + ), + ], + ), + ), + ), + Row( + spacing: 15, + children: [ + Expanded( + child: StatCard( + stat: + MapEntry('views', {'value': session.views, 'prev': -1}), + ), + ), + Expanded( + child: StatCard( + stat: MapEntry( + 'visits', {'value': session.visits, 'prev': -1}), + ), + ) + ], + ), + getData( + title: 'First Seen', + des: + '${DateFormat('EEE, MMM d y').format(session.firstAt.toLocal())} at ${DateFormat('HH:mm').format(session.firstAt.toLocal())} hrs', + ), + getData( + title: 'Last Seen', + des: + '${DateFormat('EEE, MMM d y').format(session.lastAt.toLocal())} at ${DateFormat('HH:mm').format(session.lastAt.toLocal())} hrs', + ), + getData( + title: 'Region', + des: + '${CountryCodes.name(locale: Locale(session.language, session.country))}, ${session.city}', + ), + getData( + title: 'Device', + des: session.device.capitalize(), + ), + getData( + title: 'OS', + des: session.os.capitalize(), + ), + getData( + title: 'Browser', + des: session.browser.capitalize(), + ), + ], + ), + ), + ); + } + + Column getData({ + required String title, + required String des, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: kBodyTitleTextStyle.copyWith(fontWeight: FontWeight.bold)), + Text( + des, + style: kBodyTextStyle, + ), + ], + ); + } +} diff --git a/lib/features/sessions/widget/session_card.dart b/lib/features/sessions/widget/session_card.dart index 55ea1fc..e0e6bd4 100644 --- a/lib/features/sessions/widget/session_card.dart +++ b/lib/features/sessions/widget/session_card.dart @@ -1,9 +1,12 @@ +import 'package:country_codes/country_codes.dart'; import 'package:country_flags/country_flags.dart'; import 'package:flutter/material.dart'; import 'package:flutter_boring_avatars/flutter_boring_avatars.dart'; import 'package:intl/intl.dart'; import 'package:pulse/features/sessions/model/session.dart'; +import 'package:pulse/features/sessions/screens/session_details_screen.dart'; import 'package:pulse/utils/colors.dart'; +import 'package:pulse/utils/navigation.dart'; import 'package:pulse/utils/text.dart'; import 'package:pulse/utils/utils.dart'; import 'package:pulse/widgets/container_wrapper.dart'; @@ -20,31 +23,34 @@ class SessionCard extends StatelessWidget { child: Row( spacing: 15, children: [ - Stack( - clipBehavior: Clip.none, - children: [ - SizedBox( - width: 50, - height: 50, - child: BoringAvatar( - name: session.id, - type: BoringAvatarType.beam, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6.0), + Hero( + tag: session.id, + child: Stack( + clipBehavior: Clip.none, + children: [ + SizedBox( + width: 50, + height: 50, + child: BoringAvatar( + name: session.id, + type: BoringAvatarType.beam, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), ), ), - ), - Positioned( - bottom: -5, - right: -5, - child: CountryFlag.fromCountryCode( - session.country, - shape: const RoundedRectangle(30), - height: 20, - width: 20, + Positioned( + bottom: -5, + right: -5, + child: CountryFlag.fromCountryCode( + session.country, + shape: const RoundedRectangle(30), + height: 20, + width: 20, + ), ), - ), - ], + ], + ), ), Expanded( child: Column( @@ -52,7 +58,7 @@ class SessionCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - '${session.country}, ${session.city} -- ${session.os}', + '${CountryCodes.name(locale: Locale(session.language, session.country))}, ${session.city} -- ${session.os}', style: kBodyTextStyle.copyWith( fontWeight: FontWeight.bold, color: Colors.black, @@ -69,7 +75,9 @@ class SessionCard extends StatelessWidget { bRadius: 100, icon: Icons.remove_red_eye_rounded, click: () { - Toast.showToast(message: 'Coming Soon', context: context); + Navigation.go( + screen: SessionDetailsScreen(session: session), + context: context); }, iconColor: kPrimaryColor, ), diff --git a/lib/main.dart b/lib/main.dart index a9d4bd3..7a718de 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:country_codes/country_codes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pulse/features/events/cubit/events_cubit.dart'; @@ -15,6 +16,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await dotenv.load(fileName: ".env"); + await CountryCodes.init(); prefs = await SharedPreferences.getInstance(); userTimeZone = await FlutterTimezone.getLocalTimezone(); runApp(const Pulse()); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 297893a..d9f8857 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import country_codes import flutter_timezone import package_info_plus import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + CountryCodesPlugin.register(with: registry.registrar(forPlugin: "CountryCodesPlugin")) FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 3cb6d6a..ca061e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + country_codes: + dependency: "direct main" + description: + name: country_codes + sha256: "80e441b8a4bec1e7a24d58955fe769e7c6199ffd025f4509d4172d8d0195dfc4" + url: "https://pub.dev" + source: hosted + version: "3.3.0" country_flags: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b5da9ab..991c063 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: fl_chart: ^0.71.0 flutter_timezone: ^4.1.1 flutter_dotenv: ^5.2.1 + country_codes: ^3.3.0 dev_dependencies: flutter_test: From 0f1eba3063dc4318068c6f48a51584858fc7d66f Mon Sep 17 00:00:00 2001 From: Erick Namukolo Date: Fri, 1 Aug 2025 00:16:54 +0200 Subject: [PATCH 2/5] added icons for device,region and seen --- lib/features/sessions/model/session.dart | 6 +- lib/features/sessions/model/session.g.dart | 8 +- lib/features/sessions/repo/sessions_repo.dart | 10 ++ .../screens/session_details_screen.dart | 123 +++++++++++++++--- .../sessions/widget/session_card.dart | 2 +- lib/utils/extensions.dart | 16 +++ 6 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 lib/utils/extensions.dart diff --git a/lib/features/sessions/model/session.dart b/lib/features/sessions/model/session.dart index c2e910c..710199c 100644 --- a/lib/features/sessions/model/session.dart +++ b/lib/features/sessions/model/session.dart @@ -5,6 +5,7 @@ part 'session.g.dart'; class Session { String id; String browser; + String websiteId; String os; String device; String screen; @@ -16,13 +17,14 @@ class Session { int views; int? totaltime; int? events; - DateTime createdAt; + DateTime? createdAt; DateTime firstAt; DateTime lastAt; Session({ required this.browser, - required this.createdAt, + required this.websiteId, + this.createdAt, required this.city, required this.country, required this.device, diff --git a/lib/features/sessions/model/session.g.dart b/lib/features/sessions/model/session.g.dart index 250bb53..1070db9 100644 --- a/lib/features/sessions/model/session.g.dart +++ b/lib/features/sessions/model/session.g.dart @@ -8,7 +8,10 @@ part of 'session.dart'; Session _$SessionFromJson(Map json) => Session( browser: json['browser'] as String, - createdAt: DateTime.parse(json['createdAt'] as String), + websiteId: json['websiteId'] as String, + createdAt: json['createdAt'] == null + ? null + : DateTime.parse(json['createdAt'] as String), city: json['city'] as String, country: json['country'] as String, device: json['device'] as String, @@ -28,6 +31,7 @@ Session _$SessionFromJson(Map json) => Session( Map _$SessionToJson(Session instance) => { 'id': instance.id, 'browser': instance.browser, + 'websiteId': instance.websiteId, 'os': instance.os, 'device': instance.device, 'screen': instance.screen, @@ -39,7 +43,7 @@ Map _$SessionToJson(Session instance) => { 'views': instance.views, 'totaltime': instance.totaltime, 'events': instance.events, - 'createdAt': instance.createdAt.toIso8601String(), + 'createdAt': instance.createdAt?.toIso8601String(), 'firstAt': instance.firstAt.toIso8601String(), 'lastAt': instance.lastAt.toIso8601String(), }; diff --git a/lib/features/sessions/repo/sessions_repo.dart b/lib/features/sessions/repo/sessions_repo.dart index 20555f2..b33a4a6 100644 --- a/lib/features/sessions/repo/sessions_repo.dart +++ b/lib/features/sessions/repo/sessions_repo.dart @@ -1,6 +1,7 @@ import 'package:pulse/features/sessions/model/session.dart'; import 'package:pulse/utils/endpoints.dart'; import 'package:pulse/utils/requests.dart'; +import 'package:pulse/utils/utils.dart'; class SessionsRepo { Future> getSessions({ @@ -20,4 +21,13 @@ class SessionsRepo { '${Endpoints.websites.replaceAll(Endpoints.baseUrl, 'https://api.umami.is/v1')}/$id/sessions?startAt=$startAt&endAt=$endAt&pageSize=20&page=${pageNumber ?? 1}'); return Session.toList(res['data']); } + + Future getSession(String websiteId, String id) async { + var res = await Requests.get( + useKey: true, + endpoint: + '${Endpoints.websites.replaceAll(Endpoints.baseUrl, 'https://api.umami.is/v1')}/$websiteId/sessions/$id'); + + return Session.fromJson(res); + } } diff --git a/lib/features/sessions/screens/session_details_screen.dart b/lib/features/sessions/screens/session_details_screen.dart index 1d05940..e2d4d2f 100644 --- a/lib/features/sessions/screens/session_details_screen.dart +++ b/lib/features/sessions/screens/session_details_screen.dart @@ -1,18 +1,49 @@ import 'package:country_flags/country_flags.dart'; +import 'package:fade_shimmer/fade_shimmer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_boring_avatars/flutter_boring_avatars.dart'; import 'package:icons_launcher/cli_commands.dart'; +import 'package:icons_plus/icons_plus.dart'; import 'package:intl/intl.dart'; import 'package:pulse/features/overview/widgets/stat_card.dart'; import 'package:pulse/features/sessions/model/session.dart'; +import 'package:pulse/features/sessions/repo/sessions_repo.dart'; +import 'package:pulse/utils/colors.dart'; +import 'package:pulse/utils/extensions.dart'; import 'package:pulse/utils/text.dart'; +import 'package:pulse/utils/utils.dart'; import 'package:pulse/widgets/custom_appbar.dart'; import 'package:country_codes/country_codes.dart'; -class SessionDetailsScreen extends StatelessWidget { +class SessionDetailsScreen extends StatefulWidget { final Session session; const SessionDetailsScreen({super.key, required this.session}); + @override + State createState() => _SessionDetailsScreenState(); +} + +class _SessionDetailsScreenState extends State { + Session? _session; + + @override + void initState() { + Future.delayed(Duration.zero).then((_) async { + try { + var res = await SessionsRepo() + .getSession(widget.session.websiteId, widget.session.id); + + setState(() { + _session = res; + }); + } catch (e, st) { + logger.i(st); + Toast.showToast(message: e.toString(), context: context); + } + }); + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -24,7 +55,7 @@ class SessionDetailsScreen extends StatelessWidget { spacing: 15, children: [ Hero( - tag: session.id, + tag: widget.session.id, child: Align( alignment: Alignment.center, child: Stack( @@ -34,7 +65,7 @@ class SessionDetailsScreen extends StatelessWidget { width: 100, height: 100, child: BoringAvatar( - name: session.id, + name: widget.session.id, type: BoringAvatarType.beam, shape: CircleBorder(), ), @@ -43,7 +74,7 @@ class SessionDetailsScreen extends StatelessWidget { bottom: 0, right: 0, child: CountryFlag.fromCountryCode( - session.country, + widget.session.country, shape: const RoundedRectangle(30), height: 25, width: 25, @@ -58,44 +89,96 @@ class SessionDetailsScreen extends StatelessWidget { children: [ Expanded( child: StatCard( - stat: - MapEntry('views', {'value': session.views, 'prev': -1}), + stat: MapEntry( + 'views', {'value': widget.session.views, 'prev': -1}), ), ), Expanded( child: StatCard( stat: MapEntry( - 'visits', {'value': session.visits, 'prev': -1}), + 'visits', {'value': widget.session.visits, 'prev': -1}), ), ) ], ), + _session == null + ? FadeShimmer( + height: 100, + width: double.infinity, + radius: 16, + fadeTheme: FadeTheme.light, + ) + : Row( + spacing: 15, + children: [ + Expanded( + child: StatCard( + stat: MapEntry('events', + {'value': _session!.events, 'prev': -1}), + ), + ), + Expanded( + child: StatCard( + stat: MapEntry('totaltime', + {'value': _session!.totaltime, 'prev': -1}), + ), + ) + ], + ), getData( title: 'First Seen', + icon: Icon( + Icons.timer_rounded, + color: kPrimaryColor.withOpacity(.8), + size: 18, + ), des: - '${DateFormat('EEE, MMM d y').format(session.firstAt.toLocal())} at ${DateFormat('HH:mm').format(session.firstAt.toLocal())} hrs', + '${DateFormat('EEE, MMM d y').format(widget.session.firstAt.toLocal())} at ${DateFormat('HH:mm').format(widget.session.firstAt.toLocal())} hrs', ), getData( title: 'Last Seen', + icon: Icon( + Icons.timer_rounded, + color: kPrimaryColor.withOpacity(.8), + size: 18, + ), des: - '${DateFormat('EEE, MMM d y').format(session.lastAt.toLocal())} at ${DateFormat('HH:mm').format(session.lastAt.toLocal())} hrs', + '${DateFormat('EEE, MMM d y').format(widget.session.lastAt.toLocal())} at ${DateFormat('HH:mm').format(widget.session.lastAt.toLocal())} hrs', ), getData( title: 'Region', + icon: Icon( + Icons.pin_drop_rounded, + color: kPrimaryColor.withOpacity(.8), + size: 18, + ), des: - '${CountryCodes.name(locale: Locale(session.language, session.country))}, ${session.city}', + '${CountryCodes.name(locale: Locale(widget.session.language, widget.session.country))}, ${widget.session.city}', ), getData( title: 'Device', - des: session.device.capitalize(), + icon: Icon( + widget.session.device.toDeviceIcon, + color: kPrimaryColor.withOpacity(.8), + size: 18, + ), + des: widget.session.device.capitalize(), ), getData( title: 'OS', - des: session.os.capitalize(), + icon: Brand( + Brands.android_os, + size: 18, + ), + des: widget.session.os.capitalize(), ), getData( title: 'Browser', - des: session.browser.capitalize(), + icon: Brand( + Brands.chrome, + size: 18, + ), + des: widget.session.browser.capitalize(), ), ], ), @@ -106,15 +189,23 @@ class SessionDetailsScreen extends StatelessWidget { Column getData({ required String title, required String des, + Widget? icon, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: kBodyTitleTextStyle.copyWith(fontWeight: FontWeight.bold)), - Text( - des, - style: kBodyTextStyle, + Row( + spacing: 5, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (icon != null) icon, + Text( + des, + style: kBodyTextStyle, + ), + ], ), ], ); diff --git a/lib/features/sessions/widget/session_card.dart b/lib/features/sessions/widget/session_card.dart index e0e6bd4..c9f5f6c 100644 --- a/lib/features/sessions/widget/session_card.dart +++ b/lib/features/sessions/widget/session_card.dart @@ -65,7 +65,7 @@ class SessionCard extends StatelessWidget { ), ), Text( - '${DateFormat('EEE, MMM d y').format(session.createdAt.toLocal())} at ${DateFormat('HH:mm').format(session.createdAt.toLocal())} hrs', + '${DateFormat('EEE, MMM d y').format(session.createdAt!.toLocal())} at ${DateFormat('HH:mm').format(session.createdAt!.toLocal())} hrs', style: kBodyTextStyle, ), ], diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart new file mode 100644 index 0000000..728b214 --- /dev/null +++ b/lib/utils/extensions.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +extension StringExtensions on String { + IconData get toDeviceIcon { + switch (toLowerCase()) { + case 'laptop': + return Icons.laptop_mac_rounded; + case 'desktop': + return Icons.desktop_windows_rounded; + case 'mobile': + return Icons.phone_android_rounded; + default: + return Icons.phone_android_rounded; + } + } +} From 146039c44a2d19f6bdde222d29c1c86d2c0aa172 Mon Sep 17 00:00:00 2001 From: Erick Namukolo Date: Fri, 1 Aug 2025 00:37:01 +0200 Subject: [PATCH 3/5] added os icons --- .../screens/session_details_screen.dart | 2 +- lib/utils/extensions.dart | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/features/sessions/screens/session_details_screen.dart b/lib/features/sessions/screens/session_details_screen.dart index e2d4d2f..87f89f8 100644 --- a/lib/features/sessions/screens/session_details_screen.dart +++ b/lib/features/sessions/screens/session_details_screen.dart @@ -167,7 +167,7 @@ class _SessionDetailsScreenState extends State { getData( title: 'OS', icon: Brand( - Brands.android_os, + widget.session.os.toLowerCase().toOsIcon, size: 18, ), des: widget.session.os.capitalize(), diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index 728b214..4932dc4 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:pulse/utils/utils.dart'; extension StringExtensions on String { IconData get toDeviceIcon { @@ -13,4 +15,25 @@ extension StringExtensions on String { return Icons.phone_android_rounded; } } + + String get toOsIcon { + logger.i(this); + if (contains('ios')) { + return Brands.apple_logo; + } else if (contains('mac')) { + return Brands.mac_logo; + } else if (contains('android')) { + return Brands.android_os; + } else if (contains('windows 10')) { + return Brands.windows_10; + } else if (contains('windows 11')) { + return Brands.windows_11; + } else if (contains('windows 7') || contains('windows')) { + return Brands.windows8; + } else if (contains('linux')) { + return Brands.kali_linux; + } else { + return Brands.mac_logo; + } + } } From e0383905003f46dbd3da183dba22af31925ea7b6 Mon Sep 17 00:00:00 2001 From: Erick Namukolo Date: Fri, 1 Aug 2025 00:44:00 +0200 Subject: [PATCH 4/5] done with session details screen --- lib/features/overview/widgets/stat_card.dart | 2 -- .../screens/session_details_screen.dart | 2 +- lib/utils/extensions.dart | 20 ++++++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/features/overview/widgets/stat_card.dart b/lib/features/overview/widgets/stat_card.dart index 8123047..fa7a16d 100644 --- a/lib/features/overview/widgets/stat_card.dart +++ b/lib/features/overview/widgets/stat_card.dart @@ -3,7 +3,6 @@ import 'package:icons_plus/icons_plus.dart'; import 'package:pulse/features/overview/repo/overview_repo.dart'; import 'package:pulse/utils/text.dart'; import 'package:intl/intl.dart'; -import 'package:pulse/utils/utils.dart'; import '../../../utils/colors.dart'; import '../../../widgets/container_wrapper.dart'; @@ -16,7 +15,6 @@ class StatCard extends StatelessWidget { @override Widget build(BuildContext context) { - logger.i(stat); double percentage = OverviewRepo().getPercentage( current: stat.value['value'], previous: stat.value['prev']); return ContainerWrapper( diff --git a/lib/features/sessions/screens/session_details_screen.dart b/lib/features/sessions/screens/session_details_screen.dart index 87f89f8..ac3c3fc 100644 --- a/lib/features/sessions/screens/session_details_screen.dart +++ b/lib/features/sessions/screens/session_details_screen.dart @@ -175,7 +175,7 @@ class _SessionDetailsScreenState extends State { getData( title: 'Browser', icon: Brand( - Brands.chrome, + widget.session.browser.toLowerCase().toBrowserIcon, size: 18, ), des: widget.session.browser.capitalize(), diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index 4932dc4..f477ded 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -17,7 +17,6 @@ extension StringExtensions on String { } String get toOsIcon { - logger.i(this); if (contains('ios')) { return Brands.apple_logo; } else if (contains('mac')) { @@ -36,4 +35,23 @@ extension StringExtensions on String { return Brands.mac_logo; } } + + String get toBrowserIcon { + logger.i(this); + if (contains('chrome')) { + return Brands.chrome; + } else if (contains('ios')) { + return Brands.safari; + } else if (contains('opera')) { + return Brands.opera; + } else if (contains('chromium')) { + return Brands.chromium; + } else if (contains('samsung')) { + return Brands.samsung; + } else if (contains('instagram')) { + return Brands.instagram; + } else { + return Brands.chromium; + } + } } From 7447258689b7d90e9b7d3d86e1639d18d3c9b19d Mon Sep 17 00:00:00 2001 From: Erick Namukolo Date: Fri, 1 Aug 2025 00:46:35 +0200 Subject: [PATCH 5/5] .. --- lib/features/settings/screens/settings_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/features/settings/screens/settings_screen.dart b/lib/features/settings/screens/settings_screen.dart index 913a116..393c739 100644 --- a/lib/features/settings/screens/settings_screen.dart +++ b/lib/features/settings/screens/settings_screen.dart @@ -55,7 +55,8 @@ class _SettingsScreenState extends State { type: 'app', icon: Icons.shield_rounded, click: () { - Links.goToLink('https://adcash-zm.vercel.app/privacy-policy', + Links.goToLink( + 'https://doc-hosting.flycricket.io/pulse-privacy-policy/76342146-9ec1-4f25-8c7a-62109aefc65e/privacy', mode: LaunchMode.inAppBrowserView); }, ),