feat: tela de apps com logos/menu de exclusao, correcao de tema nas mini apps e sidebar de navegacao
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

This commit is contained in:
2026-09-19 00:08:35 -03:00
parent 1f692b1e4e
commit 6c689d1f34
23 changed files with 1039 additions and 114 deletions

View File

@@ -4,6 +4,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:matrix/matrix.dart';
import 'package:tatchat/utils/miniapp_meta.dart';
/// Account data key that holds the list of installed mini app IDs.
const String installedMiniAppsKey = 'im.tatuchat.miniapps.installed';
@@ -32,16 +33,22 @@ extension MiniAppStoreExtension on Client {
if (apps.contains(appId)) return;
apps.add(appId);
await setAccountData(userID!, installedMiniAppsKey, {'apps': apps});
_cacheAccountData(installedMiniAppsKey, {'apps': apps});
}
Future<void> uninstallMiniApp(String appId) async {
final apps = installedMiniApps..remove(appId);
await setAccountData(userID!, installedMiniAppsKey, {'apps': apps});
_cacheAccountData(installedMiniAppsKey, {'apps': apps});
// Clear the custom HTML source if present.
await setAccountData(userID!, '$miniAppSourceKeyPrefix$appId', {
'html': '',
'name': '',
});
_cacheAccountData(
'$miniAppSourceKeyPrefix$appId',
{'html': '', 'name': ''},
);
}
/// Reads the JSON document of the given mini app. Returns an empty map if
@@ -50,29 +57,47 @@ extension MiniAppStoreExtension on Client {
accountData['$miniAppDataKeyPrefix$appId']?.content ?? const {};
/// Overwrites the JSON document of the given mini app.
Future<void> setMiniAppData(String appId, Map<String, Object?> data) =>
setAccountData(userID!, '$miniAppDataKeyPrefix$appId', data);
Future<void> setMiniAppData(String appId, Map<String, Object?> data) async {
final key = '$miniAppDataKeyPrefix$appId';
await setAccountData(userID!, key, data);
_cacheAccountData(key, data);
}
// ---------- Imported (custom) apps ----------
/// Stores the HTML source and a display name for a user-imported mini app
/// and adds it to the installed list if not already present.
/// Stores the HTML source and the manifest of a user-imported mini app and
/// adds it to the installed list if not already present. The manifest's
/// [MiniAppMeta.id] is used as the account data key for the app.
Future<void> importMiniApp({
required String appId,
required String name,
required MiniAppMeta meta,
required String html,
}) async {
await setAccountData(userID!, '$miniAppSourceKeyPrefix$appId', {
'name': name,
final sourceKey = '$miniAppSourceKeyPrefix${meta.id}';
final content = <String, Object?>{
'name': meta.name,
'category': meta.category,
'description': meta.description,
'logo': meta.logo,
'inChat': meta.inChat,
'html': html,
});
};
await setAccountData(userID!, sourceKey, content);
_cacheAccountData(sourceKey, content);
final apps = installedMiniApps;
if (!apps.contains(appId)) {
apps.add(appId);
if (!apps.contains(meta.id)) {
apps.add(meta.id);
await setAccountData(userID!, installedMiniAppsKey, {'apps': apps});
_cacheAccountData(installedMiniAppsKey, {'apps': apps});
}
}
/// Mirrors the just-written account data into the in-memory cache so that
/// reads right after a write (e.g. a mini app saving and immediately
/// loading its store) see the new value without waiting for the next sync.
void _cacheAccountData(String key, Map<String, Object?> content) {
accountData[key] = BasicEvent(type: key, content: content);
}
/// Returns the raw HTML source of an imported mini app, or null if the app
/// is a built-in app or was never imported.
String? getMiniAppSource(String appId) {
@@ -85,10 +110,36 @@ extension MiniAppStoreExtension on Client {
/// Returns the display name of an imported mini app, or null.
String? getMiniAppName(String appId) {
final content =
accountData['$miniAppSourceKeyPrefix$appId']?.content;
if (content == null) return null;
final name = content['name'];
return (name is String && name.isNotEmpty) ? name : null;
final content = accountData['$miniAppSourceKeyPrefix$appId']?.content;
return _stringContent(content, 'name');
}
/// Returns the category of an imported mini app, or null.
String? getMiniAppCategory(String appId) {
final content = accountData['$miniAppSourceKeyPrefix$appId']?.content;
return _stringContent(content, 'category');
}
/// Returns the description of an imported mini app, or null.
String? getMiniAppDescription(String appId) {
final content = accountData['$miniAppSourceKeyPrefix$appId']?.content;
return _stringContent(content, 'description');
}
/// Returns the logo (data URI or URL) of an imported mini app, or null.
String? getMiniAppLogo(String appId) {
final content = accountData['$miniAppSourceKeyPrefix$appId']?.content;
return _stringContent(content, 'logo');
}
/// Whether an imported mini app declares that it can run inside a chat.
bool getMiniAppInChat(String appId) {
final content = accountData['$miniAppSourceKeyPrefix$appId']?.content;
return content?['inChat'] == true;
}
String? _stringContent(Map<String, Object?>? content, String key) {
final value = content?[key];
return (value is String && value.isNotEmpty) ? value : null;
}
}