From be20eee16b1331c0686cf3ebb9a52eeb95a03962 Mon Sep 17 00:00:00 2001 From: Ramadhan Sjamsani Date: Fri, 5 Jun 2026 15:10:47 +0800 Subject: [PATCH] feat(client_app): open privacy policy in in-app webview Add a reusable WebPageScreen (webview_flutter host with close button + progress bar, no nav interception) and wire the profile 'kebijakan privasi' menu item to open https://mybestieindonesia.com/privacy in it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../lib/core/widgets/web_page_screen.dart | 96 +++++++++++++++++++ .../lib/features/profile/profile_screen.dart | 10 +- 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 client_app/lib/core/widgets/web_page_screen.dart diff --git a/client_app/lib/core/widgets/web_page_screen.dart b/client_app/lib/core/widgets/web_page_screen.dart new file mode 100644 index 0000000..be75aed --- /dev/null +++ b/client_app/lib/core/widgets/web_page_screen.dart @@ -0,0 +1,96 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import '../theme/halo_tokens.dart'; + +/// Generic in-app WebView host for static external pages (privacy policy, +/// terms & conditions, etc). +/// +/// Unlike [XenditCheckoutScreen] this carries no navigation-interception logic +/// — it just loads [url] and lets the user read it, with a close button and a +/// progress bar. Push it with a plain `Navigator.push(MaterialPageRoute(...))`. +class WebPageScreen extends StatefulWidget { + final String url; + final String title; + + const WebPageScreen({ + super.key, + required this.url, + required this.title, + }); + + @override + State createState() => _WebPageScreenState(); +} + +class _WebPageScreenState extends State { + late final WebViewController _controller; + int _progress = 0; + + @override + void initState() { + super.initState(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(HaloTokens.surface) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (p) { + if (!mounted) return; + setState(() => _progress = p); + }, + onWebResourceError: (error) { + if (kDebugMode) { + debugPrint( + '[WebPageScreen] WebResourceError ' + 'code=${error.errorCode} type=${error.errorType} ' + 'desc=${error.description} url=${error.url}', + ); + } + }, + ), + ) + ..loadRequest(Uri.parse(widget.url)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: HaloTokens.surface, + appBar: AppBar( + backgroundColor: HaloTokens.surface, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.close, color: HaloTokens.brandDark), + tooltip: 'Tutup', + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + widget.title, + style: const TextStyle( + fontFamily: HaloTokens.fontDisplay, + fontSize: 16, + fontWeight: FontWeight.w700, + color: HaloTokens.brandDark, + ), + ), + centerTitle: true, + bottom: _progress < 100 + ? PreferredSize( + preferredSize: const Size.fromHeight(2), + child: LinearProgressIndicator( + value: _progress / 100.0, + minHeight: 2, + backgroundColor: HaloTokens.brandSofter, + valueColor: const AlwaysStoppedAnimation( + HaloTokens.brand, + ), + ), + ) + : null, + ), + body: WebViewWidget(controller: _controller), + ); + } +} diff --git a/client_app/lib/features/profile/profile_screen.dart b/client_app/lib/features/profile/profile_screen.dart index e098053..5eacd83 100644 --- a/client_app/lib/features/profile/profile_screen.dart +++ b/client_app/lib/features/profile/profile_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/auth/auth_notifier.dart'; import '../../core/theme/halo_tokens.dart'; +import '../../core/widgets/web_page_screen.dart'; import '../home/widgets/halo_tab_bar.dart'; /// "Kamu" tab — profile screen. @@ -78,7 +79,14 @@ class ProfileScreen extends ConsumerWidget { _MenuItemData( icon: Icons.lock_outline, label: 'kebijakan privasi', - onTap: () {}, + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const WebPageScreen( + url: 'https://mybestieindonesia.com/privacy', + title: 'kebijakan privasi', + ), + ), + ), ), ]), const SizedBox(height: 16),