Skip to content

Commit

Permalink
feat: Add support for multiple profiles.
Browse files Browse the repository at this point in the history
Also basically rewrote everything and moved to riverpod.
  • Loading branch information
iphydf committed Jan 26, 2025
1 parent 2f748a8 commit daa90fe
Show file tree
Hide file tree
Showing 44 changed files with 1,340 additions and 493 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
branches: [master]

env:
FLUTTER_VERSION: "3.27.1"
FLUTTER_VERSION: "3.27.3"

jobs:
common:
Expand Down Expand Up @@ -48,6 +48,12 @@ jobs:
- run: ./tools/prepare
- run: flutter test --coverage
- run: bash <(curl -s https://codecov.io/bash)
- name: Upload failed test goldens
if: failure()
uses: actions/upload-artifact@v4
with:
name: failed-test-goldens
path: test/failures

android-build:
runs-on: ubuntu-24.04
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:

env:
FLUTTER_VERSION: "3.27.1"
FLUTTER_VERSION: "3.27.3"

jobs:
deploy-pages:
Expand Down
11 changes: 7 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
Expand All @@ -26,14 +29,10 @@
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

Expand All @@ -44,3 +43,7 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release

# Generated files
*.freezed.dart
*.g.dart
17 changes: 9 additions & 8 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ ignoreWords: []
flagWords: []
import: []
ignorePaths:
- ".github/**"
- "build/**"
- "LICENSE*"
- .github/**
- build/**
- LICENSE*
- "*.json"
words:
- "btox"
- "iphydf"
- "kannywood"
- "robinlinden"
- "yanciman"
- btox
- iphydf
- kannywood
- nospam
- robinlinden
- yanciman
20 changes: 10 additions & 10 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (3.47.2):
- sqlite3/common (= 3.47.2)
- sqlite3/common (3.47.2)
- sqlite3/dbstatvtab (3.47.2):
- sqlite3 (3.48.0):
- sqlite3/common (= 3.48.0)
- sqlite3/common (3.48.0)
- sqlite3/dbstatvtab (3.48.0):
- sqlite3/common
- sqlite3/fts5 (3.47.2):
- sqlite3/fts5 (3.48.0):
- sqlite3/common
- sqlite3/perf-threadsafe (3.47.2):
- sqlite3/perf-threadsafe (3.48.0):
- sqlite3/common
- sqlite3/rtree (3.47.2):
- sqlite3/rtree (3.48.0):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.47.2)
- sqlite3 (~> 3.48.0)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
Expand All @@ -43,8 +43,8 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71
sqlite3_flutter_libs: 5235ce0546528db87927a3ef1baff8b7d5107f0e
sqlite3: 3da10a59910c809fb584a93aa46a3f05b785e12e
sqlite3_flutter_libs: c26d86af4ad88f1465dc4e07e6dc6931eef228e4

PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

Expand Down
11 changes: 0 additions & 11 deletions lib/btox_actions.dart

This file was deleted.

74 changes: 47 additions & 27 deletions lib/btox_app.dart
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
import 'package:btox/btox_state.dart';
import 'package:btox/contact_list_page.dart';
import 'package:btox/db/database.dart';
import 'package:btox/pages/contact_list_page.dart';
import 'package:btox/pages/create_profile_page.dart';
import 'package:btox/pages/select_profile_page.dart';
import 'package:btox/providers/database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final class BtoxApp extends StatelessWidget {
final Database database;
final Store<BtoxState> store;

const BtoxApp({
super.key,
required this.database,
required this.store,
});
final class BtoxApp extends ConsumerWidget {
const BtoxApp({super.key});

@override
Widget build(BuildContext context) {
return StoreProvider<BtoxState>(
store: store,
child: MaterialApp(
title: 'bTox',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
),
home: ContactListPage(
database: database,
),
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'bTox',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
),
home: ref.watch(databaseProvider).when(
loading: () => const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
),
error: (error, _) => Scaffold(
body: Center(
child: Text('Error: $error'),
),
),
data: (db) => StreamBuilder<List<Profile>>(
stream: db.watchProfiles(),
builder: (context, snapshot) {
final profiles = snapshot.data ?? const [];
if (profiles.isEmpty) {
return const CreateProfilePage();
}

final activeProfiles =
profiles.where((profile) => profile.active).toList();
if (activeProfiles.isEmpty) {
return SelectProfilePage(profiles: profiles);
}

return ContactListPage(
database: db,
profile: activeProfiles.first,
);
},
),
),
);
}
}
18 changes: 0 additions & 18 deletions lib/btox_reducer.dart

This file was deleted.

23 changes: 0 additions & 23 deletions lib/btox_state.dart

This file was deleted.

104 changes: 81 additions & 23 deletions lib/db/database.dart
Original file line number Diff line number Diff line change
@@ -1,39 +1,97 @@
import 'package:btox/models/id.dart';
import 'package:btox/models/persistence.dart';
import 'package:btox/models/profile_settings.dart';
import 'package:drift/drift.dart';

part 'database.g.dart';

class Contacts extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get publicKey => text()();
TextColumn get name => text().nullable()();
}
export 'package:drift/drift.dart' show Value;

class Messages extends Table {
IntColumn get contactId => integer().references(Contacts, #id)();
TextColumn get content => text()();
DateTimeColumn get timestamp => dateTime()();
}
part 'database.g.dart';

@DriftDatabase(tables: [Contacts, Messages])
@DriftDatabase(tables: [
Contacts,
Messages,
Profiles,
])
final class Database extends _$Database {
Database(super.e);

@override
int get schemaVersion => 2;

// TODO(robinlinden): Remove before first real release.
@override
MigrationStrategy get migration => destructiveFallback;

void addContact(ContactsCompanion entry) => into(contacts).insert(entry);
@override
int get schemaVersion => 1;

Future<void> activateProfile(Id<Profiles> id) async {
await deactivateProfiles();

await (update(profiles)..where((p) => p.id.equals(id.value)))
.write(ProfilesCompanion(
active: Value(true),
));
}

Future<void> deactivateProfiles() async {
await update(profiles).write(ProfilesCompanion(
active: Value(false),
));
}

Future<void> deleteProfile(Id<Profiles> id) async {
transaction(() async {
// Find all the contacts for the profile.
final contactsForProfile = await (select(contacts)
..where((c) => c.profileId.equals(id.value)))
.get();
// Delete all their messages.
batch((batch) {
for (final contact in contactsForProfile) {
batch.deleteWhere(
messages,
(m) => m.contactId.equals(contact.id.value),
);
}
});
// Then delete the contacts.
await (delete(contacts)..where((c) => c.profileId.equals(id.value))).go();
// Finally delete the profile.
await (delete(profiles)..where((p) => p.id.equals(id.value))).go();
});
}

Future<Id<Contacts>> addContact(ContactsCompanion entry) async =>
Id(await into(contacts).insert(entry));

Future<Id<Messages>> addMessage(MessagesCompanion entry) async =>
Id(await into(messages).insert(entry));

Future<Id<Profiles>> addProfile(ProfilesCompanion entry) async =>
Id(await into(profiles).insert(entry));

Future<void> updateProfileSettings(
Id<Profiles> id, ProfileSettings settings) async {
await (update(profiles)..where((p) => p.id.equals(id.value))).write(
ProfilesCompanion(
settings: Value(settings),
),
);
}

Stream<Contact> watchContact(Id<Contacts> id) =>
(select(contacts)..where((c) => c.id.equals(id.value))).watchSingle();

Stream<List<Contact>> watchContacts() => select(contacts).watch();
Stream<List<Contact>> watchContactsFor(Id<Profiles> id) => (select(contacts)
..where((c) => c.profileId.equals(id.value))
..orderBy([
(c) => OrderingTerm(expression: c.name),
]))
.watch();

Stream<Contact> watchContact(int id) =>
(select(contacts)..where((c) => c.id.equals(id))).watchSingle();
Stream<List<Message>> watchMessagesFor(Id<Contacts> id) =>
(select(messages)..where((m) => m.contactId.equals(id.value))).watch();

void addMessage(MessagesCompanion entry) => into(messages).insert(entry);
Stream<Profile> watchProfile(Id<Profiles> id) =>
(select(profiles)..where((p) => p.id.equals(id.value))).watchSingle();

Stream<List<Message>> watchMessagesFor(int id) =>
(select(messages)..where((m) => m.contactId.equals(id))).watch();
Stream<List<Profile>> watchProfiles() => select(profiles).watch();
}
Loading

0 comments on commit daa90fe

Please # to comment.