sign in with pocketbase working

This commit is contained in:
davmar
2026-09-09 19:59:38 +02:00
parent ae464ff304
commit c6d1fdb19e
27 changed files with 1267 additions and 312 deletions
+87
View File
@@ -0,0 +1,87 @@
/// PocketBase sign-in state. Local tx/bank data is unchanged by auth.
library;
import 'package:flutter/foundation.dart';
import 'package:pocketbase/pocketbase.dart';
import '../services/storage_service.dart';
import '../utils/constants.dart';
/// Owns the PocketBase client, session persistence, and login/logout.
class AuthController extends ChangeNotifier {
AuthController(this._storage);
final StorageService _storage;
PocketBase? _client;
PocketBase get client {
final pb = _client;
if (pb == null) {
throw StateError('AuthController.init() must be called before use.');
}
return pb;
}
bool get isLoggedIn => _client?.authStore.isValid ?? false;
RecordModel? get user => _client?.authStore.record;
String? get email {
final value = user?.getStringValue('email');
return value == null || value.isEmpty ? null : value;
}
String? get name {
final value = user?.getStringValue('name');
return value == null || value.isEmpty ? null : value;
}
String get displayName {
final n = name;
if (n != null && n.isNotEmpty) return n;
final e = email;
if (e != null && e.isNotEmpty) return e.split('@').first;
return 'Account';
}
String get initials {
final source = name ?? email ?? '?';
final parts = source.trim().split(RegExp(r'\s+'));
if (parts.length >= 2 && parts[0].isNotEmpty && parts[1].isNotEmpty) {
return '${parts[0][0]}${parts[1][0]}'.toUpperCase();
}
if (source.isNotEmpty) return source[0].toUpperCase();
return '?';
}
Future<void> init() async {
final store = AsyncAuthStore(
save: (String data) async {
if (data.isEmpty) {
await _storage.clearPocketBaseAuth();
} else {
await _storage.setPocketBaseAuth(data);
}
},
initial: _storage.pocketBaseAuth,
);
_client = PocketBase(kPocketBaseUrl, authStore: store);
_client!.authStore.onChange.listen((_) => notifyListeners());
notifyListeners();
}
Future<void> login({
required String email,
required String password,
}) async {
await client
.collection(kPocketBaseUsersCollection)
.authWithPassword(email.trim(), password);
notifyListeners();
}
Future<void> logout() async {
client.authStore.clear();
notifyListeners();
}
}
+44 -3
View File
@@ -278,11 +278,14 @@ class TxMetaController extends ChangeNotifier {
: (inheritedNotes ?? ''));
final recurring = stored?.recurring ?? template?.recurring ?? false;
final internalTransfer =
stored?.internalTransfer ?? template?.internalTransfer ?? false;
return TxMeta(
labelIds: List<String>.of(labelIds),
notes: notes,
recurring: recurring,
internalTransfer: internalTransfer,
);
}
@@ -570,6 +573,29 @@ class TxMetaController extends ChangeNotifier {
tx.exactKey: TxExactTemplate(
notes: existing.notes.isNotEmpty ? existing.notes : current.notes,
recurring: recurring,
internalTransfer:
existing.internalTransfer || current.internalTransfer,
),
};
_schedulePersist();
}
Future<void> setInternalTransfer(
BankTransaction tx,
bool internalTransfer,
) async {
final current = metaFor(tx);
_byId = {
..._byId,
tx.id: current.copyWith(internalTransfer: internalTransfer),
};
final existing = _exactTemplates[tx.exactKey] ?? const TxExactTemplate();
_exactTemplates = {
..._exactTemplates,
tx.exactKey: TxExactTemplate(
notes: existing.notes.isNotEmpty ? existing.notes : current.notes,
recurring: existing.recurring || current.recurring,
internalTransfer: internalTransfer,
),
};
_schedulePersist();
@@ -604,7 +630,12 @@ class TxMetaController extends ChangeNotifier {
if (!applying) {
final current = metaFor(tx);
final existing = _exactTemplates[tx.exactKey] ?? const TxExactTemplate();
if (trimmed.isEmpty && !existing.recurring && !current.recurring) {
final keepTemplate = trimmed.isNotEmpty ||
existing.recurring ||
current.recurring ||
existing.internalTransfer ||
current.internalTransfer;
if (!keepTemplate) {
_exactTemplates = Map.of(_exactTemplates)..remove(tx.exactKey);
} else {
_exactTemplates = {
@@ -612,6 +643,8 @@ class TxMetaController extends ChangeNotifier {
tx.exactKey: TxExactTemplate(
notes: trimmed,
recurring: existing.recurring || current.recurring,
internalTransfer:
existing.internalTransfer || current.internalTransfer,
),
};
}
@@ -641,6 +674,10 @@ class TxMetaController extends ChangeNotifier {
return setRecurring(tx, !metaFor(tx).recurring);
}
Future<void> toggleInternalTransfer(BankTransaction tx) {
return setInternalTransfer(tx, !metaFor(tx).internalTransfer);
}
/// Search description, notes, label name, and date.
List<BankTransaction> search(
List<BankTransaction> txs,
@@ -751,7 +788,9 @@ class TxMetaController extends ChangeNotifier {
final totals = <String, double>{};
for (final tx in txs) {
if (tx.amount >= 0) continue;
final ids = metaFor(tx).labelIds;
final meta = metaFor(tx);
if (meta.internalTransfer) continue;
final ids = meta.labelIds;
if (ids.isEmpty) continue;
final amount = tx.amount.abs();
for (final id in ids) {
@@ -766,7 +805,8 @@ class TxMetaController extends ChangeNotifier {
var total = 0.0;
for (final tx in txs) {
if (tx.amount >= 0) continue;
if (!metaFor(tx).hasLabel) continue;
final meta = metaFor(tx);
if (meta.internalTransfer || !meta.hasLabel) continue;
total += tx.amount.abs();
}
return total;
@@ -787,6 +827,7 @@ class TxMetaController extends ChangeNotifier {
}
for (final tx in txs) {
if (tx.amount >= 0) continue;
if (metaFor(tx).internalTransfer) continue;
final parsed = DateTime.tryParse(tx.date);
if (parsed == null) continue;
if (parsed.year != month.year || parsed.month != month.month) continue;