import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:http/http.dart' as http;
// Configuration constants
const String YOUR_BACKEND_AUTH_URL = 'https://your-api.com/auth/learnink';
const String WEBVIEW_BASE_URL = 'https://m.learn.ink';
const String ORG_ID = 'acme';
const int REQUEST_TIMEOUT = 5000;
class MessageTypes {
static const String CLOSE = 'CLOSE';
static const String SESSION_EXPIRED = 'SESSION_EXPIRED';
}
class LearnInkWebView extends StatefulWidget {
final String path;
final VoidCallback onClose;
final String userId;
const LearnInkWebView({
Key? key,
required this.path,
required this.onClose,
required this.userId,
}) : super(key: key);
@override
State<LearnInkWebView> createState() => _LearnInkWebViewState();
}
class _LearnInkWebViewState extends State<LearnInkWebView> {
String? _webViewUrl;
bool _loading = true;
late final WebViewController _webViewController;
@override
void initState() {
super.initState();
_initializeWebViewController();
_authenticateUser();
}
void _initializeWebViewController() {
_webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) => setState(() => _loading = true),
onPageFinished: (String url) => setState(() => _loading = false),
),
)
..addJavaScriptChannel(
'LearnInkWebView',
onMessageReceived: (JavaScriptMessage message) {
_handleMessage(message.message);
},
);
}
String _buildWebViewUrl({String? token}) {
final params = {
'webview': 'true',
if (token != null) 'token': token,
};
final queryString = Uri(queryParameters: params).query;
return '$WEBVIEW_BASE_URL/$ORG_ID/${widget.path}?$queryString';
}
Future<void> _authenticateUser() async {
setState(() => _loading = true);
try {
final token = await _fetchAuthToken();
final url = _buildWebViewUrl(token: token);
await _webViewController.loadRequest(Uri.parse(url));
setState(() => _webViewUrl = url);
} catch (error) {
debugPrint('Error authenticating user: $error');
// Fall back to loading without a token if the user has an existing session
final url = _buildWebViewUrl();
await _webViewController.loadRequest(Uri.parse(url));
setState(() => _webViewUrl = url);
} finally {
setState(() => _loading = false);
}
}
Future<String> _fetchAuthToken() async {
final response = await http
.post(
Uri.parse(YOUR_BACKEND_AUTH_URL),
headers: {
'Content-Type': 'application/json',
// Add your own auth headers here, e.g.:
// 'Authorization': 'Bearer $yourUserSessionToken',
},
body: jsonEncode({'id': widget.userId}),
)
.timeout(const Duration(milliseconds: REQUEST_TIMEOUT));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['token'];
} else {
final error = jsonDecode(response.body);
throw Exception(error['message'] ?? 'Failed to authenticate');
}
}
void _handleMessage(String messageData) {
try {
final message = jsonDecode(messageData);
switch (message['type']) {
case MessageTypes.CLOSE:
widget.onClose();
break;
case MessageTypes.SESSION_EXPIRED:
_handleSessionExpired();
break;
default:
debugPrint('Unknown message type: ${message['type']}');
}
} catch (error) {
debugPrint('Error handling message: $error');
}
}
Future<void> _handleSessionExpired() async {
setState(() => _loading = true);
try {
final token = await _fetchAuthToken();
final url = _buildWebViewUrl(token: token);
await _webViewController.loadRequest(Uri.parse(url));
setState(() => _webViewUrl = url);
} catch (error) {
debugPrint('Error refreshing session: $error');
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Session Refresh Failed'),
content: const Text('Unable to refresh your session. Please close and try again.'),
actions: [
TextButton(
onPressed: () { Navigator.of(context).pop(); widget.onClose(); },
child: const Text('Close'),
),
TextButton(
onPressed: () { Navigator.of(context).pop(); _handleSessionExpired(); },
child: const Text('Retry'),
),
],
),
);
}
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
if (_loading || _webViewUrl == null) {
return const Scaffold(
body: Center(child: CircularProgressIndicator(color: Color(0xFF007AFF))),
);
}
return Scaffold(
body: SafeArea(
child: Stack(
children: [
WebViewWidget(controller: _webViewController),
if (_loading)
Container(
color: Colors.white,
child: const Center(
child: CircularProgressIndicator(color: Color(0xFF007AFF)),
),
),
],
),
),
);
}
}