QOL: Download Full Business QR Code Image

This commit is contained in:
2025-09-23 13:35:09 +02:00
parent 291882abc5
commit a4be7fb74d
8 changed files with 250 additions and 138 deletions

View File

@@ -1,8 +1,12 @@
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:file_picker/file_picker.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:go_router/go_router.dart';
import 'package:ken_logger/ken_logger.dart';
import 'package:mzansi_innovation_hub/main.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart';
@@ -16,8 +20,8 @@ import 'package:mzansi_innovation_hub/mih_config/mih_env.dart';
import 'package:mzansi_innovation_hub/mih_services/mih_file_services.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart';
import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_circle_avatar.dart';
import 'package:screenshot/screenshot.dart';
import 'package:supertokens_flutter/supertokens.dart';
import 'package:url_launcher/url_launcher.dart';
class MihBusinessQrCode extends StatefulWidget {
final Business business;
@@ -38,6 +42,8 @@ class _MihBusinessQrCodeState extends State<MihBusinessQrCode> {
late String qrCodedata;
int qrSize = 500;
bool _isUserSignedIn = false;
ScreenshotController screenshotController = ScreenshotController();
Uint8List? businessQRImageFile;
Future<void> _checkUserSession() async {
final doesSessionExist = await SuperTokens.doesSessionExist();
@@ -65,12 +71,88 @@ class _MihBusinessQrCodeState extends State<MihBusinessQrCode> {
return "https://api.qrserver.com/v1/create-qr-code/?data=$encodedData&size=${qrSize}x${qrSize}&bgcolor=$bgColor&color=$color";
}
Future<void> saveImage(Uint8List imageBytes) async {
final String filename =
"${widget.business.Name}_QR_Code_${DateTime.now().millisecondsSinceEpoch}.png";
if (kIsWeb) {
await FileSaver.instance.saveFile(
name: filename,
bytes: imageBytes,
fileExtension: "png",
mimeType: MimeType.png,
);
} else {
await FileSaver.instance.saveAs(
name: filename,
bytes: imageBytes,
fileExtension: "png",
mimeType: MimeType.png,
);
}
// if (kIsWeb) {
// final blob = html.Blob([imageBytes]);
// final url = html.Url.createObjectUrlFromBlob(blob);
// final anchor = html.document.createElement('a') as html.AnchorElement
// ..href = url
// ..style.display = 'none'
// ..download = filename; // Suggested filename for the download
// html.document.body!.children.add(anchor);
// anchor.click(); // Programmatically click the link to trigger download
// html.document.body!.children.remove(anchor);
// html.Url.revokeObjectUrl(url);
// } else {
// var permission = await FlDownloader.requestPermission();
// if (permission == StoragePermissionStatus.granted) {
// try {
// mihLoadingPopUp();
// KenLogger.success("Downloading from URL: $url");
// await FlDownloader.download(url);
// Navigator.of(context).pop();
// } on Exception catch (error) {
// Navigator.of(context).pop();
// print(error);
// }
// } else {
// print("denied");
// }
// try {
// final directory = await getDownloadsDirectory();
// final file = File('${directory?.path}/$filename');
// await file.writeAsBytes(imageBytes);
// KenLogger.success("File saved at: ${file.path}");
// } catch (e) {
// KenLogger.error("Error saving file: $e");
// }
// }
}
void mihLoadingPopUp() {
showDialog(
context: context,
builder: (context) {
return const Mihloadingcircle();
},
);
}
Future<void> downloadQrCode() async {
if (_isUserSignedIn) {
final Uri uri = Uri.parse(getQrCodeData(1024));
if (!await launchUrl(uri)) {
throw 'Could not launch $uri';
}
// final Uri uri = Uri.parse(getQrCodeData(1024));
// if (!await launchUrl(uri)) {
// throw 'Could not launch $uri';
// }
await screenshotController.capture().then((image) {
KenLogger.success("Image Captured: $image");
setState(() {
businessQRImageFile = image;
});
}).catchError((onError) {
KenLogger.error(onError);
});
KenLogger.success("QR Code Image Captured : $businessQRImageFile");
saveImage(businessQRImageFile!);
} else {
showSignInRequiredAlert();
}
@@ -134,6 +216,137 @@ class _MihBusinessQrCodeState extends State<MihBusinessQrCode> {
);
}
Widget displayBusinessQRCode(double profilePictureWidth) {
return Screenshot(
controller: screenshotController,
child: Material(
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode == "Dark")
.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(25),
elevation: 10,
shadowColor: Colors.black,
child: Container(
decoration: BoxDecoration(
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
FutureBuilder(
future: futureImageUrl,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState ==
ConnectionState.done &&
asyncSnapshot.hasData) {
if (asyncSnapshot.requireData != "") {
return MihCircleAvatar(
imageFile: NetworkImage(asyncSnapshot.requireData),
width: profilePictureWidth,
editable: false,
fileNameController: TextEditingController(),
userSelectedfile: file,
frameColor: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
backgroundColor: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
onChange: () {},
);
} else {
return Icon(
MihIcons.iDontKnow,
size: profilePictureWidth,
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
);
}
} else {
return Icon(
MihIcons.mihRing,
size: profilePictureWidth,
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
);
}
},
),
FittedBox(
child: Text(
widget.business.Name,
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
),
),
),
FittedBox(
child: Text(
widget.business.type,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
),
),
),
const SizedBox(height: 5),
FittedBox(
child: Text(
"Powered by MIH",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
),
),
),
const SizedBox(height: 10),
SizedBox(
width: 300,
height: 300,
child: CachedNetworkImage(
imageUrl: getQrCodeData(qrSize.toInt()),
placeholder: (context, url) => const Mihloadingcircle(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
),
),
const SizedBox(height: 10),
FittedBox(
child: Text(
"Scan & Connect",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
),
),
),
],
)),
),
),
);
}
@override
void dispose() {
super.dispose();
@@ -175,137 +388,7 @@ class _MihBusinessQrCodeState extends State<MihBusinessQrCode> {
horizontal: screenSize.width * 0), //.075),
child: Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Material(
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode == "Dark")
.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(25),
elevation: 10,
shadowColor: Colors.black,
child: Container(
decoration: BoxDecoration(
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!.theme.mode ==
"Dark"),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
FutureBuilder(
future: futureImageUrl,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.connectionState ==
ConnectionState.done &&
asyncSnapshot.hasData) {
if (asyncSnapshot.requireData != "") {
return MihCircleAvatar(
imageFile: NetworkImage(
asyncSnapshot.requireData),
width: profilePictureWidth,
editable: false,
fileNameController:
TextEditingController(),
userSelectedfile: file,
frameColor: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
backgroundColor:
MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
onChange: () {},
);
} else {
return Icon(
MihIcons.iDontKnow,
size: profilePictureWidth,
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
);
}
} else {
return Icon(
MihIcons.mihRing,
size: profilePictureWidth,
color: MihColors.getSecondaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
);
}
},
),
FittedBox(
child: Text(
widget.business.Name,
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
),
),
),
FittedBox(
child: Text(
widget.business.type,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
),
),
),
const SizedBox(height: 10),
SizedBox(
width: 300,
height: 300,
child: CachedNetworkImage(
imageUrl: getQrCodeData(qrSize.toInt()),
placeholder: (context, url) =>
const Mihloadingcircle(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
),
),
const SizedBox(height: 10),
FittedBox(
child: Text(
"Scan & Connect",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: MihColors.getPrimaryColor(
MzansiInnovationHub.of(context)!
.theme
.mode ==
"Dark"),
),
),
),
],
)),
),
),
child: displayBusinessQRCode(profilePictureWidth),
),
),
),

View File

@@ -6,10 +6,14 @@
#include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <printing/printing_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
g_autoptr(FlPluginRegistrar) printing_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
printing_plugin_register_with_registrar(printing_registrar);

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_saver
printing
url_launcher_linux
)

View File

@@ -8,6 +8,7 @@ import Foundation
import app_settings
import device_info_plus
import file_picker
import file_saver
import flutter_tts
import geolocator_apple
import local_auth_darwin
@@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))

View File

@@ -417,6 +417,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "10.1.9"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
fixnum:
dependency: transitive
description:
@@ -1232,6 +1240,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
screenshot:
dependency: "direct main"
description:
name: screenshot
sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
scroll_to_index:
dependency: transitive
description:

View File

@@ -1,8 +1,8 @@
name: mzansi_innovation_hub
description: ""
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# version: 1.2.0+97
version: 1.1.0+97 #--- Updated version for upgrader package testing ---
version: 1.2.0+97
# version: 1.1.1+97 #--- Updated version for upgrader package testing
environment:
sdk: '>=3.5.3 <4.0.0'
@@ -54,6 +54,8 @@ dependencies:
cached_network_image: ^3.4.1
gpt_markdown: ^1.1.2
upgrader: ^12.0.0
screenshot: ^3.0.0
file_saver: ^0.3.1
dev_dependencies:
flutter_test:

View File

@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <fl_downloader/fl_downloader_plugin_c_api.h>
#include <flutter_tts/flutter_tts_plugin.h>
#include <geolocator_windows/geolocator_windows.h>
@@ -17,6 +18,8 @@
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
FlDownloaderPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlDownloaderPluginCApi"));
FlutterTtsPluginRegisterWithRegistrar(

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_saver
fl_downloader
flutter_tts
geolocator_windows