Add web profile config and branded PWA icon
@@ -35,6 +35,7 @@ migrate_working_dir/
|
||||
*.tgz
|
||||
/deploy/gitlab/
|
||||
/deploy/gitea/
|
||||
/deploy/calendar-web/app/runtime-config.json
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 436 B |
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"a10d8ac38de835021c8d2f920dbf50a920ccc0
|
||||
|
||||
_flutter.loader.load({
|
||||
serviceWorkerSettings: {
|
||||
serviceWorkerVersion: "128966474" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
||||
serviceWorkerVersion: "2721834507" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
||||
}
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 11 KiB |
@@ -18,18 +18,18 @@
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
<meta name="description" content="KözösTér családi naptár és otthoni organizer.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="otthon_naptar">
|
||||
<meta name="apple-mobile-web-app-title" content="KözösTér">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>otthon_naptar</title>
|
||||
<title>KözösTér</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "otthon_naptar",
|
||||
"short_name": "otthon_naptar",
|
||||
"name": "KözösTér",
|
||||
"short_name": "KözösTér",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "A new Flutter project.",
|
||||
"background_color": "#F7F5EF",
|
||||
"theme_color": "#18332D",
|
||||
"description": "Családi naptár és otthoni organizer.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
|
||||
@@ -50,6 +50,7 @@ class _OtthonAppState extends State<OtthonApp> {
|
||||
loaded = true;
|
||||
});
|
||||
_loadRemoteAppConfig();
|
||||
_loadWebRuntimeConfig();
|
||||
}
|
||||
|
||||
Future<void> _saveSettings() async {
|
||||
@@ -96,6 +97,42 @@ class _OtthonAppState extends State<OtthonApp> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadWebRuntimeConfig() async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
try {
|
||||
final response = await http
|
||||
.get(Uri.parse('/runtime-config.json'))
|
||||
.timeout(const Duration(seconds: 8));
|
||||
if (response.statusCode != 200) return;
|
||||
final payload = jsonDecode(response.body);
|
||||
if (payload is! Map<String, dynamic>) return;
|
||||
|
||||
final apiUrl = (payload['jarvisApiUrl'] as String?)?.trim();
|
||||
final apiToken = (payload['jarvisApiToken'] as String?)?.trim();
|
||||
final workspaceToken = (payload['workspaceToken'] as String?)?.trim();
|
||||
if (apiUrl == null ||
|
||||
apiUrl.isEmpty ||
|
||||
apiToken == null ||
|
||||
apiToken.isEmpty ||
|
||||
workspaceToken == null ||
|
||||
workspaceToken.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final nextJarvisSettings = jarvisSettings.copyWith(
|
||||
apiUrl: apiUrl,
|
||||
apiToken: apiToken,
|
||||
workspaceToken: workspaceToken,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => jarvisSettings = nextJarvisSettings);
|
||||
await _saveSettings();
|
||||
} catch (_) {
|
||||
// Weben is maradjon offline-képes: ha nincs runtime config, a helyi cache él tovább.
|
||||
}
|
||||
}
|
||||
|
||||
void _updatePeople(List<Person> nextPeople) {
|
||||
setState(() => people = nextPeople);
|
||||
_saveSettings();
|
||||
@@ -260,6 +297,7 @@ class _HomeShellState extends State<HomeShell> {
|
||||
int selected = 0;
|
||||
bool jarvisBackendOnline = false;
|
||||
bool workspaceSyncing = false;
|
||||
String? autoLoadedWorkspaceToken;
|
||||
Timer? jarvisBackendTimer;
|
||||
|
||||
@override
|
||||
@@ -272,12 +310,35 @@ class _HomeShellState extends State<HomeShell> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HomeShell oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.jarvisSettings.workspaceToken !=
|
||||
widget.jarvisSettings.workspaceToken ||
|
||||
oldWidget.jarvisSettings.apiToken != widget.jarvisSettings.apiToken ||
|
||||
oldWidget.jarvisSettings.apiUrl != widget.jarvisSettings.apiUrl) {
|
||||
_checkJarvisBackend();
|
||||
_downloadWebWorkspaceIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
jarvisBackendTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _downloadWebWorkspaceIfNeeded() {
|
||||
if (!kIsWeb) return;
|
||||
final workspaceToken = widget.jarvisSettings.workspaceToken?.trim();
|
||||
if (workspaceToken == null || workspaceToken.isEmpty) return;
|
||||
if (widget.jarvisSettings.apiToken.trim().isEmpty) return;
|
||||
if (autoLoadedWorkspaceToken == workspaceToken) return;
|
||||
|
||||
autoLoadedWorkspaceToken = workspaceToken;
|
||||
Future.microtask(() => _downloadWorkspaceState(silent: true));
|
||||
}
|
||||
|
||||
final events = <CalendarEvent>[
|
||||
CalendarEvent(
|
||||
'Csal\u00e1di reggeli',
|
||||
@@ -463,6 +524,20 @@ class _HomeShellState extends State<HomeShell> {
|
||||
],
|
||||
);
|
||||
|
||||
final content = wide
|
||||
? body
|
||||
: Column(
|
||||
children: [
|
||||
JarvisTopStatusBar(
|
||||
online: jarvisBackendOnline,
|
||||
workspaceToken: widget.jarvisSettings.workspaceToken,
|
||||
onSettings: _openJarvisSettings,
|
||||
onRefresh: _checkJarvisBackend,
|
||||
),
|
||||
Expanded(child: body),
|
||||
],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Row(
|
||||
@@ -774,7 +849,7 @@ class _HomeShellState extends State<HomeShell> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: body),
|
||||
Expanded(child: content),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -854,23 +929,24 @@ class _HomeShellState extends State<HomeShell> {
|
||||
_checkJarvisBackend();
|
||||
}
|
||||
|
||||
Future<void> _downloadWorkspaceState() async {
|
||||
final workspaceToken = widget.jarvisSettings.workspaceToken?.trim();
|
||||
Future<void> _downloadWorkspaceState({bool silent = false}) async {
|
||||
final settings = widget.jarvisSettings;
|
||||
final workspaceToken = settings.workspaceToken?.trim();
|
||||
if (workspaceToken == null || workspaceToken.isEmpty) {
|
||||
_showWorkspaceMessage('Adj meg k\u00f6z\u00f6s munkat\u00e9r tokent.');
|
||||
if (!silent) {
|
||||
_showWorkspaceMessage('Adj meg k\u00f6z\u00f6s munkat\u00e9r tokent.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => workspaceSyncing = true);
|
||||
try {
|
||||
final baseUri = Uri.parse(
|
||||
normalizeJarvisUrl(widget.jarvisSettings.apiUrl),
|
||||
);
|
||||
final baseUri = Uri.parse(normalizeJarvisUrl(settings.apiUrl));
|
||||
final response = await http
|
||||
.get(
|
||||
baseUri.resolve('/api/workspace/state'),
|
||||
headers: {
|
||||
'X-Otthon-Token': widget.jarvisSettings.apiToken,
|
||||
'X-Otthon-Token': settings.apiToken,
|
||||
'X-Workspace-Token': workspaceToken,
|
||||
},
|
||||
)
|
||||
@@ -915,9 +991,13 @@ class _HomeShellState extends State<HomeShell> {
|
||||
..addAll(nextStock);
|
||||
});
|
||||
widget.onPeopleChanged(nextPeople);
|
||||
_showWorkspaceMessage('K\u00f6z\u00f6s munkat\u00e9r let\u00f6ltve.');
|
||||
if (!silent) {
|
||||
_showWorkspaceMessage('K\u00f6z\u00f6s munkat\u00e9r let\u00f6ltve.');
|
||||
}
|
||||
} catch (error) {
|
||||
_showWorkspaceMessage('Workspace hiba: $error');
|
||||
if (!silent) {
|
||||
_showWorkspaceMessage('Workspace hiba: $error');
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => workspaceSyncing = false);
|
||||
}
|
||||
@@ -1505,6 +1585,96 @@ class CompactLanguageSwitcher extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class JarvisTopStatusBar extends StatelessWidget {
|
||||
const JarvisTopStatusBar({
|
||||
super.key,
|
||||
required this.online,
|
||||
required this.workspaceToken,
|
||||
required this.onSettings,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
final bool online;
|
||||
final String? workspaceToken;
|
||||
final VoidCallback onSettings;
|
||||
final VoidCallback onRefresh;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final workspace = workspaceToken?.trim();
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(12, 10, 12, 0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: .92),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: sage.withValues(alpha: .12)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: sage.withValues(alpha: .08),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: online ? Colors.green : Colors.redAccent,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (online ? Colors.green : Colors.redAccent).withValues(
|
||||
alpha: .28,
|
||||
),
|
||||
blurRadius: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'JARVIS ${online ? 'online' : 'offline'}'
|
||||
'${workspace == null || workspace.isEmpty ? '' : ' · $workspace'}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: ink,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Friss\u00edt\u00e9s',
|
||||
onPressed: onRefresh,
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size(30, 30),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'JARVIS be\u00e1ll\u00edt\u00e1sok',
|
||||
onPressed: onSettings,
|
||||
icon: const Icon(Icons.settings_outlined, size: 18),
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size(30, 30),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CompactLanguagePill extends StatelessWidget {
|
||||
const _CompactLanguagePill({
|
||||
required this.selected,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 436 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 11 KiB |
@@ -18,18 +18,18 @@
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
<meta name="description" content="KözösTér családi naptár és otthoni organizer.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="otthon_naptar">
|
||||
<meta name="apple-mobile-web-app-title" content="KözösTér">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>otthon_naptar</title>
|
||||
<title>KözösTér</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "otthon_naptar",
|
||||
"short_name": "otthon_naptar",
|
||||
"name": "KözösTér",
|
||||
"short_name": "KözösTér",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "A new Flutter project.",
|
||||
"background_color": "#F7F5EF",
|
||||
"theme_color": "#18332D",
|
||||
"description": "Családi naptár és otthoni organizer.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
|
||||