From 518e88ca781e3b5a8c5dc10c93784db96266f1c3 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 13:53:15 +0300 Subject: [PATCH 1/8] Refactor dashboard and profile pages; implement navigation to ProfilePage and update SettingsWidget layout --- .../app/dashboard/pages/dashboard_page.dart | 15 +- .../src/app/profile/pages/profile_page.dart | 28 ++- .../pages/widgets/settings_widget.dart | 201 +++++++++++++----- 3 files changed, 165 insertions(+), 79 deletions(-) diff --git a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart index c9c4a77146..cbcd25a502 100644 --- a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart +++ b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart @@ -13,6 +13,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:flutter_svg/svg.dart'; import 'package:intl/intl.dart'; +import 'package:airqo/src/app/profile/pages/profile_page.dart'; import '../models/airquality_response.dart'; @@ -96,13 +97,13 @@ class _DashboardPageState extends State { ), SizedBox(width: 8), GestureDetector( - // onTap: () => Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) { - // return ProfilePage(); - // }, - // ), - // ), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return ProfilePage(); + }, + ), + ), child: BlocBuilder( builder: (context, state) { if (state is UserLoaded) { diff --git a/mobile-v3/lib/src/app/profile/pages/profile_page.dart b/mobile-v3/lib/src/app/profile/pages/profile_page.dart index ec80c54825..0b7e749377 100644 --- a/mobile-v3/lib/src/app/profile/pages/profile_page.dart +++ b/mobile-v3/lib/src/app/profile/pages/profile_page.dart @@ -1,6 +1,4 @@ import 'package:airqo/src/app/profile/bloc/user_bloc.dart'; -import 'package:airqo/src/app/profile/pages/widgets/devices_widget.dart'; -import 'package:airqo/src/app/profile/pages/widgets/exposure_widget.dart'; import 'package:airqo/src/app/profile/pages/widgets/settings_widget.dart'; import 'package:airqo/src/meta/utils/colors.dart'; import 'package:flutter/material.dart'; @@ -29,7 +27,7 @@ class _ProfilePageState extends State { String firstName = state.model.users[0].firstName; String lastName = state.model.users[0].lastName; return DefaultTabController( - length: 3, + length: 1, child: Scaffold( appBar: AppBar( automaticallyImplyLeading: false, @@ -123,21 +121,21 @@ class _ProfilePageState extends State { ? Colors.white : AppColors.primaryColor, tabs: [ - Tab( - height: 60, - icon: TabIcon( - image: "assets/profile/exposure.svg", - label: "Exposure")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/exposure.svg", + // label: "Exposure")), // Tab( // height: 60, // icon: TabIcon( // image: "assets/profile/places.svg", // label: "Places")), - Tab( - height: 60, - icon: TabIcon( - image: "assets/profile/devices.svg", - label: "Devices")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/devices.svg", + // label: "Devices")), Tab( height: 60, icon: TabIcon( @@ -146,9 +144,9 @@ class _ProfilePageState extends State { ]), Expanded( child: TabBarView(children: [ - ExposureWidget(), + // ExposureWidget(), // Container(child: Text("devices")), - DevicesWidget(), + // DevicesWidget(), SettingsWidget() ]), ) diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index 30ba5db175..2f7fb10ea0 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -1,68 +1,155 @@ -import 'package:airqo/src/app/profile/pages/widgets/settings_tile.dart'; import 'package:flutter/material.dart'; +import 'package:airqo/src/app/profile/pages/widgets/settings_tile.dart'; +import 'package:flutter_svg/svg.dart'; class SettingsWidget extends StatelessWidget { const SettingsWidget({super.key}); @override Widget build(BuildContext context) { - return Column( - children: [ - SizedBox(height: 8), - SettingsTile( - switchValue: true, - iconPath: "assets/images/shared/location_icon.svg", - title: "Location", - onChanged: (value) { - print(value); - }, - description: - "AirQo to use your precise location to locate the Air Quality of your nearest location"), - SettingsTile( - switchValue: true, - iconPath: "assets/icons/notification.svg", - title: "Notifications", - onChanged: (value) { - print(value); - }, - description: - "AirQo to send you in-app & push notifications & spike alerts."), - SettingsTile( - iconPath: "assets/images/shared/feedback_icon.svg", - title: "Send Feedback", - onChanged: (value) { - print(value); - }, - ), - SettingsTile( - iconPath: "assets/images/shared/airqo_story_icon.svg", - title: "Our Story", - onChanged: (value) { - print(value); - }, - ), - SettingsTile( - iconPath: "assets/images/shared/rate_app_icon.svg", - title: "Rate the App", - onChanged: (value) { - print(value); - }, - ), - SettingsTile( - iconPath: "assets/images/shared/terms_and_privacy.svg", - title: "Terms and Privacy Policy", - onChanged: (value) { - print(value); - }, + final screenHeight = MediaQuery.of(context).size.height; + final screenWidth = MediaQuery.of(context).size.width; + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: screenHeight * 0.02), + + // Location Setting + SettingsTile( + switchValue: true, + iconPath: "assets/images/shared/location_icon.svg", + title: "Location", + onChanged: (value) { + print("Location setting: \$value"); + }, + description: + "AirQo to use your precise location to locate the Air Quality of your nearest location", + ), + + // Notifications Setting + SettingsTile( + switchValue: true, + iconPath: "assets/icons/notification.svg", + title: "Notifications", + onChanged: (value) { + print("Notifications setting: \$value"); + }, + description: + "AirQo to send you in-app & push notifications & spike alerts.", + ), + + // Send Feedback + SettingsTile( + iconPath: "assets/images/shared/feedback_icon.svg", + title: "Send Feedback", + onChanged: (value) { + print("Send Feedback setting: \$value"); + }), + + // Our Story + SettingsTile( + iconPath: "assets/images/shared/airqo_story_icon.svg", + title: "Our Story", + onChanged: (value) { + print("Our Story setting: \$value"); + }, + ), + + // Terms and Privacy Policy + SettingsTile( + iconPath: "assets/images/shared/terms_and_privacy.svg", + title: "Terms and Privacy Policy", + onChanged: (value) { + print("Terms and Privacy Policy setting: \$value"); + }, + ), + + // Logout Button + Padding( + padding: EdgeInsets.symmetric(vertical: screenHeight * 0.02), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + minimumSize: Size.fromHeight(screenHeight * 0.07), + ), + onPressed: () { + print("Logout tapped"); + }, + child: const Text( + "Log out", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + + // Delete Account Section + Padding( + padding: EdgeInsets.symmetric(horizontal: screenWidth * 0.04), + child: InkWell( + onTap: () { + print("Delete Account tapped"); + }, + child: Text( + "Delete Account", + style: TextStyle( + color: Colors.red.shade300, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ), + ), + ), + ), + + SizedBox(height: screenHeight * 0.03), + + // App Info + Center( + child: Column( + children: [ + SvgPicture.asset( + "assets/images/shared/logo.svg", + height: screenHeight * 0.05, + ), + SizedBox(height: screenHeight * 0.01), + const Text( + "3.40.1(1)", + style: TextStyle( + color: Colors.grey, + fontSize: 12, + ), + ), + SizedBox(height: screenHeight * 0.005), + const Text( + "A PROJECT BY", + style: TextStyle( + color: Colors.grey, + fontSize: 12, + ), + ), + const Text( + "Makerere University", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + color: Colors.white, + ), + ), + ], + ), + ), + ], ), - SettingsTile( - iconPath: "assets/images/shared/terms_and_privacy.svg", - title: "Logout", - onChanged: (value) { - print(value); - }, - ) - ], + ), ); } } From 5e05dc27076bc39181af5fd6c6a2052f0b41fd8e Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:14:13 +0300 Subject: [PATCH 2/8] Update SettingsWidget layout; adjust padding and styles for improved UI --- .../pages/widgets/settings_widget.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index 2f7fb10ea0..0f652f6909 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -12,7 +12,7 @@ class SettingsWidget extends StatelessWidget { return SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -70,10 +70,10 @@ class SettingsWidget extends StatelessWidget { // Logout Button Padding( - padding: EdgeInsets.symmetric(vertical: screenHeight * 0.02), + padding: EdgeInsets.symmetric(vertical: screenHeight * 0.05), child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, + backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -85,7 +85,7 @@ class SettingsWidget extends StatelessWidget { child: const Text( "Log out", style: TextStyle( - color: Colors.white, + color: Colors.black, fontWeight: FontWeight.bold, ), ), @@ -94,7 +94,7 @@ class SettingsWidget extends StatelessWidget { // Delete Account Section Padding( - padding: EdgeInsets.symmetric(horizontal: screenWidth * 0.04), + padding: EdgeInsets.symmetric(horizontal: screenWidth * 0.3), child: InkWell( onTap: () { print("Delete Account tapped"); @@ -128,7 +128,7 @@ class SettingsWidget extends StatelessWidget { fontSize: 12, ), ), - SizedBox(height: screenHeight * 0.005), + SizedBox(height: screenHeight * 0.01), const Text( "A PROJECT BY", style: TextStyle( @@ -136,11 +136,11 @@ class SettingsWidget extends StatelessWidget { fontSize: 12, ), ), - const Text( - "Makerere University", - style: TextStyle( + Text( + "Makerere University".toUpperCase(), + style: const TextStyle( fontWeight: FontWeight.bold, - fontSize: 12, + fontSize: 20, color: Colors.white, ), ), From b20f83c35ca6a26e9bac6e39f7d26a48b5e2c6fa Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:26:04 +0300 Subject: [PATCH 3/8] Refactor ProfilePage layout; replace Column with SingleChildScrollView for improved scrolling and UI responsiveness --- .../src/app/profile/pages/profile_page.dart | 219 ++++++++++-------- 1 file changed, 116 insertions(+), 103 deletions(-) diff --git a/mobile-v3/lib/src/app/profile/pages/profile_page.dart b/mobile-v3/lib/src/app/profile/pages/profile_page.dart index 0b7e749377..2054bbc8d6 100644 --- a/mobile-v3/lib/src/app/profile/pages/profile_page.dart +++ b/mobile-v3/lib/src/app/profile/pages/profile_page.dart @@ -38,119 +38,132 @@ class _ProfilePageState extends State { SizedBox(width: 16) ], ), - body: Column( - children: [ - SizedBox( - height: 100, - child: Row( - children: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: CircleAvatar( - backgroundColor: Theme.of(context).highlightColor, - child: Center( - child: SvgPicture.asset( - "assets/icons/user_icon.svg"), - ), - radius: 50, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + body: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: 120, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Text( - "${firstName} ${lastName}", - style: TextStyle( - color: AppColors.boldHeadlineColor, - fontSize: 24, - fontWeight: FontWeight.w700, + Container( + margin: + const EdgeInsets.symmetric(horizontal: 16), + child: CircleAvatar( + backgroundColor: + Theme.of(context).highlightColor, + child: Center( + child: SvgPicture.asset( + "assets/icons/user_icon.svg"), + ), + radius: 50, ), ), - Spacer(), - Row( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 32), - height: 50, - child: Center( - child: Text("Edit your profile")), - // child: InkWell( - // onTap: () => Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => - // EditProfile())), - // child: Text( - // "Edit your profile", - // style: TextStyle( - // fontWeight: FontWeight.w500, - // color: Colors.white, - // ), - // ), - //)), - decoration: BoxDecoration( - color: Theme.of(context).highlightColor, - borderRadius: - BorderRadius.circular(200)), + Text( + "${firstName} ${lastName}", + style: TextStyle( + color: AppColors.boldHeadlineColor, + fontSize: 24, + fontWeight: FontWeight.w700, + ), ), - SizedBox(width: 8), - CircleAvatar( - backgroundColor: - Theme.of(context).highlightColor, - radius: 26, - child: SvgPicture.asset( - "assets/icons/notification.svg")) + SizedBox(height: 8), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 32), + height: 50, + child: Center( + child: Text("Edit your profile")), + // child: InkWell( + // onTap: () => Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => + // EditProfile())), + // child: Text( + // "Edit your profile", + // style: TextStyle( + // fontWeight: FontWeight.w500, + // color: Colors.white, + // ), + // ), + //)), + decoration: BoxDecoration( + color: Theme.of(context) + .highlightColor, + borderRadius: + BorderRadius.circular(200)), + ), + SizedBox(width: 8), + CircleAvatar( + backgroundColor: Theme.of(context) + .highlightColor, + radius: 26, + child: SvgPicture.asset( + "assets/icons/notification.svg")) + ], + ), + ) ], ) ], - ) - ], + ), + ), ), - ), - SizedBox(height: 32), - TabBar( - indicatorSize: TabBarIndicatorSize.tab, - labelColor: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : AppColors.primaryColor, - overlayColor: - WidgetStatePropertyAll(Colors.transparent), - indicatorColor: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : AppColors.primaryColor, - tabs: [ - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/exposure.svg", - // label: "Exposure")), - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/places.svg", - // label: "Places")), - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/devices.svg", - // label: "Devices")), - Tab( - height: 60, - icon: TabIcon( - image: "assets/profile/settings.svg", - label: "Settings")), + SizedBox(height: 32), + TabBar( + indicatorSize: TabBarIndicatorSize.tab, + labelColor: + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : AppColors.primaryColor, + overlayColor: + WidgetStatePropertyAll(Colors.transparent), + indicatorColor: + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : AppColors.primaryColor, + tabs: [ + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/exposure.svg", + // label: "Exposure")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/places.svg", + // label: "Places")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/devices.svg", + // label: "Devices")), + Tab( + height: 60, + icon: TabIcon( + image: "assets/profile/settings.svg", + label: "Settings")), + ]), + Expanded( + child: TabBarView(children: [ + // ExposureWidget(), + // Container(child: Text("devices")), + // DevicesWidget(), + SettingsWidget() ]), - Expanded( - child: TabBarView(children: [ - // ExposureWidget(), - // Container(child: Text("devices")), - // DevicesWidget(), - SettingsWidget() - ]), - ) - ], + ) + ], + ), )), ); } From d35864660084f8764adcf45fc8593e2c889d6ccd Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:35:50 +0300 Subject: [PATCH 4/8] Refactor ProfilePage layout; replace nested SingleChildScrollView with Column for improved performance and UI consistency --- .../src/app/profile/pages/profile_page.dart | 219 ++++++++---------- 1 file changed, 103 insertions(+), 116 deletions(-) diff --git a/mobile-v3/lib/src/app/profile/pages/profile_page.dart b/mobile-v3/lib/src/app/profile/pages/profile_page.dart index 2054bbc8d6..0b7e749377 100644 --- a/mobile-v3/lib/src/app/profile/pages/profile_page.dart +++ b/mobile-v3/lib/src/app/profile/pages/profile_page.dart @@ -38,132 +38,119 @@ class _ProfilePageState extends State { SizedBox(width: 16) ], ), - body: SingleChildScrollView( - child: Column( - children: [ - SizedBox( - height: 120, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisSize: MainAxisSize.min, + body: Column( + children: [ + SizedBox( + height: 100, + child: Row( + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + child: CircleAvatar( + backgroundColor: Theme.of(context).highlightColor, + child: Center( + child: SvgPicture.asset( + "assets/icons/user_icon.svg"), + ), + radius: 50, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - margin: - const EdgeInsets.symmetric(horizontal: 16), - child: CircleAvatar( - backgroundColor: - Theme.of(context).highlightColor, - child: Center( - child: SvgPicture.asset( - "assets/icons/user_icon.svg"), - ), - radius: 50, + Text( + "${firstName} ${lastName}", + style: TextStyle( + color: AppColors.boldHeadlineColor, + fontSize: 24, + fontWeight: FontWeight.w700, ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, + Spacer(), + Row( children: [ - Text( - "${firstName} ${lastName}", - style: TextStyle( - color: AppColors.boldHeadlineColor, - fontSize: 24, - fontWeight: FontWeight.w700, - ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 32), + height: 50, + child: Center( + child: Text("Edit your profile")), + // child: InkWell( + // onTap: () => Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => + // EditProfile())), + // child: Text( + // "Edit your profile", + // style: TextStyle( + // fontWeight: FontWeight.w500, + // color: Colors.white, + // ), + // ), + //)), + decoration: BoxDecoration( + color: Theme.of(context).highlightColor, + borderRadius: + BorderRadius.circular(200)), ), - SizedBox(height: 8), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 32), - height: 50, - child: Center( - child: Text("Edit your profile")), - // child: InkWell( - // onTap: () => Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => - // EditProfile())), - // child: Text( - // "Edit your profile", - // style: TextStyle( - // fontWeight: FontWeight.w500, - // color: Colors.white, - // ), - // ), - //)), - decoration: BoxDecoration( - color: Theme.of(context) - .highlightColor, - borderRadius: - BorderRadius.circular(200)), - ), - SizedBox(width: 8), - CircleAvatar( - backgroundColor: Theme.of(context) - .highlightColor, - radius: 26, - child: SvgPicture.asset( - "assets/icons/notification.svg")) - ], - ), - ) + SizedBox(width: 8), + CircleAvatar( + backgroundColor: + Theme.of(context).highlightColor, + radius: 26, + child: SvgPicture.asset( + "assets/icons/notification.svg")) ], ) ], - ), - ), + ) + ], ), - SizedBox(height: 32), - TabBar( - indicatorSize: TabBarIndicatorSize.tab, - labelColor: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : AppColors.primaryColor, - overlayColor: - WidgetStatePropertyAll(Colors.transparent), - indicatorColor: - Theme.of(context).brightness == Brightness.dark - ? Colors.white - : AppColors.primaryColor, - tabs: [ - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/exposure.svg", - // label: "Exposure")), - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/places.svg", - // label: "Places")), - // Tab( - // height: 60, - // icon: TabIcon( - // image: "assets/profile/devices.svg", - // label: "Devices")), - Tab( - height: 60, - icon: TabIcon( - image: "assets/profile/settings.svg", - label: "Settings")), - ]), - Expanded( - child: TabBarView(children: [ - // ExposureWidget(), - // Container(child: Text("devices")), - // DevicesWidget(), - SettingsWidget() + ), + SizedBox(height: 32), + TabBar( + indicatorSize: TabBarIndicatorSize.tab, + labelColor: + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : AppColors.primaryColor, + overlayColor: + WidgetStatePropertyAll(Colors.transparent), + indicatorColor: + Theme.of(context).brightness == Brightness.dark + ? Colors.white + : AppColors.primaryColor, + tabs: [ + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/exposure.svg", + // label: "Exposure")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/places.svg", + // label: "Places")), + // Tab( + // height: 60, + // icon: TabIcon( + // image: "assets/profile/devices.svg", + // label: "Devices")), + Tab( + height: 60, + icon: TabIcon( + image: "assets/profile/settings.svg", + label: "Settings")), ]), - ) - ], - ), + Expanded( + child: TabBarView(children: [ + // ExposureWidget(), + // Container(child: Text("devices")), + // DevicesWidget(), + SettingsWidget() + ]), + ) + ], )), ); } From a2586bb717569ebde9a230403aec7066aac65384 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:37:08 +0300 Subject: [PATCH 5/8] Remove unused ProfilePage navigation code from DashboardPage --- .../src/app/dashboard/pages/dashboard_page.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart index cbcd25a502..c9c4a77146 100644 --- a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart +++ b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart @@ -13,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:flutter_svg/svg.dart'; import 'package:intl/intl.dart'; -import 'package:airqo/src/app/profile/pages/profile_page.dart'; import '../models/airquality_response.dart'; @@ -97,13 +96,13 @@ class _DashboardPageState extends State { ), SizedBox(width: 8), GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return ProfilePage(); - }, - ), - ), + // onTap: () => Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) { + // return ProfilePage(); + // }, + // ), + // ), child: BlocBuilder( builder: (context, state) { if (state is UserLoaded) { From 4bfd629e30c68e0b7e8e20cb9295975a1a7446af Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:49:16 +0300 Subject: [PATCH 6/8] Add package_info_plus plugin and enhance SettingsWidget with app version display and logout/delete account functionality --- .../plugins/GeneratedPluginRegistrant.java | 5 + .../ios/Runner/GeneratedPluginRegistrant.m | 7 + .../pages/widgets/settings_widget.dart | 138 +++++++++++++++--- mobile-v3/pubspec.lock | 24 +++ mobile-v3/pubspec.yaml | 1 + 5 files changed, 153 insertions(+), 22 deletions(-) diff --git a/mobile-v3/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/mobile-v3/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index 5f086b275f..2de4621042 100644 --- a/mobile-v3/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/mobile-v3/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -30,6 +30,11 @@ public static void registerWith(@NonNull FlutterEngine flutterEngine) { } catch (Exception e) { Log.e(TAG, "Error registering plugin google_maps_flutter_android, io.flutter.plugins.googlemaps.GoogleMapsPlugin", e); } + try { + flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin()); + } catch (Exception e) { + Log.e(TAG, "Error registering plugin package_info_plus, dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin", e); + } try { flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin()); } catch (Exception e) { diff --git a/mobile-v3/ios/Runner/GeneratedPluginRegistrant.m b/mobile-v3/ios/Runner/GeneratedPluginRegistrant.m index 523e1cd8f2..e1a144462e 100644 --- a/mobile-v3/ios/Runner/GeneratedPluginRegistrant.m +++ b/mobile-v3/ios/Runner/GeneratedPluginRegistrant.m @@ -18,6 +18,12 @@ @import google_maps_flutter_ios; #endif +#if __has_include() +#import +#else +@import package_info_plus; +#endif + #if __has_include() #import #else @@ -29,6 +35,7 @@ @implementation GeneratedPluginRegistrant + (void)registerWithRegistry:(NSObject*)registry { [ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]]; [FLTGoogleMapsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTGoogleMapsPlugin"]]; + [FPPPackageInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPPackageInfoPlusPlugin"]]; [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]]; } diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index 0f652f6909..81c1cf1199 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -1,10 +1,101 @@ import 'package:flutter/material.dart'; import 'package:airqo/src/app/profile/pages/widgets/settings_tile.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:package_info/package_info.dart'; -class SettingsWidget extends StatelessWidget { +class SettingsWidget extends StatefulWidget { const SettingsWidget({super.key}); + @override + _SettingsWidgetState createState() => _SettingsWidgetState(); +} + +class _SettingsWidgetState extends State { + String _appVersion = ''; + bool _locationEnabled = true; + bool _notificationsEnabled = true; + + @override + void initState() { + super.initState(); + _getAppVersion(); + } + + Future _getAppVersion() async { + final packageInfo = await PackageInfo.fromPlatform(); + setState(() { + _appVersion = '${packageInfo.version}(${packageInfo.buildNumber})'; + }); + } + + void _showLogoutConfirmation() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Confirm Logout'), + content: const Text('Are you sure you want to log out?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + // TODO: Implement actual logout logic + // e.g., clear user session, revoke tokens + Navigator.of(context).pushReplacementNamed('/login'); + }, + child: const Text('Log Out'), + ), + ], + ), + ); + } + + void _showDeleteAccountDialog() { + final TextEditingController passwordController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Delete Account'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'WARNING: This action cannot be undone. All your data will be permanently deleted.', + style: TextStyle(color: Colors.red), + ), + const SizedBox(height: 16), + TextField( + controller: passwordController, + obscureText: true, + decoration: const InputDecoration( + labelText: 'Enter Password to Confirm', + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + // TODO: Implement actual account deletion logic + // Validate password, call backend deletion endpoint + Navigator.of(context).pushReplacementNamed('/login'); + }, + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + child: const Text('Delete Account'), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; @@ -20,11 +111,14 @@ class SettingsWidget extends StatelessWidget { // Location Setting SettingsTile( - switchValue: true, + switchValue: _locationEnabled, iconPath: "assets/images/shared/location_icon.svg", title: "Location", onChanged: (value) { - print("Location setting: \$value"); + setState(() { + _locationEnabled = value; + }); + print("Location setting: $value"); }, description: "AirQo to use your precise location to locate the Air Quality of your nearest location", @@ -32,11 +126,14 @@ class SettingsWidget extends StatelessWidget { // Notifications Setting SettingsTile( - switchValue: true, + switchValue: _notificationsEnabled, iconPath: "assets/icons/notification.svg", title: "Notifications", onChanged: (value) { - print("Notifications setting: \$value"); + setState(() { + _notificationsEnabled = value; + }); + print("Notifications setting: $value"); }, description: "AirQo to send you in-app & push notifications & spike alerts.", @@ -44,18 +141,19 @@ class SettingsWidget extends StatelessWidget { // Send Feedback SettingsTile( - iconPath: "assets/images/shared/feedback_icon.svg", - title: "Send Feedback", - onChanged: (value) { - print("Send Feedback setting: \$value"); - }), + iconPath: "assets/images/shared/feedback_icon.svg", + title: "Send Feedback", + onChanged: (value) { + print("Send Feedback tapped"); + }, + ), // Our Story SettingsTile( iconPath: "assets/images/shared/airqo_story_icon.svg", title: "Our Story", onChanged: (value) { - print("Our Story setting: \$value"); + print("Our Story tapped"); }, ), @@ -64,7 +162,7 @@ class SettingsWidget extends StatelessWidget { iconPath: "assets/images/shared/terms_and_privacy.svg", title: "Terms and Privacy Policy", onChanged: (value) { - print("Terms and Privacy Policy setting: \$value"); + print("Terms and Privacy Policy tapped"); }, ), @@ -79,9 +177,7 @@ class SettingsWidget extends StatelessWidget { ), minimumSize: Size.fromHeight(screenHeight * 0.07), ), - onPressed: () { - print("Logout tapped"); - }, + onPressed: _showLogoutConfirmation, child: const Text( "Log out", style: TextStyle( @@ -96,9 +192,7 @@ class SettingsWidget extends StatelessWidget { Padding( padding: EdgeInsets.symmetric(horizontal: screenWidth * 0.3), child: InkWell( - onTap: () { - print("Delete Account tapped"); - }, + onTap: _showDeleteAccountDialog, child: Text( "Delete Account", style: TextStyle( @@ -121,9 +215,9 @@ class SettingsWidget extends StatelessWidget { height: screenHeight * 0.05, ), SizedBox(height: screenHeight * 0.01), - const Text( - "3.40.1(1)", - style: TextStyle( + Text( + _appVersion, + style: const TextStyle( color: Colors.grey, fontSize: 12, ), @@ -152,4 +246,4 @@ class SettingsWidget extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/mobile-v3/pubspec.lock b/mobile-v3/pubspec.lock index 1f13bd2d3b..8dd601d822 100644 --- a/mobile-v3/pubspec.lock +++ b/mobile-v3/pubspec.lock @@ -645,6 +645,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" + url: "https://pub.dev" + source: hosted + version: "8.1.3" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + url: "https://pub.dev" + source: hosted + version: "3.0.2" path: dependency: transitive description: @@ -978,6 +994,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + url: "https://pub.dev" + source: hosted + version: "5.10.0" xdg_directories: dependency: transitive description: diff --git a/mobile-v3/pubspec.yaml b/mobile-v3/pubspec.yaml index a8f89d228d..6fcb1b26a8 100644 --- a/mobile-v3/pubspec.yaml +++ b/mobile-v3/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: connectivity_plus: ^6.1.0 flutter_loggy: ^2.0.3+1 jwt_decoder: ^2.0.1 + package_info_plus: ^8.1.3 dev_dependencies: flutter_test: From 0af7a80c2a484cecc7e85e4456bf4208422596ae Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Thu, 23 Jan 2025 15:54:55 +0300 Subject: [PATCH 7/8] Update package_info dependency to package_info_plus for improved functionality --- .../lib/src/app/profile/pages/widgets/settings_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index 81c1cf1199..bdefe87cc9 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:airqo/src/app/profile/pages/widgets/settings_tile.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:package_info/package_info.dart'; +import 'package:package_info_plus/package_info_plus.dart'; class SettingsWidget extends StatefulWidget { const SettingsWidget({super.key}); @@ -246,4 +246,4 @@ class _SettingsWidgetState extends State { ), ); } -} \ No newline at end of file +} From 1f461ab96cadff3f83a4a56c2127a2ed336e7034 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Fri, 24 Jan 2025 11:40:23 +0300 Subject: [PATCH 8/8] Refactor ProfilePage layout for improved readability and responsiveness; update SettingsWidget state creation method --- .../src/app/profile/pages/profile_page.dart | 125 ++++++++++-------- .../pages/widgets/settings_widget.dart | 3 +- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/mobile-v3/lib/src/app/profile/pages/profile_page.dart b/mobile-v3/lib/src/app/profile/pages/profile_page.dart index 0b7e749377..d82cecc6d2 100644 --- a/mobile-v3/lib/src/app/profile/pages/profile_page.dart +++ b/mobile-v3/lib/src/app/profile/pages/profile_page.dart @@ -42,69 +42,78 @@ class _ProfilePageState extends State { children: [ SizedBox( height: 100, - child: Row( - children: [ - Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: CircleAvatar( - backgroundColor: Theme.of(context).highlightColor, - child: Center( - child: SvgPicture.asset( - "assets/icons/user_icon.svg"), - ), - radius: 50, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: LayoutBuilder( + builder: (context, constraints) { + return Row( children: [ - Text( - "${firstName} ${lastName}", - style: TextStyle( - color: AppColors.boldHeadlineColor, - fontSize: 24, - fontWeight: FontWeight.w700, + Container( + margin: + const EdgeInsets.symmetric(horizontal: 16), + child: CircleAvatar( + backgroundColor: + Theme.of(context).highlightColor, + child: Center( + child: SvgPicture.asset( + "assets/icons/user_icon.svg"), + ), + radius: 50, ), ), - Spacer(), - Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 32), - height: 50, - child: Center( - child: Text("Edit your profile")), - // child: InkWell( - // onTap: () => Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => - // EditProfile())), - // child: Text( - // "Edit your profile", - // style: TextStyle( - // fontWeight: FontWeight.w500, - // color: Colors.white, - // ), - // ), - //)), - decoration: BoxDecoration( - color: Theme.of(context).highlightColor, - borderRadius: - BorderRadius.circular(200)), - ), - SizedBox(width: 8), - CircleAvatar( - backgroundColor: - Theme.of(context).highlightColor, - radius: 26, - child: SvgPicture.asset( - "assets/icons/notification.svg")) - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${firstName} ${lastName}", + style: TextStyle( + color: AppColors.boldHeadlineColor, + fontSize: 24, + fontWeight: FontWeight.w700, + ), + ), + Spacer(), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 32), + height: 50, + child: Center( + child: Text("Edit your profile")), + // child: InkWell( + // onTap: () => Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => + // EditProfile())), + // child: Text( + // "Edit your profile", + // style: TextStyle( + // fontWeight: FontWeight.w500, + // color: Colors.white, + // ), + // ), + //)), + decoration: BoxDecoration( + color: Theme.of(context) + .highlightColor, + borderRadius: + BorderRadius.circular(200)), + ), + SizedBox(width: 8), + CircleAvatar( + backgroundColor: + Theme.of(context).highlightColor, + radius: 26, + child: SvgPicture.asset( + "assets/icons/notification.svg")) + ], + ) + ], + ), ) ], - ) - ], + ); + }, ), ), SizedBox(height: 32), diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index bdefe87cc9..1a02748f34 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -7,9 +7,8 @@ class SettingsWidget extends StatefulWidget { const SettingsWidget({super.key}); @override - _SettingsWidgetState createState() => _SettingsWidgetState(); + State createState() => _SettingsWidgetState(); } - class _SettingsWidgetState extends State { String _appVersion = ''; bool _locationEnabled = true;