initial offline mode, hive setup, home display, mzansi rofile display completed

This commit is contained in:
yaso 2026-06-24 11:52:53 +02:00
parent 3d5fdde322
commit f19a316be8
20 changed files with 981 additions and 304 deletions

View file

@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:ken_logger/ken_logger.dart';
import 'package:mih_package_toolkit/mih_package_toolkit.dart';
import 'package:mzansi_innovation_hub/mih_hive/mzansi_profile_hive_data.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';
@ -121,7 +123,7 @@ class _MzansiInnovationHubState extends State<MzansiInnovationHub> {
create: (context) => MihAuthenticationProvider(),
),
ChangeNotifierProvider(
create: (context) => MzansiProfileProvider(),
create: (context) => MzansiProfileProvider(MzansiProfileHiveData()),
),
ChangeNotifierProvider(
create: (context) => MzansiWalletProvider(),

View file

@ -9,9 +9,16 @@ import 'package:go_router/go_router.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_config/mih_go_router.dart';
import 'package:mzansi_innovation_hub/mih_hive/hive_registrar.g.dart';
import 'package:mzansi_innovation_hub/mih_objects/app_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_objects/business_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/profile_link.dart';
import 'package:mzansi_innovation_hub/mih_objects/user_consent.dart';
import 'package:pwa_install/pwa_install.dart';
import 'mih_config/mih_env.dart';
import 'package:supertokens_flutter/supertokens.dart';
import 'package:hive_ce_flutter/hive_ce_flutter.dart';
void main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
@ -21,6 +28,15 @@ void main() async {
apiDomain: AppEnviroment.baseApiUrl,
apiBasePath: "/auth",
);
await Hive.initFlutter('mih_offline_storage');
Hive.registerAdapters();
await Hive.openBox<AppUser>('user_box');
await Hive.openBox<Business>('business_box');
await Hive.openBox<BusinessUser>('business_user_box');
await Hive.openBox<UserConsent>('user_consent_box');
await Hive.openBox<String>('image_urls_box');
await Hive.openBox<ProfileLink>('personal_profile_links_box');
// await Firebase.initializeApp(
// // options: DefaultFirebaseOptions.currentPlatform,
// options: (Platform.isLinux)

View file

@ -0,0 +1,16 @@
// hive_adapters.dart
import 'package:hive_ce/hive_ce.dart';
import 'package:mzansi_innovation_hub/mih_objects/app_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_objects/business_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/profile_link.dart';
import 'package:mzansi_innovation_hub/mih_objects/user_consent.dart';
@GenerateAdapters([
AdapterSpec<AppUser>(),
AdapterSpec<Business>(),
AdapterSpec<BusinessUser>(),
AdapterSpec<UserConsent>(),
AdapterSpec<ProfileLink>(),
])
part 'hive_adapters.g.dart';

View file

@ -0,0 +1,285 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'hive_adapters.dart';
// **************************************************************************
// AdaptersGenerator
// **************************************************************************
class AppUserAdapter extends TypeAdapter<AppUser> {
@override
final typeId = 0;
@override
AppUser read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return AppUser(
(fields[0] as num).toInt(),
fields[1] as String,
fields[2] as String,
fields[3] as String,
fields[4] as String,
fields[5] as String,
fields[6] as String,
fields[7] as String,
fields[8] as String,
);
}
@override
void write(BinaryWriter writer, AppUser obj) {
writer
..writeByte(9)
..writeByte(0)
..write(obj.idUser)
..writeByte(1)
..write(obj.email)
..writeByte(2)
..write(obj.fname)
..writeByte(3)
..write(obj.lname)
..writeByte(4)
..write(obj.type)
..writeByte(5)
..write(obj.app_id)
..writeByte(6)
..write(obj.username)
..writeByte(7)
..write(obj.pro_pic_path)
..writeByte(8)
..write(obj.purpose);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppUserAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class BusinessAdapter extends TypeAdapter<Business> {
@override
final typeId = 1;
@override
Business read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Business(
fields[0] as String,
fields[1] as String,
fields[2] as String,
fields[3] as String,
fields[4] as String,
fields[5] as String,
fields[6] as String,
fields[7] as String,
fields[8] as String,
fields[9] as String,
fields[10] as String,
fields[11] as String,
fields[12] as String,
fields[13] as String,
fields[14] as String,
);
}
@override
void write(BinaryWriter writer, Business obj) {
writer
..writeByte(15)
..writeByte(0)
..write(obj.business_id)
..writeByte(1)
..write(obj.Name)
..writeByte(2)
..write(obj.type)
..writeByte(3)
..write(obj.registration_no)
..writeByte(4)
..write(obj.logo_name)
..writeByte(5)
..write(obj.logo_path)
..writeByte(6)
..write(obj.contact_no)
..writeByte(7)
..write(obj.bus_email)
..writeByte(8)
..write(obj.app_id)
..writeByte(9)
..write(obj.gps_location)
..writeByte(10)
..write(obj.practice_no)
..writeByte(11)
..write(obj.vat_no)
..writeByte(12)
..write(obj.website)
..writeByte(13)
..write(obj.rating)
..writeByte(14)
..write(obj.mission_vision);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BusinessAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class BusinessUserAdapter extends TypeAdapter<BusinessUser> {
@override
final typeId = 2;
@override
BusinessUser read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return BusinessUser(
(fields[0] as num).toInt(),
fields[1] as String,
fields[2] as String,
fields[3] as String,
fields[4] as String,
fields[5] as String,
fields[6] as String,
);
}
@override
void write(BinaryWriter writer, BusinessUser obj) {
writer
..writeByte(7)
..writeByte(0)
..write(obj.idbusiness_users)
..writeByte(1)
..write(obj.business_id)
..writeByte(2)
..write(obj.app_id)
..writeByte(3)
..write(obj.signature)
..writeByte(4)
..write(obj.sig_path)
..writeByte(5)
..write(obj.title)
..writeByte(6)
..write(obj.access);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BusinessUserAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class UserConsentAdapter extends TypeAdapter<UserConsent> {
@override
final typeId = 3;
@override
UserConsent read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserConsent(
app_id: fields[0] as String,
privacy_policy_accepted: fields[1] as DateTime,
terms_of_services_accepted: fields[2] as DateTime,
);
}
@override
void write(BinaryWriter writer, UserConsent obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.app_id)
..writeByte(1)
..write(obj.privacy_policy_accepted)
..writeByte(2)
..write(obj.terms_of_services_accepted);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserConsentAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class ProfileLinkAdapter extends TypeAdapter<ProfileLink> {
@override
final typeId = 4;
@override
ProfileLink read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ProfileLink(
idprofile_links: (fields[0] as num).toInt(),
app_id: fields[1] as String,
business_id: fields[2] as String,
site_name: fields[3] as String,
custom_name: fields[4] as String,
destination: fields[5] as String,
order: (fields[6] as num).toInt(),
);
}
@override
void write(BinaryWriter writer, ProfileLink obj) {
writer
..writeByte(7)
..writeByte(0)
..write(obj.idprofile_links)
..writeByte(1)
..write(obj.app_id)
..writeByte(2)
..write(obj.business_id)
..writeByte(3)
..write(obj.site_name)
..writeByte(4)
..write(obj.custom_name)
..writeByte(5)
..write(obj.destination)
..writeByte(6)
..write(obj.order);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ProfileLinkAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View file

@ -0,0 +1,107 @@
# Generated by Hive CE
# Manual modifications may be necessary for certain migrations
# Check in to version control
nextTypeId: 5
types:
AppUser:
typeId: 0
nextIndex: 9
fields:
idUser:
index: 0
email:
index: 1
fname:
index: 2
lname:
index: 3
type:
index: 4
app_id:
index: 5
username:
index: 6
pro_pic_path:
index: 7
purpose:
index: 8
Business:
typeId: 1
nextIndex: 15
fields:
business_id:
index: 0
Name:
index: 1
type:
index: 2
registration_no:
index: 3
logo_name:
index: 4
logo_path:
index: 5
contact_no:
index: 6
bus_email:
index: 7
app_id:
index: 8
gps_location:
index: 9
practice_no:
index: 10
vat_no:
index: 11
website:
index: 12
rating:
index: 13
mission_vision:
index: 14
BusinessUser:
typeId: 2
nextIndex: 7
fields:
idbusiness_users:
index: 0
business_id:
index: 1
app_id:
index: 2
signature:
index: 3
sig_path:
index: 4
title:
index: 5
access:
index: 6
UserConsent:
typeId: 3
nextIndex: 3
fields:
app_id:
index: 0
privacy_policy_accepted:
index: 1
terms_of_services_accepted:
index: 2
ProfileLink:
typeId: 4
nextIndex: 7
fields:
idprofile_links:
index: 0
app_id:
index: 1
business_id:
index: 2
site_name:
index: 3
custom_name:
index: 4
destination:
index: 5
order:
index: 6

View file

@ -0,0 +1,26 @@
// Generated by Hive CE
// Do not modify
// Check in to version control
import 'package:hive_ce/hive.dart';
import 'package:mzansi_innovation_hub/mih_hive/hive_adapters.dart';
extension HiveRegistrar on HiveInterface {
void registerAdapters() {
registerAdapter(AppUserAdapter());
registerAdapter(BusinessAdapter());
registerAdapter(BusinessUserAdapter());
registerAdapter(ProfileLinkAdapter());
registerAdapter(UserConsentAdapter());
}
}
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
void registerAdapters() {
registerAdapter(AppUserAdapter());
registerAdapter(BusinessAdapter());
registerAdapter(BusinessUserAdapter());
registerAdapter(ProfileLinkAdapter());
registerAdapter(UserConsentAdapter());
}
}

View file

@ -0,0 +1,116 @@
import 'package:hive_ce_flutter/hive_ce_flutter.dart';
import 'package:ken_logger/ken_logger.dart';
import 'package:mzansi_innovation_hub/mih_objects/app_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_objects/business_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/profile_link.dart';
import 'package:mzansi_innovation_hub/mih_objects/user_consent.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_file_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_my_business_user_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_profile_links_service.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_user_consent_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_user_services.dart';
class MzansiProfileHiveData {
final Box<AppUser> _userBox = Hive.box<AppUser>('user_box');
final Box<Business> _businessBox = Hive.box<Business>('business_box');
final Box<BusinessUser> _businessUserBox =
Hive.box<BusinessUser>('business_user_box');
final Box<UserConsent> _userConsentBox =
Hive.box<UserConsent>('user_consent_box');
final Box<ProfileLink> _personalProfileLinksBox =
Hive.box<ProfileLink>('personal_profile_links_box');
final Box<String> _resolvedUrlsBox = Hive.box<String>('image_urls_box');
static const String kUserKey = 'current_user';
static const String kBusinessKey = 'current_business';
static const String kBusinessUserKey = 'current_business_user';
static const String kConsentKey = 'current_consent';
static const String kUserPicUrlKey = 'user_pic_url';
static const String kBusinessPicUrlKey = 'business_pic_url';
static const String kSignatureUrlKey = 'signature_url';
AppUser? getCachedUser() => _userBox.get(kUserKey);
Business? getCachedBusiness() => _businessBox.get(kBusinessKey);
BusinessUser? getCachedBusinessUser() =>
_businessUserBox.get(kBusinessUserKey);
UserConsent? getCachedConsent() => _userConsentBox.get(kConsentKey);
String? getCachedUserPicUrl() => _resolvedUrlsBox.get(kUserPicUrlKey);
String? getCachedBusinessPicUrl() => _resolvedUrlsBox.get(kBusinessPicUrlKey);
String? getCachedSignatureUrl() => _resolvedUrlsBox.get(kSignatureUrlKey);
List<ProfileLink> getCachedPersonalProfileLinks() {
final links = _personalProfileLinksBox.values.toList();
links.sort((a, b) => a.order.compareTo(b.order));
return links;
}
Future<void> cacheUserData(AppUser remoteUser) async {
await _userBox.put(kUserKey, remoteUser);
if (remoteUser.pro_pic_path.isNotEmpty) {
String userPicUrl =
await MihFileApi.getMinioFileUrl(remoteUser.pro_pic_path);
await _resolvedUrlsBox.put(kUserPicUrlKey, userPicUrl);
}
}
Future<void> cacheUserConsentData(UserConsent remoteConsent) async {
await _userConsentBox.put(kConsentKey, remoteConsent);
}
Future<void> cacheBusinessData(Business remoteBusiness) async {
await _businessBox.put(kBusinessKey, remoteBusiness);
if (remoteBusiness.logo_path.isNotEmpty) {
String logoUrl =
await MihFileApi.getMinioFileUrl(remoteBusiness.logo_path);
await _resolvedUrlsBox.put(kBusinessPicUrlKey, logoUrl);
}
BusinessUser? remoteBizUser =
await MihMyBusinessUserServices().getBusinessUserV2();
if (remoteBizUser != null) {
cacheBusinessUserData(remoteBizUser);
}
}
Future<void> cacheBusinessUserData(BusinessUser remoteBizUser) async {
await _businessUserBox.put(kBusinessUserKey, remoteBizUser);
if (remoteBizUser.sig_path.isNotEmpty) {
String signatureUrl =
await MihFileApi.getMinioFileUrl(remoteBizUser.sig_path);
await _resolvedUrlsBox.put(kSignatureUrlKey, signatureUrl);
}
}
Future<void> cachePersonalProfileLinksData(
List<ProfileLink> remoteLinks) async {
await _personalProfileLinksBox.clear();
await _personalProfileLinksBox.addAll(remoteLinks);
}
Future<void> syncProfileDataWithServer() async {
try {
AppUser? remoteUser = await MihUserServices().getMyUserDetailsV2();
if (remoteUser != null) {
cacheUserData(remoteUser);
UserConsent? remoteConsent =
await MihUserConsentServices().getUserConsentStatusV2();
if (remoteConsent != null) {
cacheUserConsentData(remoteConsent);
}
final remoteLinks = await MihProfileLinksServices.getUserProfileLinksV2(
remoteUser.app_id);
await cachePersonalProfileLinksData(remoteLinks);
}
Business? remoteBusiness =
await MihBusinessDetailsServices().getBusinessDetailsByUserV2();
if (remoteBusiness != null) {
cacheBusinessData(remoteBusiness);
}
} catch (error) {
KenLogger.warning("App operating offline mode. Sync paused");
// KenLogger.warning("App operating offline mode. Sync paused: $error");
}
}
}

View file

@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class MIHYTVideoPlayer extends StatefulWidget {
final String videoYTLink;
const MIHYTVideoPlayer({
super.key,
required this.videoYTLink,
});
@override
State<MIHYTVideoPlayer> createState() => _MIHYTVideoPlayerState();
}
class _MIHYTVideoPlayerState extends State<MIHYTVideoPlayer> {
late YoutubePlayerController _controller;
@override
void dispose() {
_controller.close();
super.dispose();
}
@override
void initState() {
_controller = YoutubePlayerController(
params: const YoutubePlayerParams(
enableCaption: false,
showControls: true,
mute: false,
showFullscreenButton: false,
loop: false,
),
);
_controller.loadVideoById(videoId: widget.videoYTLink);
super.initState();
}
@override
Widget build(BuildContext context) {
return YoutubePlayer(
controller: _controller,
aspectRatio: 16 / 9,
);
}
}

View file

@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:ken_logger/ken_logger.dart';
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_providers/about_mih_provider.dart';
import 'package:mzansi_innovation_hub/mih_providers/mzansi_profile_provider.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_user_consent_services.dart';
import 'package:provider/provider.dart';
class MihUserConsentWindow extends StatefulWidget {
const MihUserConsentWindow({super.key});
@override
State<MihUserConsentWindow> createState() => _MihUserConsentWindowState();
}
class _MihUserConsentWindowState extends State<MihUserConsentWindow> {
void createOrUpdateAccpetance(MzansiProfileProvider mzansiProfileProvider) {
UserConsent? userConsent = mzansiProfileProvider.userConsent;
userConsent != null
? MihUserConsentServices()
.updateUserConsentStatus(
DateTime.now().toIso8601String(),
DateTime.now().toIso8601String(),
mzansiProfileProvider,
context,
)
.then((value) {
if (!mounted) return;
if (value == 200) {
context.goNamed("mihHome");
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("Thank you for accepting our Policies"),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("There was an error, please try again later"),
),
);
}
})
: MihUserConsentServices()
.insertUserConsentStatus(
DateTime.now().toIso8601String(),
DateTime.now().toIso8601String(),
mzansiProfileProvider,
context,
)
.then((value) {
if (value == 201) {
context.goNamed("mihHome");
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("Thank you for accepting our Policies"),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("There was an error, please try again later"),
),
);
}
});
}
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context,
MzansiProfileProvider mzansiProfileProvider, Widget? child) {
return Container(
color: Colors.black.withValues(alpha: 0.5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
MihPackageWindow(
fullscreen: false,
windowTitle: null,
onWindowTapClose: null,
windowBody: Column(
children: [
Icon(
Icons.policy,
size: 150,
color: MihColors.secondary(),
),
const SizedBox(height: 10),
Text(
"Welcome to the MIH App",
textAlign: TextAlign.center,
style: TextStyle(
color: MihColors.secondary(),
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
"To keep using the MIH app, please take a moment to review and accept our Policies. Our agreements helps us keep things running smoothly and securely.",
textAlign: TextAlign.center,
style: TextStyle(
color: MihColors.secondary(),
fontSize: 18,
fontWeight: FontWeight.normal,
),
),
const SizedBox(height: 20),
Center(
child: Wrap(
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.center,
spacing: 10,
runSpacing: 10,
children: [
MihButton(
onPressed: () {
WidgetsBinding.instance
.addPostFrameCallback((_) async {
context
.read<AboutMihProvider>()
.setToolIndex(1);
});
context.goNamed("aboutMih",
extra: mzansiProfileProvider.personalHome);
},
buttonColor: MihColors.orange(),
elevation: 10,
width: 300,
child: Text(
"Privacy Policy",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
onPressed: () {
WidgetsBinding.instance
.addPostFrameCallback((_) async {
context
.read<AboutMihProvider>()
.setToolIndex(2);
});
context.goNamed("aboutMih",
extra: mzansiProfileProvider.personalHome);
},
buttonColor: MihColors.yellow(),
elevation: 10,
width: 300,
child: Text(
"Terms of Service",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
onPressed: () {
DateTime now = DateTime.now();
KenLogger.success("Date Time Now: $now");
createOrUpdateAccpetance(mzansiProfileProvider);
},
buttonColor: MihColors.green(),
elevation: 10,
width: 300,
child: Text(
"Accept",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const SizedBox(height: 10),
],
),
),
],
),
);
},
);
}
}

View file

@ -1,16 +1,12 @@
import 'package:go_router/go_router.dart';
import 'package:ken_logger/ken_logger.dart';
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_providers/about_mih_provider.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/components/mih_user_consent_window.dart';
import 'package:mzansi_innovation_hub/mih_providers/mzansi_profile_provider.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/components/mih_app_drawer.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_business_home.dart';
import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_personal_home.dart';
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_data_helper_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_user_consent_services.dart';
import 'package:provider/provider.dart';
class MihHome extends StatefulWidget {
@ -25,32 +21,6 @@ class MihHome extends StatefulWidget {
class _MihHomeState extends State<MihHome> {
DateTime latestPrivacyPolicyDate = DateTime.parse("2024-12-01");
DateTime latestTermOfServiceDate = DateTime.parse("2024-12-01");
bool _isLoadingInitialData = true;
late final MihPersonalHome _personalHome;
late final MihBusinessHome? _businessHome;
Future<void> _loadInitialData() async {
setState(() {
_isLoadingInitialData = true;
});
MzansiProfileProvider mzansiProfileProvider =
context.read<MzansiProfileProvider>();
if (mzansiProfileProvider.user == null) {
await MihDataHelperServices().loadUserDataWithBusinessesData(
mzansiProfileProvider,
);
}
_personalHome = const MihPersonalHome();
_businessHome = mzansiProfileProvider.business != null
? MihBusinessHome(isLoading: _isLoadingInitialData)
: null;
if (mounted) {
setState(() {
_isLoadingInitialData = false;
});
}
}
bool showPolicyWindow(UserConsent? userConsent) {
if (userConsent == null) {
@ -67,175 +37,13 @@ class _MihHomeState extends State<MihHome> {
}
}
void createOrUpdateAccpetance(MzansiProfileProvider mzansiProfileProvider) {
UserConsent? userConsent = mzansiProfileProvider.userConsent;
userConsent != null
? MihUserConsentServices()
.updateUserConsentStatus(
DateTime.now().toIso8601String(),
DateTime.now().toIso8601String(),
mzansiProfileProvider,
context,
)
.then((value) {
if (!mounted) return;
if (value == 200) {
context.goNamed("mihHome");
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("Thank you for accepting our Policies"),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("There was an error, please try again later"),
),
);
Future<void> _syncProfileData() async {
MzansiProfileProvider mzansiProfileProvider =
context.read<MzansiProfileProvider>();
mzansiProfileProvider.loadCachedProfileState();
if (mzansiProfileProvider.user == null) {
await mzansiProfileProvider.syncWithCloudPipeline();
}
})
: MihUserConsentServices()
.insertUserConsentStatus(
DateTime.now().toIso8601String(),
DateTime.now().toIso8601String(),
mzansiProfileProvider,
context,
)
.then((value) {
if (value == 201) {
context.goNamed("mihHome");
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("Thank you for accepting our Policies"),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
MihSnackBar(
child: Text("There was an error, please try again later"),
),
);
}
});
}
Widget displayConsentWindow(MzansiProfileProvider mzansiProfileProvider) {
return Container(
color: Colors.black.withValues(alpha: 0.5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
MihPackageWindow(
fullscreen: false,
windowTitle: null,
onWindowTapClose: null,
windowBody: Column(
children: [
Icon(
Icons.policy,
size: 150,
color: MihColors.secondary(),
),
const SizedBox(height: 10),
Text(
"Welcome to the MIH App",
textAlign: TextAlign.center,
style: TextStyle(
color: MihColors.secondary(),
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
"To keep using the MIH app, please take a moment to review and accept our Policies. Our agreements helps us keep things running smoothly and securely.",
textAlign: TextAlign.center,
style: TextStyle(
color: MihColors.secondary(),
fontSize: 18,
fontWeight: FontWeight.normal,
),
),
const SizedBox(height: 20),
Center(
child: Wrap(
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.center,
spacing: 10,
runSpacing: 10,
children: [
MihButton(
onPressed: () {
WidgetsBinding.instance
.addPostFrameCallback((_) async {
context.read<AboutMihProvider>().setToolIndex(1);
});
context.goNamed("aboutMih",
extra: mzansiProfileProvider.personalHome);
},
buttonColor: MihColors.orange(),
elevation: 10,
width: 300,
child: Text(
"Privacy Policy",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
onPressed: () {
WidgetsBinding.instance
.addPostFrameCallback((_) async {
context.read<AboutMihProvider>().setToolIndex(2);
});
context.goNamed("aboutMih",
extra: mzansiProfileProvider.personalHome);
},
buttonColor: MihColors.yellow(),
elevation: 10,
width: 300,
child: Text(
"Terms of Service",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
MihButton(
onPressed: () {
DateTime now = DateTime.now();
KenLogger.success("Date Time Now: $now");
createOrUpdateAccpetance(mzansiProfileProvider);
},
buttonColor: MihColors.green(),
elevation: 10,
width: 300,
child: Text(
"Accept",
style: TextStyle(
color: MihColors.primary(),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
const SizedBox(height: 10),
],
),
),
],
),
);
}
@override
@ -246,7 +54,11 @@ class _MihHomeState extends State<MihHome> {
@override
void initState() {
super.initState();
_loadInitialData();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_syncProfileData();
}
});
}
List<String> getToolTitle() {
@ -262,20 +74,18 @@ class _MihHomeState extends State<MihHome> {
return Consumer<MzansiProfileProvider>(
builder: (BuildContext context,
MzansiProfileProvider mzansiProfileProvider, Widget? child) {
if (_isLoadingInitialData) {
if (mzansiProfileProvider.user == null) {
return Scaffold(
body: Center(
child: Mihloadingcircle(),
),
);
}
// bool showConsentWindow =
// showPolicyWindow(mzansiProfileProvider.userConsent);
return Stack(
children: [
RefreshIndicator(
onRefresh: () async {
await _loadInitialData();
await mzansiProfileProvider.syncWithCloudPipeline();
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
@ -298,7 +108,7 @@ class _MihHomeState extends State<MihHome> {
),
),
if (showPolicyWindow(mzansiProfileProvider.userConsent))
displayConsentWindow(mzansiProfileProvider),
MihUserConsentWindow(),
],
);
},
@ -373,15 +183,10 @@ class _MihHomeState extends State<MihHome> {
}
List<Widget> getToolBody(MzansiProfileProvider mzansiProfileProvider) {
if (mzansiProfileProvider.business == null) {
return [
_personalHome,
const MihPersonalHome(),
if (mzansiProfileProvider.business != null)
const MihBusinessHome(isLoading: false)
];
} else {
return [
_personalHome,
_businessHome!,
];
}
}
}

View file

@ -204,7 +204,7 @@ class _MihPersonalHomeState extends State<MihPersonalHome>
searchController.addListener(searchPackage);
MzansiProfileProvider profileProvider =
context.read<MzansiProfileProvider>();
if (profileProvider.user!.username == "") {
if (profileProvider.user == null || profileProvider.user?.username == "") {
personalPackagesMap = setNerUserPersonalPackage();
autoNavToProfile();
} else {
@ -233,7 +233,8 @@ class _MihPersonalHomeState extends State<MihPersonalHome>
return Column(
children: [
Visibility(
visible: profileProvider.user!.username != "",
visible: profileProvider.user != null &&
profileProvider.user?.username != "",
child: Padding(
padding: EdgeInsets.symmetric(horizontal: width / 20),
child: MihSearchBar(

View file

@ -5,8 +5,6 @@ import 'package:mzansi_innovation_hub/mih_providers/mzansi_profile_provider.dart
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/personal_profile/package_tools/mih_personal_profile.dart';
import 'package:mzansi_innovation_hub/mih_packages/mzansi_profile/personal_profile/package_tools/mih_personal_settings.dart';
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_data_helper_services.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_profile_links_service.dart';
import 'package:provider/provider.dart';
class MzansiProfile extends StatefulWidget {
@ -25,20 +23,12 @@ class _MzansiProfileState extends State<MzansiProfile> {
late final MihPersonalSettings _personalSettings;
Future<void> _loadInitialData() async {
setState(() {
_isLoadingInitialData = true;
});
MzansiProfileProvider mzansiProfileProvider =
context.read<MzansiProfileProvider>();
mzansiProfileProvider.loadCachedProfileState();
if (mzansiProfileProvider.user == null) {
await MihDataHelperServices().loadUserDataWithBusinessesData(
mzansiProfileProvider,
);
mzansiProfileProvider.syncWithCloudPipeline();
}
await MihProfileLinksServices.getUserProfileLinks(
mzansiProfileProvider,
mzansiProfileProvider.user!.app_id,
);
setState(() {
_isLoadingInitialData = false;
});
@ -50,7 +40,9 @@ class _MzansiProfileState extends State<MzansiProfile> {
_personalProfile = const MihPersonalProfile();
_personalQrCode = const MihPersonalQrCode(user: null);
_personalSettings = const MihPersonalSettings();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadInitialData();
});
}
@override

View file

@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:mzansi_innovation_hub/mih_hive/mzansi_profile_hive_data.dart';
import 'package:mzansi_innovation_hub/mih_objects/app_user.dart';
import 'package:mzansi_innovation_hub/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_objects/business_employee.dart';
@ -8,6 +9,8 @@ import 'package:mzansi_innovation_hub/mih_objects/profile_link.dart';
import 'package:mzansi_innovation_hub/mih_objects/user_consent.dart';
class MzansiProfileProvider extends ChangeNotifier {
final MzansiProfileHiveData _hiveData;
bool personalHome;
int personalIndex;
int businessIndex;
@ -27,13 +30,45 @@ class MzansiProfileProvider extends ChangeNotifier {
List<ProfileLink> personalLinks = [];
List<ProfileLink> businessLinks = [];
MzansiProfileProvider({
MzansiProfileProvider(
this._hiveData, {
this.personalHome = true,
this.personalIndex = 0,
this.businessIndex = 0,
this.hideBusinessUserDetails = true,
});
void loadCachedProfileState() {
user = _hiveData.getCachedUser();
userConsent = _hiveData.getCachedConsent();
business = _hiveData.getCachedBusiness();
businessUser = _hiveData.getCachedBusinessUser();
String? cachedUserUrl = _hiveData.getCachedUserPicUrl();
if (cachedUserUrl != null && cachedUserUrl.isNotEmpty) {
userProfilePicUrl = cachedUserUrl;
userProfilePicture = CachedNetworkImageProvider(cachedUserUrl);
}
String? cachedBizUrl = _hiveData.getCachedBusinessPicUrl();
if (cachedBizUrl != null && cachedBizUrl.isNotEmpty) {
businessProfilePicUrl = cachedBizUrl;
businessProfilePicture = CachedNetworkImageProvider(cachedBizUrl);
}
String? cachedSigUrl = _hiveData.getCachedSignatureUrl();
if (cachedSigUrl != null && cachedSigUrl.isNotEmpty) {
businessUserSignatureUrl = cachedSigUrl;
businessUserSignature = CachedNetworkImageProvider(cachedSigUrl);
}
personalLinks = _hiveData.getCachedPersonalProfileLinks();
notifyListeners();
}
Future<void> syncWithCloudPipeline() async {
await _hiveData.syncProfileDataWithServer();
loadCachedProfileState();
}
void reset() {
personalHome = true;
personalIndex = 0;

View file

@ -98,6 +98,24 @@ class MihBusinessDetailsServices {
}
}
Future<Business?> getBusinessDetailsByUserV2() async {
String app_id = await SuperTokens.getUserId();
var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/business/app_id/$app_id"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
);
if (response.statusCode == 200) {
String body = response.body;
var jsonBody = jsonDecode(body);
Business? business = Business.fromJson(jsonBody);
return business;
} else {
return null;
}
}
Future<Business?> getBusinessDetailsByBusinessId(
String business_id,
) async {

View file

@ -32,6 +32,24 @@ class MihMyBusinessUserServices {
}
}
Future<BusinessUser?> getBusinessUserV2() async {
String app_id = await SuperTokens.getUserId();
var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/business-user/$app_id"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
);
if (response.statusCode == 200) {
// KenLogger.success(response.body);
BusinessUser? businessUser =
BusinessUser.fromJson(jsonDecode(response.body));
return businessUser;
} else {
return null;
}
}
Future<int> createBusinessUser(
String business_id,
String app_id,

View file

@ -43,6 +43,21 @@ class MihProfileLinksServices {
}
}
static Future<List<ProfileLink>> getUserProfileLinksV2(
String app_id,
) async {
final response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/profile-links/user/$app_id"));
if (response.statusCode == 200) {
Iterable l = jsonDecode(response.body);
List<ProfileLink> myLinks =
List<ProfileLink>.from(l.map((model) => ProfileLink.fromJson(model)));
return myLinks;
} else {
throw Exception('failed to fecth user profile links');
}
}
static Future<List<ProfileLink>> getBusinessProfileLinksMD(
String business_id,
) async {

View file

@ -21,6 +21,19 @@ class MihUserConsentServices {
}
}
Future<UserConsent?> getUserConsentStatusV2() async {
var app_id = await SuperTokens.getUserId();
final response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/user/$app_id"));
if (response.statusCode == 200) {
Map<String, dynamic> userMap = jsonDecode(response.body);
UserConsent userConsent = UserConsent.fromJson(userMap);
return userConsent;
} else {
return null;
}
}
Future<int> insertUserConsentStatus(
String latestPrivacyPolicyDate,
String latestTermOfServiceDate,

View file

@ -154,6 +154,23 @@ class MihUserServices {
}
}
Future<AppUser?> getMyUserDetailsV2() async {
String app_id = await SuperTokens.getUserId();
var response = await http.get(
Uri.parse("${AppEnviroment.baseApiUrl}/user/$app_id"),
headers: <String, String>{
"Content-Type": "application/json; charset=UTF-8"
},
);
if (response.statusCode == 200) {
String body = response.body;
var jsonBody = jsonDecode(body);
return AppUser.fromJson(jsonBody);
} else {
return null;
}
}
Future<int> updateUserV2(
AppUser signedInUser,
String firstName,

View file

@ -50,7 +50,7 @@ packages:
source: hosted
version: "4.0.7"
args:
dependency: "direct main"
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
@ -928,6 +928,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.8"
hive_ce:
dependency: "direct main"
description:
name: hive_ce
sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418"
url: "https://pub.dev"
source: hosted
version: "2.19.3"
hive_ce_flutter:
dependency: "direct main"
description:
name: hive_ce_flutter
sha256: "2677e95a333ff15af43ccd06af7eb7abbf1a4f154ea071997f3de4346cae913a"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
hive_ce_generator:
dependency: "direct dev"
description:
name: hive_ce_generator
sha256: "609678c10ebee7503505a0007050af40a0a4f498b1fb7def3220df341e573a89"
url: "https://pub.dev"
source: hosted
version: "1.9.2"
html:
dependency: transitive
description:
@ -1048,6 +1072,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
isolate_channel:
dependency: transitive
description:
name: isolate_channel
sha256: a9d3d620695bc984244dafae00b95e4319d6974b2d77f4b9e1eb4f2efe099094
url: "https://pub.dev"
source: hosted
version: "0.6.1"
js:
dependency: transitive
description:
@ -1773,6 +1805,22 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
url: "https://pub.dev"
source: hosted
version: "1.3.7"
source_maps:
dependency: transitive
description:
@ -2269,22 +2317,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.3"
youtube_player_iframe:
dependency: "direct main"
description:
name: youtube_player_iframe
sha256: "66020f7756accfb22b3297565d845f9bef14249c730dd51e1ec648fa155fb24a"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
youtube_player_iframe_web:
yaml_writer:
dependency: transitive
description:
name: youtube_player_iframe_web
sha256: "05222a228937932e7ee7a6171e8020fee4cd23d1c7bf6b4128c569484338c593"
name: yaml_writer
sha256: "69651cd7238411179ac32079937d4aa9a2970150d6b2ae2c6fe6de09402a5dc5"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "2.1.0"
sdks:
dart: ">=3.10.0-0 <3.13.0-z"
flutter: ">=3.29.0"

View file

@ -1,7 +1,7 @@
name: mzansi_innovation_hub
description: ""
publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.3.0+133
version: 1.4.0+134
environment:
sdk: ">=3.5.3 <4.0.0"
@ -22,14 +22,13 @@ dependencies:
file_picker: ^10.1.9
supertokens_flutter: ^0.6.3
http: ^1.2.1
args: ^2.7.0
# args: ^2.7.0
intl: ^0.20.2
flutter_native_splash: ^2.4.6
printing: ^5.14.2
geolocator: ^14.0.1
geolocator_linux: ^0.2.4
table_calendar: ^3.1.2
youtube_player_iframe: ^5.2.0
mobile_scanner: ^7.0.1
flutter_launcher_icons: ^0.14.4
barcode_widget: ^2.0.4 #Generate Barcodes
@ -63,6 +62,8 @@ dependencies:
flutter_markdown_plus: ^1.0.5
cross_file: ^0.3.5+1
quick_actions: ^1.1.0
hive_ce:
hive_ce_flutter:
dependency_overrides:
supertokens_flutter: any
@ -71,11 +72,12 @@ dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.8
build_web_compilers: ^4.1.5
flutter_lints: ^6.0.0
hive_ce_generator:
build_runner:
flutter:
uses-material-design: true
assets: