mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 02:07:58 -06:00
Bugfixes, finished filter rework
This commit is contained in:
@@ -37,4 +37,10 @@ class ConnectivityCubit extends Cubit<ConnectivityState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectivityState { connected, notConnected, undefined }
|
enum ConnectivityState {
|
||||||
|
connected,
|
||||||
|
notConnected,
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
bool get isConnected => this == connected;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ComingSoon extends StatelessWidget {
|
|
||||||
const ComingSoon({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
"Coming Soon\u2122",
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ElevatedConfirmationButton extends StatefulWidget {
|
|
||||||
factory ElevatedConfirmationButton.icon(BuildContext context,
|
|
||||||
{required void Function() onPressed,
|
|
||||||
required Icon icon,
|
|
||||||
required Widget label}) {
|
|
||||||
final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
|
|
||||||
final double gap =
|
|
||||||
scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
|
|
||||||
return ElevatedConfirmationButton(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
|
|
||||||
),
|
|
||||||
onPressed: onPressed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ElevatedConfirmationButton({
|
|
||||||
Key? key,
|
|
||||||
this.color,
|
|
||||||
required this.onPressed,
|
|
||||||
required this.child,
|
|
||||||
this.confirmWidget = const Text("Confirm?"),
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final Color? color;
|
|
||||||
final void Function()? onPressed;
|
|
||||||
final Widget child;
|
|
||||||
final Widget confirmWidget;
|
|
||||||
@override
|
|
||||||
State<ElevatedConfirmationButton> createState() =>
|
|
||||||
_ElevatedConfirmationButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ElevatedConfirmationButtonState
|
|
||||||
extends State<ElevatedConfirmationButton> {
|
|
||||||
bool _clickedOnce = false;
|
|
||||||
double? _originalWidth;
|
|
||||||
final GlobalKey _originalWidgetKey = GlobalKey();
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (!_clickedOnce) {
|
|
||||||
return ElevatedButton(
|
|
||||||
key: _originalWidgetKey,
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(widget.color),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
_originalWidth = (_originalWidgetKey.currentContext
|
|
||||||
?.findRenderObject() as RenderBox)
|
|
||||||
.size
|
|
||||||
.width;
|
|
||||||
setState(() => _clickedOnce = true);
|
|
||||||
},
|
|
||||||
child: widget.child,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Builder(builder: (context) {
|
|
||||||
return SizedBox(
|
|
||||||
width: _originalWidth,
|
|
||||||
child: ElevatedButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: MaterialStateProperty.all(widget.color),
|
|
||||||
),
|
|
||||||
onPressed: widget.onPressed,
|
|
||||||
child: widget.confirmWidget,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class ExpandableFloatingActionButton extends StatefulWidget {
|
|
||||||
const ExpandableFloatingActionButton({
|
|
||||||
super.key,
|
|
||||||
this.initialOpen,
|
|
||||||
required this.distance,
|
|
||||||
required this.children,
|
|
||||||
});
|
|
||||||
|
|
||||||
final bool? initialOpen;
|
|
||||||
final double distance;
|
|
||||||
final List<Widget> children;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ExpandableFloatingActionButton> createState() =>
|
|
||||||
_ExpandableFloatingActionButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ExpandableFloatingActionButtonState
|
|
||||||
extends State<ExpandableFloatingActionButton>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
late final AnimationController _controller;
|
|
||||||
late final Animation<double> _expandAnimation;
|
|
||||||
bool _open = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_open = widget.initialOpen ?? false;
|
|
||||||
_controller = AnimationController(
|
|
||||||
value: _open ? 1.0 : 0.0,
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_expandAnimation = CurvedAnimation(
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
reverseCurve: Curves.easeOutQuad,
|
|
||||||
parent: _controller,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _toggle() {
|
|
||||||
setState(() {
|
|
||||||
_open = !_open;
|
|
||||||
if (_open) {
|
|
||||||
_controller.forward();
|
|
||||||
} else {
|
|
||||||
_controller.reverse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox.expand(
|
|
||||||
child: Stack(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
_buildTapToCloseFab(),
|
|
||||||
..._buildExpandingActionButtons(),
|
|
||||||
_buildTapToOpenFab(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTapToCloseFab() {
|
|
||||||
return SizedBox(
|
|
||||||
width: 56.0,
|
|
||||||
height: 56.0,
|
|
||||||
child: Center(
|
|
||||||
child: Material(
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
elevation: 4.0,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: _toggle,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildExpandingActionButtons() {
|
|
||||||
final children = <Widget>[];
|
|
||||||
final count = widget.children.length;
|
|
||||||
final step = 90.0 / (count - 1);
|
|
||||||
for (var i = 0, angleInDegrees = 0.0;
|
|
||||||
i < count;
|
|
||||||
i++, angleInDegrees += step) {
|
|
||||||
children.add(
|
|
||||||
_ExpandingActionButton(
|
|
||||||
directionInDegrees: angleInDegrees,
|
|
||||||
maxDistance: widget.distance,
|
|
||||||
progress: _expandAnimation,
|
|
||||||
child: widget.children[i],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTapToOpenFab() {
|
|
||||||
return IgnorePointer(
|
|
||||||
ignoring: _open,
|
|
||||||
child: AnimatedContainer(
|
|
||||||
transformAlignment: Alignment.center,
|
|
||||||
transform: Matrix4.diagonal3Values(
|
|
||||||
_open ? 0.7 : 1.0,
|
|
||||||
_open ? 0.7 : 1.0,
|
|
||||||
1.0,
|
|
||||||
),
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: _open ? 0.0 : 1.0,
|
|
||||||
curve: const Interval(0.25, 1.0, curve: Curves.easeInOut),
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
child: FloatingActionButton(
|
|
||||||
onPressed: _toggle,
|
|
||||||
child: const Icon(Icons.create),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class _ExpandingActionButton extends StatelessWidget {
|
|
||||||
const _ExpandingActionButton({
|
|
||||||
required this.directionInDegrees,
|
|
||||||
required this.maxDistance,
|
|
||||||
required this.progress,
|
|
||||||
required this.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double directionInDegrees;
|
|
||||||
final double maxDistance;
|
|
||||||
final Animation<double> progress;
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: progress,
|
|
||||||
builder: (context, child) {
|
|
||||||
final offset = Offset.fromDirection(
|
|
||||||
directionInDegrees * (math.pi / 180.0),
|
|
||||||
progress.value * maxDistance,
|
|
||||||
);
|
|
||||||
return Positioned(
|
|
||||||
right: 4.0 + offset.dx,
|
|
||||||
bottom: 4.0 + offset.dy,
|
|
||||||
child: Transform.rotate(
|
|
||||||
angle: (1.0 - progress.value) * math.pi / 2,
|
|
||||||
child: child!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: FadeTransition(
|
|
||||||
opacity: progress,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class ExpandableActionButton extends StatelessWidget {
|
|
||||||
const ExpandableActionButton({
|
|
||||||
super.key,
|
|
||||||
this.color,
|
|
||||||
this.onPressed,
|
|
||||||
required this.icon,
|
|
||||||
});
|
|
||||||
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
final Widget icon;
|
|
||||||
final Color? color;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 48,
|
|
||||||
width: 48,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: onPressed,
|
|
||||||
child: icon,
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
backgroundColor: MaterialStateProperty.all(color),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,20 +6,26 @@ class OfflineBanner extends StatelessWidget with PreferredSizeWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return ColoredBox(
|
||||||
color: Theme.of(context).disabledColor,
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.cloud_off,
|
Icons.cloud_off,
|
||||||
size: 24,
|
size: 24,
|
||||||
|
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
S.of(context).genericMessageOfflineText,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(S.of(context).genericMessageOfflineText),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,21 +45,15 @@ class DocumentDetailsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
initializeDateFormatting();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isDownloadPending = false;
|
bool _isDownloadPending = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () {
|
onWillPop: () async {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pop(BlocProvider.of<DocumentDetailsCubit>(context).state.document);
|
.pop(BlocProvider.of<DocumentDetailsCubit>(context).state.document);
|
||||||
return Future.value(false);
|
return false;
|
||||||
},
|
},
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
@@ -325,7 +319,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
_separator(),
|
_separator(),
|
||||||
_DetailsItem.text(
|
_DetailsItem.text(
|
||||||
DateFormat().format(document.created),
|
DateFormat.yMMMd().format(document.created),
|
||||||
context: context,
|
context: context,
|
||||||
label: S.of(context).documentCreatedPropertyLabel,
|
label: S.of(context).documentCreatedPropertyLabel,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -66,55 +66,66 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
previous != ConnectivityState.connected &&
|
previous != ConnectivityState.connected &&
|
||||||
current == ConnectivityState.connected,
|
current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_documentsCubit.load();
|
try {
|
||||||
|
_documentsCubit.load();
|
||||||
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
builder: (context, connectivityState) {
|
builder: (context, connectivityState) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: BlocProvider.value(
|
drawer: BlocProvider.value(
|
||||||
value: BlocProvider.of<AuthenticationCubit>(context),
|
value: BlocProvider.of<AuthenticationCubit>(context),
|
||||||
child: InfoDrawer(
|
child: InfoDrawer(
|
||||||
afterInboxClosed: () => _documentsCubit.reload(),
|
afterInboxClosed: () => _documentsCubit.reload(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
),
|
||||||
builder: (context, state) {
|
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
builder: (context, state) {
|
||||||
return Badge(
|
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||||
toAnimate: false,
|
return Badge(
|
||||||
showBadge: appliedFiltersCount > 0,
|
toAnimate: false,
|
||||||
badgeContent: appliedFiltersCount > 0
|
animationType: BadgeAnimationType.fade,
|
||||||
? Text(state.filter.appliedFiltersCount.toString())
|
showBadge: appliedFiltersCount > 0,
|
||||||
: null,
|
badgeContent: appliedFiltersCount > 0
|
||||||
child: FloatingActionButton(
|
? Text(
|
||||||
child: const Icon(Icons.filter_alt),
|
state.filter.appliedFiltersCount.toString(),
|
||||||
onPressed: _openDocumentFilter,
|
style: const TextStyle(color: Colors.white),
|
||||||
),
|
)
|
||||||
);
|
: null,
|
||||||
},
|
child: FloatingActionButton(
|
||||||
),
|
child: const Icon(Icons.filter_alt_rounded),
|
||||||
resizeToAvoidBottomInset: true,
|
onPressed: _openDocumentFilter,
|
||||||
body: _buildBody(connectivityState));
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
body: _buildBody(connectivityState),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openDocumentFilter() async {
|
void _openDocumentFilter() async {
|
||||||
final filter = await showModalBottomSheet(
|
final filter = await showModalBottomSheet<DocumentFilter>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height - kToolbarHeight - 16,
|
|
||||||
child: LabelsBlocProvider(
|
|
||||||
child: DocumentFilterPanel(
|
|
||||||
initialFilter: _documentsCubit.state.filter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isDismissible: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(16.0),
|
topLeft: Radius.circular(16),
|
||||||
topRight: Radius.circular(16.0),
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => DraggableScrollableSheet(
|
||||||
|
expand: false,
|
||||||
|
snap: true,
|
||||||
|
initialChildSize: .9,
|
||||||
|
builder: (context, controller) => LabelsBlocProvider(
|
||||||
|
child: DocumentFilterPanel(
|
||||||
|
initialFilter: _documentsCubit.state.filter,
|
||||||
|
scrollController: controller,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -125,6 +136,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(ConnectivityState connectivityState) {
|
Widget _buildBody(ConnectivityState connectivityState) {
|
||||||
|
final isConnected = connectivityState == ConnectivityState.connected;
|
||||||
return BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
return BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||||
builder: (context, settings) {
|
builder: (context, settings) {
|
||||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
@@ -143,8 +155,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
state: state,
|
state: state,
|
||||||
onSelected: _onSelected,
|
onSelected: _onSelected,
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
hasInternetConnection:
|
hasInternetConnection: isConnected,
|
||||||
connectivityState == ConnectivityState.connected,
|
|
||||||
onTagSelected: _addTagToFilter,
|
onTagSelected: _addTagToFilter,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -154,8 +165,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
state: state,
|
state: state,
|
||||||
onSelected: _onSelected,
|
onSelected: _onSelected,
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
hasInternetConnection:
|
hasInternetConnection: isConnected,
|
||||||
connectivityState == ConnectivityState.connected,
|
|
||||||
onTagSelected: _addTagToFilter,
|
onTagSelected: _addTagToFilter,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -175,6 +185,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: _onRefresh,
|
onRefresh: _onRefresh,
|
||||||
|
notificationPredicate: (_) => isConnected,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
BlocListener<SavedViewCubit, SavedViewState>(
|
BlocListener<SavedViewCubit, SavedViewState>(
|
||||||
@@ -198,6 +209,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: DocumentsPageAppBar(
|
child: DocumentsPageAppBar(
|
||||||
|
isOffline:
|
||||||
|
connectivityState != ConnectivityState.connected,
|
||||||
actions: [
|
actions: [
|
||||||
const SortDocumentsButton(),
|
const SortDocumentsButton(),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class DocumentGridItem extends StatelessWidget {
|
|||||||
? Theme.of(context).colorScheme.inversePrimary
|
? Theme.of(context).colorScheme.inversePrimary
|
||||||
: Theme.of(context).cardColor,
|
: Theme.of(context).cardColor,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
@@ -74,8 +75,9 @@ class DocumentGridItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
DateFormat.yMMMd(Intl.getCurrentLocale())
|
DateFormat.yMMMd().format(
|
||||||
.format(document.created),
|
document.created,
|
||||||
|
),
|
||||||
style: Theme.of(context).textTheme.caption,
|
style: Theme.of(context).textTheme.caption,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ enum DateRangeSelection { before, after }
|
|||||||
|
|
||||||
class DocumentFilterPanel extends StatefulWidget {
|
class DocumentFilterPanel extends StatefulWidget {
|
||||||
final DocumentFilter initialFilter;
|
final DocumentFilter initialFilter;
|
||||||
|
final ScrollController scrollController;
|
||||||
const DocumentFilterPanel({
|
const DocumentFilterPanel({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.initialFilter,
|
required this.initialFilter,
|
||||||
|
required this.scrollController,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,80 +37,68 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
|
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
|
|
||||||
if (start == null && end == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (start != null && end != null) {
|
|
||||||
return DateTimeRange(start: start, end: end);
|
|
||||||
}
|
|
||||||
assert(start != null || end != null);
|
|
||||||
final singleDate = (start ?? end)!;
|
|
||||||
return DateTimeRange(start: singleDate, end: singleDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const radius = Radius.circular(16);
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: radius,
|
topLeft: Radius.circular(16),
|
||||||
topRight: radius,
|
topRight: Radius.circular(16),
|
||||||
),
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
|
floatingActionButton: Visibility(
|
||||||
|
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.done),
|
||||||
|
label: Text(S.of(context).documentFilterApplyFilterLabel),
|
||||||
|
onPressed: _onApplyFilter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: _resetFilter,
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: Text(S.of(context).documentFilterResetLabel),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
body: FormBuilder(
|
body: FormBuilder(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: ListView(
|
||||||
|
controller: widget.scrollController,
|
||||||
children: [
|
children: [
|
||||||
_buildDraggableResetHeader(),
|
Text(
|
||||||
Row(
|
S.of(context).documentFilterTitle,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
children: [
|
).paddedOnly(
|
||||||
Text(
|
top: 16.0,
|
||||||
S.of(context).documentFilterTitle,
|
left: 16.0,
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
bottom: 24,
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: _onApplyFilter,
|
|
||||||
child: Text(S.of(context).documentFilterApplyFilterLabel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(),
|
|
||||||
Expanded(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16.0),
|
|
||||||
topRight: Radius.circular(16.0),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(S.of(context).documentFilterSearchLabel),
|
|
||||||
).paddedOnly(left: 8.0),
|
|
||||||
_buildQueryFormField().padded(),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
S.of(context).documentFilterAdvancedLabel,
|
|
||||||
),
|
|
||||||
).padded(),
|
|
||||||
_buildCreatedDateRangePickerFormField(),
|
|
||||||
_buildAddedDateRangePickerFormField(),
|
|
||||||
_buildCorrespondentFormField().padded(),
|
|
||||||
_buildDocumentTypeFormField().padded(),
|
|
||||||
_buildStoragePathFormField().padded(),
|
|
||||||
_buildTagsFormField()
|
|
||||||
.paddedSymmetrically(horizontal: 8, vertical: 4.0),
|
|
||||||
],
|
|
||||||
).paddedOnly(bottom: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(S.of(context).documentFilterSearchLabel),
|
||||||
|
).paddedOnly(left: 8.0),
|
||||||
|
_buildQueryFormField().padded(),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
S.of(context).documentFilterAdvancedLabel,
|
||||||
|
),
|
||||||
|
).padded(),
|
||||||
|
_buildCreatedDateRangePickerFormField(),
|
||||||
|
_buildAddedDateRangePickerFormField(),
|
||||||
|
_buildCorrespondentFormField().padded(),
|
||||||
|
_buildDocumentTypeFormField().padded(),
|
||||||
|
_buildStoragePathFormField().padded(),
|
||||||
|
_buildTagsFormField().padded(),
|
||||||
],
|
],
|
||||||
),
|
).paddedOnly(bottom: 16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -128,29 +117,11 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stack _buildDraggableResetHeader() {
|
void _resetFilter() async {
|
||||||
return Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
_buildDragLine(),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
child: TextButton.icon(
|
|
||||||
icon: const Icon(Icons.refresh),
|
|
||||||
label: Text(S.of(context).documentFilterResetLabel),
|
|
||||||
onPressed: () => _resetFilter(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetFilter(BuildContext context) async {
|
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.pop(context, DocumentFilter.initial);
|
Navigator.pop(context, DocumentFilter.initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Check if the blocs can be found in the context, otherwise just provide repository and create new bloc inside LabelFormField!
|
|
||||||
Widget _buildDocumentTypeFormField() {
|
Widget _buildDocumentTypeFormField() {
|
||||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -416,42 +387,11 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDragLine() {
|
|
||||||
return Container(
|
|
||||||
width: 48,
|
|
||||||
height: 5,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onApplyFilter() async {
|
void _onApplyFilter() async {
|
||||||
_formKey.currentState?.save();
|
_formKey.currentState?.save();
|
||||||
if (_formKey.currentState?.validate() ?? false) {
|
if (_formKey.currentState?.validate() ?? false) {
|
||||||
final v = _formKey.currentState!.value;
|
final v = _formKey.currentState!.value;
|
||||||
DocumentFilter newFilter = DocumentFilter(
|
DocumentFilter newFilter = _assembleFilter();
|
||||||
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end,
|
|
||||||
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start,
|
|
||||||
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
|
|
||||||
DocumentFilter.initial.correspondent,
|
|
||||||
documentType: v[fkDocumentType] as DocumentTypeQuery? ??
|
|
||||||
DocumentFilter.initial.documentType,
|
|
||||||
storagePath: v[fkStoragePath] as StoragePathQuery? ??
|
|
||||||
DocumentFilter.initial.storagePath,
|
|
||||||
tags: v[DocumentModel.tagsKey] as TagsQuery? ??
|
|
||||||
DocumentFilter.initial.tags,
|
|
||||||
queryText: v[fkQuery] as String?,
|
|
||||||
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end,
|
|
||||||
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
|
||||||
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
|
||||||
asnQuery: widget.initialFilter.asnQuery,
|
|
||||||
page: 1,
|
|
||||||
pageSize: widget.initialFilter.pageSize,
|
|
||||||
sortField: widget.initialFilter.sortField,
|
|
||||||
sortOrder: widget.initialFilter.sortOrder,
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
Navigator.pop(context, newFilter);
|
Navigator.pop(context, newFilter);
|
||||||
@@ -461,23 +401,40 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _patchFromFilter(DocumentFilter f) {
|
DocumentFilter _assembleFilter() {
|
||||||
_formKey.currentState?.patchValue({
|
final v = _formKey.currentState!.value;
|
||||||
fkCorrespondent: f.correspondent,
|
return DocumentFilter(
|
||||||
fkDocumentType: f.documentType,
|
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end,
|
||||||
fkQuery: f.queryText,
|
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start,
|
||||||
fkStoragePath: f.storagePath,
|
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
|
||||||
DocumentModel.tagsKey: f.tags,
|
DocumentFilter.initial.correspondent,
|
||||||
DocumentModel.titleKey: f.queryText,
|
documentType: v[fkDocumentType] as DocumentTypeQuery? ??
|
||||||
QueryTypeFormField.fkQueryType: f.queryType,
|
DocumentFilter.initial.documentType,
|
||||||
fkCreatedAt: _dateTimeRangeOfNullable(
|
storagePath: v[fkStoragePath] as StoragePathQuery? ??
|
||||||
f.createdDateAfter,
|
DocumentFilter.initial.storagePath,
|
||||||
f.createdDateBefore,
|
tags:
|
||||||
),
|
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||||
fkAddedAt: _dateTimeRangeOfNullable(
|
queryText: v[fkQuery] as String?,
|
||||||
f.addedDateAfter,
|
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end,
|
||||||
f.addedDateBefore,
|
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
||||||
),
|
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
||||||
});
|
asnQuery: widget.initialFilter.asnQuery,
|
||||||
|
page: 1,
|
||||||
|
pageSize: widget.initialFilter.pageSize,
|
||||||
|
sortField: widget.initialFilter.sortField,
|
||||||
|
sortOrder: widget.initialFilter.sortOrder,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
|
||||||
|
if (start == null && end == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (start != null && end != null) {
|
||||||
|
return DateTimeRange(start: start, end: end);
|
||||||
|
}
|
||||||
|
assert(start != null || end != null);
|
||||||
|
final singleDate = (start ?? end)!;
|
||||||
|
return DateTimeRange(start: singleDate, end: singleDate);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.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_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.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/features/saved_view/view/saved_view_selection_widget.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
|
||||||
class DocumentsPageAppBar extends StatefulWidget with PreferredSizeWidget {
|
class DocumentsPageAppBar extends StatefulWidget with PreferredSizeWidget {
|
||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
|
final bool isOffline;
|
||||||
|
|
||||||
const DocumentsPageAppBar({
|
const DocumentsPageAppBar({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.isOffline,
|
||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
@@ -21,19 +25,27 @@ class DocumentsPageAppBar extends StatefulWidget with PreferredSizeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||||
static const _flexibleAreaHeight = kToolbarHeight + 48.0;
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
const savedViewWidgetHeight = 48.0;
|
||||||
|
final flexibleAreaHeight = kToolbarHeight -
|
||||||
|
16 +
|
||||||
|
savedViewWidgetHeight +
|
||||||
|
(widget.isOffline ? 24 : 0);
|
||||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, documentsState) {
|
builder: (context, documentsState) {
|
||||||
final hasSelection = documentsState.selection.isNotEmpty;
|
final hasSelection = documentsState.selection.isNotEmpty;
|
||||||
if (hasSelection) {
|
if (hasSelection) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
expandedHeight: kToolbarHeight + _flexibleAreaHeight,
|
expandedHeight: kToolbarHeight + flexibleAreaHeight,
|
||||||
snap: true,
|
snap: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
flexibleSpace: _buildFlexibleArea(false, documentsState.filter),
|
flexibleSpace: _buildFlexibleArea(
|
||||||
|
false,
|
||||||
|
documentsState.filter,
|
||||||
|
savedViewWidgetHeight,
|
||||||
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
@@ -50,13 +62,14 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
expandedHeight: kToolbarHeight + _flexibleAreaHeight,
|
expandedHeight: kToolbarHeight + flexibleAreaHeight,
|
||||||
snap: true,
|
snap: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
flexibleSpace: _buildFlexibleArea(
|
flexibleSpace: _buildFlexibleArea(
|
||||||
true,
|
true,
|
||||||
documentsState.filter,
|
documentsState.filter,
|
||||||
|
savedViewWidgetHeight,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(documentsState.count)})',
|
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(documentsState.count)})',
|
||||||
@@ -70,30 +83,31 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFlexibleArea(bool enabled, DocumentFilter filter) {
|
Widget _buildFlexibleArea(
|
||||||
|
bool enabled,
|
||||||
|
DocumentFilter filter,
|
||||||
|
double savedViewHeight,
|
||||||
|
) {
|
||||||
return FlexibleSpaceBar(
|
return FlexibleSpaceBar(
|
||||||
background: Padding(
|
background: Column(
|
||||||
padding: const EdgeInsets.all(8.0),
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
if (widget.isOffline) const OfflineBanner(),
|
||||||
children: [
|
SavedViewSelectionWidget(
|
||||||
SavedViewSelectionWidget(
|
height: savedViewHeight,
|
||||||
height: 48,
|
enabled: enabled,
|
||||||
enabled: enabled,
|
currentFilter: filter,
|
||||||
currentFilter: filter,
|
).paddedSymmetrically(horizontal: 8.0),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDelete(BuildContext context, DocumentsState documentsState) async {
|
void _onDelete(BuildContext context, DocumentsState documentsState) async {
|
||||||
final shouldDelete = await showDialog<bool>(
|
final shouldDelete = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
BulkDeleteConfirmationDialog(state: documentsState),
|
BulkDeleteConfirmationDialog(state: documentsState)) ??
|
||||||
) ??
|
|
||||||
false;
|
false;
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class HomePage extends StatefulWidget {
|
|||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
|
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -35,52 +35,47 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
return BlocListener<ConnectivityCubit, ConnectivityState>(
|
||||||
//Only re-initialize data if the connectivity changed from not connected to connected
|
//Only re-initialize data if the connectivity changed from not connected to connected
|
||||||
listenWhen: (previous, current) => current == ConnectivityState.connected,
|
listenWhen: (previous, current) => current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_initializeData(context);
|
_initializeData(context);
|
||||||
},
|
},
|
||||||
builder: (context, connectivityState) {
|
child: Scaffold(
|
||||||
return Scaffold(
|
key: rootScaffoldKey,
|
||||||
appBar: connectivityState == ConnectivityState.connected
|
bottomNavigationBar: BottomNavBar(
|
||||||
? null
|
selectedIndex: _currentIndex,
|
||||||
: const OfflineBanner(),
|
onNavigationChanged: (index) {
|
||||||
key: rootScaffoldKey,
|
if (_currentIndex != index) {
|
||||||
bottomNavigationBar: BottomNavBar(
|
setState(() => _currentIndex = index);
|
||||||
selectedIndex: _currentIndex,
|
}
|
||||||
onNavigationChanged: (index) {
|
},
|
||||||
if (_currentIndex != index) {
|
),
|
||||||
setState(() => _currentIndex = index);
|
drawer: const InfoDrawer(),
|
||||||
}
|
body: [
|
||||||
},
|
MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider.value(
|
||||||
|
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => SavedViewCubit(
|
||||||
|
RepositoryProvider.of<SavedViewRepository>(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const DocumentsPage(),
|
||||||
),
|
),
|
||||||
drawer: const InfoDrawer(),
|
BlocProvider.value(
|
||||||
body: [
|
value: _scannerCubit,
|
||||||
MultiBlocProvider(
|
child: const ScannerPage(),
|
||||||
providers: [
|
),
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||||
),
|
child: const LabelsPage(),
|
||||||
BlocProvider(
|
),
|
||||||
create: (context) => SavedViewCubit(
|
][_currentIndex],
|
||||||
RepositoryProvider.of<SavedViewRepository>(context),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const DocumentsPage(),
|
|
||||||
),
|
|
||||||
BlocProvider.value(
|
|
||||||
value: DocumentScannerCubit(),
|
|
||||||
child: const ScannerPage(),
|
|
||||||
),
|
|
||||||
BlocProvider.value(
|
|
||||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
|
||||||
child: const LabelsPage(),
|
|
||||||
),
|
|
||||||
][_currentIndex],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
S.of(context).inboxPageDocumentRemovedMessageText,
|
S.of(context).inboxPageDocumentRemovedMessageText,
|
||||||
action: SnackBarAction(
|
action: SnackBarActionConfig(
|
||||||
label: S.of(context).inboxPageUndoRemoveText,
|
label: S.of(context).inboxPageUndoRemoveText,
|
||||||
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||||
@@ -39,152 +41,176 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 3,
|
length: 3,
|
||||||
child: Scaffold(
|
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
drawer: const InfoDrawer(),
|
builder: (context, connectedState) {
|
||||||
appBar: AppBar(
|
return Scaffold(
|
||||||
title: Text(
|
drawer: const InfoDrawer(),
|
||||||
[
|
appBar: AppBar(
|
||||||
S.of(context).labelsPageCorrespondentsTitleText,
|
title: Text(
|
||||||
S.of(context).labelsPageDocumentTypesTitleText,
|
[
|
||||||
S.of(context).labelsPageTagsTitleText,
|
S.of(context).labelsPageCorrespondentsTitleText,
|
||||||
S.of(context).labelsPageStoragePathTitleText
|
S.of(context).labelsPageDocumentTypesTitleText,
|
||||||
][_currentIndex],
|
S.of(context).labelsPageTagsTitleText,
|
||||||
),
|
S.of(context).labelsPageStoragePathTitleText
|
||||||
actions: [
|
][_currentIndex],
|
||||||
IconButton(
|
),
|
||||||
onPressed: [
|
actions: [
|
||||||
_openAddCorrespondentPage,
|
IconButton(
|
||||||
_openAddDocumentTypePage,
|
onPressed: [
|
||||||
_openAddTagPage,
|
_openAddCorrespondentPage,
|
||||||
_openAddStoragePathPage,
|
_openAddDocumentTypePage,
|
||||||
][_currentIndex],
|
_openAddTagPage,
|
||||||
icon: const Icon(Icons.add),
|
_openAddStoragePathPage,
|
||||||
)
|
][_currentIndex],
|
||||||
],
|
icon: const Icon(Icons.add),
|
||||||
bottom: PreferredSize(
|
)
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
],
|
||||||
child: ColoredBox(
|
bottom: PreferredSize(
|
||||||
color: Theme.of(context).bottomAppBarColor,
|
preferredSize: Size.fromHeight(
|
||||||
child: TabBar(
|
kToolbarHeight + (!connectedState.isConnected ? 16 : 0)),
|
||||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
child: Column(
|
||||||
controller: _tabController,
|
children: [
|
||||||
tabs: [
|
if (!connectedState.isConnected) const OfflineBanner(),
|
||||||
Tab(
|
ColoredBox(
|
||||||
icon: Icon(
|
color: Theme.of(context).bottomAppBarColor,
|
||||||
Icons.person_outline,
|
child: TabBar(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.person_outline,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.description_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.label_outline,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.folder_open,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => LabelCubit(
|
||||||
|
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||||
|
context),
|
||||||
),
|
),
|
||||||
Tab(
|
child: LabelTabView<Correspondent>(
|
||||||
icon: Icon(
|
filterBuilder: (label) => DocumentFilter(
|
||||||
Icons.description_outlined,
|
correspondent: CorrespondentQuery.fromId(label.id),
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
pageSize: label.documentCount ?? 0,
|
||||||
),
|
),
|
||||||
|
onEdit: _openEditCorrespondentPage,
|
||||||
|
emptyStateActionButtonLabel: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription: S
|
||||||
|
.of(context)
|
||||||
|
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddCorrespondentPage,
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
icon: Icon(
|
BlocProvider(
|
||||||
Icons.label_outline,
|
create: (context) => LabelCubit(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||||
),
|
context),
|
||||||
),
|
),
|
||||||
Tab(
|
child: LabelTabView<DocumentType>(
|
||||||
icon: Icon(
|
filterBuilder: (label) => DocumentFilter(
|
||||||
Icons.folder_open,
|
documentType: DocumentTypeQuery.fromId(label.id),
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
pageSize: label.documentCount ?? 0,
|
||||||
),
|
),
|
||||||
)
|
onEdit: _openEditDocumentTypePage,
|
||||||
],
|
emptyStateActionButtonLabel: S
|
||||||
),
|
.of(context)
|
||||||
),
|
.labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||||
),
|
emptyStateDescription: S
|
||||||
),
|
.of(context)
|
||||||
body: TabBarView(
|
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||||
controller: _tabController,
|
onAddNew: _openAddDocumentTypePage,
|
||||||
children: [
|
),
|
||||||
BlocProvider(
|
|
||||||
create: (context) => LabelCubit(
|
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
|
||||||
),
|
|
||||||
child: LabelTabView<Correspondent>(
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
correspondent: CorrespondentQuery.fromId(label.id),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
),
|
||||||
onEdit: _openEditCorrespondentPage,
|
BlocProvider(
|
||||||
emptyStateActionButtonLabel:
|
create: (context) => LabelCubit<Tag>(
|
||||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||||
emptyStateDescription: S
|
),
|
||||||
.of(context)
|
child: LabelTabView<Tag>(
|
||||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
filterBuilder: (label) => DocumentFilter(
|
||||||
onAddNew: _openAddCorrespondentPage,
|
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||||
),
|
pageSize: label.documentCount ?? 0,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
onEdit: _openEditTagPage,
|
||||||
create: (context) => LabelCubit(
|
leadingBuilder: (t) => CircleAvatar(
|
||||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
backgroundColor: t.color,
|
||||||
),
|
child: t.isInboxTag ?? false
|
||||||
child: LabelTabView<DocumentType>(
|
? Icon(
|
||||||
filterBuilder: (label) => DocumentFilter(
|
Icons.inbox,
|
||||||
documentType: DocumentTypeQuery.fromId(label.id),
|
color: t.textColor,
|
||||||
pageSize: label.documentCount ?? 0,
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
contentBuilder: (t) => Text(t.match ?? ''),
|
||||||
|
emptyStateActionButtonLabel:
|
||||||
|
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||||
|
emptyStateDescription:
|
||||||
|
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddTagPage,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onEdit: _openEditDocumentTypePage,
|
BlocProvider(
|
||||||
emptyStateActionButtonLabel:
|
create: (context) => LabelCubit<StoragePath>(
|
||||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
RepositoryProvider.of<LabelRepository<StoragePath>>(
|
||||||
emptyStateDescription: S
|
context),
|
||||||
.of(context)
|
),
|
||||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
child: LabelTabView<StoragePath>(
|
||||||
onAddNew: _openAddDocumentTypePage,
|
onEdit: _openEditStoragePathPage,
|
||||||
),
|
filterBuilder: (label) => DocumentFilter(
|
||||||
),
|
storagePath: StoragePathQuery.fromId(label.id),
|
||||||
BlocProvider(
|
pageSize: label.documentCount ?? 0,
|
||||||
create: (context) => LabelCubit<Tag>(
|
),
|
||||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
contentBuilder: (path) => Text(path.path ?? ""),
|
||||||
),
|
emptyStateActionButtonLabel: S
|
||||||
child: LabelTabView<Tag>(
|
.of(context)
|
||||||
filterBuilder: (label) => DocumentFilter(
|
.labelsPageStoragePathEmptyStateAddNewLabel,
|
||||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
emptyStateDescription: S
|
||||||
pageSize: label.documentCount ?? 0,
|
.of(context)
|
||||||
|
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||||
|
onAddNew: _openAddStoragePathPage,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onEdit: _openEditTagPage,
|
],
|
||||||
leadingBuilder: (t) => CircleAvatar(
|
|
||||||
backgroundColor: t.color,
|
|
||||||
child: t.isInboxTag ?? false
|
|
||||||
? Icon(
|
|
||||||
Icons.inbox,
|
|
||||||
color: t.textColor,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
contentBuilder: (t) => Text(t.match ?? ''),
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddTagPage,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
BlocProvider(
|
);
|
||||||
create: (context) => LabelCubit<StoragePath>(
|
},
|
||||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
|
||||||
),
|
|
||||||
child: LabelTabView<StoragePath>(
|
|
||||||
onEdit: _openEditStoragePathPage,
|
|
||||||
filterBuilder: (label) => DocumentFilter(
|
|
||||||
storagePath: StoragePathQuery.fromId(label.id),
|
|
||||||
pageSize: label.documentCount ?? 0,
|
|
||||||
),
|
|
||||||
contentBuilder: (path) => Text(path.path ?? ""),
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
|
||||||
onAddNew: _openAddStoragePathPage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.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/document_upload/view/document_upload_preparation_page.dart';
|
||||||
@@ -38,23 +40,28 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
drawer: const InfoDrawer(),
|
builder: (context, connectedState) {
|
||||||
floatingActionButton: FloatingActionButton(
|
return Scaffold(
|
||||||
onPressed: () => _openDocumentScanner(context),
|
drawer: const InfoDrawer(),
|
||||||
child: const Icon(Icons.add_a_photo_outlined),
|
floatingActionButton: FloatingActionButton(
|
||||||
),
|
onPressed: () => _openDocumentScanner(context),
|
||||||
appBar: _buildAppBar(context),
|
child: const Icon(Icons.add_a_photo_outlined),
|
||||||
body: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(8.0),
|
appBar: _buildAppBar(context, connectedState.isConnected),
|
||||||
child: _buildBody(),
|
body: Padding(
|
||||||
),
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: _buildBody(connectedState.isConnected),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppBar _buildAppBar(BuildContext context) {
|
AppBar _buildAppBar(BuildContext context, bool isConnected) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(S.of(context).documentScannerPageTitle),
|
title: Text(S.of(context).documentScannerPageTitle),
|
||||||
|
bottom: !isConnected ? const OfflineBanner() : null,
|
||||||
actions: [
|
actions: [
|
||||||
BlocBuilder<DocumentScannerCubit, List<File>>(
|
BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -86,7 +93,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
BlocBuilder<DocumentScannerCubit, List<File>>(
|
BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: state.isEmpty
|
onPressed: state.isEmpty || !isConnected
|
||||||
? null
|
? null
|
||||||
: () => _onPrepareDocumentUpload(context),
|
: () => _onPrepareDocumentUpload(context),
|
||||||
icon: const Icon(Icons.done),
|
icon: const Icon(Icons.done),
|
||||||
@@ -127,35 +134,39 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
BlocProvider.of<DocumentScannerCubit>(context).state,
|
BlocProvider.of<DocumentScannerCubit>(context).state,
|
||||||
);
|
);
|
||||||
final bytes = await doc.save();
|
final bytes = await doc.save();
|
||||||
Navigator.of(context).push(
|
final uploaded = await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => LabelRepositoriesProvider(
|
builder: (_) => LabelRepositoriesProvider(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
localVault: getIt<LocalVault>(),
|
localVault: getIt<LocalVault>(),
|
||||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||||
correspondentRepository:
|
correspondentRepository:
|
||||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||||
context,
|
context,
|
||||||
|
),
|
||||||
|
documentTypeRepository:
|
||||||
|
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: DocumentUploadPreparationPage(
|
||||||
|
fileBytes: bytes,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
documentTypeRepository:
|
|
||||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DocumentUploadPreparationPage(
|
|
||||||
fileBytes: bytes,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
) ??
|
||||||
),
|
false;
|
||||||
);
|
if (uploaded) {
|
||||||
|
BlocProvider.of<DocumentScannerCubit>(context).reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody() {
|
Widget _buildBody(bool isConnected) {
|
||||||
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||||
builder: (context, scans) {
|
builder: (context, scans) {
|
||||||
if (scans.isNotEmpty) {
|
if (scans.isNotEmpty) {
|
||||||
@@ -181,7 +192,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
child: Text(S
|
child: Text(S
|
||||||
.of(context)
|
.of(context)
|
||||||
.documentScannerPageUploadFromThisDeviceButtonLabel),
|
.documentScannerPageUploadFromThisDeviceButtonLabel),
|
||||||
onPressed: _onUploadFromFilesystem,
|
onPressed: isConnected ? _onUploadFromFilesystem : null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -195,7 +206,7 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
itemCount: scans.length,
|
itemCount: scans.length,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 1 / sqrt(2),
|
childAspectRatio: 1 / sqrt(2),
|
||||||
crossAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
mainAxisSpacing: 10,
|
mainAxisSpacing: 10,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
typedef DeleteCallback = void Function();
|
typedef DeleteCallback = void Function();
|
||||||
@@ -28,7 +29,6 @@ class GridImageItemWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridImageItemWidgetState extends State<GridImageItemWidget> {
|
class _GridImageItemWidgetState extends State<GridImageItemWidget> {
|
||||||
bool isProcessing = false;
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -37,70 +37,86 @@ class _GridImageItemWidgetState extends State<GridImageItemWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Card _buildImageItem(BuildContext context) {
|
Widget _buildImageItem(BuildContext context) {
|
||||||
return Card(
|
final borderRadius = BorderRadius.circular(12);
|
||||||
child: Padding(
|
return ClipRRect(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
children: [
|
children: [
|
||||||
Align(alignment: Alignment.bottomCenter, child: _buildNumbering()),
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topCenter,
|
||||||
child: IconButton(
|
child: ClipRRect(
|
||||||
onPressed: widget.onDelete,
|
borderRadius: borderRadius,
|
||||||
icon: const Icon(Icons.close),
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 100,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Image.file(
|
||||||
|
widget.file,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"${widget.index + 1}/${widget.totalNumberOfFiles}",
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: widget.onDelete,
|
||||||
|
child: Text("Remove"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isProcessing
|
|
||||||
? _buildIsProcessing()
|
|
||||||
: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 4 / 3,
|
|
||||||
child: Image.file(
|
|
||||||
widget.file,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Center _buildIsProcessing() {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: const [
|
|
||||||
CircularProgressIndicator(),
|
|
||||||
Text(
|
|
||||||
"Processing transformation...",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showImage(BuildContext context) {
|
void _showImage(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => Scaffold(
|
builder: (context) => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: _buildNumbering(prefix: "Image"),
|
title: Text(
|
||||||
|
"${S.of(context).scannerPageImagePreviewTitle} ${widget.index + 1}/${widget.totalNumberOfFiles}"),
|
||||||
),
|
),
|
||||||
body: PhotoView(imageProvider: FileImage(widget.file)),
|
body: PhotoView(imageProvider: FileImage(widget.file)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNumbering({String? prefix}) {
|
|
||||||
return Text(
|
|
||||||
"${prefix ?? ""} ${widget.index + 1}/${widget.totalNumberOfFiles}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
"@genericActionUpdateLabel": {},
|
"@genericActionUpdateLabel": {},
|
||||||
"genericActionUploadLabel": "Nahrát",
|
"genericActionUploadLabel": "Nahrát",
|
||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Jste offline. Ověřte připojení.",
|
"genericMessageOfflineText": "Jste offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Dokument odstraněn z inboxu.",
|
"inboxPageDocumentRemovedMessageText": "Dokument odstraněn z inboxu.",
|
||||||
"@inboxPageDocumentRemovedMessageText": {},
|
"@inboxPageDocumentRemovedMessageText": {},
|
||||||
@@ -408,6 +408,8 @@
|
|||||||
"@savedViewShowOnDashboardLabel": {},
|
"@savedViewShowOnDashboardLabel": {},
|
||||||
"savedViewsLabel": "Uložené náhledy",
|
"savedViewsLabel": "Uložené náhledy",
|
||||||
"@savedViewsLabel": {},
|
"@savedViewsLabel": {},
|
||||||
|
"scannerPageImagePreviewTitle": "",
|
||||||
|
"@scannerPageImagePreviewTitle": {},
|
||||||
"serverInformationPaperlessVersionText": "Verze Paperless serveru",
|
"serverInformationPaperlessVersionText": "Verze Paperless serveru",
|
||||||
"@serverInformationPaperlessVersionText": {},
|
"@serverInformationPaperlessVersionText": {},
|
||||||
"settingsPageAppearanceSettingDarkThemeLabel": "Tmavý vzhled",
|
"settingsPageAppearanceSettingDarkThemeLabel": "Tmavý vzhled",
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
"@genericActionUpdateLabel": {},
|
"@genericActionUpdateLabel": {},
|
||||||
"genericActionUploadLabel": "Hochladen",
|
"genericActionUploadLabel": "Hochladen",
|
||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "Du bist offline. Überprüfe deine Verbindung.",
|
"genericMessageOfflineText": "Du bist offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
|
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
|
||||||
"@inboxPageDocumentRemovedMessageText": {},
|
"@inboxPageDocumentRemovedMessageText": {},
|
||||||
@@ -408,6 +408,8 @@
|
|||||||
"@savedViewShowOnDashboardLabel": {},
|
"@savedViewShowOnDashboardLabel": {},
|
||||||
"savedViewsLabel": "Gespeicherte Ansichten",
|
"savedViewsLabel": "Gespeicherte Ansichten",
|
||||||
"@savedViewsLabel": {},
|
"@savedViewsLabel": {},
|
||||||
|
"scannerPageImagePreviewTitle": "Aufnahme",
|
||||||
|
"@scannerPageImagePreviewTitle": {},
|
||||||
"serverInformationPaperlessVersionText": "Paperless Server-Version",
|
"serverInformationPaperlessVersionText": "Paperless Server-Version",
|
||||||
"@serverInformationPaperlessVersionText": {},
|
"@serverInformationPaperlessVersionText": {},
|
||||||
"settingsPageAppearanceSettingDarkThemeLabel": "Dunkler Modus",
|
"settingsPageAppearanceSettingDarkThemeLabel": "Dunkler Modus",
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
"@genericActionUpdateLabel": {},
|
"@genericActionUpdateLabel": {},
|
||||||
"genericActionUploadLabel": "Upload",
|
"genericActionUploadLabel": "Upload",
|
||||||
"@genericActionUploadLabel": {},
|
"@genericActionUploadLabel": {},
|
||||||
"genericMessageOfflineText": "You're offline. Check your connection.",
|
"genericMessageOfflineText": "You're offline.",
|
||||||
"@genericMessageOfflineText": {},
|
"@genericMessageOfflineText": {},
|
||||||
"inboxPageDocumentRemovedMessageText": "Document removed from inbox.",
|
"inboxPageDocumentRemovedMessageText": "Document removed from inbox.",
|
||||||
"@inboxPageDocumentRemovedMessageText": {},
|
"@inboxPageDocumentRemovedMessageText": {},
|
||||||
@@ -408,6 +408,8 @@
|
|||||||
"@savedViewShowOnDashboardLabel": {},
|
"@savedViewShowOnDashboardLabel": {},
|
||||||
"savedViewsLabel": "Saved Views",
|
"savedViewsLabel": "Saved Views",
|
||||||
"@savedViewsLabel": {},
|
"@savedViewsLabel": {},
|
||||||
|
"scannerPageImagePreviewTitle": "Scan",
|
||||||
|
"@scannerPageImagePreviewTitle": {},
|
||||||
"serverInformationPaperlessVersionText": "Paperless server version",
|
"serverInformationPaperlessVersionText": "Paperless server version",
|
||||||
"@serverInformationPaperlessVersionText": {},
|
"@serverInformationPaperlessVersionText": {},
|
||||||
"settingsPageAppearanceSettingDarkThemeLabel": "Dark Theme",
|
"settingsPageAppearanceSettingDarkThemeLabel": "Dark Theme",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:intl/intl_standalone.dart';
|
import 'package:intl/intl_standalone.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -25,14 +26,15 @@ import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart';
|
|||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
|
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.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/document_upload/view/document_upload_preparation_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
@@ -114,8 +116,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme:
|
colorSchemeSeed: Colors.lightGreen,
|
||||||
ColorScheme.fromSeed(seedColor: Colors.lightGreen).copyWith(),
|
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
scrolledUnderElevation: 0.0,
|
scrolledUnderElevation: 0.0,
|
||||||
),
|
),
|
||||||
@@ -123,7 +124,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
vertical: 16.0,
|
vertical: 16.0,
|
||||||
),
|
),
|
||||||
@@ -143,7 +144,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
vertical: 16.0,
|
vertical: 16.0,
|
||||||
),
|
),
|
||||||
@@ -195,7 +196,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
}
|
}
|
||||||
late final SharedMediaFile file;
|
late final SharedMediaFile file;
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
// Workaround: https://stackoverflow.com/a/72813212
|
// Workaround for file not found on iOS: https://stackoverflow.com/a/72813212
|
||||||
file = SharedMediaFile(
|
file = SharedMediaFile(
|
||||||
files.first.path.replaceAll('file://', ''),
|
files.first.path.replaceAll('file://', ''),
|
||||||
files.first.thumbnail,
|
files.first.thumbnail,
|
||||||
@@ -221,8 +222,22 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
final success = await Navigator.push(
|
final success = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (context) => BlocProvider(
|
||||||
value: getIt<DocumentScannerCubit>(),
|
create: (BuildContext context) => DocumentUploadCubit(
|
||||||
|
localVault: getIt<LocalVault>(),
|
||||||
|
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||||
|
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
correspondentRepository:
|
||||||
|
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
documentTypeRepository:
|
||||||
|
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
),
|
||||||
child: DocumentUploadPreparationPage(
|
child: DocumentUploadPreparationPage(
|
||||||
fileBytes: bytes,
|
fileBytes: bytes,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
@@ -244,6 +259,7 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
initializeDateFormatting();
|
||||||
// For sharing files coming from outside the app while the app is still opened
|
// For sharing files coming from outside the app while the app is still opened
|
||||||
ReceiveSharingIntent.getMediaStream().listen(handleReceivedFiles);
|
ReceiveSharingIntent.getMediaStream().listen(handleReceivedFiles);
|
||||||
// For sharing files coming from outside the app while the app is closed
|
// For sharing files coming from outside the app while the app is closed
|
||||||
@@ -312,12 +328,12 @@ class BiometricAuthenticationPage extends StatelessWidget {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
BlocProvider.of<AuthenticationCubit>(context).logout(),
|
BlocProvider.of<AuthenticationCubit>(context).logout(),
|
||||||
child: Text("Log out"),
|
child: const Text("Log out"),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => BlocProvider.of<AuthenticationCubit>(context)
|
onPressed: () => BlocProvider.of<AuthenticationCubit>(context)
|
||||||
.restoreSessionState(),
|
.restoreSessionState(),
|
||||||
child: Text("Authenticate"),
|
child: const Text("Authenticate"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,11 +16,21 @@ final dateFormat = DateFormat("yyyy-MM-dd");
|
|||||||
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
late PackageInfo kPackageInfo;
|
late PackageInfo kPackageInfo;
|
||||||
|
|
||||||
|
class SnackBarActionConfig {
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
SnackBarActionConfig({
|
||||||
|
required this.label,
|
||||||
|
required this.onPressed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void showSnackBar(
|
void showSnackBar(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String message, {
|
String message, {
|
||||||
String? details,
|
String? details,
|
||||||
SnackBarAction? action,
|
SnackBarActionConfig? action,
|
||||||
}) {
|
}) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
..hideCurrentSnackBar()
|
..hideCurrentSnackBar()
|
||||||
@@ -29,7 +39,13 @@ void showSnackBar(
|
|||||||
content: Text(
|
content: Text(
|
||||||
message + (details != null ? ' ($details)' : ''),
|
message + (details != null ? ' ($details)' : ''),
|
||||||
),
|
),
|
||||||
action: action,
|
action: action != null
|
||||||
|
? SnackBarAction(
|
||||||
|
label: action.label,
|
||||||
|
onPressed: action.onPressed,
|
||||||
|
textColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
duration: const Duration(seconds: 5),
|
duration: const Duration(seconds: 5),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -43,9 +59,8 @@ void showGenericError(
|
|||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
error.toString(),
|
error.toString(),
|
||||||
action: SnackBarAction(
|
action: SnackBarActionConfig(
|
||||||
label: S.of(context).errorReportLabel,
|
label: S.of(context).errorReportLabel,
|
||||||
textColor: Colors.amber,
|
|
||||||
onPressed: () => GithubIssueService.createIssueFromError(
|
onPressed: () => GithubIssueService.createIssueFromError(
|
||||||
context,
|
context,
|
||||||
stackTrace: stackTrace,
|
stackTrace: stackTrace,
|
||||||
@@ -69,14 +84,6 @@ void showErrorMessage(
|
|||||||
context,
|
context,
|
||||||
translateError(context, error.code),
|
translateError(context, error.code),
|
||||||
details: error.details,
|
details: error.details,
|
||||||
action: SnackBarAction(
|
|
||||||
label: S.of(context).errorReportLabel,
|
|
||||||
textColor: Colors.amber,
|
|
||||||
onPressed: () => GithubIssueService.createIssueFromError(
|
|
||||||
context,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
log(
|
log(
|
||||||
"An error has occurred.",
|
"An error has occurred.",
|
||||||
|
|||||||
Reference in New Issue
Block a user