Add app lock, multi-account support, and redesigned account settings

App lock (local_auth) gates the app behind the OS's biometric/device
credential prompt via a new lock screen, layered on top of the existing
Nextcloud session rather than replacing it.

Multi-account support lets users save several Nextcloud logins and switch
between them (AccountStore, per-account browsing prefs, avatar swipe-to-
cycle); logout() now keeps an account's saved credentials/prefs intact so
it can be resumed from a "Continue as..." list, while removeAccount()
stays the only destructive path.

The account settings screen is redesigned around a single profile card
(avatar/name/email, server host, storage quota with groups) with
Refresh Cache/Logout/Remove actions, replacing the old separate "Server
Connection Info" section.

Also fixes activity-tab timestamps: the OCS Activity API reports time as
an ISO 8601 `datetime` string, not the numeric `timestamp` field the code
incorrectly assumed, which made every activity render as Jan 1 1970.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 16:09:22 -04:00
co-authored by Claude Sonnet 5
parent da22030d43
commit 06bc25ae1e
18 changed files with 800 additions and 156 deletions
+15 -6
View File
@@ -44,6 +44,15 @@ DateTime? _parseDavDate(String? raw) {
}
}
/// The OCS Activity API reports each event's time as an ISO 8601 string in
/// a `datetime` field (e.g. "2025-09-15T12:34:56+00:00") - there is no
/// numeric `timestamp` field despite that being a very easy name to guess.
/// Falls back to now() only if the field is missing/unparseable, so a
/// broken response reads as "just now" rather than the Unix epoch.
DateTime _parseActivityDateTime(dynamic raw) {
return DateTime.tryParse(raw?.toString() ?? '') ?? DateTime.now();
}
/// Normalizes a WebDAV `href` (which servers may return as either a bare
/// path or a full absolute URL) down to just its path, trailing slash
/// stripped, so hrefs from either form can be compared directly.
@@ -872,6 +881,9 @@ class NextcloudService {
final pct = total > 0 ? (used / total).clamp(0.0, 1.0) : 0.0;
final display = ocsData['displayname'] ?? username;
final email = ocsData['email'] ?? '$username@$serverUrl';
final groups = ((ocsData['groups'] as List?) ?? [])
.map((g) => g.toString())
.toList();
return NextcloudUserQuota(
usedBytes: used,
@@ -880,6 +892,7 @@ class NextcloudService {
userName: display,
email: email,
serverVersion: 'Nextcloud Server',
groups: groups,
);
} else {
throw Exception(
@@ -914,9 +927,7 @@ class NextcloudService {
id: (a['activity_id'] ?? '').toString(),
title: a['subject'] ?? 'Server Activity',
subject: a['message'] ?? (a['subject'] ?? ''),
timestamp: DateTime.fromMillisecondsSinceEpoch(
(a['timestamp'] as int? ?? 0) * 1000,
),
timestamp: _parseActivityDateTime(a['datetime']),
icon: Icons.cloud_outlined,
author: a['user'] ?? username,
);
@@ -1500,9 +1511,7 @@ class NextcloudService {
id: (a['activity_id'] ?? '').toString(),
title: a['subject'] ?? 'Activity',
subject: a['message'] ?? (a['subject'] ?? ''),
timestamp: DateTime.fromMillisecondsSinceEpoch(
(a['timestamp'] as int? ?? 0) * 1000,
),
timestamp: _parseActivityDateTime(a['datetime']),
icon: Icons.cloud_outlined,
author: a['user'] ?? username,
);