Files
noo/lib/services/app_lock_service.dart
ayushyaandClaude Sonnet 5 06bc25ae1e 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>
2026-09-16 16:09:22 -04:00

49 lines
2.0 KiB
Dart

import 'package:local_auth/local_auth.dart';
/// Thin wrapper around `local_auth`. Deliberately doesn't implement its own
/// PIN storage/hashing - `authenticate` delegates to whatever the OS
/// already has configured (fingerprint/face, or the device's own PIN/
/// pattern/password as a fallback when `biometricOnly` is false), which is
/// both simpler and more secure than reimplementing credential storage.
class AppLockService {
static final LocalAuthentication _auth = LocalAuthentication();
/// Whether this device can do *some* form of local auth - biometric
/// enrolled, or at minimum a device PIN/pattern/password set up.
static Future<bool> isDeviceSupported() async {
try {
final canCheckBiometrics = await _auth.canCheckBiometrics;
if (canCheckBiometrics) return true;
return await _auth.isDeviceSupported();
} catch (_) {
return false;
}
}
/// Prompts for biometric or device-credential auth. Returns false (never
/// throws) on cancellation, failure, or any platform error, so callers
/// can treat every non-true result the same way: stay locked/blocked.
static Future<bool> authenticate(String reason) async {
try {
return await _auth.authenticate(
localizedReason: reason,
options: const AuthenticationOptions(
biometricOnly: false,
stickyAuth: true,
// Default (true) requires an extra manual confirmation tap after
// a *passive* modality like face/iris recognizes you - Android's
// own guidance reserves that for confirming risky actions
// (a purchase); unlocking the app is exactly the "lower-risk"
// case they say to pass false for, and on some devices/Android
// versions it also determines whether face is offered as an
// option in the prompt at all, not just whether it needs a
// second tap to accept.
sensitiveTransaction: false,
),
);
} catch (_) {
return false;
}
}
}