db: FINALLY ADD A DATABASE

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

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,30 +14,45 @@ 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) {
return StoreProvider(

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,