Add Google calendar API and workspace sync

This commit is contained in:
zsolt
2026-08-13 00:50:47 +02:00
parent 164f7ccad7
commit ab6ce48a83
11 changed files with 1119 additions and 677 deletions
+172
View File
@@ -187,6 +187,7 @@ class HomeShell extends StatefulWidget {
class _HomeShellState extends State<HomeShell> {
int selected = 0;
bool jarvisBackendOnline = false;
bool workspaceSyncing = false;
Timer? jarvisBackendTimer;
@override
@@ -567,6 +568,64 @@ class _HomeShellState extends State<HomeShell> {
],
),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: workspaceSyncing
? null
: _downloadWorkspaceState,
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: BorderSide(
color: Colors.white.withValues(
alpha: .22,
),
),
padding: const EdgeInsets.symmetric(
vertical: 6,
),
),
child: const Icon(
Icons.cloud_download_outlined,
size: 16,
),
),
),
const SizedBox(width: 6),
Expanded(
child: OutlinedButton(
onPressed: workspaceSyncing
? null
: _uploadWorkspaceState,
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: BorderSide(
color: Colors.white.withValues(
alpha: .22,
),
),
padding: const EdgeInsets.symmetric(
vertical: 6,
),
),
child: workspaceSyncing
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(
Icons.cloud_upload_outlined,
size: 16,
),
),
),
],
),
const SizedBox(height: 10),
Center(
child: Opacity(
@@ -641,6 +700,119 @@ class _HomeShellState extends State<HomeShell> {
_checkJarvisBackend();
}
Future<void> _downloadWorkspaceState() async {
final workspaceToken = widget.jarvisSettings.workspaceToken?.trim();
if (workspaceToken == null || workspaceToken.isEmpty) {
_showWorkspaceMessage('Adj meg k\u00f6z\u00f6s munkat\u00e9r tokent.');
return;
}
setState(() => workspaceSyncing = true);
try {
final baseUri = Uri.parse(normalizeJarvisUrl(widget.jarvisSettings.apiUrl));
final response = await http
.get(
baseUri.resolve('/api/workspace/state'),
headers: {
'X-Otthon-Token': widget.jarvisSettings.apiToken,
'X-Workspace-Token': workspaceToken,
},
)
.timeout(const Duration(seconds: 12));
if (response.statusCode != 200) {
throw Exception('workspace GET ${response.statusCode}');
}
final payload = jsonDecode(response.body);
if (payload is! Map<String, dynamic>) {
throw Exception('Invalid workspace response.');
}
final stateJson = payload['stateJson'] as String? ?? '{}';
final state = jsonDecode(stateJson);
if (state is! Map<String, dynamic>) {
throw Exception('Invalid workspace state.');
}
final nextPeople = state['people'] is List
? (state['people'] as List)
.whereType<Map<String, dynamic>>()
.map(Person.fromJson)
.toList()
: widget.people;
final nextShopping = state['shopping'] is List
? (state['shopping'] as List)
.whereType<Map<String, dynamic>>()
.map(ShoppingItem.fromJson)
.toList()
: shopping;
final nextStock = state['stock'] is List
? (state['stock'] as List)
.whereType<Map<String, dynamic>>()
.map(StockItem.fromJson)
.toList()
: stock;
setState(() {
shopping
..clear()
..addAll(nextShopping);
stock
..clear()
..addAll(nextStock);
});
widget.onPeopleChanged(nextPeople);
_showWorkspaceMessage('K\u00f6z\u00f6s munkat\u00e9r let\u00f6ltve.');
} catch (error) {
_showWorkspaceMessage('Workspace hiba: $error');
} finally {
if (mounted) setState(() => workspaceSyncing = false);
}
}
Future<void> _uploadWorkspaceState() async {
final workspaceToken = widget.jarvisSettings.workspaceToken?.trim();
if (workspaceToken == null || workspaceToken.isEmpty) {
_showWorkspaceMessage('Adj meg k\u00f6z\u00f6s munkat\u00e9r tokent.');
return;
}
setState(() => workspaceSyncing = true);
try {
final baseUri = Uri.parse(normalizeJarvisUrl(widget.jarvisSettings.apiUrl));
final stateJson = jsonEncode({
'version': 1,
'updatedAt': DateTime.now().toIso8601String(),
'people': widget.people.map((person) => person.toJson()).toList(),
'shopping': shopping.map((item) => item.toJson()).toList(),
'stock': stock.map((item) => item.toJson()).toList(),
});
final response = await http
.put(
baseUri.resolve('/api/workspace/state'),
headers: {
'Content-Type': 'application/json',
'X-Otthon-Token': widget.jarvisSettings.apiToken,
'X-Workspace-Token': workspaceToken,
},
body: jsonEncode({'stateJson': stateJson}),
)
.timeout(const Duration(seconds: 12));
if (response.statusCode != 200) {
throw Exception('workspace PUT ${response.statusCode}');
}
_showWorkspaceMessage('K\u00f6z\u00f6s munkat\u00e9r felt\u00f6ltve.');
} catch (error) {
_showWorkspaceMessage('Workspace hiba: $error');
} finally {
if (mounted) setState(() => workspaceSyncing = false);
}
}
void _showWorkspaceMessage(String message) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Future<JarvisSettings> _registerJarvisDevice(
JarvisSettings settings,
String deviceName,