ecard download
This commit is contained in:
parent
cbb2f9f33d
commit
a157694897
@ -5,7 +5,7 @@ class Environment {
|
|||||||
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
||||||
|
|
||||||
/// Chatbot FAB + window. Set to `true` when ready to enable.
|
/// Chatbot FAB + window. Set to `true` when ready to enable.
|
||||||
static const bool chatbotEnabled = false;
|
static const bool chatbotEnabled = true;
|
||||||
|
|
||||||
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'download_result.dart';
|
import 'download_result.dart';
|
||||||
|
|
||||||
@ -32,6 +33,14 @@ Future<DownloadResult> downloadEcardImpl({
|
|||||||
required String fileName,
|
required String fileName,
|
||||||
Map<String, String>? headers,
|
Map<String, String>? headers,
|
||||||
}) async {
|
}) async {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return _downloadAndShareOnIos(
|
||||||
|
url: url,
|
||||||
|
fileName: fileName,
|
||||||
|
headers: headers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final hasPermission = await ensureStorageAccessImpl();
|
final hasPermission = await ensureStorageAccessImpl();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
@ -89,6 +98,54 @@ Future<DownloadResult> downloadEcardImpl({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<DownloadResult> _downloadAndShareOnIos({
|
||||||
|
required String url,
|
||||||
|
required String fileName,
|
||||||
|
Map<String, String>? headers,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final safeFileName = _sanitizeFileName(fileName);
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
final filePath = '${tempDir.path}/$safeFileName';
|
||||||
|
final file = File(filePath);
|
||||||
|
if (await file.exists()) {
|
||||||
|
await file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dio.download(
|
||||||
|
url,
|
||||||
|
filePath,
|
||||||
|
options: Options(
|
||||||
|
headers: headers,
|
||||||
|
responseType: ResponseType.bytes,
|
||||||
|
),
|
||||||
|
deleteOnError: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!await file.exists() || await file.length() == 0) {
|
||||||
|
return DownloadResult.failure('Download failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
await Share.shareXFiles(
|
||||||
|
[XFile(filePath)],
|
||||||
|
text: 'Save eCard to Files',
|
||||||
|
subject: safeFileName,
|
||||||
|
);
|
||||||
|
|
||||||
|
return DownloadResult.success(
|
||||||
|
message: 'Choose "Save to Files" to store eCard',
|
||||||
|
);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final hasStatusCode = e.response?.statusCode != null;
|
||||||
|
final message = hasStatusCode
|
||||||
|
? 'Download failed (${e.response!.statusCode})'
|
||||||
|
: 'Download failed';
|
||||||
|
return DownloadResult.failure(message);
|
||||||
|
} catch (_) {
|
||||||
|
return DownloadResult.failure('Download failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _sanitizeFileName(String fileName) {
|
String _sanitizeFileName(String fileName) {
|
||||||
final replaced = fileName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
|
final replaced = fileName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
|
||||||
if (replaced.isEmpty) return 'ecard.pdf';
|
if (replaced.isEmpty) return 'ecard.pdf';
|
||||||
|
|||||||
@ -104,6 +104,7 @@ class _HomeState extends State<Home> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
apiService = ApiService(context); // Initialize ApiService here
|
apiService = ApiService(context); // Initialize ApiService here
|
||||||
|
dataManager.init(apiService);
|
||||||
|
|
||||||
|
|
||||||
if (session.client_id == null || session.client_id!.isEmpty ||
|
if (session.client_id == null || session.client_id!.isEmpty ||
|
||||||
|
|||||||
@ -383,6 +383,11 @@ class ApiService {
|
|||||||
|
|
||||||
Future<Map<String, dynamic>> _makeGetRequest(
|
Future<Map<String, dynamic>> _makeGetRequest(
|
||||||
Uri url, Map<String, String> headers) async {
|
Uri url, Map<String, String> headers) async {
|
||||||
|
// Always refresh token at request time to avoid using stale login tokens.
|
||||||
|
if (headers.containsKey('Authorization')) {
|
||||||
|
_postToken = await TokenService.getPostToken();
|
||||||
|
headers['Authorization'] = 'Bearer ${_postToken ?? ''}';
|
||||||
|
}
|
||||||
// Add global APP_SIGNATURE header
|
// Add global APP_SIGNATURE header
|
||||||
headers['APP-SIGNATURE'] =
|
headers['APP-SIGNATURE'] =
|
||||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
||||||
@ -392,6 +397,11 @@ class ApiService {
|
|||||||
|
|
||||||
Future<Map<String, dynamic>> _makePostRequest(
|
Future<Map<String, dynamic>> _makePostRequest(
|
||||||
Uri url, Map<String, String> body, Map<String, String> headers) async {
|
Uri url, Map<String, String> body, Map<String, String> headers) async {
|
||||||
|
// Always refresh token at request time to avoid using stale login tokens.
|
||||||
|
if (headers.containsKey('Authorization')) {
|
||||||
|
_postToken = await TokenService.getPostToken();
|
||||||
|
headers['Authorization'] = 'Bearer ${_postToken ?? ''}';
|
||||||
|
}
|
||||||
// Add global APP_SIGNATURE header
|
// Add global APP_SIGNATURE header
|
||||||
headers['APP-SIGNATURE'] =
|
headers['APP-SIGNATURE'] =
|
||||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
||||||
@ -401,6 +411,11 @@ class ApiService {
|
|||||||
|
|
||||||
Future<Map<String, dynamic>> _makePostRequestWithoutFormData(
|
Future<Map<String, dynamic>> _makePostRequestWithoutFormData(
|
||||||
Uri url, String body, Map<String, String> headers) async {
|
Uri url, String body, Map<String, String> headers) async {
|
||||||
|
// Always refresh token at request time to avoid using stale login tokens.
|
||||||
|
if (headers.containsKey('Authorization')) {
|
||||||
|
_postToken = await TokenService.getPostToken();
|
||||||
|
headers['Authorization'] = 'Bearer ${_postToken ?? ''}';
|
||||||
|
}
|
||||||
// Add global APP_SIGNATURE header
|
// Add global APP_SIGNATURE header
|
||||||
headers['APP-SIGNATURE'] =
|
headers['APP-SIGNATURE'] =
|
||||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
||||||
|
|||||||
@ -18,7 +18,11 @@ class TokenService {
|
|||||||
static const postTokenKey = 'post_token';
|
static const postTokenKey = 'post_token';
|
||||||
static const preTokenKey = 'pre_token';
|
static const preTokenKey = 'pre_token';
|
||||||
|
|
||||||
static final _secureStorage = FlutterSecureStorage();
|
static const _secureStorage = FlutterSecureStorage(
|
||||||
|
iOptions: IOSOptions(
|
||||||
|
accessibility: KeychainAccessibility.first_unlock_this_device,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
/// Web: tokens live only in [sessionStorage] (tab-scoped). Mobile: secure storage.
|
/// Web: tokens live only in [sessionStorage] (tab-scoped). Mobile: secure storage.
|
||||||
static Future<void> saveTokens({String? postToken, String? preToken}) async {
|
static Future<void> saveTokens({String? postToken, String? preToken}) async {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import local_auth_darwin
|
|||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import printing
|
import printing
|
||||||
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
@ -32,6 +33,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
|
||||||
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|||||||
@ -79,6 +79,7 @@ dependencies:
|
|||||||
open_filex: ^4.7.0
|
open_filex: ^4.7.0
|
||||||
flutter_local_notifications: ^19.4.2
|
flutter_local_notifications: ^19.4.2
|
||||||
permission_handler: ^12.0.1
|
permission_handler: ^12.0.1
|
||||||
|
share_plus: ^11.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include <local_auth_windows/local_auth_plugin.h>
|
#include <local_auth_windows/local_auth_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <printing/printing_plugin.h>
|
#include <printing/printing_plugin.h>
|
||||||
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
@ -27,6 +28,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
PrintingPluginRegisterWithRegistrar(
|
PrintingPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||||
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
local_auth_windows
|
local_auth_windows
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
printing
|
printing
|
||||||
|
share_plus
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user