MIH Auth Offline First Change

This commit is contained in:
yaso 2026-07-08 13:08:03 +02:00
parent a5bdee892c
commit 9d30face3d
13 changed files with 425 additions and 55 deletions

View file

@ -33,7 +33,9 @@ class _MihSignInState extends State<MihSignIn> {
//sign user in
Future<void> signUserIn() async {
context.read<MzansiProfileProvider>().reset();
MzansiProfileProvider profileProvider =
context.read<MzansiProfileProvider>();
profileProvider.reset();
try {
successfulSignIn = await MihAuthenticationServices().signUserIn(
emailController.text,
@ -43,6 +45,8 @@ class _MihSignInState extends State<MihSignIn> {
if (!successfulSignIn) {
MihAlertServices().loginErrorAlert(context);
passwordController.clear();
} else {
await profileProvider.syncWithMihServerData();
}
} on Exception {
Navigator.of(context).pop();

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mih_package_toolkit/mih_package_toolkit.dart';
import 'package:mzansi_innovation_hub/mih_package_components/mih_circle_avatar.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/components/mih_sign_out_warning.dart';
import 'package:mzansi_innovation_hub/mih_providers/about_mih_provider.dart';
import 'package:mzansi_innovation_hub/mih_providers/mih_access_controlls_provider.dart';
import 'package:mzansi_innovation_hub/mih_providers/mih_authentication_provider.dart';
@ -45,11 +46,37 @@ class _MIHAppDrawerState extends State<MIHAppDrawer> {
await context.read<MzansiProfileProvider>().clearProfileCacheAndProvider();
}
Future<bool> signOut() async {
await SuperTokens.signOut(completionHandler: (error) {
// handle error if any
});
return true;
Future<void> signOut() async {
if (!context.mounted) return;
final bool continueSignOut = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => const MihSignOutWarning(),
) ??
false;
if (!continueSignOut) {
if (context.mounted) {
context.pop();
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: const Text("Sign Out Cancelled"),
),
);
}
return; // Stop execution here; local data remains safe
} else {
await SuperTokens.signOut(completionHandler: (error) {
print(error);
});
if (await SuperTokens.doesSessionExist() == false) {
await clearCacheAndProviders();
if (context.mounted) {
context.goNamed(
'mihHome',
);
}
}
}
}
Widget displayProPic(MzansiProfileProvider mzansiProfileProvider) {
@ -260,19 +287,7 @@ class _MIHAppDrawerState extends State<MIHAppDrawer> {
],
),
onTap: () async {
await SuperTokens.signOut(
completionHandler: (error) {
print(error);
});
if (await SuperTokens.doesSessionExist() ==
false) {
await clearCacheAndProviders();
if (context.mounted) {
context.goNamed(
'mihHome',
);
}
}
signOut();
},
),
],

View file

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mih_package_toolkit/mih_package_toolkit.dart';
class MihSignOutWarning extends StatefulWidget {
const MihSignOutWarning({super.key});
@override
State<MihSignOutWarning> createState() => _MihSignOutWarningState();
}
class _MihSignOutWarningState extends State<MihSignOutWarning> {
@override
Widget build(BuildContext context) {
return MihPackageWindow(
fullscreen: false,
backgroundColor: MihColors.secondary(),
windowTitle: null,
onWindowTapClose: null,
windowBody: Column(
children: [
Icon(
Icons.warning_amber_rounded,
size: 150,
color: MihColors.primary(),
),
Center(
child: Text(
"Are You Sure?",
textAlign: TextAlign.center,
style: TextStyle(
color: MihColors.primary(),
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 15),
Text(
"Signing out clears your local app data. If any of your recent offline changes or updates haven't finished syncing to the MIH cloud, they will be permanently lost.\n\nPlease ensure you are connected to the internet and fully synced before signing out.",
style: TextStyle(
color: MihColors.primary(),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
Center(
child: Wrap(
spacing: 10.0,
runSpacing: 10.0,
alignment: WrapAlignment.center,
children: [
MihButton(
onPressed: () {
context.pop(false);
},
buttonColor: MihColors.green(),
width: 300,
child: Text(
"Dismiss",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 10),
MihButton(
onPressed: () {
context.pop(true);
},
buttonColor: MihColors.red(),
width: 300,
child: Text(
"Sign Out",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
);
}
}

View file

@ -0,0 +1,173 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mih_package_toolkit/mih_package_toolkit.dart';
import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_config/mih_env.dart';
import 'package:mzansi_innovation_hub/mih_providers/mzansi_profile_provider.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_authentication_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_validation_services.dart';
import 'package:provider/provider.dart';
class MihSoftLoginPopup extends StatefulWidget {
const MihSoftLoginPopup({super.key});
@override
State<MihSoftLoginPopup> createState() => _MihSoftLoginPopupState();
}
class _MihSoftLoginPopupState extends State<MihSoftLoginPopup> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
void autoFillTestUser() {
setState(() {
emailController.text = "test@mzansi-innovation-hub.co.za";
passwordController.text = "Testprofile@1234";
});
}
//sign user in
Future<void> signUserIn() async {
MzansiProfileProvider profileProvider =
context.read<MzansiProfileProvider>();
try {
bool successfulSignIn = await MihAuthenticationServices().signUserIn(
emailController.text,
passwordController.text,
context,
);
if (!successfulSignIn) {
MihAlertServices().loginErrorAlert(context);
passwordController.clear();
} else {
await profileProvider.syncWithMihServerData();
context.pop(true);
}
} on Exception {
context.pop();
MihAlertServices().internetConnectionAlert(context);
passwordController.clear();
}
}
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return MihPackageWindow(
fullscreen: false,
windowTitle: null,
onWindowTapClose: () {
context.pop();
},
windowBody: MihSingleChildScroll(
scrollbarOn: false,
child: Padding(
padding:
MzansiInnovationHub.of(context)!.theme.screenType == "desktop"
? EdgeInsets.symmetric(horizontal: width * 0.05)
: EdgeInsets.symmetric(horizontal: width * 0),
child: Column(
children: [
Icon(
Icons.lock,
size: 100,
color: MihColors.secondary(),
),
const SizedBox(height: 10),
Text(
'Sign In',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: MihColors.secondary(),
),
),
const SizedBox(height: 10),
MihForm(
formKey: _formKey,
formFields: [
MihTextFormField(
fillColor: MihColors.secondary(),
inputColor: MihColors.primary(),
controller: emailController,
multiLineInput: false,
requiredText: true,
hintText: "Email",
autofillHints: const [AutofillHints.email],
validator: (value) {
return MihValidationServices().validateEmail(value);
},
),
const SizedBox(height: 10),
MihTextFormField(
fillColor: MihColors.secondary(),
inputColor: MihColors.primary(),
controller: passwordController,
multiLineInput: false,
requiredText: true,
hintText: "Password",
passwordMode: true,
autofillHints: const [AutofillHints.password],
validator: (value) {
return MihValidationServices().validatePassword(value);
},
),
const SizedBox(height: 20),
Center(
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
spacing: 10,
runSpacing: 10,
children: [
MihButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
signUserIn();
} else {
MihAlertServices().inputErrorAlert(context);
}
},
buttonColor: MihColors.green(),
width: 300,
child: Text(
"Sign In",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 10),
if (AppEnviroment.getEnv() == "Dev")
MihButton(
onPressed: () {
autoFillTestUser();
},
buttonColor: MihColors.yellow(),
width: 300,
child: Text(
"Autofill",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const SizedBox(height: 20),
],
),
],
),
),
),
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:mih_package_toolkit/mih_package_toolkit.dart';
import 'package:mzansi_innovation_hub/mih_objects/user_consent.dart';
import 'package:mzansi_innovation_hub/mih_package_components/mih_circle_avatar.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/components/mih_soft_login_popup.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/components/mih_user_consent_window.dart';
import 'package:mzansi_innovation_hub/mih_providers/about_mih_provider.dart';
import 'package:mzansi_innovation_hub/mih_providers/mzansi_profile_provider.dart';
@ -10,6 +11,7 @@ import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_pe
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_providers/mzansi_wallet_provider.dart';
import 'package:provider/provider.dart';
import 'package:supertokens_flutter/supertokens.dart';
class MihHome extends StatefulWidget {
const MihHome({
@ -24,6 +26,46 @@ class _MihHomeState extends State<MihHome> {
DateTime latestPrivacyPolicyDate = DateTime.parse("2024-12-01");
DateTime latestTermOfServiceDate = DateTime.parse("2024-12-01");
Future<void> globalMihDataSync(
MzansiProfileProvider mzansiProfileProvider) async {
MzansiWalletProvider walletProvider = context.read<MzansiWalletProvider>();
AboutMihProvider aboutProvider = context.read<AboutMihProvider>();
final bool isUserSignedIn = await SuperTokens.doesSessionExist();
if (!isUserSignedIn) {
if (!context.mounted) return;
final bool didReauthenticate = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => const MihSoftLoginPopup(),
) ??
false;
if (!didReauthenticate) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: const Text("Sync paused: Please log in to sync changes."),
),
);
}
return; // Stop execution here; local data remains safe
}
}
await mzansiProfileProvider.syncWithMihServerData();
await walletProvider.syncWithMihServerData(mzansiProfileProvider);
bool success = await aboutProvider.syncWithMihServerData();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text(
success
? "Data Synced with MIH Cloud."
: "MIH App operation in Offline Mode",
),
),
);
}
}
bool showPolicyWindow(UserConsent? userConsent) {
if (userConsent == null) {
return true;
@ -46,8 +88,8 @@ class _MihHomeState extends State<MihHome> {
if (mzansiProfileProvider.user == null) {
await mzansiProfileProvider.syncWithMihServerData();
}
if(mzansiProfileProvider.isLocalModificationsPending()){
mzansiProfileProvider.syncWithMihServerData();
if (mzansiProfileProvider.isLocalModificationsPending()) {
mzansiProfileProvider.syncWithMihServerData();
}
}
@ -91,26 +133,7 @@ class _MihHomeState extends State<MihHome> {
RefreshIndicator(
key: mzansiProfileProvider.refreshIndicatorKey,
onRefresh: () async {
MzansiWalletProvider walletProvider =
context.read<MzansiWalletProvider>();
AboutMihProvider aboutProvider =
context.read<AboutMihProvider>();
await mzansiProfileProvider.syncWithMihServerData();
await walletProvider
.syncWithMihServerData(mzansiProfileProvider);
bool success = await aboutProvider.syncWithMihServerData();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text(
success
? "Data Synced with MIH Server."
: "MIH App operation in Offline Mode",
),
// backgroundColor: success ? null : MihColors.red(),
),
);
}
await globalMihDataSync(mzansiProfileProvider);
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),

View file

@ -185,7 +185,7 @@ class _MihCardsState extends State<MihCards> {
MihSnackBar(
child: Text(
success
? "Wallet Synced with MIH Server."
? "Wallet Synced with MIH Cloud."
: "MIH App operation in Offline Mode",
),
// backgroundColor: success ? null : MihColors.red(),