Skip to content

Commit 15b8caf

Browse files
authored
Merge pull request #44 from stride-tasks/feature/multi-repository
Feature multi-repository
2 parents b7ec3ca + 89a9789 commit 15b8caf

26 files changed

+2051
-677
lines changed

app/lib/blocs/settings_bloc.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import 'package:flutter/material.dart';
55
import 'package:stride/blocs/log_bloc.dart';
66
import 'package:stride/bridge/api/logging.dart';
77
import 'package:stride/bridge/api/settings.dart';
8+
import 'package:uuid/uuid.dart';
9+
10+
extension CurrentRepositoryUuidOrFirstExt on Settings {
11+
UuidValue? currentRepositoryUuidOrFirst() =>
12+
currentRepository ?? repositories.firstOrNull?.uuid;
13+
}
814

915
@immutable
1016
abstract class SettingsEvent {}

app/lib/blocs/tasks_bloc.dart

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:stride/bridge/api/repository/git.dart';
1111
import 'package:stride/bridge/git/known_hosts.dart';
1212
import 'package:stride/bridge/task.dart';
1313
import 'package:stride/routes/encryption_key_route.dart';
14+
import 'package:uuid/uuid.dart';
1415

1516
@immutable
1617
abstract class TaskEvent {}
@@ -28,7 +29,8 @@ final class TaskRemoveEvent extends TaskEvent {
2829
}
2930

3031
final class TaskRemoveAllEvent extends TaskEvent {
31-
TaskRemoveAllEvent();
32+
final bool all;
33+
TaskRemoveAllEvent({this.all = false});
3234
}
3335

3436
final class TaskForcePushEvent extends TaskEvent {
@@ -76,7 +78,8 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
7678
final LogBloc logBloc;
7779
StreamSubscription<SettingsState>? settingsSubscription;
7880

79-
final TaskStorage repository;
81+
UuidValue? repositoryUuid;
82+
TaskStorage? storage;
8083
Filter? filter;
8184

8285
Timer? syncTimer;
@@ -89,6 +92,8 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
8992
);
9093
}
9194

95+
repositoryUuid ??= settingsBloc.settings.currentRepositoryUuidOrFirst();
96+
9297
settingsSubscription = settingsBloc.stream.listen((event) {
9398
if (event.settings.periodicSync) {
9499
syncTimer ??= Timer.periodic(
@@ -99,14 +104,32 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
99104
syncTimer?.cancel();
100105
syncTimer = null;
101106
}
107+
108+
final nextRepositoryUuid = event.settings.currentRepositoryUuidOrFirst();
109+
if (repositoryUuid != nextRepositoryUuid) {
110+
storage?.unload();
111+
storage = null;
112+
repositoryUuid = nextRepositoryUuid;
113+
add(TaskFetchEvent());
114+
}
102115
});
103116
}
104117

118+
TaskStorage? repository() {
119+
if (storage != null) {
120+
return storage;
121+
}
122+
if (repositoryUuid == null) {
123+
return null;
124+
}
125+
return storage = TaskStorage.load(uuid: repositoryUuid!);
126+
}
127+
105128
TaskBloc({
106-
required this.repository,
107129
required this.settingsBloc,
108130
required this.logBloc,
109131
required this.dialogBloc,
132+
this.storage,
110133
}) : super(const TaskState(tasks: [])) {
111134
_initializeSettingsStream();
112135

@@ -115,15 +138,15 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
115138
});
116139

117140
on<TaskAddEvent>((event, emit) async {
118-
await repository.add(task: event.task);
141+
await repository()?.add(task: event.task);
119142
emit(TaskState(tasks: await _tasks()));
120143
});
121144

122145
on<TaskRemoveEvent>((event, emit) async {
123146
if (event.task.status == TaskStatus.deleted) {
124-
await repository.removeByTask(task: event.task);
147+
await repository()?.removeByTask(task: event.task);
125148
} else {
126-
await repository.changeCategory(
149+
await repository()?.changeCategory(
127150
task: event.task,
128151
status: TaskStatus.deleted,
129152
);
@@ -133,25 +156,29 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
133156
});
134157

135158
on<TaskRemoveAllEvent>((event, emit) async {
136-
await repository.clear();
159+
if (event.all) {
160+
await repository()?.deleteAll();
161+
} else {
162+
await repository()?.clear();
163+
}
137164
emit(TaskState(tasks: await _tasks()));
138165
});
139166

140167
on<TaskForcePushEvent>((event, emit) async {
141-
await repository.push(force: true);
168+
await repository()?.push(force: true);
142169
emit(TaskState(tasks: await _tasks()));
143170
});
144171

145172
on<TaskChangeStatusEvent>((event, emit) async {
146-
await repository.changeCategory(
173+
await repository()?.changeCategory(
147174
task: event.task,
148175
status: event.status,
149176
);
150177
emit(TaskState(tasks: await _tasks()));
151178
});
152179

153180
on<TaskUpdateEvent>((event, emit) async {
154-
await repository.update(task: event.task);
181+
await repository()?.update(task: event.task);
155182
emit(TaskState(tasks: await _tasks()));
156183
});
157184

@@ -160,7 +187,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
160187
emit(TaskState(tasks: tasks, syncing: true));
161188

162189
try {
163-
await repository.sync_();
190+
await repository()?.sync_();
164191
} catch (error) {
165192
emit(TaskState(tasks: tasks, syncingError: error));
166193
rethrow;
@@ -174,20 +201,20 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
174201
});
175202

176203
on<TaskCheckoutBranchEvent>((event, emit) async {
177-
await repository.checkout();
204+
await repository()?.checkout();
178205
emit(TaskState(tasks: await _tasks()));
179206
});
180207
}
181208

182209
Future<List<Task>> _tasks() async {
183210
if (filter == null) {
184-
final tasks = await repository.tasksWithFilter(
211+
final tasks = await repository()?.tasksWithFilter(
185212
filter: await Filter.default_(),
186213
);
187-
return tasks;
214+
return tasks ?? [];
188215
} else {
189-
final tasks = await repository.tasksWithFilter(filter: filter!);
190-
return tasks;
216+
final tasks = await repository()?.tasksWithFilter(filter: filter!);
217+
return tasks ?? [];
191218
}
192219
}
193220

@@ -228,7 +255,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
228255
await Navigator.of(context).push<void>(
229256
MaterialPageRoute(
230257
builder: (context) => EncryptionKeyRoute(
231-
encryption: settingsBloc.settings.repository.encryption,
258+
repository: settingsBloc.settings.repositories.first,
232259
),
233260
),
234261
);

app/lib/bridge/api/repository/git.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import 'package:stride/bridge/task.dart';
2020
import 'package:stride/bridge/task/annotation.dart';
2121
import 'package:uuid/uuid.dart';
2222

23-
// These functions are ignored because they are not marked as `pub`: `append`, `clear`, `do_merge`, `fast_forward`, `filter`, `generate_iv`, `get_by_id`, `get_index`, `load`, `new`, `rebase`, `remove_task2`, `remove`, `resolve_conflicts`, `save`, `ssh_key`, `storage_mut`, `unload`, `update2`, `update`, `with_authentication`
23+
// These functions are ignored because they are not marked as `pub`: `append`, `clear`, `do_merge`, `fast_forward`, `filter`, `generate_iv`, `get_by_id`, `get_index`, `init_repotitory`, `load`, `new`, `rebase`, `remove_task2`, `remove`, `resolve_conflicts`, `save`, `ssh_key`, `storage_mut`, `unload`, `update2`, `update`, `with_authentication`
2424
// These types are ignored because they are not used by any `pub` functions: `DecryptedTask`, `LogIter`, `Storage`, `TaskDiff`
2525
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`
2626
// These functions are ignored (category: IgnoreBecauseExplicitAttribute): `pull`
@@ -52,6 +52,8 @@ abstract class TaskStorage implements RustOpaqueInterface, StrideRepository {
5252
@override
5353
Future<void> commit();
5454

55+
Future<void> deleteAll();
56+
5557
@override
5658
Future<String> export_();
5759

@@ -60,13 +62,17 @@ abstract class TaskStorage implements RustOpaqueInterface, StrideRepository {
6062
@override
6163
Future<void> import_({required String content});
6264

63-
Future<void> initRepotitory();
65+
static TaskStorage load({required UuidValue uuid}) =>
66+
RustLib.instance.api.crateApiRepositoryGitTaskStorageLoad(uuid: uuid);
6467

6568
Future<List<CommitItem>?> log({Oid? oid, int? n});
6669

67-
factory TaskStorage({required String path, required Settings settings}) =>
68-
RustLib.instance.api
69-
.crateApiRepositoryGitTaskStorageNew(path: path, settings: settings);
70+
factory TaskStorage(
71+
{required UuidValue repositoryUuid,
72+
required String path,
73+
required Settings settings}) =>
74+
RustLib.instance.api.crateApiRepositoryGitTaskStorageNew(
75+
repositoryUuid: repositoryUuid, path: path, settings: settings);
7076

7177
Future<void> push({required bool force});
7278

app/lib/bridge/api/settings.dart

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import 'package:uuid/uuid.dart';
2020

2121
part 'settings.freezed.dart';
2222

23-
// These functions are ignored because they are not marked as `pub`: `application_cache_path`, `application_document_path`, `application_log_path`, `application_support_path`, `default_author`, `default_branch_name`, `default_email`, `default_theme_mode`, `ssh_key_path`, `ssh_key`
23+
// These functions are ignored because they are not marked as `pub`: `application_cache_path`, `application_document_path`, `application_log_path`, `application_support_path`, `default_author`, `default_branch_name`, `default_email`, `default_repository_name`, `default_theme_mode`, `repository_mut`, `repository`, `ssh_key_path`, `ssh_key`
2424
// These types are ignored because they are not used by any `pub` functions: `State`
2525
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`
2626
// These functions are ignored (category: IgnoreBecauseOwnerTyShouldIgnore): `default`
@@ -97,11 +97,17 @@ class EncryptionKey {
9797
static Future<EncryptionKey> generate() =>
9898
RustLib.instance.api.crateApiSettingsEncryptionKeyGenerate();
9999

100-
static Future<bool> removeKey() =>
101-
RustLib.instance.api.crateApiSettingsEncryptionKeyRemoveKey();
100+
static Future<bool> removeKey({required UuidValue repositoryUuid}) => RustLib
101+
.instance.api
102+
.crateApiSettingsEncryptionKeyRemoveKey(repositoryUuid: repositoryUuid);
102103

103-
static Future<EncryptionKey> save({required String key}) =>
104-
RustLib.instance.api.crateApiSettingsEncryptionKeySave(key: key);
104+
static Future<EncryptionKey> save(
105+
{required UuidValue repositoryUuid, required String key}) =>
106+
RustLib.instance.api.crateApiSettingsEncryptionKeySave(
107+
repositoryUuid: repositoryUuid, key: key);
108+
109+
static String? validate({required String key}) =>
110+
RustLib.instance.api.crateApiSettingsEncryptionKeyValidate(key: key);
105111

106112
@override
107113
int get hashCode => key.hashCode;
@@ -118,6 +124,7 @@ class EncryptionKey {
118124
class Repository with _$Repository {
119125
const factory Repository({
120126
required UuidValue uuid,
127+
required String name,
121128
required String origin,
122129
required String author,
123130
required String email,
@@ -136,10 +143,11 @@ class Settings with _$Settings {
136143
const Settings._();
137144
const factory Settings.raw({
138145
required bool darkMode,
139-
required Repository repository,
140146
required bool periodicSync,
141147
required List<Filter> filters,
142148
FilterSelection? selectedFilter,
149+
UuidValue? currentRepository,
150+
required List<Repository> repositories,
143151
}) = _Settings;
144152
static Stream<Settings> createStream() =>
145153
RustLib.instance.api.crateApiSettingsSettingsCreateStream();

0 commit comments

Comments
 (0)