mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2026-01-31 14:24:58 -06:00
Merge pull request #99 from astubenbord/feature/detailed-view-type
Feature: Detailed view type
This commit is contained in:
@@ -75,22 +75,17 @@ To get a local copy up and running follow these simple steps.
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
* Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions)
|
* Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions)
|
||||||
|
* Install the flutter SDK (https://docs.flutter.dev/get-started/install) _or_ use the flutter git submodule pinned in this project by running `git submodule update --init` inside the project root directory.
|
||||||
|
*
|
||||||
### Install dependencies and generate files
|
### Install dependencies and generate files
|
||||||
1. First, clone the repository:
|
1. First, clone the repository:
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/astubenbord/paperless-mobile.git
|
git clone https://github.com/astubenbord/paperless-mobile.git
|
||||||
```
|
```
|
||||||
In this project, flutter is pinned at a specific version as a git submodule to ensure all contributors work with the same environment and build with the same flutter version. You can also use your local flutter installation, just make sure that the app also compiles with the same flutter version as pinned in the `flutter` submodule when opening a pull request.
|
|
||||||
|
|
||||||
To download the pinned flutter SDK from the submodule and plan to install the dependencies manually in the next step, simply run
|
You can now run the `scripts/install_dependencies.sh` script at the root of the project, which will automatically install dependencies and generate files for both the app and local packages.
|
||||||
```sh
|
|
||||||
git submodule update --init
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now run the `scripts/install_dependencies.sh` script at the root of the project, which will automatically install dependencies and generate files for both the app and subpackages. Note that the `install_dependencies.sh` script will pull the flutter submodule and use the SDK to execute the flutter commands.
|
If you want to manually install dependencies and build generated files, you can also run the following commands:
|
||||||
|
|
||||||
If you don't want to use submodules, you can also run the following commands using your local flutter installation:
|
|
||||||
|
|
||||||
#### Inside the `packages/paperless_api/` folder:
|
#### Inside the `packages/paperless_api/` folder:
|
||||||
2. Install the dependencies for `paperless_api`
|
2. Install the dependencies for `paperless_api`
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
DioError(
|
DioError(
|
||||||
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
error: const PaperlessServerException(ErrorCode.deviceOffline),
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.connectTimeout,
|
type: DioErrorType.connectionTimeout,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
DioError(
|
DioError(
|
||||||
error: errorMessages,
|
error: errorMessages,
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.response,
|
type: DioErrorType.badResponse,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
handler.reject(
|
handler.reject(
|
||||||
DioError(
|
DioError(
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.response,
|
type: DioErrorType.badResponse,
|
||||||
error: const PaperlessServerException(
|
error: const PaperlessServerException(
|
||||||
ErrorCode.missingClientCertificate),
|
ErrorCode.missingClientCertificate),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ class RetryOnConnectionChangeInterceptor extends Interceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRetryOnHttpException(DioError err) {
|
bool _shouldRetryOnHttpException(DioError err) {
|
||||||
return err.type == DioErrorType.other &&
|
return err.type == DioErrorType.unknown &&
|
||||||
((err.error is HttpException &&
|
(err.error is HttpException &&
|
||||||
err.message.contains(
|
(err.message?.contains(
|
||||||
'Connection closed before full header was received')));
|
'Connection closed before full header was received',
|
||||||
|
) ??
|
||||||
|
false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ServerReachabilityErrorInterceptor extends Interceptor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err.type == DioErrorType.connectTimeout) {
|
if (err.type == DioErrorType.connectionTimeout) {
|
||||||
return _rejectWithStatus(
|
return _rejectWithStatus(
|
||||||
ReachabilityStatus.connectionTimeout,
|
ReachabilityStatus.connectionTimeout,
|
||||||
err,
|
err,
|
||||||
@@ -55,6 +55,6 @@ void _rejectWithStatus(
|
|||||||
error: reachabilityStatus,
|
error: reachabilityStatus,
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
response: err.response,
|
response: err.response,
|
||||||
type: DioErrorType.other,
|
type: DioErrorType.unknown,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/adapter.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:dio/io.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
|
|
||||||
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
@@ -19,10 +19,12 @@ class SessionManager {
|
|||||||
|
|
||||||
static Dio _initDio(List<Interceptor> interceptors) {
|
static Dio _initDio(List<Interceptor> interceptors) {
|
||||||
//en- and decoded by utf8 by default
|
//en- and decoded by utf8 by default
|
||||||
final Dio dio = Dio(BaseOptions());
|
final Dio dio = Dio(
|
||||||
dio.options.receiveTimeout = const Duration(seconds: 25).inMilliseconds;
|
BaseOptions(contentType: Headers.jsonContentType),
|
||||||
|
);
|
||||||
|
dio.options.receiveTimeout = const Duration(seconds: 25);
|
||||||
dio.options.responseType = ResponseType.json;
|
dio.options.responseType = ResponseType.json;
|
||||||
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
|
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
||||||
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
||||||
dio.interceptors.addAll([
|
dio.interceptors.addAll([
|
||||||
...interceptors,
|
...interceptors,
|
||||||
@@ -59,7 +61,7 @@ class SessionManager {
|
|||||||
clientCertificate.bytes,
|
clientCertificate.bytes,
|
||||||
password: clientCertificate.passphrase,
|
password: clientCertificate.passphrase,
|
||||||
);
|
);
|
||||||
final adapter = DefaultHttpClientAdapter()
|
final adapter = IOHttpClientAdapter()
|
||||||
..onHttpClientCreate = (client) => HttpClient(context: context)
|
..onHttpClientCreate = (client) => HttpClient(context: context)
|
||||||
..badCertificateCallback =
|
..badCertificateCallback =
|
||||||
(X509Certificate cert, String host, int port) => true;
|
(X509Certificate cert, String host, int port) => true;
|
||||||
@@ -72,7 +74,9 @@ class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authToken != null) {
|
if (authToken != null) {
|
||||||
client.options.headers.addAll({'Authorization': 'Token $authToken'});
|
client.options.headers.addAll({
|
||||||
|
HttpHeaders.authorizationHeader: 'Token $authToken',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverInformation != null) {
|
if (serverInformation != null) {
|
||||||
@@ -81,9 +85,9 @@ class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetSettings() {
|
void resetSettings() {
|
||||||
client.httpClientAdapter = DefaultHttpClientAdapter();
|
client.httpClientAdapter = IOHttpClientAdapter();
|
||||||
client.options.baseUrl = '';
|
client.options.baseUrl = '';
|
||||||
client.options.headers.remove('Authorization');
|
client.options.headers.remove(HttpHeaders.authorizationHeader);
|
||||||
serverInformation = PaperlessServerInformationModel();
|
serverInformation = PaperlessServerInformationModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:dio/adapter.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_mobile/core/global/os_error_codes.dart';
|
import 'package:paperless_mobile/core/global/os_error_codes.dart';
|
||||||
import 'package:paperless_mobile/core/interceptor/server_reachability_error_interceptor.dart';
|
import 'package:paperless_mobile/core/interceptor/server_reachability_error_interceptor.dart';
|
||||||
@@ -71,8 +69,8 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
SessionManager manager =
|
SessionManager manager =
|
||||||
SessionManager([ServerReachabilityErrorInterceptor()])
|
SessionManager([ServerReachabilityErrorInterceptor()])
|
||||||
..updateSettings(clientCertificate: clientCertificate)
|
..updateSettings(clientCertificate: clientCertificate)
|
||||||
..client.options.connectTimeout = 5000
|
..client.options.connectTimeout = const Duration(seconds: 5)
|
||||||
..client.options.receiveTimeout = 5000;
|
..client.options.receiveTimeout = const Duration(seconds: 5);
|
||||||
|
|
||||||
final response = await manager.client.get('$serverAddress/api/');
|
final response = await manager.client.get('$serverAddress/api/');
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
@@ -80,7 +78,7 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
}
|
}
|
||||||
return ReachabilityStatus.notReachable;
|
return ReachabilityStatus.notReachable;
|
||||||
} on DioError catch (error) {
|
} on DioError catch (error) {
|
||||||
if (error.type == DioErrorType.other &&
|
if (error.type == DioErrorType.unknown &&
|
||||||
error.error is ReachabilityStatus) {
|
error.error is ReachabilityStatus) {
|
||||||
return error.error as ReachabilityStatus;
|
return error.error as ReachabilityStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,22 @@ extension WidgetPadding on Widget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget paddedSymmetrically({double horizontal = 0.0, double vertical = 0.0}) {
|
Widget paddedSymmetrically({
|
||||||
|
double horizontal = 0.0,
|
||||||
|
double vertical = 0.0,
|
||||||
|
}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
|
padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
|
||||||
child: this,
|
child: this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget paddedOnly(
|
Widget paddedOnly({
|
||||||
{double top = 0.0,
|
double top = 0.0,
|
||||||
double bottom = 0.0,
|
double bottom = 0.0,
|
||||||
double left = 0.0,
|
double left = 0.0,
|
||||||
double right = 0.0}) {
|
double right = 0.0,
|
||||||
|
}) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: top,
|
top: top,
|
||||||
@@ -30,6 +34,13 @@ extension WidgetPadding on Widget {
|
|||||||
child: this,
|
child: this,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget paddedLTRB(double left, double top, double right, double bottom) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(left, top, right, bottom),
|
||||||
|
child: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WidgetsPadding on List<Widget> {
|
extension WidgetsPadding on List<Widget> {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_loadMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadMetaData() {
|
||||||
_metaData = context
|
_metaData = context
|
||||||
.read<PaperlessDocumentsApi>()
|
.read<PaperlessDocumentsApi>()
|
||||||
.getMetaData(context.read<DocumentDetailsCubit>().state.document);
|
.getMetaData(context.read<DocumentDetailsCubit>().state.document);
|
||||||
@@ -64,108 +68,117 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
},
|
},
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
length: 4,
|
length: 4,
|
||||||
child: Scaffold(
|
child: BlocListener<ConnectivityCubit, ConnectivityState>(
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
listenWhen: (previous, current) =>
|
||||||
floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
|
!previous.isConnected && current.isConnected,
|
||||||
bottomNavigationBar: _buildBottomAppBar(),
|
listener: (context, state) {
|
||||||
body: NestedScrollView(
|
_loadMetaData();
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
setState(() {});
|
||||||
SliverAppBar(
|
},
|
||||||
leading: const BackButton(),
|
child: Scaffold(
|
||||||
floating: true,
|
floatingActionButtonLocation:
|
||||||
pinned: true,
|
FloatingActionButtonLocation.endDocked,
|
||||||
expandedHeight: 200.0,
|
floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
|
||||||
flexibleSpace:
|
bottomNavigationBar: _buildBottomAppBar(),
|
||||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
body: NestedScrollView(
|
||||||
builder: (context, state) => DocumentPreview(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
id: state.document.id,
|
SliverAppBar(
|
||||||
fit: BoxFit.cover,
|
leading: const BackButton(),
|
||||||
|
floating: true,
|
||||||
|
pinned: true,
|
||||||
|
expandedHeight: 200.0,
|
||||||
|
flexibleSpace:
|
||||||
|
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
|
builder: (context, state) => DocumentPreview(
|
||||||
|
document: state.document,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottom: ColoredTabBar(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
tabBar: TabBar(
|
||||||
|
isScrollable: true,
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
S.of(context).documentDetailsPageTabOverviewLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
S.of(context).documentDetailsPageTabContentLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
S.of(context).documentDetailsPageTabMetaDataLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
S
|
||||||
|
.of(context)
|
||||||
|
.documentDetailsPageTabSimilarDocumentsLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottom: ColoredTabBar(
|
],
|
||||||
backgroundColor:
|
body: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
Theme.of(context).colorScheme.primaryContainer,
|
builder: (context, state) {
|
||||||
tabBar: TabBar(
|
return BlocProvider(
|
||||||
isScrollable: true,
|
create: (context) => SimilarDocumentsCubit(
|
||||||
tabs: [
|
context.read(),
|
||||||
Tab(
|
context.read(),
|
||||||
child: Text(
|
documentId: state.document.id,
|
||||||
S.of(context).documentDetailsPageTabOverviewLabel,
|
),
|
||||||
style: TextStyle(
|
child: TabBarView(
|
||||||
color: Theme.of(context)
|
children: [
|
||||||
.colorScheme
|
DocumentOverviewWidget(
|
||||||
.onPrimaryContainer,
|
document: state.document,
|
||||||
),
|
itemSpacing: _itemPadding,
|
||||||
|
queryString: widget.titleAndContentQueryString,
|
||||||
),
|
),
|
||||||
),
|
DocumentContentWidget(
|
||||||
Tab(
|
isFullContentLoaded: state.isFullContentLoaded,
|
||||||
child: Text(
|
document: state.document,
|
||||||
S.of(context).documentDetailsPageTabContentLabel,
|
fullContent: state.fullContent,
|
||||||
style: TextStyle(
|
queryString: widget.titleAndContentQueryString,
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
DocumentMetaDataWidget(
|
||||||
Tab(
|
document: state.document,
|
||||||
child: Text(
|
itemSpacing: _itemPadding,
|
||||||
S.of(context).documentDetailsPageTabMetaDataLabel,
|
metaData: _metaData,
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const SimilarDocumentsView(),
|
||||||
Tab(
|
],
|
||||||
child: Text(
|
),
|
||||||
S
|
);
|
||||||
.of(context)
|
},
|
||||||
.documentDetailsPageTabSimilarDocumentsLabel,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
body: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => SimilarDocumentsCubit(
|
|
||||||
context.read(),
|
|
||||||
context.read(),
|
|
||||||
documentId: state.document.id,
|
|
||||||
),
|
|
||||||
child: TabBarView(
|
|
||||||
children: [
|
|
||||||
DocumentOverviewWidget(
|
|
||||||
document: state.document,
|
|
||||||
itemSpacing: _itemPadding,
|
|
||||||
queryString: widget.titleAndContentQueryString,
|
|
||||||
),
|
|
||||||
DocumentContentWidget(
|
|
||||||
isFullContentLoaded: state.isFullContentLoaded,
|
|
||||||
document: state.document,
|
|
||||||
fullContent: state.fullContent,
|
|
||||||
queryString: widget.titleAndContentQueryString,
|
|
||||||
),
|
|
||||||
DocumentMetaDataWidget(
|
|
||||||
document: state.document,
|
|
||||||
itemSpacing: _itemPadding,
|
|
||||||
metaData: _metaData,
|
|
||||||
),
|
|
||||||
const SimilarDocumentsView(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -25,18 +25,17 @@ class DocumentMetaDataWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, state) {
|
builder: (context, connectivity) {
|
||||||
if (!state.isConnected) {
|
|
||||||
return const Center(
|
|
||||||
child: OfflineWidget(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return FutureBuilder<DocumentMetaData>(
|
return FutureBuilder<DocumentMetaData>(
|
||||||
future: metaData,
|
future: metaData,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
if (!connectivity.isConnected && !snapshot.hasData) {
|
||||||
|
return OfflineWidget();
|
||||||
|
}
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
final meta = snapshot.data!;
|
final meta = snapshot.data!;
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -55,7 +54,9 @@ class DocumentMetaDataWidget extends StatelessWidget {
|
|||||||
label: Text(S
|
label: Text(S
|
||||||
.of(context)
|
.of(context)
|
||||||
.documentDetailsPageAssignAsnButtonLabel),
|
.documentDetailsPageAssignAsnButtonLabel),
|
||||||
onPressed: () => _assignAsn(context),
|
onPressed: connectivity.isConnected
|
||||||
|
? () => _assignAsn(context)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
DetailsItem.text(DateFormat().format(document.modified),
|
DetailsItem.text(DateFormat().format(document.modified),
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ class _DocumentViewState extends State<DocumentView> {
|
|||||||
),
|
),
|
||||||
body: PdfView(
|
body: PdfView(
|
||||||
builders: PdfViewBuilders<DefaultBuilderOptions>(
|
builders: PdfViewBuilders<DefaultBuilderOptions>(
|
||||||
options: const DefaultBuilderOptions(),
|
options: const DefaultBuilderOptions(
|
||||||
|
loaderSwitchDuration: Duration(milliseconds: 500),
|
||||||
|
),
|
||||||
pageLoaderBuilder: (context) => const Center(
|
pageLoaderBuilder: (context) => const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
controller: _pdfController,
|
controller: _pdfController,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_
|
|||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
||||||
import 'package:paperless_mobile/features/labels/cubit/providers/labels_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/cubit/providers/labels_bloc_provider.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
||||||
@@ -42,10 +43,18 @@ class DocumentsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentsPageState extends State<DocumentsPage>
|
class _DocumentsPageState extends State<DocumentsPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with
|
||||||
|
SingleTickerProviderStateMixin,
|
||||||
|
DocumentPagingViewMixin<DocumentsPage, DocumentsCubit> {
|
||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ScrollController get pagingScrollController =>
|
||||||
|
_nestedScrollViewKey.currentState?.innerController ?? ScrollController();
|
||||||
|
|
||||||
|
final GlobalKey<NestedScrollViewState> _nestedScrollViewKey = GlobalKey();
|
||||||
int _currentTab = 0;
|
int _currentTab = 0;
|
||||||
|
bool _showBackToTopButton = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -53,21 +62,40 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
initialIndex: 0,
|
|
||||||
);
|
);
|
||||||
try {
|
Future.wait([
|
||||||
context.read<DocumentsCubit>().reload();
|
context.read<DocumentsCubit>().reload(),
|
||||||
context.read<SavedViewCubit>().reload();
|
context.read<SavedViewCubit>().reload(),
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
]).onError<PaperlessServerException>(
|
||||||
showErrorMessage(context, error, stackTrace);
|
(error, stackTrace) {
|
||||||
}
|
showErrorMessage(context, error, stackTrace);
|
||||||
_tabController.addListener(_listenForTabChanges);
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
_tabController.addListener(_tabChangesListener);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
_nestedScrollViewKey.currentState!.innerController
|
||||||
|
..addListener(_scrollExtentListener)
|
||||||
|
..addListener(shouldLoadMoreDocumentsListener);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenForTabChanges() {
|
void _tabChangesListener() {
|
||||||
setState(() {
|
setState(() => _currentTab = _tabController.index);
|
||||||
_currentTab = _tabController.index;
|
}
|
||||||
});
|
|
||||||
|
void _scrollExtentListener() {
|
||||||
|
if (pagingScrollController.position.pixels >
|
||||||
|
MediaQuery.of(context).size.height) {
|
||||||
|
if (!_showBackToTopButton) {
|
||||||
|
setState(() => _showBackToTopButton = true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_showBackToTopButton) {
|
||||||
|
setState(() => _showBackToTopButton = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -145,186 +173,100 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: NestedScrollView(
|
child: Stack(
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
children: [
|
||||||
SliverOverlapAbsorber(
|
NestedScrollView(
|
||||||
// This widget takes the overlapping behavior of the SliverAppBar,
|
key: _nestedScrollViewKey,
|
||||||
// and redirects it to the SliverOverlapInjector below. If it is
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
// missing, then it is possible for the nested "inner" scroll view
|
SliverOverlapAbsorber(
|
||||||
// below to end up under the SliverAppBar even when the inner
|
// This widget takes the overlapping behavior of the SliverAppBar,
|
||||||
// scroll view thinks it has not been scrolled.
|
// and redirects it to the SliverOverlapInjector below. If it is
|
||||||
// This is not necessary if the "headerSliverBuilder" only builds
|
// missing, then it is possible for the nested "inner" scroll view
|
||||||
// widgets that do not overlap the next sliver.
|
// below to end up under the SliverAppBar even when the inner
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
// scroll view thinks it has not been scrolled.
|
||||||
context,
|
// This is not necessary if the "headerSliverBuilder" only builds
|
||||||
),
|
// widgets that do not overlap the next sliver.
|
||||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||||
builder: (context, state) {
|
context,
|
||||||
if (state.selection.isNotEmpty) {
|
),
|
||||||
return SliverAppBar(
|
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
floating: false,
|
builder: (context, state) {
|
||||||
pinned: true,
|
if (state.selection.isNotEmpty) {
|
||||||
leading: IconButton(
|
return SliverAppBar(
|
||||||
icon: const Icon(Icons.close),
|
floating: false,
|
||||||
onPressed: () => context
|
pinned: true,
|
||||||
.read<DocumentsCubit>()
|
leading: IconButton(
|
||||||
.resetSelection(),
|
icon: const Icon(Icons.close),
|
||||||
),
|
onPressed: () => context
|
||||||
title: Text(
|
.read<DocumentsCubit>()
|
||||||
"${state.selection.length} ${S.of(context).documentsSelectedText}",
|
.resetSelection(),
|
||||||
),
|
),
|
||||||
actions: [
|
title: Text(
|
||||||
IconButton(
|
"${state.selection.length} ${S.of(context).documentsSelectedText}",
|
||||||
icon: const Icon(Icons.delete),
|
),
|
||||||
onPressed: () => _onDelete(state),
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () => _onDelete(state),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SearchAppBar(
|
||||||
|
hintText:
|
||||||
|
S.of(context).documentSearchSearchDocuments,
|
||||||
|
onOpenSearch: showDocumentSearchPage,
|
||||||
|
bottom: TabBar(
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: [
|
||||||
|
Tab(text: S.of(context).documentsPageTitle),
|
||||||
|
Tab(text: S.of(context).savedViewsLabel),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
body: NotificationListener<ScrollNotification>(
|
||||||
|
onNotification: (notification) {
|
||||||
|
final metrics = notification.metrics;
|
||||||
|
if (metrics.maxScrollExtent == 0) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return SearchAppBar(
|
final desiredTab =
|
||||||
hintText: S.of(context).documentSearchSearchDocuments,
|
(metrics.pixels / metrics.maxScrollExtent).round();
|
||||||
onOpenSearch: showDocumentSearchPage,
|
if (metrics.axis == Axis.horizontal &&
|
||||||
bottom: TabBar(
|
_currentTab != desiredTab) {
|
||||||
controller: _tabController,
|
setState(() => _currentTab = desiredTab);
|
||||||
tabs: [
|
}
|
||||||
Tab(text: S.of(context).documentsPageTitle),
|
return false;
|
||||||
Tab(text: S.of(context).savedViewsLabel),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
child: TabBarView(
|
||||||
),
|
controller: _tabController,
|
||||||
],
|
children: [
|
||||||
body: NotificationListener<ScrollNotification>(
|
Builder(
|
||||||
onNotification: (notification) {
|
builder: (context) {
|
||||||
final metrics = notification.metrics;
|
return _buildDocumentsTab(
|
||||||
if (metrics.maxScrollExtent == 0) {
|
connectivityState,
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final desiredTab =
|
|
||||||
(metrics.pixels / metrics.maxScrollExtent).round();
|
|
||||||
if (metrics.axis == Axis.horizontal &&
|
|
||||||
_currentTab != desiredTab) {
|
|
||||||
setState(() => _currentTab = desiredTab);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: NotificationListener<ScrollMetricsNotification>(
|
|
||||||
onNotification: (notification) {
|
|
||||||
// Listen for scroll notifications to load new data.
|
|
||||||
// Scroll controller does not work here due to nestedscrollview limitations.
|
|
||||||
final currState = context.read<DocumentsCubit>().state;
|
|
||||||
final max = notification.metrics.maxScrollExtent;
|
|
||||||
if (max == 0 ||
|
|
||||||
_currentTab != 0 ||
|
|
||||||
currState.isLoading ||
|
|
||||||
currState.isLastPageLoaded) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final offset = notification.metrics.pixels;
|
|
||||||
if (offset >= max * 0.7) {
|
|
||||||
context
|
|
||||||
.read<DocumentsCubit>()
|
|
||||||
.loadMore()
|
|
||||||
.onError<PaperlessServerException>(
|
|
||||||
(error, stackTrace) => showErrorMessage(
|
|
||||||
context,
|
context,
|
||||||
error,
|
);
|
||||||
stackTrace,
|
},
|
||||||
),
|
),
|
||||||
);
|
Builder(
|
||||||
}
|
builder: (context) {
|
||||||
return false;
|
return _buildSavedViewsTab(
|
||||||
},
|
connectivityState,
|
||||||
child: TabBarView(
|
context,
|
||||||
controller: _tabController,
|
);
|
||||||
children: [
|
},
|
||||||
Builder(
|
),
|
||||||
builder: (context) {
|
],
|
||||||
return RefreshIndicator(
|
),
|
||||||
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
|
||||||
onRefresh: _onReloadDocuments,
|
|
||||||
notificationPredicate: (_) =>
|
|
||||||
connectivityState.isConnected,
|
|
||||||
child: CustomScrollView(
|
|
||||||
key: const PageStorageKey<String>("documents"),
|
|
||||||
slivers: <Widget>[
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(
|
|
||||||
context),
|
|
||||||
),
|
|
||||||
_buildViewActions(),
|
|
||||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.hasLoaded &&
|
|
||||||
state.documents.isEmpty) {
|
|
||||||
return SliverToBoxAdapter(
|
|
||||||
child: DocumentsEmptyState(
|
|
||||||
state: state,
|
|
||||||
onReset: () {
|
|
||||||
context
|
|
||||||
.read<DocumentsCubit>()
|
|
||||||
.resetFilter();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SliverAdaptiveDocumentsView(
|
|
||||||
viewType: state.viewType,
|
|
||||||
onTap: _openDetails,
|
|
||||||
onSelected: context
|
|
||||||
.read<DocumentsCubit>()
|
|
||||||
.toggleDocumentSelection,
|
|
||||||
hasInternetConnection:
|
|
||||||
connectivityState.isConnected,
|
|
||||||
onTagSelected: _addTagToFilter,
|
|
||||||
onCorrespondentSelected:
|
|
||||||
_addCorrespondentToFilter,
|
|
||||||
onDocumentTypeSelected:
|
|
||||||
_addDocumentTypeToFilter,
|
|
||||||
onStoragePathSelected:
|
|
||||||
_addStoragePathToFilter,
|
|
||||||
documents: state.documents,
|
|
||||||
hasLoaded: state.hasLoaded,
|
|
||||||
isLabelClickable: true,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
selectedDocumentIds: state.selectedIds,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return RefreshIndicator(
|
|
||||||
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
|
||||||
onRefresh: _onReloadSavedViews,
|
|
||||||
notificationPredicate: (_) =>
|
|
||||||
connectivityState.isConnected,
|
|
||||||
child: CustomScrollView(
|
|
||||||
key: const PageStorageKey<String>("savedViews"),
|
|
||||||
slivers: <Widget>[
|
|
||||||
SliverOverlapInjector(
|
|
||||||
handle: NestedScrollView
|
|
||||||
.sliverOverlapAbsorberHandleFor(
|
|
||||||
context),
|
|
||||||
),
|
|
||||||
const SavedViewList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (_showBackToTopButton) _buildBackToTopAction(context),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -333,6 +275,109 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildBackToTopAction(BuildContext context) {
|
||||||
|
return Transform.translate(
|
||||||
|
offset: const Offset(24, -24),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: ActionChip(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
side: BorderSide.none,
|
||||||
|
avatar: Icon(
|
||||||
|
Icons.expand_less,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await pagingScrollController.animateTo(
|
||||||
|
0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInExpo,
|
||||||
|
);
|
||||||
|
_nestedScrollViewKey.currentState?.outerController.jumpTo(0);
|
||||||
|
},
|
||||||
|
label: Text(
|
||||||
|
"Go to top", //TODO: INTL
|
||||||
|
style: DefaultTextStyle.of(context).style.apply(
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSavedViewsTab(
|
||||||
|
ConnectivityState connectivityState,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
||||||
|
onRefresh: _onReloadSavedViews,
|
||||||
|
notificationPredicate: (_) => connectivityState.isConnected,
|
||||||
|
child: CustomScrollView(
|
||||||
|
key: const PageStorageKey<String>("savedViews"),
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
const SavedViewList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDocumentsTab(
|
||||||
|
ConnectivityState connectivityState,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
edgeOffset: kToolbarHeight + kTextTabBarHeight,
|
||||||
|
onRefresh: _onReloadDocuments,
|
||||||
|
notificationPredicate: (_) => connectivityState.isConnected,
|
||||||
|
child: CustomScrollView(
|
||||||
|
key: const PageStorageKey<String>("documents"),
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
_buildViewActions(),
|
||||||
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.hasLoaded && state.documents.isEmpty) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: DocumentsEmptyState(
|
||||||
|
state: state,
|
||||||
|
onReset: context.read<DocumentsCubit>().resetFilter,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverAdaptiveDocumentsView(
|
||||||
|
viewType: state.viewType,
|
||||||
|
onTap: _openDetails,
|
||||||
|
onSelected:
|
||||||
|
context.read<DocumentsCubit>().toggleDocumentSelection,
|
||||||
|
hasInternetConnection: connectivityState.isConnected,
|
||||||
|
onTagSelected: _addTagToFilter,
|
||||||
|
onCorrespondentSelected: _addCorrespondentToFilter,
|
||||||
|
onDocumentTypeSelected: _addDocumentTypeToFilter,
|
||||||
|
onStoragePathSelected: _addStoragePathToFilter,
|
||||||
|
documents: state.documents,
|
||||||
|
hasLoaded: state.hasLoaded,
|
||||||
|
isLabelClickable: true,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
selectedDocumentIds: state.selectedIds,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildViewActions() {
|
Widget _buildViewActions() {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.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/features/documents/view/widgets/document_grid_loading_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/documents_list_loading_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||||
|
|
||||||
abstract class AdaptiveDocumentsView extends StatelessWidget {
|
abstract class AdaptiveDocumentsView extends StatelessWidget {
|
||||||
@@ -41,6 +43,24 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
|
|||||||
required this.hasLoaded,
|
required this.hasLoaded,
|
||||||
this.enableHeroAnimation = true,
|
this.enableHeroAnimation = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AdaptiveDocumentsView.fromPagedState(
|
||||||
|
DocumentPagingState state, {
|
||||||
|
super.key,
|
||||||
|
this.onSelected,
|
||||||
|
this.onTap,
|
||||||
|
this.onCorrespondentSelected,
|
||||||
|
this.onDocumentTypeSelected,
|
||||||
|
this.onStoragePathSelected,
|
||||||
|
this.onTagSelected,
|
||||||
|
this.isLabelClickable = true,
|
||||||
|
this.enableHeroAnimation = true,
|
||||||
|
required this.hasInternetConnection,
|
||||||
|
this.viewType = ViewType.list,
|
||||||
|
this.selectedDocumentIds = const [],
|
||||||
|
}) : documents = state.documents,
|
||||||
|
isLoading = state.isLoading,
|
||||||
|
hasLoaded = state.hasLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
||||||
@@ -69,6 +89,8 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
return _buildGridView();
|
return _buildGridView();
|
||||||
case ViewType.list:
|
case ViewType.list:
|
||||||
return _buildListView();
|
return _buildListView();
|
||||||
|
case ViewType.detailed:
|
||||||
|
return _buildFullView(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +123,39 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildFullView(BuildContext context) {
|
||||||
|
if (showLoadingPlaceholder) {
|
||||||
|
//TODO: Build detailed loading animation
|
||||||
|
return DocumentsListLoadingWidget.sliver();
|
||||||
|
}
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
childCount: documents.length,
|
||||||
|
(context, index) {
|
||||||
|
final document = documents.elementAt(index);
|
||||||
|
return LabelRepositoriesProvider(
|
||||||
|
child: DocumentDetailedItem(
|
||||||
|
isLabelClickable: isLabelClickable,
|
||||||
|
document: document,
|
||||||
|
onTap: onTap,
|
||||||
|
isSelected: selectedDocumentIds.contains(document.id),
|
||||||
|
onSelected: onSelected,
|
||||||
|
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
|
onCorrespondentSelected: onCorrespondentSelected,
|
||||||
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildGridView() {
|
Widget _buildGridView() {
|
||||||
if (showLoadingPlaceholder) {
|
if (showLoadingPlaceholder) {
|
||||||
return DocumentGridLoadingWidget.sliver();
|
return const DocumentGridLoadingWidget.sliver();
|
||||||
}
|
}
|
||||||
return SliverGrid.builder(
|
return SliverGrid.builder(
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
@@ -161,6 +213,8 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
return _buildGridView();
|
return _buildGridView();
|
||||||
case ViewType.list:
|
case ViewType.list:
|
||||||
return _buildListView();
|
return _buildListView();
|
||||||
|
case ViewType.detailed:
|
||||||
|
return _buildFullView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +224,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
primary: false,
|
primary: false,
|
||||||
itemCount: documents.length,
|
itemCount: documents.length,
|
||||||
@@ -194,11 +249,44 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildFullView() {
|
||||||
|
if (showLoadingPlaceholder) {
|
||||||
|
return DocumentsListLoadingWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
physics: const PageScrollPhysics(),
|
||||||
|
controller: scrollController,
|
||||||
|
primary: false,
|
||||||
|
itemCount: documents.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final document = documents.elementAt(index);
|
||||||
|
return LabelRepositoriesProvider(
|
||||||
|
child: DocumentDetailedItem(
|
||||||
|
isLabelClickable: isLabelClickable,
|
||||||
|
document: document,
|
||||||
|
onTap: onTap,
|
||||||
|
isSelected: selectedDocumentIds.contains(document.id),
|
||||||
|
onSelected: onSelected,
|
||||||
|
isSelectionActive: selectedDocumentIds.isNotEmpty,
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
|
onCorrespondentSelected: onCorrespondentSelected,
|
||||||
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildGridView() {
|
Widget _buildGridView() {
|
||||||
if (showLoadingPlaceholder) {
|
if (showLoadingPlaceholder) {
|
||||||
return DocumentGridLoadingWidget();
|
return DocumentGridLoadingWidget();
|
||||||
}
|
}
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
primary: false,
|
primary: false,
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
|||||||
@@ -2,51 +2,59 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
class DocumentPreview extends StatelessWidget {
|
class DocumentPreview extends StatelessWidget {
|
||||||
final int id;
|
final DocumentModel document;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
final double borderRadius;
|
final double borderRadius;
|
||||||
final bool enableHero;
|
final bool enableHero;
|
||||||
|
final double scale;
|
||||||
|
|
||||||
const DocumentPreview({
|
const DocumentPreview({
|
||||||
super.key,
|
super.key,
|
||||||
required this.id,
|
required this.document,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.alignment = Alignment.center,
|
this.alignment = Alignment.topCenter,
|
||||||
this.borderRadius = 8.0,
|
this.borderRadius = 12.0,
|
||||||
this.enableHero = true,
|
this.enableHero = true,
|
||||||
|
this.scale = 1.1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!enableHero) {
|
return HeroMode(
|
||||||
return _buildPreview(context);
|
enabled: enableHero,
|
||||||
}
|
child: Hero(
|
||||||
return Hero(
|
tag: "thumb_${document.id}",
|
||||||
tag: "thumb_$id",
|
child: _buildPreview(context),
|
||||||
child: _buildPreview(context),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipRRect _buildPreview(BuildContext context) {
|
Widget _buildPreview(BuildContext context) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(borderRadius),
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
child: CachedNetworkImage(
|
child: Transform.scale(
|
||||||
fit: fit,
|
scale: scale,
|
||||||
alignment: Alignment.topCenter,
|
child: CachedNetworkImage(
|
||||||
cacheKey: "thumb_$id",
|
fit: fit,
|
||||||
imageUrl: context.read<PaperlessDocumentsApi>().getThumbnailUrl(id),
|
alignment: alignment,
|
||||||
errorWidget: (ctxt, msg, __) => Text(msg),
|
cacheKey: "thumb_${document.id}",
|
||||||
placeholder: (context, value) => Shimmer.fromColors(
|
imageUrl: context
|
||||||
baseColor: Colors.grey[300]!,
|
.read<PaperlessDocumentsApi>()
|
||||||
highlightColor: Colors.grey[100]!,
|
.getThumbnailUrl(document.id),
|
||||||
child: const SizedBox(height: 100, width: 100),
|
errorWidget: (ctxt, msg, __) => Text(msg),
|
||||||
|
placeholder: (context, value) => Shimmer.fromColors(
|
||||||
|
baseColor: Colors.grey[300]!,
|
||||||
|
highlightColor: Colors.grey[100]!,
|
||||||
|
child: const SizedBox(height: 100, width: 100),
|
||||||
|
),
|
||||||
|
cacheManager: context.watch<CacheManager>(),
|
||||||
),
|
),
|
||||||
cacheManager: context.watch<CacheManager>(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
|
||||||
|
class DocumentDetailedItem extends DocumentItem {
|
||||||
|
const DocumentDetailedItem({
|
||||||
|
super.key,
|
||||||
|
required super.document,
|
||||||
|
required super.isSelected,
|
||||||
|
required super.isSelectionActive,
|
||||||
|
required super.isLabelClickable,
|
||||||
|
required super.enableHeroAnimation,
|
||||||
|
super.onCorrespondentSelected,
|
||||||
|
super.onDocumentTypeSelected,
|
||||||
|
super.onSelected,
|
||||||
|
super.onStoragePathSelected,
|
||||||
|
super.onTagSelected,
|
||||||
|
super.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final insets = MediaQuery.of(context).viewInsets;
|
||||||
|
final padding = MediaQuery.of(context).viewPadding;
|
||||||
|
final availableHeight = size.height -
|
||||||
|
insets.top -
|
||||||
|
insets.bottom -
|
||||||
|
padding.top -
|
||||||
|
padding.bottom -
|
||||||
|
kBottomNavigationBarHeight -
|
||||||
|
kToolbarHeight;
|
||||||
|
final maxHeight = min(500.0, availableHeight);
|
||||||
|
return Card(
|
||||||
|
child: InkWell(
|
||||||
|
enableFeedback: true,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
onTap: () {
|
||||||
|
if (isSelectionActive) {
|
||||||
|
onSelected?.call(document);
|
||||||
|
} else {
|
||||||
|
onTap?.call(document);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
onSelected?.call(document);
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
width: double.infinity,
|
||||||
|
height: maxHeight / 2,
|
||||||
|
),
|
||||||
|
child: DocumentPreview(
|
||||||
|
document: document,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat.yMMMMd().format(document.created),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.apply(color: Theme.of(context).hintColor),
|
||||||
|
),
|
||||||
|
if (document.archiveSerialNumber != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'#${document.archiveSerialNumber}',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.apply(color: Theme.of(context).hintColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddedLTRB(8, 8, 8, 4),
|
||||||
|
Text(
|
||||||
|
document.title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.person_outline,
|
||||||
|
size: 16,
|
||||||
|
).paddedOnly(right: 4.0),
|
||||||
|
CorrespondentWidget(
|
||||||
|
onSelected: onCorrespondentSelected,
|
||||||
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
correspondentId: document.correspondent,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.description_outlined,
|
||||||
|
size: 16,
|
||||||
|
).paddedOnly(right: 4.0),
|
||||||
|
DocumentTypeWidget(
|
||||||
|
onSelected: onDocumentTypeSelected,
|
||||||
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
documentTypeId: document.documentType,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
TagsWidget(
|
||||||
|
isMultiLine: false,
|
||||||
|
tagIds: document.tags,
|
||||||
|
).padded(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).padded();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,66 +25,62 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return Padding(
|
||||||
onTap: _onTap,
|
padding: const EdgeInsets.all(8.0),
|
||||||
onLongPress: onSelected != null ? () => onSelected!(document) : null,
|
child: Card(
|
||||||
child: AbsorbPointer(
|
elevation: 1.0,
|
||||||
absorbing: isSelectionActive,
|
color: isSelected
|
||||||
child: Padding(
|
? Theme.of(context).colorScheme.inversePrimary
|
||||||
padding: const EdgeInsets.all(8.0),
|
: Theme.of(context).cardColor,
|
||||||
child: Card(
|
child: InkWell(
|
||||||
elevation: 1.0,
|
borderRadius: BorderRadius.circular(12),
|
||||||
color: isSelected
|
onTap: _onTap,
|
||||||
? Theme.of(context).colorScheme.inversePrimary
|
onLongPress: onSelected != null ? () => onSelected!(document) : null,
|
||||||
: Theme.of(context).cardColor,
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
AspectRatio(
|
||||||
AspectRatio(
|
aspectRatio: 1,
|
||||||
aspectRatio: 1,
|
child: DocumentPreview(
|
||||||
child: DocumentPreview(
|
document: document,
|
||||||
id: document.id,
|
borderRadius: 12.0,
|
||||||
borderRadius: 12.0,
|
enableHero: enableHeroAnimation,
|
||||||
enableHero: enableHeroAnimation,
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CorrespondentWidget(
|
||||||
|
correspondentId: document.correspondent,
|
||||||
|
),
|
||||||
|
DocumentTypeWidget(
|
||||||
|
documentTypeId: document.documentType,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
document.title,
|
||||||
|
maxLines: document.tags.isEmpty ? 3 : 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
TagsWidget(
|
||||||
|
tagIds: document.tags,
|
||||||
|
isMultiLine: false,
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
DateFormat.yMMMd().format(document.created),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: Padding(
|
],
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
CorrespondentWidget(
|
|
||||||
correspondentId: document.correspondent,
|
|
||||||
),
|
|
||||||
DocumentTypeWidget(
|
|
||||||
documentTypeId: document.documentType,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
document.title,
|
|
||||||
maxLines: document.tags.isEmpty ? 3 : 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
TagsWidget(
|
|
||||||
tagIds: document.tags,
|
|
||||||
isMultiLine: false,
|
|
||||||
onTagSelected: onTagSelected,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
DateFormat.yMMMd().format(
|
|
||||||
document.created,
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class DocumentListItem extends DocumentItem {
|
|||||||
aspectRatio: _a4AspectRatio,
|
aspectRatio: _a4AspectRatio,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: DocumentPreview(
|
child: DocumentPreview(
|
||||||
id: document.id,
|
document: document,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
enableHero: enableHeroAnimation,
|
enableHero: enableHeroAnimation,
|
||||||
|
|||||||
+18
-25
@@ -1,24 +1,15 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
|
||||||
|
|
||||||
class DocumentGridLoadingWidget extends StatelessWidget
|
class DocumentGridLoadingWidget extends StatelessWidget {
|
||||||
with DocumentItemPlaceholder {
|
|
||||||
final bool _isSliver;
|
final bool _isSliver;
|
||||||
@override
|
@override
|
||||||
final Random random = Random(1257195195);
|
const DocumentGridLoadingWidget({super.key}) : _isSliver = false;
|
||||||
DocumentGridLoadingWidget({super.key}) : _isSliver = false;
|
|
||||||
|
|
||||||
DocumentGridLoadingWidget.sliver({super.key}) : _isSliver = true;
|
const DocumentGridLoadingWidget.sliver({super.key}) : _isSliver = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -41,8 +32,6 @@ class DocumentGridLoadingWidget extends StatelessWidget
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPlaceholderGridItem(BuildContext context) {
|
Widget _buildPlaceholderGridItem(BuildContext context) {
|
||||||
final values = nextValues;
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -68,21 +57,25 @@ class DocumentGridLoadingWidget extends StatelessWidget
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TextPlaceholder(
|
const TextPlaceholder(
|
||||||
length: values.correspondentLength,
|
length: 70,
|
||||||
|
fontSize: 16,
|
||||||
|
).padded(1),
|
||||||
|
const TextPlaceholder(
|
||||||
|
length: 50,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
).padded(1),
|
).padded(1),
|
||||||
TextPlaceholder(
|
TextPlaceholder(
|
||||||
length: values.titleLength,
|
length: 200,
|
||||||
fontSize: 16,
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.titleMedium?.fontSize ??
|
||||||
|
10,
|
||||||
|
).padded(1),
|
||||||
|
const Spacer(),
|
||||||
|
const TagsPlaceholder(
|
||||||
|
count: 2,
|
||||||
|
dense: true,
|
||||||
),
|
),
|
||||||
if (values.tagCount > 0) ...[
|
|
||||||
const Spacer(),
|
|
||||||
TagsPlaceholder(
|
|
||||||
count: values.tagCount,
|
|
||||||
dense: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
TextPlaceholder(
|
TextPlaceholder(
|
||||||
length: 100,
|
length: 100,
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
mixin DocumentItemPlaceholder {
|
|
||||||
static const _tags = [" ", " ", " "];
|
|
||||||
static const _titleLengths = <double>[double.infinity, 150.0, 200.0];
|
|
||||||
static const _correspondentLengths = <double>[120.0, 80.0, 40.0];
|
|
||||||
|
|
||||||
Random get random;
|
|
||||||
|
|
||||||
RandomDocumentItemPlaceholderValues get nextValues {
|
|
||||||
return RandomDocumentItemPlaceholderValues(
|
|
||||||
tagCount: random.nextInt(_tags.length + 1),
|
|
||||||
correspondentLength: _correspondentLengths[
|
|
||||||
random.nextInt(_correspondentLengths.length - 1)],
|
|
||||||
titleLength: _titleLengths[random.nextInt(_titleLengths.length - 1)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RandomDocumentItemPlaceholderValues {
|
|
||||||
final int tagCount;
|
|
||||||
final double correspondentLength;
|
|
||||||
final double titleLength;
|
|
||||||
|
|
||||||
RandomDocumentItemPlaceholderValues({
|
|
||||||
required this.tagCount,
|
|
||||||
required this.correspondentLength,
|
|
||||||
required this.titleLength,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
+16
-23
@@ -1,21 +1,13 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_item_placeholder.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
||||||
|
|
||||||
class DocumentsListLoadingWidget extends StatelessWidget
|
class DocumentsListLoadingWidget extends StatelessWidget {
|
||||||
with DocumentItemPlaceholder {
|
|
||||||
final bool _isSliver;
|
final bool _isSliver;
|
||||||
DocumentsListLoadingWidget({super.key}) : _isSliver = false;
|
const DocumentsListLoadingWidget({super.key}) : _isSliver = false;
|
||||||
|
|
||||||
DocumentsListLoadingWidget.sliver({super.key}) : _isSliver = true;
|
const DocumentsListLoadingWidget.sliver({super.key}) : _isSliver = true;
|
||||||
|
|
||||||
@override
|
|
||||||
final Random random = Random(1209571050);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -35,26 +27,31 @@ class DocumentsListLoadingWidget extends StatelessWidget
|
|||||||
|
|
||||||
Widget _buildFakeListItem(BuildContext context) {
|
Widget _buildFakeListItem(BuildContext context) {
|
||||||
const fontSize = 14.0;
|
const fontSize = 14.0;
|
||||||
final values = nextValues;
|
|
||||||
return ShimmerPlaceholder(
|
return ShimmerPlaceholder(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.all(8),
|
contentPadding: const EdgeInsets.all(8),
|
||||||
dense: true,
|
dense: true,
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
leading: ClipRRect(
|
leading: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
width: 35,
|
width: 35,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TextPlaceholder(
|
const TextPlaceholder(
|
||||||
length: values.correspondentLength,
|
length: 120,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
TextPlaceholder(
|
||||||
|
length: 220,
|
||||||
|
fontSize: Theme.of(context).textTheme.titleMedium!.fontSize!,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
@@ -63,14 +60,10 @@ class DocumentsListLoadingWidget extends StatelessWidget
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
|
TagsPlaceholder(count: 2, dense: true),
|
||||||
|
SizedBox(height: 2),
|
||||||
TextPlaceholder(
|
TextPlaceholder(
|
||||||
length: values.titleLength,
|
length: 250,
|
||||||
fontSize: fontSize,
|
|
||||||
),
|
|
||||||
if (values.tagCount > 0)
|
|
||||||
TagsPlaceholder(count: values.tagCount, dense: true),
|
|
||||||
TextPlaceholder(
|
|
||||||
length: 100,
|
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize!,
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize!,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -15,6 +15,7 @@ class TagsPlaceholder extends StatelessWidget {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 32,
|
height: 32,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemBuilder: (context, index) => FilterChip(
|
itemBuilder: (context, index) => FilterChip(
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
|
|
||||||
class TextPlaceholder extends StatelessWidget {
|
class TextPlaceholder extends StatelessWidget {
|
||||||
final double length;
|
final double length;
|
||||||
|
|||||||
@@ -14,13 +14,65 @@ class ViewTypeSelectionWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final next = viewType.toggle();
|
late final IconData icon;
|
||||||
final icon = next == ViewType.grid ? Icons.grid_view_rounded : Icons.list;
|
switch (viewType) {
|
||||||
return IconButton(
|
case ViewType.grid:
|
||||||
|
icon = Icons.grid_view_rounded;
|
||||||
|
break;
|
||||||
|
case ViewType.list:
|
||||||
|
icon = Icons.list;
|
||||||
|
break;
|
||||||
|
case ViewType.detailed:
|
||||||
|
icon = Icons.article_outlined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return PopupMenuButton<ViewType>(
|
||||||
|
initialValue: viewType,
|
||||||
icon: Icon(icon),
|
icon: Icon(icon),
|
||||||
onPressed: () {
|
itemBuilder: (context) => [
|
||||||
|
_buildViewTypeOption(
|
||||||
|
context,
|
||||||
|
type: ViewType.list,
|
||||||
|
label: 'List', //TODO: INTL
|
||||||
|
icon: Icons.list,
|
||||||
|
),
|
||||||
|
_buildViewTypeOption(
|
||||||
|
context,
|
||||||
|
type: ViewType.grid,
|
||||||
|
label: 'Grid', //TODO: INTL
|
||||||
|
icon: Icons.grid_view_rounded,
|
||||||
|
),
|
||||||
|
_buildViewTypeOption(
|
||||||
|
context,
|
||||||
|
type: ViewType.detailed,
|
||||||
|
label: 'Detailed', //TODO: INTL
|
||||||
|
icon: Icons.article_outlined,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (next) {
|
||||||
onChanged(next);
|
onChanged(next);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PopupMenuItem<ViewType> _buildViewTypeOption(
|
||||||
|
BuildContext context, {
|
||||||
|
required ViewType type,
|
||||||
|
required String label,
|
||||||
|
required IconData icon,
|
||||||
|
}) {
|
||||||
|
final selected = type == viewType;
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: type,
|
||||||
|
child: ListTile(
|
||||||
|
selected: selected,
|
||||||
|
trailing: selected ? const Icon(Icons.done) : null,
|
||||||
|
title: Text(label),
|
||||||
|
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
textColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
leading: Icon(icon),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
|||||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.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/features/tasks/cubit/task_status_cubit.dart';
|
||||||
@@ -63,7 +62,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
context.read(),
|
context.read(),
|
||||||
);
|
);
|
||||||
_listenToInboxChanges();
|
_listenToInboxChanges();
|
||||||
context.read<ConnectivityCubit>().reload();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
_listenForReceivedFiles();
|
_listenForReceivedFiles();
|
||||||
});
|
});
|
||||||
@@ -82,12 +81,22 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
if (state == AppLifecycleState.resumed && !_inboxTimer.isActive) {
|
switch (state) {
|
||||||
log('App is now in foreground, start polling for statistics.');
|
case AppLifecycleState.resumed:
|
||||||
_listenToInboxChanges();
|
log('App is now in foreground');
|
||||||
} else if (state != AppLifecycleState.resumed) {
|
context.read<ConnectivityCubit>().reload();
|
||||||
log('App is now in background, stop polling for statistics.');
|
log("Reloaded device connectivity state");
|
||||||
_inboxTimer.cancel();
|
if (!_inboxTimer.isActive) {
|
||||||
|
_listenToInboxChanges();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
default:
|
||||||
|
log('App is now in background');
|
||||||
|
_inboxTimer.cancel();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,21 +281,10 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
],
|
],
|
||||||
child: const LabelsPage(),
|
child: const LabelsPage(),
|
||||||
),
|
),
|
||||||
MultiBlocProvider(
|
BlocProvider<InboxCubit>.value(
|
||||||
providers: [
|
value: _inboxCubit,
|
||||||
// We need to manually downcast the inboxcubit to the
|
|
||||||
// mixed-in DocumentPagingBlocMixin to use the
|
|
||||||
// DocumentPagingViewMixin in the inbox.
|
|
||||||
BlocProvider<DocumentPagingBlocMixin>.value(
|
|
||||||
value: _inboxCubit,
|
|
||||||
),
|
|
||||||
BlocProvider<InboxCubit>.value(
|
|
||||||
value: _inboxCubit,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const InboxPage(),
|
child: const InboxPage(),
|
||||||
),
|
),
|
||||||
// const SettingsPage(),
|
|
||||||
];
|
];
|
||||||
return MultiBlocListener(
|
return MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/documents_list_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.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/features/inbox/view/widgets/inbox_item.dart';
|
||||||
@@ -24,7 +24,8 @@ class InboxPage extends StatefulWidget {
|
|||||||
State<InboxPage> createState() => _InboxPageState();
|
State<InboxPage> createState() => _InboxPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin {
|
class _InboxPageState extends State<InboxPage>
|
||||||
|
with DocumentPagingViewMixin<InboxPage, InboxCubit> {
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: InboxItem._a4AspectRatio,
|
aspectRatio: InboxItem._a4AspectRatio,
|
||||||
child: DocumentPreview(
|
child: DocumentPreview(
|
||||||
id: widget.document.id,
|
document: widget.document,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
enableHero: false,
|
enableHero: false,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class DocumentTypeWidget extends StatelessWidget {
|
|||||||
state.labels[documentTypeId]?.toString() ?? "-",
|
state.labels[documentTypeId]?.toString() ?? "-",
|
||||||
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
||||||
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class LinkedDocumentsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
||||||
with DocumentPagingViewMixin {
|
with DocumentPagingViewMixin<LinkedDocumentsPage, LinkedDocumentsCubit> {
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ 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/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
|
||||||
|
|
||||||
mixin DocumentPagingViewMixin<T extends StatefulWidget> on State<T> {
|
mixin DocumentPagingViewMixin<T extends StatefulWidget,
|
||||||
|
Bloc extends DocumentPagingBlocMixin> on State<T> {
|
||||||
ScrollController get pagingScrollController;
|
ScrollController get pagingScrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,7 +20,7 @@ mixin DocumentPagingViewMixin<T extends StatefulWidget> on State<T> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentPagingBlocMixin get _bloc => context.read<DocumentPagingBlocMixin>();
|
DocumentPagingBlocMixin get _bloc => context.read<Bloc>();
|
||||||
|
|
||||||
void shouldLoadMoreDocumentsListener() async {
|
void shouldLoadMoreDocumentsListener() async {
|
||||||
if (shouldLoadMoreDocuments) {
|
if (shouldLoadMoreDocuments) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
@@ -12,50 +13,55 @@ class SavedViewList extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final savedViewCubit = context.read<SavedViewCubit>();
|
final savedViewCubit = context.read<SavedViewCubit>();
|
||||||
return BlocBuilder<SavedViewCubit, SavedViewState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, state) {
|
builder: (context, connectivity) {
|
||||||
if (state.value.isEmpty) {
|
return BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||||
return SliverToBoxAdapter(
|
builder: (context, state) {
|
||||||
child: HintCard(
|
if (state.value.isEmpty) {
|
||||||
hintText: S.of(context).savedViewsEmptyStateText,
|
return SliverToBoxAdapter(
|
||||||
),
|
child: HintCard(
|
||||||
);
|
hintText: S.of(context).savedViewsEmptyStateText,
|
||||||
}
|
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) {
|
|
||||||
final view = state.value.values.elementAt(index);
|
|
||||||
return ListTile(
|
|
||||||
title: Text(view.name),
|
|
||||||
subtitle: Text(
|
|
||||||
S
|
|
||||||
.of(context)
|
|
||||||
.savedViewsFiltersSetCount(view.filterRules.length),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
);
|
||||||
Navigator.of(context).push(
|
}
|
||||||
MaterialPageRoute(
|
return SliverList(
|
||||||
builder: (context) => MultiBlocProvider(
|
delegate: SliverChildBuilderDelegate(
|
||||||
providers: [
|
(context, index) {
|
||||||
BlocProvider(
|
final view = state.value.values.elementAt(index);
|
||||||
create: (context) => SavedViewDetailsCubit(
|
return ListTile(
|
||||||
context.read(),
|
enabled: connectivity.isConnected,
|
||||||
context.read(),
|
title: Text(view.name),
|
||||||
savedView: view,
|
subtitle: Text(
|
||||||
|
S
|
||||||
|
.of(context)
|
||||||
|
.savedViewsFiltersSetCount(view.filterRules.length),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => SavedViewDetailsCubit(
|
||||||
|
context.read(),
|
||||||
|
context.read(),
|
||||||
|
savedView: view,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: SavedViewDetailsPage(
|
||||||
|
onDelete: savedViewCubit.remove,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
child: SavedViewDetailsPage(
|
|
||||||
onDelete: savedViewCubit.remove,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
childCount: state.value.length,
|
||||||
},
|
),
|
||||||
childCount: state.value.length,
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SavedViewDetailsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
||||||
with DocumentPagingViewMixin {
|
with DocumentPagingViewMixin<SavedViewDetailsPage, SavedViewDetailsCubit> {
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
|||||||
onChanged: cubit.setViewType,
|
onChanged: cubit.setViewType,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
body: BlocBuilder<SavedViewDetailsCubit, SavedViewDetailsState>(
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart';
|
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart';
|
import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart';
|
||||||
@@ -29,13 +32,13 @@ class _SearchAppBarState extends State<SearchAppBar> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
snap: true,
|
snap: true,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
backgroundColor: widget.backgroundColor,
|
backgroundColor: widget.backgroundColor,
|
||||||
title: SearchBar(
|
title: SearchBar(
|
||||||
height: kToolbarHeight - 8,
|
height: kToolbarHeight - 12,
|
||||||
supportingText: widget.hintText,
|
supportingText: widget.hintText,
|
||||||
onTap: () => widget.onOpenSearch(context),
|
onTap: () => widget.onOpenSearch(context),
|
||||||
leadingIcon: IconButton(
|
leadingIcon: IconButton(
|
||||||
@@ -43,17 +46,22 @@ class _SearchAppBarState extends State<SearchAppBar> {
|
|||||||
onPressed: Scaffold.of(context).openDrawer,
|
onPressed: Scaffold.of(context).openDrawer,
|
||||||
),
|
),
|
||||||
trailingIcon: IconButton(
|
trailingIcon: IconButton(
|
||||||
icon: const CircleAvatar(
|
icon: BlocBuilder<PaperlessServerInformationCubit,
|
||||||
child: Text("A"),
|
PaperlessServerInformationState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return CircleAvatar(
|
||||||
|
child: Text(state.information?.userInitials ?? ''),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountSettingsDialog(),
|
builder: (context) => const AccountSettingsDialog(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).paddedOnly(top: 4, bottom: 4),
|
).paddedOnly(top: 8, bottom: 4),
|
||||||
bottom: widget.bottom,
|
bottom: widget.bottom,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
enum ViewType {
|
enum ViewType {
|
||||||
grid,
|
grid,
|
||||||
list;
|
list,
|
||||||
|
detailed;
|
||||||
|
|
||||||
ViewType toggle() {
|
ViewType toggle() {
|
||||||
return this == grid ? list : grid;
|
return ViewType.values[(index + 1) % ViewType.values.length];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ class AccountSettingsDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
child: Text(state.information?.username
|
child: Text(state.information?.userInitials ?? ''),
|
||||||
?.toUpperCase()
|
|
||||||
.substring(0, 1) ??
|
|
||||||
''),
|
|
||||||
),
|
),
|
||||||
title: Text(state.information?.username ?? ''),
|
title: Text(state.information?.username ?? ''),
|
||||||
subtitle: Text(state.information?.host ?? ''),
|
subtitle: Text(state.information?.host ?? ''),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
@@ -17,7 +18,7 @@ class SimilarDocumentsView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
||||||
with DocumentPagingViewMixin {
|
with DocumentPagingViewMixin<SimilarDocumentsView, SimilarDocumentsCubit> {
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
|
|
||||||
@@ -33,44 +34,50 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, state) {
|
listenWhen: (previous, current) =>
|
||||||
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty) {
|
!previous.isConnected && current.isConnected,
|
||||||
return DocumentsEmptyState(
|
listener: (context, state) =>
|
||||||
state: state,
|
context.read<SimilarDocumentsCubit>().initialize(),
|
||||||
onReset: () => context.read<SimilarDocumentsCubit>().updateFilter(
|
builder: (context, connectivity) {
|
||||||
filter: DocumentFilter.initial.copyWith(
|
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
||||||
moreLike: () =>
|
builder: (context, state) {
|
||||||
context.read<SimilarDocumentsCubit>().documentId,
|
if (!connectivity.isConnected && !state.hasLoaded) {
|
||||||
),
|
return const OfflineWidget();
|
||||||
),
|
}
|
||||||
);
|
if (state.hasLoaded &&
|
||||||
}
|
!state.isLoading &&
|
||||||
|
state.documents.isEmpty) {
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return DocumentsEmptyState(
|
||||||
builder: (context, connectivity) {
|
state: state,
|
||||||
return CustomScrollView(
|
onReset: () => context
|
||||||
controller: pagingScrollController,
|
.read<SimilarDocumentsCubit>()
|
||||||
slivers: [
|
.updateFilter(
|
||||||
SliverAdaptiveDocumentsView(
|
filter: DocumentFilter.initial.copyWith(
|
||||||
documents: state.documents,
|
moreLike: () =>
|
||||||
hasInternetConnection: connectivity.isConnected,
|
context.read<SimilarDocumentsCubit>().documentId,
|
||||||
isLabelClickable: false,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
hasLoaded: state.hasLoaded,
|
|
||||||
enableHeroAnimation: false,
|
|
||||||
onTap: (document) {
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
DocumentDetailsRoute.routeName,
|
|
||||||
arguments: DocumentDetailsRouteArguments(
|
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
}
|
||||||
],
|
return DefaultAdaptiveDocumentsView(
|
||||||
|
scrollController: pagingScrollController,
|
||||||
|
documents: state.documents,
|
||||||
|
hasInternetConnection: connectivity.isConnected,
|
||||||
|
isLabelClickable: false,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
hasLoaded: state.hasLoaded,
|
||||||
|
enableHeroAnimation: false,
|
||||||
|
onTap: (document) {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
DocumentDetailsRoute.routeName,
|
||||||
|
arguments: DocumentDetailsRouteArguments(
|
||||||
|
document: document,
|
||||||
|
isLabelClickable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ class PaperlessServerInformationModel {
|
|||||||
final String? username;
|
final String? username;
|
||||||
final String? host;
|
final String? host;
|
||||||
|
|
||||||
|
String? get userInitials {
|
||||||
|
return username?.substring(0, 1).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
PaperlessServerInformationModel({
|
PaperlessServerInformationModel({
|
||||||
this.host,
|
this.host,
|
||||||
this.username,
|
this.username,
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
|
|||||||
} on DioError catch (error) {
|
} on DioError catch (error) {
|
||||||
if (error.error is PaperlessServerException ||
|
if (error.error is PaperlessServerException ||
|
||||||
error.error is Map<String, String>) {
|
error.error is Map<String, String>) {
|
||||||
throw error.error;
|
throw error.error as Map<String, String>;
|
||||||
} else {
|
} else {
|
||||||
throw PaperlessServerException(
|
throw PaperlessServerException(
|
||||||
ErrorCode.authenticationFailed,
|
ErrorCode.authenticationFailed,
|
||||||
|
|||||||
+12
-16
@@ -2,14 +2,10 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_api/src/constants.dart';
|
import 'package:paperless_api/src/constants.dart';
|
||||||
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
|
|
||||||
|
|
||||||
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||||
static const _dateTimeConverter = LocalDateTimeJsonConverter();
|
|
||||||
|
|
||||||
final Dio client;
|
final Dio client;
|
||||||
|
|
||||||
PaperlessDocumentsApiImpl(this.client);
|
PaperlessDocumentsApiImpl(this.client);
|
||||||
@@ -65,7 +61,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +78,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +105,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
throw const PaperlessServerException(ErrorCode.documentLoadFailed);
|
throw const PaperlessServerException(ErrorCode.documentLoadFailed);
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +119,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.documentDeleteFailed);
|
throw const PaperlessServerException(ErrorCode.documentDeleteFailed);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +146,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
throw const PaperlessServerException(ErrorCode.documentPreviewFailed);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +168,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
} on PaperlessServerException {
|
} on PaperlessServerException {
|
||||||
throw const PaperlessServerException(ErrorCode.documentAsnQueryFailed);
|
throw const PaperlessServerException(ErrorCode.documentAsnQueryFailed);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +187,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +204,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +218,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
response.data as Map<String, dynamic>,
|
response.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +237,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +252,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.suggestionsQueryError);
|
throw const PaperlessServerException(ErrorCode.suggestionsQueryError);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +266,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
|||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.unknown);
|
throw const PaperlessServerException(ErrorCode.unknown);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -39,7 +39,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Future<T> getSingleResult<T>(
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +66,13 @@ Future<List<T>> getCollection<T>(
|
|||||||
httpStatusCode: response.statusCode,
|
httpStatusCode: response.statusCode,
|
||||||
);
|
);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
throw err.error;
|
throw err.error!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<T> _collectionFromJson<T>(
|
List<T> _collectionFromJson<T>(
|
||||||
_CollectionFromJsonSerializationParams<T> params) {
|
_CollectionFromJsonSerializationParams<T> params,
|
||||||
|
) {
|
||||||
return params.list.map<T>((result) => params.fromJson(result)).toList();
|
return params.list.map<T>((result) => params.fromJson(result)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ dependencies:
|
|||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
dio: ^4.0.6
|
dio: ^5.0.0
|
||||||
collection: ^1.17.0
|
collection: ^1.17.0
|
||||||
jiffy: ^5.0.0
|
jiffy: ^5.0.0
|
||||||
|
|
||||||
|
|||||||
+38
-70
@@ -45,10 +45,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.0"
|
||||||
asn1lib:
|
asn1lib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -101,18 +101,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: bloc
|
name: bloc
|
||||||
sha256: bd4f8027bfa60d96c8046dec5ce74c463b2c918dce1b0d36593575995344534a
|
sha256: "658a5ae59edcf1e58aac98b000a71c762ad8f46f1394c34a52050cafb3e11a80"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.0"
|
version: "8.1.1"
|
||||||
bloc_test:
|
bloc_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: bloc_test
|
name: bloc_test
|
||||||
sha256: "622b97678bf8c06a94f4c26a89ee9ebf7319bf775383dee2233e86e1f94ee28d"
|
sha256: ffbb60c17ee3d8e3784cb78071088e353199057233665541e8ac6cd438dca8ad
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.1.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -253,50 +253,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: "3f8fe4e504c2d33696dac671a54909743bc6a902a9bb0902306f7a2aed7e528e"
|
sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.9"
|
version: "3.0.3"
|
||||||
connectivity_plus_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_linux
|
|
||||||
sha256: "3caf859d001f10407b8e48134c761483e4495ae38094ffcca97193f6c271f5e2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
connectivity_plus_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_macos
|
|
||||||
sha256: "488d2de1e47e1224ad486e501b20b088686ba1f4ee9c4420ecbc3b9824f0b920"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.6"
|
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus_platform_interface
|
name: connectivity_plus_platform_interface
|
||||||
sha256: b8795b9238bf83b64375f63492034cb3d8e222af4d9ce59dda085edf038fa06f
|
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.2.4"
|
||||||
connectivity_plus_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_web
|
|
||||||
sha256: "81332be1b4baf8898fed17bb4fdef27abb7c6fd990bf98c54fd978478adf2f1a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.5"
|
|
||||||
connectivity_plus_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: connectivity_plus_windows
|
|
||||||
sha256: "535b0404b4d5605c4dd8453d67e5d6d2ea0dd36e3b477f50f31af51b0aeab9dd"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.2"
|
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -309,10 +277,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: "961c4aebd27917269b1896382c7cb1b1ba81629ba669ba09c27a7e5710ec9040"
|
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.2"
|
version: "1.6.3"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -341,18 +309,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: dart_code_metrics
|
name: dart_code_metrics
|
||||||
sha256: bb4ec5e729788dde5f7e8e9df4c05ec3b78532a5763e635337153ce40085514b
|
sha256: "026e28da197a03caeccccc0b174ec98ef03da3c81c4543314d7add121aab4375"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.1"
|
version: "5.6.0"
|
||||||
dart_code_metrics_presets:
|
dart_code_metrics_presets:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_code_metrics_presets
|
name: dart_code_metrics_presets
|
||||||
sha256: "43dc1fdcb424fc3aa79964304d09eeda4f199351c52cdc854f8228a9d0296b60"
|
sha256: "9c51724f836aebc4465228954cb5757e5a99737af26a452b5dec0a2d5d0b4d66"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -437,10 +405,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
|
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.6"
|
version: "5.0.0"
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -547,10 +515,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_bloc
|
name: flutter_bloc
|
||||||
sha256: "890c51c8007f0182360e523518a0c732efb89876cb4669307af7efada5b55557"
|
sha256: "434951eea948dbe87f737b674281465f610b8259c16c097b8163ce138749a775"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.1"
|
version: "8.1.2"
|
||||||
flutter_blurhash:
|
flutter_blurhash:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -735,18 +703,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
sha256: "7cc92eabe01e3f1babe1571c5560b135dfc762a34e41e9056881e2196b178ec1"
|
sha256: "774fa28b07f3a82c93596bc137be33189fec578ed3447a93a5a11c93435de394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.2"
|
version: "8.1.3"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: font_awesome_flutter
|
name: font_awesome_flutter
|
||||||
sha256: "875dbb9ec1ad30d68102019ceb682760d06c72747c1c5b7885781b95f88569cc"
|
sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.0"
|
version: "10.4.0"
|
||||||
form_builder_validators:
|
form_builder_validators:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -836,10 +804,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hydrated_bloc
|
name: hydrated_bloc
|
||||||
sha256: "5871204f14b24638dc9d18d5b94cf22a66fc4be40756925cafff3a7553c7d7b7"
|
sha256: eb92d88061b6b911c48779b08a91c8a9f3a3aa8475f80d9380045375d9876536
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.1.0"
|
||||||
image:
|
image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -937,10 +905,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_android
|
name: local_auth_android
|
||||||
sha256: ba48fe0e1cae140a0813ce68c2540250d7f573a8ae4d4b6c681b2d2583584953
|
sha256: cfcbc4936e288d61ef85a04feef6b95f49ba496d4fd98364e6abafb462b06a1f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.17"
|
version: "1.0.18"
|
||||||
local_auth_ios:
|
local_auth_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1176,10 +1144,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
|
sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.7"
|
version: "2.1.8"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1312,10 +1280,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: pretty_dio_logger
|
name: pretty_dio_logger
|
||||||
sha256: "948f7eeb36e7aa0760b51c1a8e3331d4b21e36fabd39efca81f585ed93893544"
|
sha256: "00b80053063935cf9a6190da344c5373b9d0e92da4c944c878ff2fbef0ef6dc2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0-beta-1"
|
version: "1.3.1"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1392,10 +1360,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: e387077716f80609bb979cd199331033326033ecd1c8f200a90c5f57b1c9f55e
|
sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.1"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1685,10 +1653,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809"
|
sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.8"
|
version: "6.1.9"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1701,10 +1669,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3
|
sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.18"
|
version: "6.1.0"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+2
-2
@@ -60,7 +60,7 @@ dependencies:
|
|||||||
package_info_plus: ^1.4.3+1
|
package_info_plus: ^1.4.3+1
|
||||||
font_awesome_flutter: ^10.1.0
|
font_awesome_flutter: ^10.1.0
|
||||||
local_auth: ^2.1.2
|
local_auth: ^2.1.2
|
||||||
connectivity_plus: ^2.3.9
|
connectivity_plus: ^3.0.3
|
||||||
|
|
||||||
flutter_native_splash: ^2.2.11
|
flutter_native_splash: ^2.2.11
|
||||||
share_plus: ^6.2.0
|
share_plus: ^6.2.0
|
||||||
@@ -77,7 +77,7 @@ dependencies:
|
|||||||
badges: ^2.0.3
|
badges: ^2.0.3
|
||||||
flutter_colorpicker: ^1.0.3
|
flutter_colorpicker: ^1.0.3
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
dio: ^4.0.6
|
dio: ^5.0.0
|
||||||
hydrated_bloc: ^9.0.0
|
hydrated_bloc: ^9.0.0
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
pretty_dio_logger: ^1.2.0-beta-1
|
pretty_dio_logger: ^1.2.0-beta-1
|
||||||
|
|||||||
Executable → Regular
+1
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
pushd ../
|
||||||
pushd packages/paperless_api
|
pushd packages/paperless_api
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
Reference in New Issue
Block a user