Add Google calendar API and workspace sync
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user