Files
tatuchat/lib/pages/apps/miniapp_page.dart
Welton Moura 1f692b1e4e
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
feat: mini apps customizados com importacao por arquivo/URL, paleta de cores, avatar autenticado e documentacao
2026-09-18 15:11:12 -03:00

113 lines
3.1 KiB
Dart

// SPDX-FileCopyrightText: 2026 TatuChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
import 'package:tatchat/config/miniapp_config.dart';
import 'package:tatchat/utils/miniapp_bridge.dart';
import 'package:tatchat/widgets/matrix.dart';
import 'miniapp_shell_stub.dart'
if (dart.library.js_interop) 'miniapp_shell_web.dart'
if (dart.library.ffi) 'miniapp_shell_native.dart';
/// Generic page that renders a mini app. The mini app is a self-contained HTML
/// document that talks to the TatuChat client through the [MiniappBridge]
/// protocol (see `assets/miniapps/`).
class MiniAppPage extends StatefulWidget {
final String appId;
const MiniAppPage({super.key, required this.appId});
@override
State<MiniAppPage> createState() => _MiniAppPageState();
}
class _MiniAppPageState extends State<MiniAppPage> {
Client get client => Matrix.of(context).client;
MiniAppConfig? _config;
String? _html;
bool _notFound = false;
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
final config = resolveMiniAppConfig(client, widget.appId);
if (config == null) {
if (!mounted) return;
setState(() => _notFound = true);
return;
}
if (config.htmlAsset != null) {
final html = await rootBundle.loadString(config.htmlAsset!);
if (!mounted) return;
setState(() {
_config = config;
_html = html;
});
return;
}
// Imported app: the HTML source lives in the account data store.
if (!mounted) return;
setState(() {
_config = config;
_html = config.html;
});
}
/// Entry point of the bridge: called by the web view/iframe with a request
/// coming from the mini app's JavaScript.
Future<Map<String, Object?>> handleRequest(Map<String, dynamic> request) {
final scheme = Theme.of(context).colorScheme;
final brightness = Theme.of(context).brightness;
return MiniappBridge.handleRequest(
client,
widget.appId,
request,
palette: MiniappBridge.buildPalette(scheme, brightness),
);
}
@override
Widget build(BuildContext context) {
if (_notFound) {
return Scaffold(
appBar: AppBar(
leading: const Center(child: BackButton()),
title: Text(widget.appId),
),
body: const Center(child: Text('App not found')),
);
}
final html = _html;
final config = _config;
if (html == null || config == null) {
return Scaffold(
appBar: AppBar(
leading: const Center(child: BackButton()),
title: Text(
_config?.name ?? _config?.id ?? widget.appId,
),
),
body: const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
),
);
}
return MiniAppShell(
key: ValueKey(widget.appId),
html: html,
appId: widget.appId,
title: config.name ?? config.id,
onRequest: handleRequest,
);
}
}