feat: More resturcturings, adapt code to previous changes

This commit is contained in:
Anton Stubenbord
2023-05-01 23:05:54 +02:00
parent 88085b5662
commit d5c68e023c
35 changed files with 475 additions and 200 deletions

View File

@@ -39,20 +39,13 @@ class DocumentDetailsPage extends StatefulWidget {
}
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
late Future<DocumentMetaData> _metaData;
static const double _itemSpacing = 24;
final _pagingScrollController = ScrollController();
@override
void initState() {
super.initState();
_loadMetaData();
}
void _loadMetaData() {
_metaData = context
.read<PaperlessDocumentsApi>()
.getMetaData(context.read<DocumentDetailsCubit>().state.document);
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
@@ -67,8 +60,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: BlocListener<ConnectivityCubit, ConnectivityState>(
listenWhen: (previous, current) => !previous.isConnected && current.isConnected,
listener: (context, state) {
_loadMetaData();
setState(() {});
context.read<DocumentDetailsCubit>().loadMetaData();
},
child: Scaffold(
extendBodyBehindAppBar: false,
@@ -98,7 +90,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
),
),
Positioned.fill(
Positioned.fill(
top: 0,
child: Container(
height: 100,
@@ -285,7 +277,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
DocumentDownloadButton(
document: state.document,
enabled: isConnected,
metaData: _metaData,
),
IconButton(
tooltip: S.of(context)!.previewTooltip,

View File

@@ -20,12 +20,10 @@ import 'package:permission_handler/permission_handler.dart';
class DocumentDownloadButton extends StatefulWidget {
final DocumentModel? document;
final bool enabled;
final Future<DocumentMetaData> metaData;
const DocumentDownloadButton({
super.key,
required this.document,
this.enabled = true,
required this.metaData,
});
@override

View File

@@ -38,12 +38,9 @@ class ScannerPage extends StatefulWidget {
State<ScannerPage> createState() => _ScannerPageState();
}
class _ScannerPageState extends State<ScannerPage>
with SingleTickerProviderStateMixin {
final SliverOverlapAbsorberHandle searchBarHandle =
SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle actionsHandle =
SliverOverlapAbsorberHandle();
class _ScannerPageState extends State<ScannerPage> with SingleTickerProviderStateMixin {
final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
final SliverOverlapAbsorberHandle actionsHandle = SliverOverlapAbsorberHandle();
@override
Widget build(BuildContext context) {
@@ -180,8 +177,7 @@ class _ScannerPageState extends State<ScannerPage>
final success = await EdgeDetection.detectEdge(file.path);
if (!success) {
if (kDebugMode) {
dev.log(
'[ScannerPage] Scan either not successful or canceled by user.');
dev.log('[ScannerPage] Scan either not successful or canceled by user.');
}
return;
}
@@ -198,7 +194,7 @@ class _ScannerPageState extends State<ScannerPage>
final uploadResult = await Navigator.of(context).push<DocumentUploadResult>(
MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => DocumentUploadCubit(
create: (_) => DocumentUploadCubit(
context.read(),
context.read(),
),
@@ -212,9 +208,7 @@ class _ScannerPageState extends State<ScannerPage>
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
// For paperless version older than 1.11.3, task id will always be null!
context.read<DocumentScannerCubit>().reset();
context
.read<TaskStatusCubit>()
.listenToTaskChanges(uploadResult!.taskId!);
context.read<TaskStatusCubit>().listenToTaskChanges(uploadResult!.taskId!);
}
}

View File

@@ -226,13 +226,10 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
hasLoaded: state.hasLoaded,
enableHeroAnimation: false,
onTap: (document) {
Navigator.pushNamed(
pushDocumentDetailsRoute(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
document: document,
isLabelClickable: false,
);
},
correspondents: state.correspondents,

View File

@@ -47,7 +47,10 @@ class SliverSearchBar extends StatelessWidget {
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
builder: (context, box, _) {
final account = box.get(settings.currentLoggedInUser!)!;
return UserAvatar(userId: settings.currentLoggedInUser!, account: account);
return UserAvatar(
userId: settings.currentLoggedInUser!,
account: account,
);
},
);
},

View File

@@ -32,6 +32,7 @@ class DocumentUploadPreparationPage extends StatefulWidget {
final String? filename;
final String? fileExtension;
const DocumentUploadPreparationPage({
Key? key,
required this.fileBytes,

View File

@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
@@ -443,12 +444,9 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
}
void _openDetails(DocumentModel document) {
Navigator.pushNamed(
pushDocumentDetailsRoute(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
),
document: document,
);
}

View File

@@ -2,7 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.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:shimmer/shimmer.dart';

View File

@@ -23,8 +23,13 @@ import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:provider/provider.dart';
class HomeRoute extends StatelessWidget {
/// The id of the currently authenticated user (e.g. demo@paperless.example.com)
final String localUserId;
/// The Paperless API version of the currently connected instance
final int paperlessApiVersion;
// A factory providing the API implementations given an API version
final PaperlessApiFactory paperlessProviderFactory;
const HomeRoute({
@@ -44,6 +49,7 @@ class HomeRoute extends StatelessWidget {
Provider<CacheManager>(
create: (context) => CacheManager(
Config(
// Isolated cache per user.
localUserId,
fileService: DioFileService(context.read<SessionManager>().client),
),
@@ -121,11 +127,15 @@ class HomeRoute extends StatelessWidget {
)..initialize(),
),
ProxyProvider<PaperlessServerStatsApi, ServerInformationCubit>(
update: (context, value, previous) => ServerInformationCubit(value),
update: (context, value, previous) =>
ServerInformationCubit(value)..updateInformation(),
),
ProxyProvider<LabelRepository, LabelCubit>(
update: (context, value, previous) => LabelCubit(value),
),
ProxyProvider<PaperlessTasksApi, TaskStatusCubit>(
update: (context, value, previous) => TaskStatusCubit(value),
),
],
child: const HomePage(),
);

View File

@@ -37,14 +37,11 @@ class _InboxItemState extends State<InboxItem> {
builder: (context, state) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
Navigator.pushNamed(
onTap: () {
pushDocumentDetailsRoute(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: widget.document,
isLabelClickable: false,
),
document: widget.document,
isLabelClickable: false,
);
},
child: SizedBox(

View File

@@ -52,13 +52,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: (document) {
Navigator.pushNamed(
pushDocumentDetailsRoute(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
document: document,
isLabelClickable: false,
);
},
correspondents: state.correspondents,

View File

@@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:equatable/equatable.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/adapters.dart';
@@ -9,6 +9,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
import 'package:paperless_mobile/core/security/session_manager.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
@@ -23,16 +24,14 @@ part 'authentication_state.dart';
part 'authentication_cubit.freezed.dart';
class AuthenticationCubit extends Cubit<AuthenticationState> {
final PaperlessUserApi _userApi;
final LocalAuthenticationService _localAuthService;
final PaperlessAuthenticationApi _authApi;
final PaperlessApiFactory _apiFactory;
final SessionManager _sessionManager;
AuthenticationCubit(
this._localAuthService,
this._authApi,
this._apiFactory,
this._sessionManager,
this._userApi,
) : super(const AuthenticationState.unauthenticated());
Future<void> login({
@@ -43,7 +42,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
assert(credentials.username != null && credentials.password != null);
final localUserId = "${credentials.username}@$serverUrl";
final serverUser = await _addUser(
await _addUser(
localUserId,
serverUrl,
credentials,
@@ -51,13 +50,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
_sessionManager,
);
final response = await _sessionManager.client.get("/api/");
final apiVersion = response.headers["x-api-version"] as int;
final apiVersion = await _getApiVersion(_sessionManager.client);
// Mark logged in user as currently active user.
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
globalSettings.currentLoggedInUser = localUserId;
await globalSettings.save();
emit(
AuthenticationState.authenticated(
apiVersion: apiVersion,
@@ -110,8 +109,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
globalSettings.currentLoggedInUser = localUserId;
await globalSettings.save();
final response = await _sessionManager.client.get("/api/");
final apiVersion = response.headers["x-api-version"] as int;
final apiVersion = await _getApiVersion(_sessionManager.client);
emit(AuthenticationState.authenticated(
localUserId: localUserId,
apiVersion: apiVersion,
@@ -188,8 +187,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
authToken: authentication.token,
baseUrl: userAccount.serverUrl,
);
final response = await _sessionManager.client.get("/api/");
final apiVersion = response.headers["x-api-version"] as int;
final apiVersion = await _getApiVersion(_sessionManager.client);
emit(
AuthenticationState.authenticated(
apiVersion: apiVersion,
@@ -247,26 +245,34 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
baseUrl: serverUrl,
clientCertificate: clientCert,
);
final authApi = PaperlessAuthenticationApiImpl(sessionManager.client);
final authApi = _apiFactory.createAuthenticationApi(sessionManager.client);
final token = await authApi.login(
username: credentials.username!,
password: credentials.password!,
);
sessionManager.updateSettings(
baseUrl: serverUrl,
clientCertificate: clientCert,
authToken: token,
);
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
if (userAccountBox.containsKey(localUserId)) {
throw Exception("User with id $localUserId already exists!");
}
final apiVersion = await _getApiVersion(sessionManager.client);
final serverUserId = await _userApi.findCurrentUserId();
final serverUser = await _apiFactory
.createUserApi(
sessionManager.client,
apiVersion: apiVersion,
)
.findCurrentUser();
// Create user account
await userAccountBox.put(
@@ -275,7 +281,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
id: localUserId,
settings: LocalUserSettings(),
serverUrl: serverUrl,
paperlessUserId: 1,
paperlessUser: serverUser,
),
);
@@ -295,6 +301,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
),
);
userCredentialsBox.close();
return serverUserId;
return serverUser.id;
}
Future<int> _getApiVersion(Dio dio) async {
final response = await dio.get("/api/");
return int.parse(response.headers.value('x-api-version') ?? "3");
}
}

View File

@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
@@ -37,18 +36,20 @@ class SavedViewCubit extends Cubit<SavedViewState> {
},
);
_savedViewRepository.subscribe(this, (views) {
emit(
state.maybeWhen(
loaded:
(savedViews, correspondents, documentTypes, tags, storagePaths) =>
(state as _SavedViewLoadedState).copyWith(
savedViews: views,
_savedViewRepository.addListener(
this,
onChanged: (views) {
emit(
state.maybeWhen(
loaded: (savedViews, correspondents, documentTypes, tags, storagePaths) =>
(state as _SavedViewLoadedState).copyWith(
savedViews: views.savedViews,
),
orElse: () => state,
),
orElse: () => state,
),
);
});
);
},
);
}
Future<SavedView> add(SavedView view) async {
@@ -77,7 +78,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
@override
Future<void> close() {
_savedViewRepository.unsubscribe(this);
_savedViewRepository.removeListener(this);
_labelRepository.removeListener(this);
return super.close();
}

View File

@@ -76,12 +76,13 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
isLoading: state.isLoading,
hasLoaded: state.hasLoaded,
onTap: (document) {
Navigator.pushNamed(
Navigator.push(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
MaterialPageRoute(
builder: (context) => DocumentDetailsRoute(
document: document,
isLabelClickable: false,
),
),
);
},

View File

@@ -86,11 +86,11 @@ class ManageAccountsPage extends StatelessWidget {
final child = SizedBox(
width: double.maxFinite,
child: ListTile(
title: Text(account.username),
title: Text(account.paperlessUser.username),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (account.fullName != null) Text(account.fullName!),
if (account.paperlessUser.fullName != null) Text(account.paperlessUser.fullName!),
Text(
account.serverUrl.replaceFirst(RegExp(r'https://?'), ''),
style: TextStyle(
@@ -99,7 +99,7 @@ class ManageAccountsPage extends StatelessWidget {
),
],
),
isThreeLine: account.fullName != null,
isThreeLine: account.paperlessUser.fullName != null,
leading: UserAvatar(
account: account,
userId: userId,

View File

@@ -22,7 +22,7 @@ class SettingsPage extends StatelessWidget {
final host = user!.serverUrl.replaceFirst(RegExp(r"https?://"), "");
return ListTile(
title: Text(
S.of(context)!.loggedInAs(user.username) + "@$host",
S.of(context)!.loggedInAs(user.paperlessUser.username) + "@$host",
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
),

View File

@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/repository/user_repository.dart';
class UserAvatar extends StatelessWidget {
final String userId;
@@ -16,7 +19,7 @@ class UserAvatar extends StatelessWidget {
final backgroundColor = Colors.primaries[userId.hashCode % Colors.primaries.length];
final foregroundColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
return CircleAvatar(
child: Text((account.fullName ?? account.username)
child: Text((account.paperlessUser.fullName ?? account.paperlessUser.username)
.split(" ")
.take(2)
.map((e) => e.substring(0, 1))

View File

@@ -36,10 +36,8 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
@override
Widget build(BuildContext context) {
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
listenWhen: (previous, current) =>
!previous.isConnected && current.isConnected,
listener: (context, state) =>
context.read<SimilarDocumentsCubit>().initialize(),
listenWhen: (previous, current) => !previous.isConnected && current.isConnected,
listener: (context, state) => context.read<SimilarDocumentsCubit>().initialize(),
builder: (context, connectivity) {
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
builder: (context, state) {
@@ -48,9 +46,7 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
child: OfflineWidget(),
);
}
if (state.hasLoaded &&
!state.isLoading &&
state.documents.isEmpty) {
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty) {
return SliverToBoxAdapter(
child: Center(
child: Text(S.of(context)!.noItemsFound),
@@ -65,13 +61,10 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
hasLoaded: state.hasLoaded,
enableHeroAnimation: false,
onTap: (document) {
Navigator.pushNamed(
pushDocumentDetailsRoute(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
document: document,
isLabelClickable: false,
);
},
correspondents: state.correspondents,