| j / k | Scroll down / up |
| gg | Scroll to top |
| G | Scroll to bottom |
| ⌘K / / | Open search |
| ? | Show this help |
| Esc | Close search / help |
| n / N | Next / previous section |
| h | Go to landing |
| d | Go to docs |
| : | Command mode — type section name |
Press ? or Esc to close
Uploads are tracked globally by MediaUploadCubit, a singleton registered at bootstrap. Any screen can start an upload and observe its progress by id.
final id = const Uuid().v4();
await sl<MediaUploadCubit>().upload(
filePath: picked.path,
folder: MediaFolder.avatars,
onSendProgress: (sent, total) {
// progress is already emitted into state by the cubit
},
); The cubit keeps a map of UploadItem keyed by id. Each item carries progress (0–1) and a status: uploading, success, or failure.
BlocBuilder<MediaUploadCubit, MediaUploadState>(
builder: (context, state) {
final item = state.items[id];
if (item == null) return const SizedBox.shrink();
if (item.isUploading) return AppCircularIndicator(value: item.progress);
if (item.isSuccess) return AppImage(src: item.url!);
return const AppEmptyState.error();
},
) Under the hood, MediaUploadStore calls ApiClient.postMultipart with a FormData body and forwards Dio's onSendProgress into the cubit.
Future<ApiResponse<String, void>> upload(String path, MediaFolder folder) =>
execute(
request: () => _client.postMultipart(
'/upload',
FormData.fromMap({'file': await MultipartFile.fromFile(path), 'folder': folder.name}),
onSendProgress: (sent, total) => _onProgress(sent / total),
),
parser: (d) => d['url'] as String,
empty: '',
);