mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 03:15:48 -06:00
Removed unused files, code cleanup
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
|
||||
class PaperlessServerInformationCubit
|
||||
extends Cubit<PaperlessServerInformationState> {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
class PaperlessStatisticsState {
|
||||
final bool isLoaded;
|
||||
final PaperlessServerStatisticsModel? statistics;
|
||||
|
||||
PaperlessStatisticsState({
|
||||
required this.isLoaded,
|
||||
this.statistics,
|
||||
});
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
extension DateComparisons on DateTime {
|
||||
bool isEqualToIgnoringDate(DateTime other) {
|
||||
return day == other.day && month == other.month && year == other.year;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
|
||||
extension AddressableHydratedStorage on Storage {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
|
||||
extension ClientCertificateHandlingSecurityContext on SecurityContext {
|
||||
SecurityContext withClientCertificate(ClientCertificate? clientCertificate) {
|
||||
if (clientCertificate == null) return this;
|
||||
return this
|
||||
..usePrivateKeyBytes(
|
||||
clientCertificate.bytes,
|
||||
password: clientCertificate.passphrase,
|
||||
)
|
||||
..useCertificateChainBytes(
|
||||
clientCertificate.bytes,
|
||||
password: clientCertificate.passphrase,
|
||||
)
|
||||
..setTrustedCertificatesBytes(
|
||||
clientCertificate.bytes,
|
||||
password: clientCertificate.passphrase,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
extension SizeLimitedString on String {
|
||||
String withLengthLimitedTo(int length, [String overflow = "..."]) {
|
||||
return this.length > length
|
||||
? '${substring(0, length - overflow.length)}$overflow'
|
||||
: this;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WelcomeIntroSlide extends StatelessWidget {
|
||||
const WelcomeIntroSlide({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
"Welcome to Paperless Mobile!",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
"Manage, share and create documents on the go without any compromises!",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Theme.of(context).hintColor),
|
||||
),
|
||||
),
|
||||
Align(child: Image.asset("assets/logos/paperless_logo_green.png")),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.d
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:badges/badges.dart' as b;
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class SimilarDocumentsView extends StatefulWidget {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentUploadPreparationPage extends StatefulWidget {
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentEditPage extends StatefulWidget {
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'package:paperless_mobile/features/settings/model/application_settings_st
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentFilterIntent {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
class OrderByDropdown extends StatefulWidget {
|
||||
static const fkOrderBy = "orderBy";
|
||||
const OrderByDropdown({super.key});
|
||||
|
||||
@override
|
||||
State<OrderByDropdown> createState() => _OrderByDropdownState();
|
||||
}
|
||||
|
||||
class _OrderByDropdownState extends State<OrderByDropdown> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormBuilderDropdown<SortField>(
|
||||
name: OrderByDropdown.fkOrderBy,
|
||||
items: const [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_selection_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
|
||||
class DocumentsPageAppBar extends StatefulWidget with PreferredSizeWidget {
|
||||
final List<Widget> actions;
|
||||
final bool isOffline;
|
||||
|
||||
const DocumentsPageAppBar({
|
||||
super.key,
|
||||
required this.isOffline,
|
||||
this.actions = const [],
|
||||
});
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||
@override
|
||||
State<DocumentsPageAppBar> createState() => _DocumentsPageAppBarState();
|
||||
}
|
||||
|
||||
class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const savedViewWidgetHeight = 48.0;
|
||||
final flexibleAreaHeight = kToolbarHeight -
|
||||
16 +
|
||||
savedViewWidgetHeight +
|
||||
(widget.isOffline ? 24 : 0);
|
||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, documentsState) {
|
||||
final hasSelection = documentsState.selection.isNotEmpty;
|
||||
// final PreferredSize? loadingWidget = documentsState.isLoading
|
||||
// ? const PreferredSize(
|
||||
// child: LinearProgressIndicator(),
|
||||
// preferredSize: Size.fromHeight(4.0),
|
||||
// )
|
||||
// : null;
|
||||
if (hasSelection) {
|
||||
return SliverAppBar(
|
||||
// bottom: loadingWidget,
|
||||
expandedHeight: kToolbarHeight + flexibleAreaHeight,
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(
|
||||
false,
|
||||
documentsState.filter,
|
||||
savedViewWidgetHeight,
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => context.read<DocumentsCubit>().resetSelection(),
|
||||
),
|
||||
title: Text(
|
||||
'${documentsState.selection.length} ${S.of(context).documentsSelectedText}'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => _onDelete(context, documentsState),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return SliverAppBar(
|
||||
// bottom: loadingWidget,
|
||||
expandedHeight: kToolbarHeight + flexibleAreaHeight,
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(
|
||||
true,
|
||||
documentsState.filter,
|
||||
savedViewWidgetHeight,
|
||||
),
|
||||
title: Text(
|
||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(documentsState.count)})',
|
||||
),
|
||||
actions: [
|
||||
...widget.actions,
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFlexibleArea(
|
||||
bool enabled,
|
||||
DocumentFilter filter,
|
||||
double savedViewHeight,
|
||||
) {
|
||||
return FlexibleSpaceBar(
|
||||
background: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.isOffline) const OfflineBanner(),
|
||||
SavedViewSelectionWidget(
|
||||
height: savedViewHeight,
|
||||
enabled: enabled,
|
||||
currentFilter: filter,
|
||||
).paddedSymmetrically(horizontal: 8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onDelete(BuildContext context, DocumentsState documentsState) async {
|
||||
final shouldDelete = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
BulkDeleteConfirmationDialog(state: documentsState)) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
await context
|
||||
.read<DocumentsCubit>()
|
||||
.bulkRemove(documentsState.selection);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDocumentCount(int count) {
|
||||
return count > 99 ? "99+" : count.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class ScrollListener extends ChangeNotifier {
|
||||
double top = 0;
|
||||
double _last = 0;
|
||||
|
||||
ScrollListener.initialise(ScrollController controller, [double height = 56]) {
|
||||
controller.addListener(() {
|
||||
final current = controller.offset;
|
||||
top += _last - current;
|
||||
if (top <= -height) top = -height;
|
||||
if (top >= 0) top = 0;
|
||||
_last = current;
|
||||
if (top <= 0 && top >= -height) notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:paperless_mobile/core/repository/state/repository_state.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:paperless_mobile/core/translation/matching_algorithm_localizatio
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class SubmitButtonConfig<T extends Label> {
|
||||
|
||||
@@ -29,7 +29,8 @@ import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/helpers/file_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
@@ -136,7 +137,7 @@ class _HomePageState extends State<HomePage> {
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
} catch (e) {
|
||||
Fluttertoast.showToast(
|
||||
msg: S.of(context).receiveSharedFilePermissionDeniedMessage,
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
@@ -236,7 +237,6 @@ class _HomePageState extends State<HomePage> {
|
||||
builder: (context, sizingInformation) {
|
||||
if (!sizingInformation.isMobile) {
|
||||
return Scaffold(
|
||||
key: rootScaffoldKey,
|
||||
drawer: const AppDrawer(),
|
||||
body: Row(
|
||||
children: [
|
||||
@@ -257,7 +257,6 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
key: rootScaffoldKey,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
elevation: 4.0,
|
||||
selectedIndex: _currentIndex,
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class InboxPage extends StatefulWidget {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/view/pages/linked_documents_page.dart';
|
||||
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
||||
|
||||
class LabelItem<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
@@ -37,7 +38,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
||||
return TextButton.icon(
|
||||
label: const Icon(Icons.link),
|
||||
icon: Text(_formatDocumentCount(label.documentCount)),
|
||||
icon: Text(formatMaxCount(label.documentCount)),
|
||||
onPressed: (label.documentCount ?? 0) == 0
|
||||
? null
|
||||
: () {
|
||||
@@ -57,11 +58,4 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDocumentCount(int? count) {
|
||||
if ((count ?? 0) > 99) {
|
||||
return "99+";
|
||||
}
|
||||
return (count ?? 0).toString().padLeft(3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
|
||||
|
||||
class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
@@ -5,8 +5,8 @@ import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class LinkedDocumentsPage extends StatefulWidget {
|
||||
@@ -9,6 +9,7 @@ import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
|
||||
@@ -17,17 +17,18 @@ import 'package:paperless_mobile/core/repository/state/impl/document_type_reposi
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/widgets/scanned_image_item.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/helpers/file_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/permission_helpers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
@@ -218,7 +219,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
mainAxisSpacing: 10,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return GridImageItemWidget(
|
||||
return ScannedImageItem(
|
||||
file: scans[index],
|
||||
onDelete: () async {
|
||||
try {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:photo_view/photo_view.dart';
|
||||
typedef DeleteCallback = void Function();
|
||||
typedef OnImageOperation = void Function(File);
|
||||
|
||||
class GridImageItemWidget extends StatefulWidget {
|
||||
class ScannedImageItem extends StatefulWidget {
|
||||
final File file;
|
||||
final DeleteCallback onDelete;
|
||||
//final OnImageOperation onImageOperation;
|
||||
@@ -15,7 +15,7 @@ class GridImageItemWidget extends StatefulWidget {
|
||||
final int index;
|
||||
final int totalNumberOfFiles;
|
||||
|
||||
const GridImageItemWidget({
|
||||
const ScannedImageItem({
|
||||
Key? key,
|
||||
required this.file,
|
||||
required this.onDelete,
|
||||
@@ -25,10 +25,10 @@ class GridImageItemWidget extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GridImageItemWidget> createState() => _GridImageItemWidgetState();
|
||||
State<ScannedImageItem> createState() => _ScannedImageItemState();
|
||||
}
|
||||
|
||||
class _GridImageItemWidgetState extends State<GridImageItemWidget> {
|
||||
class _ScannedImageItemState extends State<ScannedImageItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
typedef OnImageScannedCallback = void Function(File);
|
||||
|
||||
class ScannerWidget extends StatefulWidget {
|
||||
final OnImageScannedCallback onImageScannedCallback;
|
||||
const ScannerWidget({
|
||||
Key? key,
|
||||
required this.onImageScannedCallback,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ScannerWidgetState createState() => _ScannerWidgetState();
|
||||
}
|
||||
|
||||
class _ScannerWidgetState extends State<ScannerWidget> {
|
||||
List<File> documents = List.empty(growable: true);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Scan document")),
|
||||
body: FutureBuilder<bool>(
|
||||
future: askForPermission(Permission.camera),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snapshot.data!) {
|
||||
return Container();
|
||||
}
|
||||
return const Center(
|
||||
child: Text("No camera permissions, please enable in settings!"),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class UploadDialog extends StatefulWidget {
|
||||
const UploadDialog({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UploadDialog> createState() => _UploadDialogState();
|
||||
}
|
||||
|
||||
class _UploadDialogState extends State<UploadDialog> {
|
||||
late TextEditingController _controller;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final DateFormat format = DateFormat("yyyy_MM_dd_hh_mm_ss");
|
||||
final today = format.format(DateTime.now());
|
||||
_controller = TextEditingController.fromValue(
|
||||
TextEditingValue(text: "Scan_$today.pdf"));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Upload to paperless-ng"),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
validator: (text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return "Filename must be specified!";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
var txt = _controller.text;
|
||||
if (!txt.endsWith(".pdf")) {
|
||||
txt += ".pdf";
|
||||
}
|
||||
Navigator.of(context).pop(txt);
|
||||
},
|
||||
child: const Text("Upload"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'tasks_state.dart';
|
||||
|
||||
class TasksCubit extends Cubit<TasksState> {
|
||||
TasksCubit() : super(TasksInitial());
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
part of 'tasks_cubit.dart';
|
||||
|
||||
abstract class TasksState extends Equatable {
|
||||
const TasksState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TasksInitial extends TasksState {}
|
||||
3
lib/helpers/file_helpers.dart
Normal file
3
lib/helpers/file_helpers.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
String extractFilenameFromPath(String path) {
|
||||
return path.split(RegExp('[./]')).reversed.skip(1).first;
|
||||
}
|
||||
6
lib/helpers/format_helpers.dart
Normal file
6
lib/helpers/format_helpers.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
String formatMaxCount(int? count, [int maxCount = 99]) {
|
||||
if ((count ?? 0) > maxCount) {
|
||||
return "$maxCount+";
|
||||
}
|
||||
return (count ?? 0).toString().padLeft(maxCount.toString().length);
|
||||
}
|
||||
38
lib/helpers/image_helpers.dart
Normal file
38
lib/helpers/image_helpers.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
// Taken from https://github.com/flutter/flutter/issues/26127#issuecomment-782083060
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
Future<void> loadImage(ImageProvider provider) {
|
||||
final config = ImageConfiguration(
|
||||
bundle: rootBundle,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
platform: defaultTargetPlatform,
|
||||
);
|
||||
final Completer<void> completer = Completer();
|
||||
final ImageStream stream = provider.resolve(config);
|
||||
|
||||
late final ImageStreamListener listener;
|
||||
|
||||
listener = ImageStreamListener((ImageInfo image, bool sync) {
|
||||
debugPrint("Image ${image.debugLabel} finished loading");
|
||||
completer.complete();
|
||||
stream.removeListener(listener);
|
||||
}, onError: (dynamic exception, StackTrace? stackTrace) {
|
||||
completer.complete();
|
||||
stream.removeListener(listener);
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
context: ErrorDescription('image failed to load'),
|
||||
library: 'image resource service',
|
||||
exception: exception,
|
||||
stack: stackTrace,
|
||||
silent: true,
|
||||
));
|
||||
});
|
||||
|
||||
stream.addListener(listener);
|
||||
return completer.future;
|
||||
}
|
||||
111
lib/helpers/message_helpers.dart
Normal file
111
lib/helpers/message_helpers.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/github_issue_service.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class SnackBarActionConfig {
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
SnackBarActionConfig({
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
});
|
||||
}
|
||||
|
||||
void showSnackBar(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
String? details,
|
||||
SnackBarActionConfig? action,
|
||||
Duration duration = const Duration(seconds: 5),
|
||||
}) {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: (details != null)
|
||||
? RichText(
|
||||
maxLines: 5,
|
||||
text: TextSpan(
|
||||
text: message,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "\n$details",
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(message),
|
||||
action: action != null
|
||||
? SnackBarAction(
|
||||
label: action.label,
|
||||
onPressed: action.onPressed,
|
||||
textColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||
)
|
||||
: null,
|
||||
duration: duration,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showGenericError(
|
||||
BuildContext context,
|
||||
dynamic error, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(
|
||||
context,
|
||||
error.toString(),
|
||||
action: SnackBarActionConfig(
|
||||
label: S.of(context).errorReportLabel,
|
||||
onPressed: () => GithubIssueService.createIssueFromError(
|
||||
context,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
),
|
||||
);
|
||||
log(
|
||||
"An error has occurred.",
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
void showLocalizedError(
|
||||
BuildContext context,
|
||||
String localizedMessage, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(context, localizedMessage);
|
||||
log(localizedMessage, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
void showErrorMessage(
|
||||
BuildContext context,
|
||||
PaperlessServerException error, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(
|
||||
context,
|
||||
translateError(context, error.code),
|
||||
details: error.details,
|
||||
);
|
||||
log(
|
||||
"An error has occurred.",
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
14
lib/helpers/permission_helpers.dart
Normal file
14
lib/helpers/permission_helpers.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
Future<bool> askForPermission(Permission permission) async {
|
||||
final status = await permission.request();
|
||||
log("Permission requested, new status is $status");
|
||||
// If user has permanently declined permission, open settings.
|
||||
if (status == PermissionStatus.permanentlyDenied) {
|
||||
await openAppSettings();
|
||||
}
|
||||
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
182
lib/util.dart
182
lib/util.dart
@@ -1,183 +1 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/service/github_issue_service.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
final dateFormat = DateFormat("yyyy-MM-dd");
|
||||
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
class SnackBarActionConfig {
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
SnackBarActionConfig({
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
});
|
||||
}
|
||||
|
||||
void showSnackBar(
|
||||
BuildContext context,
|
||||
String message, {
|
||||
String? details,
|
||||
SnackBarActionConfig? action,
|
||||
Duration duration = const Duration(seconds: 5),
|
||||
}) {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: (details != null)
|
||||
? RichText(
|
||||
maxLines: 5,
|
||||
text: TextSpan(
|
||||
text: message,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "\n$details",
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(message),
|
||||
action: action != null
|
||||
? SnackBarAction(
|
||||
label: action.label,
|
||||
onPressed: action.onPressed,
|
||||
textColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||
)
|
||||
: null,
|
||||
duration: duration,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showGenericError(
|
||||
BuildContext context,
|
||||
dynamic error, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(
|
||||
context,
|
||||
error.toString(),
|
||||
action: SnackBarActionConfig(
|
||||
label: S.of(context).errorReportLabel,
|
||||
onPressed: () => GithubIssueService.createIssueFromError(
|
||||
context,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
),
|
||||
);
|
||||
log(
|
||||
"An error has occurred.",
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
void showLocalizedError(
|
||||
BuildContext context,
|
||||
String localizedMessage, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(context, localizedMessage);
|
||||
log(localizedMessage, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
void showErrorMessage(
|
||||
BuildContext context,
|
||||
PaperlessServerException error, [
|
||||
StackTrace? stackTrace,
|
||||
]) {
|
||||
showSnackBar(
|
||||
context,
|
||||
translateError(context, error.code),
|
||||
details: error.details,
|
||||
);
|
||||
log(
|
||||
"An error has occurred.",
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
time: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
bool isNotNull(dynamic value) {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
String formatDate(DateTime date) {
|
||||
return dateFormat.format(date);
|
||||
}
|
||||
|
||||
String? formatDateNullable(DateTime? date) {
|
||||
if (date == null) return null;
|
||||
return dateFormat.format(date);
|
||||
}
|
||||
|
||||
String extractFilenameFromPath(String path) {
|
||||
return path.split(RegExp('[./]')).reversed.skip(1).first;
|
||||
}
|
||||
|
||||
// Taken from https://github.com/flutter/flutter/issues/26127#issuecomment-782083060
|
||||
Future<void> loadImage(ImageProvider provider) {
|
||||
final config = ImageConfiguration(
|
||||
bundle: rootBundle,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
platform: defaultTargetPlatform,
|
||||
);
|
||||
final Completer<void> completer = Completer();
|
||||
final ImageStream stream = provider.resolve(config);
|
||||
|
||||
late final ImageStreamListener listener;
|
||||
|
||||
listener = ImageStreamListener((ImageInfo image, bool sync) {
|
||||
debugPrint("Image ${image.debugLabel} finished loading");
|
||||
completer.complete();
|
||||
stream.removeListener(listener);
|
||||
}, onError: (dynamic exception, StackTrace? stackTrace) {
|
||||
completer.complete();
|
||||
stream.removeListener(listener);
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
context: ErrorDescription('image failed to load'),
|
||||
library: 'image resource service',
|
||||
exception: exception,
|
||||
stack: stackTrace,
|
||||
silent: true,
|
||||
));
|
||||
});
|
||||
|
||||
stream.addListener(listener);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<bool> askForPermission(Permission permission) async {
|
||||
final status = await permission.request();
|
||||
log("Permission requested, new status is $status");
|
||||
// If user has permanently declined permission, open settings.
|
||||
if (status == PermissionStatus.permanentlyDenied) {
|
||||
await openAppSettings();
|
||||
}
|
||||
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
48
pubspec.lock
48
pubspec.lock
@@ -5,18 +5,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201"
|
||||
sha256: "8c7478991c7bbde2c1e18034ac697723176a5d3e7e0ca06c7f9aed69b6f388d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "52.0.0"
|
||||
version: "51.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4
|
||||
sha256: "120fe7ce25377ba616bb210e7584983b163861f45d6ec446744d507e3943881b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
version: "5.3.1"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin
|
||||
sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.2"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ansicolor
|
||||
sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -305,6 +321,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.2"
|
||||
dart_code_metrics:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: dart_code_metrics
|
||||
sha256: "95f22e95638c0dfb0cb4e3ba45e00bb06dd509c98f06d4c0fa45340b0a5392e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
dart_code_metrics_presets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_code_metrics_presets
|
||||
sha256: "43dc1fdcb424fc3aa79964304d09eeda4f199351c52cdc854f8228a9d0296b60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1276,6 +1308,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
pub_updater:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_updater
|
||||
sha256: "00e42b515aa046b171d05bbe2dd566c0feaab7808c33c5bacb5beff93cf16561"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -100,6 +100,7 @@ dev_dependencies:
|
||||
intl_utils: ^2.7.0
|
||||
flutter_lints: ^1.0.0
|
||||
json_serializable: ^6.5.4
|
||||
dart_code_metrics: ^5.4.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
Reference in New Issue
Block a user