db: FINALLY ADD A DATABASE

This commit is contained in:
PapaTutuWawa 2021-12-28 16:30:10 +01:00
parent 3448a03c36
commit c38770d68f
15 changed files with 288 additions and 123 deletions

1
.gitignore vendored
View File

@ -47,3 +47,4 @@ app.*.map.json
# Generated data classes
lib/data/generated/*
lib/isar.g.dart

View File

@ -12,9 +12,9 @@ issues.
Clone using `git clone --recursive https://github.com/Polynomdivision/moxxyv2.git`.
Run `nix develop` to get a development shell. Before the first build, run `make data` to
generate the data classes. After that, you can run the app using `flutter run` or build the
app with `flutter build`.
Run `nix develop` to get a development shell. Before the first build, run `make data` and
`make data` and `flutter pub run build_runner build` to generate the data classes. After
that, you can run the app using `flutter run` or build the app with `flutter build`.
## License

23
lib/db/conversation.dart Normal file
View File

@ -0,0 +1,23 @@
import "package:isar/isar.dart";
import "package:moxxyv2/isar.g.dart";
@Collection()
@Name("Conversation")
class Conversation {
int? id;
@Index(caseSensitive: false)
late String jid;
late String title;
late String avatarUrl;
late int lastChangeTimestamp;
late int unreadCounter;
late String lastMessageBody;
// sharedMediaPaths
}

View File

@ -14,29 +14,44 @@ import 'ui/pages/settings/licenses.dart';
import 'ui/pages/settings/about.dart';
import 'ui/constants.dart';
import 'repositories/roster.dart';
import 'repositories/roster.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import "repositories/conversation.dart";
import "redux/conversation/reducers.dart";
import "redux/conversation/actions.dart";
import "redux/conversations/middlewares.dart";
import "redux/state.dart";
import 'package:get_it/get_it.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import "package:isar/isar.dart";
import "isar.g.dart";
// TODO: Replace all single quotes with double quotes
// TODO: Replace all Column(children: [ Padding(), Padding, ...]) with a
// Padding(padding: ..., child: Column(children: [ ... ]))
// TODO: Theme the switches
// TODO: Find a better way to do this
void main() {
GetIt.I.registerSingleton<RosterRepository>(RosterRepository());
runApp(MyApp());
void main() async {
final isar = await openIsar();
runApp(MyApp(isar: isar));
}
class MyApp extends StatelessWidget {
final Store<MoxxyState> store = Store(moxxyReducer,
initialState: MoxxyState.initialState());
final Store<MoxxyState> store = Store(
moxxyReducer,
initialState: MoxxyState.initialState(),
middleware: [
conversationsMiddleware
]
);
final Isar isar;
MyApp({ required this.isar }) {
GetIt.I.registerSingleton<RosterRepository>(RosterRepository());
GetIt.I.registerSingleton<DatabaseRepository>(DatabaseRepository(isar: isar, store: this.store));
GetIt.I.get<DatabaseRepository>().loadConversations();
}
@override
Widget build(BuildContext context) {

View File

@ -1,16 +1,19 @@
import "dart:collection";
import "package:isar/isar.dart";
class Conversation {
final String title;
final String lastMessageBody;
final String avatarUrl;
final String jid;
final int id;
final int unreadCounter;
final int lastChangeTimestamp; // NOTE: In milliseconds since Epoch or -1 if none has ever happened
// TODO: Maybe have a model for this, but this should be enough
final List<String> sharedMediaPaths;
const Conversation({ required this.title, required this.lastMessageBody, required this.avatarUrl, required this.jid, required this.unreadCounter, required this.lastChangeTimestamp, required this.sharedMediaPaths });
const Conversation({ required this.title, required this.lastMessageBody, required this.avatarUrl, required this.jid, required this.unreadCounter, required this.lastChangeTimestamp, required this.sharedMediaPaths, required this.id });
Conversation copyWith({ String? lastMessageBody, int? unreadCounter, int unreadDelta = 0, List<String>? sharedMediaPaths, int? lastChangeTimestamp }) {
return Conversation(
@ -20,7 +23,8 @@ class Conversation {
jid: this.jid,
unreadCounter: (unreadCounter ?? this.unreadCounter) + unreadDelta,
sharedMediaPaths: sharedMediaPaths ?? this.sharedMediaPaths,
lastChangeTimestamp: lastChangeTimestamp ?? this.lastChangeTimestamp
lastChangeTimestamp: lastChangeTimestamp ?? this.lastChangeTimestamp,
id: this.id
);
}
}

View File

@ -16,6 +16,7 @@ class AddMessageAction {
final int timestamp;
final String from;
final String jid;
final int cid;
AddMessageAction({ required this.from, required this.body, required this.timestamp, required this.jid });
AddMessageAction({ required this.from, required this.body, required this.timestamp, required this.jid, required this.cid });
}

View File

@ -5,8 +5,11 @@ class AddConversationAction {
final String lastMessageBody;
final String avatarUrl;
final String jid;
final int id;
final int unreadCounter;
final List<String> sharedMediaPaths;
final int lastChangeTimestamp;
final bool triggeredByDatabase;
AddConversationAction({ required this.title, required this.lastMessageBody, required this.avatarUrl, required this.jid, required this.sharedMediaPaths, required this.lastChangeTimestamp });
AddConversationAction({ required this.title, required this.lastMessageBody, required this.avatarUrl, required this.jid, required this.sharedMediaPaths, required this.lastChangeTimestamp, required this.id, this.unreadCounter = 0, this.triggeredByDatabase = false });
}

View File

@ -0,0 +1,28 @@
import "package:moxxyv2/redux/state.dart";
import "package:moxxyv2/redux/conversations/actions.dart";
import "package:moxxyv2/redux/conversation/actions.dart";
import "package:moxxyv2/repositories/conversation.dart";
import "package:redux/redux.dart";
import "package:get_it/get_it.dart";
void conversationsMiddleware(Store<MoxxyState> store, action, NextDispatcher next) {
var repo = GetIt.I.get<DatabaseRepository>();
if (action is AddConversationAction && !action.triggeredByDatabase) {
if (repo.hasConversation(action.id)) {
// TODO
} else {
repo.addConversationFromAction(action);
}
} else if (action is AddMessageAction) {
if (repo.hasConversation(action.cid)) {
repo.updateConversation(id: action.cid, lastMessageBody: action.body, lastChangeTimestamp: action.timestamp);
} else {
// TODO
}
}
next(action);
}

View File

@ -13,7 +13,8 @@ List<Conversation> conversationReducer(List<Conversation> state, dynamic action)
// TODO: Correct?
unreadCounter: 0,
sharedMediaPaths: action.sharedMediaPaths,
lastChangeTimestamp: action.lastChangeTimestamp
lastChangeTimestamp: action.lastChangeTimestamp,
id: action.id
));
} else if (action is AddMessageAction) {
return state.map((element) {

View File

@ -0,0 +1,27 @@
import "package:hive/hive.dart";
import "package:redux/redux.dart";
import "package:moxxyv2/redux/state.dart";
import "package:moxxyv2/redux/account/actions.dart";
class AccountRepository {
final Store<MoxxyState> store;
AccountRepository({ required this.store });
void loadSettings() async {
var box = await Hive.openBox("account");
if (box.isNotEmpty) {
String? jid = await box.get("jid");
String? displayName = await box.get("displayName");
String? avatarUrl = await box.get("avatarUrl");
this.store.dispatch(SetDisplayNameAction(displayName: displayName!));
this.store.dispatch(SetAvatarAction(avatarUrl: avatarUrl!));
this.store.dispatch(SetJidAction(jid: jid!));
this.store.dispatch(NavigateToAction.replace("/conversations"));
} else {
this.store.dispatch(NavigateToAction.replace("/intro"));
}
}
}

View File

@ -0,0 +1,92 @@
import "dart:collection";
import "package:moxxyv2/db/conversation.dart" as db;
import "package:moxxyv2/models/conversation.dart" as model;
import "package:moxxyv2/redux/state.dart";
import "package:moxxyv2/redux/conversations/actions.dart";
import "package:isar/isar.dart";
import "package:redux/redux.dart";
import "package:moxxyv2/isar.g.dart";
// TODO: Either rename this ConversationRepository or put all the database stuff here and
// rename the file to database.dart
class DatabaseRepository {
final Isar isar;
final Store<MoxxyState> store;
final HashMap<int, db.Conversation> _cache = HashMap();
DatabaseRepository({ required this.isar, required this.store });
Future<void> loadConversations() async {
var conversations = await this.isar.conversations.where().findAll();
return conversations.forEach((c) {
this._cache[c.id!] = c;
this.store.dispatch(AddConversationAction(
id: c.id!,
title: c.title,
jid: c.jid,
avatarUrl: c.avatarUrl,
lastMessageBody: c.lastMessageBody,
unreadCounter: c.unreadCounter,
lastChangeTimestamp: c.lastChangeTimestamp,
sharedMediaPaths: [],
triggeredByDatabase: true
));
}
);
}
// TODO
bool hasConversation(int id) {
return this._cache.containsKey(id);
}
Future<void> updateConversation({ required int id, String? lastMessageBody, int? lastChangeTimestamp }) async {
print("updateConversation");
final c = this._cache[id]!;
if (lastMessageBody != null) {
c.lastMessageBody = lastMessageBody;
}
if (lastChangeTimestamp != null) {
c.lastChangeTimestamp = lastChangeTimestamp;
}
await this.isar.writeTxn((isar) async {
await isar.conversations.put(c);
print("DONE");
});
}
Future<void> addConversationFromAction(AddConversationAction action) async {
print("addConversationFromACtion");
final c = db.Conversation()
..jid = action.jid
..title = action.title
..avatarUrl = action.avatarUrl
..lastChangeTimestamp = action.lastChangeTimestamp
..unreadCounter = action.unreadCounter
..lastMessageBody = action.lastMessageBody;
await this.isar.writeTxn((isar) async {
await isar.conversations.put(c);
print("DONE");
});
}
Future<void> addConversation(model.Conversation conversation) async {
final c = db.Conversation()
..jid = conversation.jid
..title = conversation.title
..avatarUrl = conversation.avatarUrl
..lastChangeTimestamp = conversation.lastChangeTimestamp
..unreadCounter = conversation.unreadCounter
..lastMessageBody = conversation.lastMessageBody;
await this.isar.writeTxn((isar) async {
await isar.conversations.put(c);
});
}
}

View File

@ -164,22 +164,26 @@ class ConversationPage extends StatelessWidget {
double maxWidth = MediaQuery.of(context).size.width * 0.6;
return StoreConnector<MoxxyState, _MessageListViewModel>(
converter: (store) => _MessageListViewModel(
conversation: store.state.conversations.firstWhere((item) => item.jid == jid),
showSendButton: store.state.conversationPageState.showSendButton,
setShowSendButton: (show) => store.dispatch(SetShowSendButtonAction(show: show)),
showScrollToEndButton: store.state.conversationPageState.showScrollToEndButton,
setShowScrollToEndButton: (show) => store.dispatch(SetShowScrollToEndButtonAction(show: show)),
sendMessage: (body) => store.dispatch(
// TODO
AddMessageAction(
from: "UwU",
timestamp: DateTime.now().millisecondsSinceEpoch,
body: body,
jid: jid
converter: (store) {
Conversation conversation = store.state.conversations.firstWhere((item) => item.jid == jid);
return _MessageListViewModel(
conversation: conversation,
showSendButton: store.state.conversationPageState.showSendButton,
setShowSendButton: (show) => store.dispatch(SetShowSendButtonAction(show: show)),
showScrollToEndButton: store.state.conversationPageState.showScrollToEndButton,
setShowScrollToEndButton: (show) => store.dispatch(SetShowScrollToEndButtonAction(show: show)),
sendMessage: (body) => store.dispatch(
// TODO
AddMessageAction(
from: "UwU",
timestamp: DateTime.now().millisecondsSinceEpoch,
body: body,
jid: jid,
cid: conversation.id
)
)
)
),
);
},
builder: (context, viewModel) {
return Scaffold(
appBar: BorderlessTopbar.avatarAndName(

View File

@ -51,8 +51,10 @@ class NewConversationPage extends StatelessWidget {
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.donmai.us%2Fsample%2Fb6%2Fe6%2Fsample-b6e62e3edc1c6dfe6afdb54614b4a710.jpg&f=1&nofb=1",
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2F64.media.tumblr.com%2Fec84dc5628ca3d8405374b85a51c7328%2Fbb0fc871a5029726-04%2Fs1280x1920%2Ffa6d89e8a2c2f3ce17465d328c2fe0ed6c951f01.jpg&f=1&nofb=1"
],
lastChangeTimestamp: TIMESTAMP_NEVER
lastChangeTimestamp: TIMESTAMP_NEVER,
id: viewModel.conversations.length
);
viewModel.addConversation(conversation);
}
@ -79,7 +81,8 @@ class NewConversationPage extends StatelessWidget {
lastMessageBody: c.lastMessageBody,
jid: c.jid,
sharedMediaPaths: c.sharedMediaPaths,
lastChangeTimestamp: -1
lastChangeTimestamp: -1,
id: store.state.conversations.length
)
),
conversations: store.state.conversations,

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "31.0.0"
version: "22.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.0"
version: "1.7.2"
archive:
dependency: transitive
description:
@ -77,7 +77,7 @@ packages:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
version: "2.0.4"
build_runner:
dependency: "direct dev"
description:
@ -189,7 +189,14 @@ packages:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
version: "2.1.1"
dartx:
dependency: transitive
description:
name: dartx
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.1"
fake_async:
dependency: transitive
description:
@ -225,27 +232,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
floor:
dependency: "direct main"
description:
name: floor
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
floor_annotation:
dependency: transitive
description:
name: floor_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
floor_generator:
dependency: "direct dev"
description:
name: floor_generator
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@ -303,6 +289,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.3"
frontend_server_client:
dependency: transitive
description:
@ -366,6 +359,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
isar:
dependency: "direct main"
description:
name: isar
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
isar_flutter_libs:
dependency: "direct main"
description:
name: isar_flutter_libs
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
isar_generator:
dependency: "direct dev"
description:
name: isar_generator
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
js:
dependency: transitive
description:
@ -387,13 +401,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
lists:
dependency: transitive
description:
name: lists
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
logging:
dependency: transitive
description:
@ -608,7 +615,7 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
version: "1.0.3"
source_map_stack_trace:
dependency: transitive
description:
@ -630,34 +637,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sqflite_common_ffi:
dependency: transitive
description:
name: sqflite_common_ffi
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sqlite3:
dependency: transitive
description:
name: sqlite3
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
stack_trace:
dependency: transitive
description:
@ -686,20 +665,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
strings:
dependency: transitive
description:
name: strings
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.2"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
term_glyph:
dependency: transitive
description:
@ -728,6 +693,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
time:
dependency: transitive
description:
name: time
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
timing:
dependency: transitive
description:
@ -742,13 +714,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
unicode:
dependency: transitive
description:
name: unicode
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.1"
url_launcher:
dependency: "direct main"
description:

View File

@ -23,7 +23,6 @@ dependencies:
sdk: flutter
#cupertino_icons: ^1.0.2
flutter_speed_dial: ^5.0.0+1
floor: ^1.2.0
get_it: ^7.2.0
redux: ^5.0.0
flutter_redux: ^0.8.2
@ -34,18 +33,17 @@ dependencies:
file_picker: ^4.3.0
image_cropping: ^0.0.8
path_provider: ^2.0.8
#hive: ^2.0.5
#hive_flutter: ^1.1.0
isar: ^0.4.0
isar_flutter_libs: ^0.4.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
floor_generator: ^1.2.0
#hive_generator: ^1.1.1
build_runner: ^2.1.2
test:
flutter_launcher_icons: ^0.9.2
isar_generator: ^0.4.0
extra_licenses:
undraw.co: