db: FINALLY ADD A DATABASE
This commit is contained in:
		
							parent
							
								
									3448a03c36
								
							
						
					
					
						commit
						c38770d68f
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -46,4 +46,5 @@ app.*.map.json
 | 
				
			|||||||
/android/app/release
 | 
					/android/app/release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Generated data classes
 | 
					# Generated data classes
 | 
				
			||||||
lib/data/generated/*
 | 
					lib/data/generated/*
 | 
				
			||||||
 | 
					lib/isar.g.dart
 | 
				
			||||||
@ -12,9 +12,9 @@ issues.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Clone using `git clone --recursive https://github.com/Polynomdivision/moxxyv2.git`.
 | 
					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
 | 
					Run `nix develop` to get a development shell. Before the first build, run `make data` and
 | 
				
			||||||
generate the data classes. After that, you can run the app using `flutter run` or build the
 | 
					`make data` and `flutter pub run build_runner build` to generate the data classes. After
 | 
				
			||||||
app with `flutter build`.
 | 
					that, you can run the app using `flutter run` or build the app with `flutter build`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								lib/db/conversation.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/db/conversation.dart
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -14,30 +14,45 @@ import 'ui/pages/settings/licenses.dart';
 | 
				
			|||||||
import 'ui/pages/settings/about.dart';
 | 
					import 'ui/pages/settings/about.dart';
 | 
				
			||||||
import 'ui/constants.dart';
 | 
					import 'ui/constants.dart';
 | 
				
			||||||
import 'repositories/roster.dart';
 | 
					import 'repositories/roster.dart';
 | 
				
			||||||
import 'repositories/roster.dart';
 | 
					import "repositories/conversation.dart";
 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:flutter_redux/flutter_redux.dart';
 | 
					 | 
				
			||||||
import 'package:redux/redux.dart';
 | 
					 | 
				
			||||||
import "redux/conversation/reducers.dart";
 | 
					import "redux/conversation/reducers.dart";
 | 
				
			||||||
import "redux/conversation/actions.dart";
 | 
					import "redux/conversation/actions.dart";
 | 
				
			||||||
 | 
					import "redux/conversations/middlewares.dart";
 | 
				
			||||||
import "redux/state.dart";
 | 
					import "redux/state.dart";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:get_it/get_it.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 single quotes with double quotes
 | 
				
			||||||
// TODO: Replace all Column(children: [ Padding(), Padding, ...]) with a
 | 
					// TODO: Replace all Column(children: [ Padding(), Padding, ...]) with a
 | 
				
			||||||
//       Padding(padding: ..., child: Column(children: [ ... ]))
 | 
					//       Padding(padding: ..., child: Column(children: [ ... ]))
 | 
				
			||||||
// TODO: Theme the switches
 | 
					// TODO: Theme the switches
 | 
				
			||||||
// TODO: Find a better way to do this
 | 
					// TODO: Find a better way to do this
 | 
				
			||||||
void main() {
 | 
					void main() async {
 | 
				
			||||||
  GetIt.I.registerSingleton<RosterRepository>(RosterRepository());
 | 
					  final isar = await openIsar(); 
 | 
				
			||||||
 | 
					  runApp(MyApp(isar: isar));
 | 
				
			||||||
  runApp(MyApp());
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyApp extends StatelessWidget {
 | 
					class MyApp extends StatelessWidget {
 | 
				
			||||||
  final Store<MoxxyState> store = Store(moxxyReducer,
 | 
					  final Store<MoxxyState> store = Store(
 | 
				
			||||||
      initialState: MoxxyState.initialState());
 | 
					    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
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return StoreProvider(
 | 
					    return StoreProvider(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,19 @@
 | 
				
			|||||||
import "dart:collection";
 | 
					import "dart:collection";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "package:isar/isar.dart";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Conversation {
 | 
					class Conversation {
 | 
				
			||||||
  final String title;
 | 
					  final String title;
 | 
				
			||||||
  final String lastMessageBody;
 | 
					  final String lastMessageBody;
 | 
				
			||||||
  final String avatarUrl;
 | 
					  final String avatarUrl;
 | 
				
			||||||
  final String jid;
 | 
					  final String jid;
 | 
				
			||||||
 | 
					  final int id;
 | 
				
			||||||
  final int unreadCounter;
 | 
					  final int unreadCounter;
 | 
				
			||||||
  final int lastChangeTimestamp; // NOTE: In milliseconds since Epoch or -1 if none has ever happened
 | 
					  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
 | 
					  // TODO: Maybe have a model for this, but this should be enough
 | 
				
			||||||
  final List<String> sharedMediaPaths;
 | 
					  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 }) {
 | 
					  Conversation copyWith({ String? lastMessageBody, int? unreadCounter, int unreadDelta = 0, List<String>? sharedMediaPaths, int? lastChangeTimestamp }) {
 | 
				
			||||||
    return Conversation(
 | 
					    return Conversation(
 | 
				
			||||||
@ -20,7 +23,8 @@ class Conversation {
 | 
				
			|||||||
      jid: this.jid,
 | 
					      jid: this.jid,
 | 
				
			||||||
      unreadCounter: (unreadCounter ?? this.unreadCounter) + unreadDelta,
 | 
					      unreadCounter: (unreadCounter ?? this.unreadCounter) + unreadDelta,
 | 
				
			||||||
      sharedMediaPaths: sharedMediaPaths ?? this.sharedMediaPaths,
 | 
					      sharedMediaPaths: sharedMediaPaths ?? this.sharedMediaPaths,
 | 
				
			||||||
      lastChangeTimestamp: lastChangeTimestamp ?? this.lastChangeTimestamp
 | 
					      lastChangeTimestamp: lastChangeTimestamp ?? this.lastChangeTimestamp,
 | 
				
			||||||
 | 
					      id: this.id
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ class AddMessageAction {
 | 
				
			|||||||
  final int timestamp;
 | 
					  final int timestamp;
 | 
				
			||||||
  final String from;
 | 
					  final String from;
 | 
				
			||||||
  final String jid;
 | 
					  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 });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,11 @@ class AddConversationAction {
 | 
				
			|||||||
  final String lastMessageBody;
 | 
					  final String lastMessageBody;
 | 
				
			||||||
  final String avatarUrl;
 | 
					  final String avatarUrl;
 | 
				
			||||||
  final String jid;
 | 
					  final String jid;
 | 
				
			||||||
 | 
					  final int id;
 | 
				
			||||||
 | 
					  final int unreadCounter;
 | 
				
			||||||
  final List<String> sharedMediaPaths;
 | 
					  final List<String> sharedMediaPaths;
 | 
				
			||||||
  final int lastChangeTimestamp;
 | 
					  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 });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								lib/redux/conversations/middlewares.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/redux/conversations/middlewares.dart
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -13,7 +13,8 @@ List<Conversation> conversationReducer(List<Conversation> state, dynamic action)
 | 
				
			|||||||
        // TODO: Correct?
 | 
					        // TODO: Correct?
 | 
				
			||||||
        unreadCounter: 0,
 | 
					        unreadCounter: 0,
 | 
				
			||||||
        sharedMediaPaths: action.sharedMediaPaths,
 | 
					        sharedMediaPaths: action.sharedMediaPaths,
 | 
				
			||||||
        lastChangeTimestamp: action.lastChangeTimestamp
 | 
					        lastChangeTimestamp: action.lastChangeTimestamp,
 | 
				
			||||||
 | 
					        id: action.id
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
  } else if (action is AddMessageAction) {
 | 
					  } else if (action is AddMessageAction) {
 | 
				
			||||||
    return state.map((element) {
 | 
					    return state.map((element) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								lib/repositories/account.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/repositories/account.dart
									
									
									
									
									
										Normal 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"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								lib/repositories/conversation.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								lib/repositories/conversation.dart
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -164,22 +164,26 @@ class ConversationPage extends StatelessWidget {
 | 
				
			|||||||
    double maxWidth = MediaQuery.of(context).size.width * 0.6;
 | 
					    double maxWidth = MediaQuery.of(context).size.width * 0.6;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return StoreConnector<MoxxyState, _MessageListViewModel>(
 | 
					    return StoreConnector<MoxxyState, _MessageListViewModel>(
 | 
				
			||||||
      converter: (store) => _MessageListViewModel(
 | 
					      converter: (store) {
 | 
				
			||||||
        conversation: store.state.conversations.firstWhere((item) => item.jid == jid),
 | 
					        Conversation conversation = store.state.conversations.firstWhere((item) => item.jid == jid);
 | 
				
			||||||
        showSendButton: store.state.conversationPageState.showSendButton,
 | 
					        return _MessageListViewModel(
 | 
				
			||||||
        setShowSendButton: (show) => store.dispatch(SetShowSendButtonAction(show: show)),
 | 
					          conversation: conversation,
 | 
				
			||||||
        showScrollToEndButton: store.state.conversationPageState.showScrollToEndButton,
 | 
					          showSendButton: store.state.conversationPageState.showSendButton,
 | 
				
			||||||
        setShowScrollToEndButton: (show) => store.dispatch(SetShowScrollToEndButtonAction(show: show)),
 | 
					          setShowSendButton: (show) => store.dispatch(SetShowSendButtonAction(show: show)),
 | 
				
			||||||
        sendMessage: (body) => store.dispatch(
 | 
					          showScrollToEndButton: store.state.conversationPageState.showScrollToEndButton,
 | 
				
			||||||
          // TODO
 | 
					          setShowScrollToEndButton: (show) => store.dispatch(SetShowScrollToEndButtonAction(show: show)),
 | 
				
			||||||
          AddMessageAction(
 | 
					          sendMessage: (body) => store.dispatch(
 | 
				
			||||||
            from: "UwU",
 | 
					            // TODO
 | 
				
			||||||
            timestamp: DateTime.now().millisecondsSinceEpoch,
 | 
					            AddMessageAction(
 | 
				
			||||||
            body: body,
 | 
					              from: "UwU",
 | 
				
			||||||
            jid: jid
 | 
					              timestamp: DateTime.now().millisecondsSinceEpoch,
 | 
				
			||||||
 | 
					              body: body,
 | 
				
			||||||
 | 
					              jid: jid,
 | 
				
			||||||
 | 
					              cid: conversation.id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        )
 | 
					        );
 | 
				
			||||||
      ),
 | 
					      },
 | 
				
			||||||
      builder: (context, viewModel) {
 | 
					      builder: (context, viewModel) {
 | 
				
			||||||
        return Scaffold(
 | 
					        return Scaffold(
 | 
				
			||||||
          appBar: BorderlessTopbar.avatarAndName(
 | 
					          appBar: BorderlessTopbar.avatarAndName(
 | 
				
			||||||
 | 
				
			|||||||
@ -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%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"
 | 
					          "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);
 | 
					      viewModel.addConversation(conversation);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@ -79,7 +81,8 @@ class NewConversationPage extends StatelessWidget {
 | 
				
			|||||||
              lastMessageBody: c.lastMessageBody,
 | 
					              lastMessageBody: c.lastMessageBody,
 | 
				
			||||||
              jid: c.jid,
 | 
					              jid: c.jid,
 | 
				
			||||||
              sharedMediaPaths: c.sharedMediaPaths,
 | 
					              sharedMediaPaths: c.sharedMediaPaths,
 | 
				
			||||||
              lastChangeTimestamp: -1
 | 
					              lastChangeTimestamp: -1,
 | 
				
			||||||
 | 
					              id: store.state.conversations.length
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          conversations: store.state.conversations,
 | 
					          conversations: store.state.conversations,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										129
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								pubspec.lock
									
									
									
									
									
								
							@ -7,14 +7,14 @@ packages:
 | 
				
			|||||||
      name: _fe_analyzer_shared
 | 
					      name: _fe_analyzer_shared
 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "31.0.0"
 | 
					    version: "22.0.0"
 | 
				
			||||||
  analyzer:
 | 
					  analyzer:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: analyzer
 | 
					      name: analyzer
 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.8.0"
 | 
					    version: "1.7.2"
 | 
				
			||||||
  archive:
 | 
					  archive:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -77,7 +77,7 @@ packages:
 | 
				
			|||||||
      name: build_resolvers
 | 
					      name: build_resolvers
 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.0.6"
 | 
					    version: "2.0.4"
 | 
				
			||||||
  build_runner:
 | 
					  build_runner:
 | 
				
			||||||
    dependency: "direct dev"
 | 
					    dependency: "direct dev"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -189,7 +189,14 @@ packages:
 | 
				
			|||||||
      name: dart_style
 | 
					      name: dart_style
 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    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:
 | 
					  fake_async:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -225,27 +232,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.0.0"
 | 
					    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:
 | 
					  flutter:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description: flutter
 | 
					    description: flutter
 | 
				
			||||||
@ -303,6 +289,13 @@ packages:
 | 
				
			|||||||
    description: flutter
 | 
					    description: flutter
 | 
				
			||||||
    source: sdk
 | 
					    source: sdk
 | 
				
			||||||
    version: "0.0.0"
 | 
					    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:
 | 
					  frontend_server_client:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -366,6 +359,27 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.0.3"
 | 
					    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:
 | 
					  js:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -387,13 +401,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.0.1"
 | 
					    version: "1.0.1"
 | 
				
			||||||
  lists:
 | 
					 | 
				
			||||||
    dependency: transitive
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      name: lists
 | 
					 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					 | 
				
			||||||
    source: hosted
 | 
					 | 
				
			||||||
    version: "1.0.1"
 | 
					 | 
				
			||||||
  logging:
 | 
					  logging:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -608,7 +615,7 @@ packages:
 | 
				
			|||||||
      name: source_gen
 | 
					      name: source_gen
 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.2.1"
 | 
					    version: "1.0.3"
 | 
				
			||||||
  source_map_stack_trace:
 | 
					  source_map_stack_trace:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -630,34 +637,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.8.1"
 | 
					    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:
 | 
					  stack_trace:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -686,20 +665,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.1.0"
 | 
					    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:
 | 
					  term_glyph:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -728,6 +693,13 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.4.2"
 | 
					    version: "0.4.2"
 | 
				
			||||||
 | 
					  time:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: time
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.1.0"
 | 
				
			||||||
  timing:
 | 
					  timing:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -742,13 +714,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.3.0"
 | 
					    version: "1.3.0"
 | 
				
			||||||
  unicode:
 | 
					 | 
				
			||||||
    dependency: transitive
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      name: unicode
 | 
					 | 
				
			||||||
      url: "https://pub.dartlang.org"
 | 
					 | 
				
			||||||
    source: hosted
 | 
					 | 
				
			||||||
    version: "0.3.1"
 | 
					 | 
				
			||||||
  url_launcher:
 | 
					  url_launcher:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,6 @@ dependencies:
 | 
				
			|||||||
    sdk: flutter
 | 
					    sdk: flutter
 | 
				
			||||||
  #cupertino_icons: ^1.0.2
 | 
					  #cupertino_icons: ^1.0.2
 | 
				
			||||||
  flutter_speed_dial: ^5.0.0+1
 | 
					  flutter_speed_dial: ^5.0.0+1
 | 
				
			||||||
  floor: ^1.2.0
 | 
					 | 
				
			||||||
  get_it: ^7.2.0
 | 
					  get_it: ^7.2.0
 | 
				
			||||||
  redux: ^5.0.0
 | 
					  redux: ^5.0.0
 | 
				
			||||||
  flutter_redux: ^0.8.2
 | 
					  flutter_redux: ^0.8.2
 | 
				
			||||||
@ -34,18 +33,17 @@ dependencies:
 | 
				
			|||||||
  file_picker: ^4.3.0
 | 
					  file_picker: ^4.3.0
 | 
				
			||||||
  image_cropping: ^0.0.8
 | 
					  image_cropping: ^0.0.8
 | 
				
			||||||
  path_provider: ^2.0.8
 | 
					  path_provider: ^2.0.8
 | 
				
			||||||
  #hive: ^2.0.5
 | 
					  isar: ^0.4.0
 | 
				
			||||||
  #hive_flutter: ^1.1.0
 | 
					  isar_flutter_libs: ^0.4.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
    sdk: flutter
 | 
					    sdk: flutter
 | 
				
			||||||
  flutter_lints: ^1.0.0
 | 
					  flutter_lints: ^1.0.0
 | 
				
			||||||
  floor_generator: ^1.2.0
 | 
					 | 
				
			||||||
  #hive_generator: ^1.1.1
 | 
					 | 
				
			||||||
  build_runner: ^2.1.2
 | 
					  build_runner: ^2.1.2
 | 
				
			||||||
  test:
 | 
					  test:
 | 
				
			||||||
  flutter_launcher_icons: ^0.9.2
 | 
					  flutter_launcher_icons: ^0.9.2
 | 
				
			||||||
 | 
					  isar_generator: ^0.4.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extra_licenses:
 | 
					extra_licenses:
 | 
				
			||||||
  undraw.co:
 | 
					  undraw.co:
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user