This commit is contained in:
kyawkhantwin 2026-04-01 14:59:18 +06:30
parent 9368cc9cc6
commit a436c85ba6
5 changed files with 22 additions and 192 deletions

View File

@ -24,7 +24,7 @@ class ApiReceiptRepository implements ReceiptRepository {
required String copyFor,
}) async {
final uri = _normalizeLocalhostForAndroid(
Uri.parse('$baseUrl/transaction/pdf-html').replace(
Uri.parse('$baseUrl/transaction/pdf').replace(
queryParameters: <String, String>{
'transactionId': transactionId,
'copyFor': copyFor,
@ -70,7 +70,7 @@ class ApiReceiptRepository implements ReceiptRepository {
required String copyFor,
}) async {
final uri = _normalizeLocalhostForAndroid(
Uri.parse('$baseUrl/transaction/pdf-html').replace(
Uri.parse('$baseUrl/transaction/pdf').replace(
queryParameters: <String, String>{
'transactionId': transactionId,
'copyFor': copyFor,

View File

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:e_receipt_mobile/core/config/app_config.dart';
import 'package:e_receipt_mobile/data/repositories/api_receipt_repository.dart';
import 'package:e_receipt_mobile/domain/entities/receipt_content.dart';
import 'package:e_receipt_mobile/domain/repositories/receipt_repository.dart';
import 'package:e_receipt_mobile/presentation/auth/session_controller.dart';
import 'package:e_receipt_mobile/presentation/login/login_view_model.dart';
@ -39,12 +38,6 @@ class ReceiptPdfViewData extends ReceiptViewData {
final File file;
}
class ReceiptHtmlViewData extends ReceiptViewData {
const ReceiptHtmlViewData(this.html);
final String html;
}
final receiptRepositoryProvider = Provider<ReceiptRepository>((ref) {
return ApiReceiptRepository(
baseUrl: AppConfig.apiBaseUrl,
@ -64,27 +57,20 @@ final transactionReceiptViewDataProvider =
throw Exception('No active session');
}
final content = await ref
final bytes = await ref
.watch(receiptRepositoryProvider)
.getTransactionReceipt(
.getTransactionReceiptPdfBytes(
token: sessionUser.token,
transactionId: query.transactionId,
copyFor: query.copyFor,
);
switch (content) {
case ReceiptPdfContent():
final dir = await Directory.systemTemp.createTemp(
'e_receipt_mobile',
);
final dir = await Directory.systemTemp.createTemp('e_receipt_mobile');
final file = File(
'${dir.path}/receipt_${query.transactionId}_${query.copyFor}.pdf',
);
await file.writeAsBytes(content.bytes, flush: true);
await file.writeAsBytes(bytes, flush: true);
return ReceiptPdfViewData(file);
case ReceiptHtmlContent():
return ReceiptHtmlViewData(content.html);
}
} catch (e, st) {
debugPrint('[RECEIPT][ERROR] ${Error.safeToString(e)}\n$st');
throw Exception(Error.safeToString(e));

View File

@ -439,7 +439,7 @@ class _TerminalNextScreenState extends ConsumerState<TerminalNextScreen> {
_first(raw, const ['DE4']) ?? item.amount?.toString() ?? '-';
final currency = _first(raw, const ['DE49']) ?? 'MMK';
final status = _statusLabel(
_first(raw, const ['description', 'DE39', 'status']) ??
_first(raw, const ['DE39']) ??
item.status,
);
final type = _first(raw, const ['DE3']) ?? item.type ?? '-';
@ -605,16 +605,15 @@ class _TerminalNextScreenState extends ConsumerState<TerminalNextScreen> {
}
String _statusLabel(String? status) {
final normalized = (status ?? '').trim().toUpperCase();
if (normalized == 'A' ||
normalized == 'SUCCESS' ||
normalized == 'PAY_SUCCESS') {
final normalized = status?.trim().toUpperCase();
if (normalized == 'A') {
return 'SUCCESS';
}
if (normalized == 'E' || normalized == 'FAILED') {
if (normalized == 'E' ) {
return 'FAILED';
}
return normalized.isEmpty ? '-' : normalized;
return 'SUCCESS';
}
String? _sanitizeMultiline(String? value) {

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:e_receipt_mobile/domain/entities/receipt_content.dart';
import 'package:e_receipt_mobile/domain/entities/terminal.dart';
import 'package:e_receipt_mobile/domain/entities/transaction_record.dart';
import 'package:e_receipt_mobile/presentation/auth/session_controller.dart';
@ -13,7 +12,6 @@ import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import 'package:printing/printing.dart';
import 'package:webview_flutter/webview_flutter.dart';
class TransactionReceiptScreen extends ConsumerWidget {
const TransactionReceiptScreen({
@ -146,10 +144,6 @@ class TransactionReceiptScreen extends ConsumerWidget {
},
),
),
ReceiptHtmlViewData() => _ReceiptViewport(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
child: _ReceiptHtmlView(html: viewData.html),
),
},
),
SafeArea(
@ -356,22 +350,13 @@ Future<Uint8List> _fetchReceiptPdfBytes({
throw Exception('No active session');
}
final content = await ref.read(receiptRepositoryProvider).getTransactionReceipt(
return ref
.read(receiptRepositoryProvider)
.getTransactionReceiptPdfBytes(
token: sessionUser.token,
transactionId: transactionId,
copyFor: copyFor,
);
switch (content) {
case ReceiptPdfContent():
return content.bytes;
case ReceiptHtmlContent():
try {
return await Printing.convertHtml(html: _wrapHtml(content.html));
} catch (e) {
throw Exception('Server did not return PDF, and HTML→PDF failed: $e');
}
}
}
Future<T> _runWithProgress<T>(
@ -416,146 +401,3 @@ Future<T> _runWithProgress<T>(
}
}
}
class _ReceiptHtmlWebView extends StatefulWidget {
const _ReceiptHtmlWebView({required this.html});
final String html;
@override
State<_ReceiptHtmlWebView> createState() => _ReceiptHtmlWebViewState();
}
class _ReceiptHtmlWebViewState extends State<_ReceiptHtmlWebView> {
late final WebViewController _controller;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.disabled)
..setBackgroundColor(Colors.transparent)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (request) => NavigationDecision.prevent,
),
)
..loadHtmlString(_wrapHtml(widget.html));
}
@override
Widget build(BuildContext context) {
return WebViewWidget(controller: _controller);
}
}
class _ReceiptHtmlView extends StatelessWidget {
const _ReceiptHtmlView({required this.html});
final String html;
@override
Widget build(BuildContext context) {
final platform = defaultTargetPlatform;
final supportsWebView =
platform == TargetPlatform.android || platform == TargetPlatform.iOS;
if (!supportsWebView) {
return _ReceiptHtmlFallbackText(html: html);
}
return _ReceiptHtmlWebView(html: html);
}
}
class _ReceiptHtmlFallbackText extends StatelessWidget {
const _ReceiptHtmlFallbackText({required this.html});
final String html;
@override
Widget build(BuildContext context) {
final plainText = _htmlToPlainText(html);
return Padding(
padding: const EdgeInsets.all(16),
child: SelectionArea(
child: DefaultTextStyle(
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
fontFamily: 'monospace',
height: 1.35,
) ??
const TextStyle(fontFamily: 'monospace', height: 1.35),
child: Text(plainText),
),
),
);
}
}
String _wrapHtml(String html) {
// Keep backend HTML intact but ensure a sane viewport and remove default
// margins so it looks like the web "receipt iframe" container.
return '''
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
html, body { margin: 0; padding: 0; background: transparent; }
body { -webkit-text-size-adjust: 100%; }
</style>
</head>
<body>$html</body>
</html>
''';
}
String _htmlToPlainText(String html) {
var s = html;
s = s.replaceAll(
RegExp(
r'<(script|style)[^>]*>.*?</\1>',
caseSensitive: false,
dotAll: true,
),
'',
);
s = s.replaceAll(RegExp(r'<br\s*/?>', caseSensitive: false), '\n');
s = s.replaceAll(
RegExp(
r'</(div|p|tr|pre|table|section|header|footer)>',
caseSensitive: false,
),
'\n',
);
s = s.replaceAll(RegExp(r'<li[^>]*>', caseSensitive: false), '');
s = s.replaceAll(RegExp(r'</li>', caseSensitive: false), '\n');
s = s.replaceAll(RegExp(r'<[^>]+>'), '');
s = s.replaceAll('&nbsp;', ' ');
s = s.replaceAll('&amp;', '&');
s = s.replaceAll('&lt;', '<');
s = s.replaceAll('&gt;', '>');
s = s.replaceAll('&quot;', '"');
s = s.replaceAll('&#39;', "'");
s = s.replaceAllMapped(RegExp(r'&#(\d+);'), (m) {
final code = int.tryParse(m.group(1) ?? '');
if (code == null) return '';
return String.fromCharCode(code);
});
s = s.replaceAllMapped(RegExp(r'&#x([0-9a-fA-F]+);'), (m) {
final code = int.tryParse(m.group(1) ?? '', radix: 16);
if (code == null) return '';
return String.fromCharCode(code);
});
s = s.replaceAll(RegExp(r'[ \t]+\n'), '\n');
s = s.replaceAll(RegExp(r'\n{3,}'), '\n\n');
return s.trim();
}

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'dart:math';
import 'dart:ui';
import 'package:e_receipt_mobile/domain/entities/terminal.dart';
@ -57,6 +59,7 @@ class TerminalListView extends StatelessWidget {
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final terminal = terminals[index];
print('this is terminal ${terminal.status}');
final enabled = terminal.tid != null || terminal.id != null;
final title =
(_sanitizeMultiline(terminal.name)?.isNotEmpty ?? false)