mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 11:15:49 -06:00
fix: Fix scrolling bug on inbox page
This commit is contained in:
@@ -70,7 +70,7 @@ android {
|
|||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,8 +108,9 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSuggestionsView(DocumentSearchState state) {
|
Widget _buildSuggestionsView(DocumentSearchState state) {
|
||||||
final suggestions =
|
final suggestions = state.suggestions
|
||||||
state.suggestions.whereNot((element) => state.searchHistory.contains(element)).toList();
|
.whereNot((element) => state.searchHistory.contains(element))
|
||||||
|
.toList();
|
||||||
final historyMatches = state.searchHistory
|
final historyMatches = state.searchHistory
|
||||||
.where(
|
.where(
|
||||||
(element) => element.startsWith(query),
|
(element) => element.startsWith(query),
|
||||||
@@ -140,7 +141,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
childCount: suggestions.length,
|
childCount: suggestions.length,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (suggestions.isEmpty && historyMatches.isEmpty)
|
if (suggestions.isEmpty && historyMatches.isEmpty && state.hasLoaded)
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
@@ -191,7 +192,8 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return ViewTypeSelectionWidget(
|
return ViewTypeSelectionWidget(
|
||||||
viewType: state.viewType,
|
viewType: state.viewType,
|
||||||
onChanged: (type) => context.read<DocumentSearchCubit>().updateViewType(type),
|
onChanged: (type) =>
|
||||||
|
context.read<DocumentSearchCubit>().updateViewType(type),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,18 +16,22 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentUser =
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
.getValue()!
|
||||||
|
.currentLoggedInUser;
|
||||||
|
|
||||||
return SliverPersistentHeader(
|
return SliverPadding(
|
||||||
floating: floating,
|
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||||
pinned: pinned,
|
sliver: SliverPersistentHeader(
|
||||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
floating: floating,
|
||||||
minExtent: kToolbarHeight,
|
pinned: pinned,
|
||||||
maxExtent: kToolbarHeight,
|
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||||
child: Container(
|
minExtent: kToolbarHeight,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
maxExtent: kToolbarHeight,
|
||||||
child: const DocumentSearchBar(),
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: const DocumentSearchBar(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,9 +42,12 @@ class DocumentsPage extends StatefulWidget {
|
|||||||
State<DocumentsPage> createState() => _DocumentsPageState();
|
State<DocumentsPage> createState() => _DocumentsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProviderStateMixin {
|
class _DocumentsPageState extends State<DocumentsPage>
|
||||||
final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
|
with SingleTickerProviderStateMixin {
|
||||||
final SliverOverlapAbsorberHandle tabBarHandle = SliverOverlapAbsorberHandle();
|
final SliverOverlapAbsorberHandle searchBarHandle =
|
||||||
|
SliverOverlapAbsorberHandle();
|
||||||
|
final SliverOverlapAbsorberHandle tabBarHandle =
|
||||||
|
SliverOverlapAbsorberHandle();
|
||||||
late final TabController _tabController;
|
late final TabController _tabController;
|
||||||
|
|
||||||
int _currentTab = 0;
|
int _currentTab = 0;
|
||||||
@@ -81,7 +84,8 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<TaskStatusCubit, TaskStatusState>(
|
return BlocListener<TaskStatusCubit, TaskStatusState>(
|
||||||
listenWhen: (previous, current) => !previous.isSuccess && current.isSuccess,
|
listenWhen: (previous, current) =>
|
||||||
|
!previous.isSuccess && current.isSuccess,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
@@ -98,7 +102,8 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
},
|
},
|
||||||
child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous != ConnectivityState.connected && current == ConnectivityState.connected,
|
previous != ConnectivityState.connected &&
|
||||||
|
current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
try {
|
try {
|
||||||
context.read<DocumentsCubit>().reload();
|
context.read<DocumentsCubit>().reload();
|
||||||
@@ -146,7 +151,11 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
body: WillPopScope(
|
body: WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
if (context.read<DocumentsCubit>().state.selection.isNotEmpty) {
|
if (context
|
||||||
|
.read<DocumentsCubit>()
|
||||||
|
.state
|
||||||
|
.selection
|
||||||
|
.isNotEmpty) {
|
||||||
context.read<DocumentsCubit>().resetSelection();
|
context.read<DocumentsCubit>().resetSelection();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -161,18 +170,13 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
handle: searchBarHandle,
|
handle: searchBarHandle,
|
||||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return AnimatedSwitcher(
|
if (state.selection.isEmpty) {
|
||||||
layoutBuilder: SliverAnimatedSwitcher.defaultLayoutBuilder,
|
return const SliverSearchBar(floating: true);
|
||||||
transitionBuilder: SliverAnimatedSwitcher.defaultTransitionBuilder,
|
} else {
|
||||||
child: state.selection.isEmpty
|
return DocumentSelectionSliverAppBar(
|
||||||
? const SliverSearchBar(floating: true)
|
state: state,
|
||||||
: DocumentSelectionSliverAppBar(
|
);
|
||||||
state: state,
|
}
|
||||||
),
|
|
||||||
duration: const Duration(
|
|
||||||
milliseconds: 250,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -187,7 +191,8 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
}
|
}
|
||||||
return SliverPersistentHeader(
|
return SliverPersistentHeader(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
delegate:
|
||||||
|
CustomizableSliverPersistentHeaderDelegate(
|
||||||
minExtent: kTextTabBarHeight,
|
minExtent: kTextTabBarHeight,
|
||||||
maxExtent: kTextTabBarHeight,
|
maxExtent: kTextTabBarHeight,
|
||||||
child: ColoredTabBar(
|
child: ColoredTabBar(
|
||||||
@@ -211,15 +216,22 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
if (metrics.maxScrollExtent == 0) {
|
if (metrics.maxScrollExtent == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final desiredTab = (metrics.pixels / metrics.maxScrollExtent).round();
|
final desiredTab =
|
||||||
if (metrics.axis == Axis.horizontal && _currentTab != desiredTab) {
|
(metrics.pixels / metrics.maxScrollExtent)
|
||||||
|
.round();
|
||||||
|
if (metrics.axis == Axis.horizontal &&
|
||||||
|
_currentTab != desiredTab) {
|
||||||
setState(() => _currentTab = desiredTab);
|
setState(() => _currentTab = desiredTab);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
physics: context.watch<DocumentsCubit>().state.selection.isNotEmpty
|
physics: context
|
||||||
|
.watch<DocumentsCubit>()
|
||||||
|
.state
|
||||||
|
.selection
|
||||||
|
.isNotEmpty
|
||||||
? const NeverScrollableScrollPhysics()
|
? const NeverScrollableScrollPhysics()
|
||||||
: null,
|
: null,
|
||||||
children: [
|
children: [
|
||||||
@@ -287,13 +299,19 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
|
|
||||||
final currState = context.read<DocumentsCubit>().state;
|
final currState = context.read<DocumentsCubit>().state;
|
||||||
final max = notification.metrics.maxScrollExtent;
|
final max = notification.metrics.maxScrollExtent;
|
||||||
if (max == 0 || _currentTab != 0 || currState.isLoading || currState.isLastPageLoaded) {
|
if (max == 0 ||
|
||||||
|
_currentTab != 0 ||
|
||||||
|
currState.isLoading ||
|
||||||
|
currState.isLastPageLoaded) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final offset = notification.metrics.pixels;
|
final offset = notification.metrics.pixels;
|
||||||
if (offset >= max * 0.7) {
|
if (offset >= max * 0.7) {
|
||||||
context.read<DocumentsCubit>().loadMore().onError<PaperlessServerException>(
|
context
|
||||||
|
.read<DocumentsCubit>()
|
||||||
|
.loadMore()
|
||||||
|
.onError<PaperlessServerException>(
|
||||||
(error, stackTrace) => showErrorMessage(
|
(error, stackTrace) => showErrorMessage(
|
||||||
context,
|
context,
|
||||||
error,
|
error,
|
||||||
@@ -324,16 +342,20 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final allowToggleFilter = state.selection.isEmpty;
|
||||||
return SliverAdaptiveDocumentsView(
|
return SliverAdaptiveDocumentsView(
|
||||||
viewType: state.viewType,
|
viewType: state.viewType,
|
||||||
onTap: _openDetails,
|
onTap: _openDetails,
|
||||||
onSelected: context.read<DocumentsCubit>().toggleDocumentSelection,
|
onSelected:
|
||||||
|
context.read<DocumentsCubit>().toggleDocumentSelection,
|
||||||
hasInternetConnection: connectivityState.isConnected,
|
hasInternetConnection: connectivityState.isConnected,
|
||||||
onTagSelected: _addTagToFilter,
|
onTagSelected: allowToggleFilter ? _addTagToFilter : null,
|
||||||
onCorrespondentSelected: _addCorrespondentToFilter,
|
onCorrespondentSelected:
|
||||||
onDocumentTypeSelected: _addDocumentTypeToFilter,
|
allowToggleFilter ? _addCorrespondentToFilter : null,
|
||||||
onStoragePathSelected: _addStoragePathToFilter,
|
onDocumentTypeSelected:
|
||||||
|
allowToggleFilter ? _addDocumentTypeToFilter : null,
|
||||||
|
onStoragePathSelected:
|
||||||
|
allowToggleFilter ? _addStoragePathToFilter : null,
|
||||||
documents: state.documents,
|
documents: state.documents,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
isLabelClickable: true,
|
isLabelClickable: true,
|
||||||
@@ -401,7 +423,8 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
snapSizes: const [0.9, 1],
|
snapSizes: const [0.9, 1],
|
||||||
initialChildSize: .9,
|
initialChildSize: .9,
|
||||||
maxChildSize: 1,
|
maxChildSize: 1,
|
||||||
builder: (context, controller) => BlocBuilder<DocumentsCubit, DocumentsState>(
|
builder: (context, controller) =>
|
||||||
|
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return DocumentFilterPanel(
|
return DocumentFilterPanel(
|
||||||
initialFilter: context.read<DocumentsCubit>().state.filter,
|
initialFilter: context.read<DocumentsCubit>().state.filter,
|
||||||
@@ -422,7 +445,9 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
if (filterIntent.shouldReset) {
|
if (filterIntent.shouldReset) {
|
||||||
await context.read<DocumentsCubit>().resetFilter();
|
await context.read<DocumentsCubit>().resetFilter();
|
||||||
} else {
|
} else {
|
||||||
await context.read<DocumentsCubit>().updateFilter(filter: filterIntent.filter!);
|
await context
|
||||||
|
.read<DocumentsCubit>()
|
||||||
|
.updateFilter(filter: filterIntent.filter!);
|
||||||
}
|
}
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
@@ -439,7 +464,6 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
|
|
||||||
void _addTagToFilter(int tagId) {
|
void _addTagToFilter(int tagId) {
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cubit.state.filter.tags.maybeMap(
|
cubit.state.filter.tags.maybeMap(
|
||||||
ids: (state) {
|
ids: (state) {
|
||||||
@@ -447,7 +471,9 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(
|
(filter) => filter.copyWith(
|
||||||
tags: state.copyWith(
|
tags: state.copyWith(
|
||||||
include: state.include.whereNot((element) => element == tagId).toList(),
|
include: state.include
|
||||||
|
.whereNot((element) => element == tagId)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -455,7 +481,9 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(
|
(filter) => filter.copyWith(
|
||||||
tags: state.copyWith(
|
tags: state.copyWith(
|
||||||
exclude: state.exclude.whereNot((element) => element == tagId).toList(),
|
exclude: state.exclude
|
||||||
|
.whereNot((element) => element == tagId)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -481,22 +509,26 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
void _addCorrespondentToFilter(int? correspondentId) {
|
void _addCorrespondentToFilter(int? correspondentId) {
|
||||||
if (correspondentId == null) return;
|
if (correspondentId == null) return;
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cubit.state.filter.correspondent.maybeWhen(
|
cubit.state.filter.correspondent.maybeWhen(
|
||||||
fromId: (id) {
|
fromId: (id) {
|
||||||
if (id == correspondentId) {
|
if (id == correspondentId) {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(correspondent: const IdQueryParameter.unset()),
|
(filter) => filter.copyWith(
|
||||||
|
correspondent: const IdQueryParameter.unset()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(correspondent: IdQueryParameter.fromId(correspondentId)),
|
(filter) => filter.copyWith(
|
||||||
|
correspondent: IdQueryParameter.fromId(correspondentId)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
orElse: () {
|
orElse: () {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(correspondent: IdQueryParameter.fromId(correspondentId)),
|
(filter) => filter.copyWith(
|
||||||
|
correspondent: IdQueryParameter.fromId(correspondentId)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -508,22 +540,26 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
void _addDocumentTypeToFilter(int? documentTypeId) {
|
void _addDocumentTypeToFilter(int? documentTypeId) {
|
||||||
if (documentTypeId == null) return;
|
if (documentTypeId == null) return;
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cubit.state.filter.documentType.maybeWhen(
|
cubit.state.filter.documentType.maybeWhen(
|
||||||
fromId: (id) {
|
fromId: (id) {
|
||||||
if (id == documentTypeId) {
|
if (id == documentTypeId) {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(documentType: const IdQueryParameter.unset()),
|
(filter) =>
|
||||||
|
filter.copyWith(documentType: const IdQueryParameter.unset()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(documentType: IdQueryParameter.fromId(documentTypeId)),
|
(filter) => filter.copyWith(
|
||||||
|
documentType: IdQueryParameter.fromId(documentTypeId)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
orElse: () {
|
orElse: () {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(documentType: IdQueryParameter.fromId(documentTypeId)),
|
(filter) => filter.copyWith(
|
||||||
|
documentType: IdQueryParameter.fromId(documentTypeId)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -535,22 +571,26 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
void _addStoragePathToFilter(int? pathId) {
|
void _addStoragePathToFilter(int? pathId) {
|
||||||
if (pathId == null) return;
|
if (pathId == null) return;
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cubit.state.filter.storagePath.maybeWhen(
|
cubit.state.filter.storagePath.maybeWhen(
|
||||||
fromId: (id) {
|
fromId: (id) {
|
||||||
if (id == pathId) {
|
if (id == pathId) {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(storagePath: const IdQueryParameter.unset()),
|
(filter) =>
|
||||||
|
filter.copyWith(storagePath: const IdQueryParameter.unset()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
(filter) =>
|
||||||
|
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
orElse: () {
|
orElse: () {
|
||||||
cubit.updateCurrentFilter(
|
cubit.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
(filter) =>
|
||||||
|
filter.copyWith(storagePath: IdQueryParameter.fromId(pathId)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,40 +81,25 @@ class DocumentListItem extends DocumentItem {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: DateFormat.yMMMd().format(document.created),
|
text: DateFormat.yMMMd().format(document.created),
|
||||||
style: Theme.of(context).textTheme.labelSmall?.apply(color: Colors.grey),
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall
|
||||||
|
?.apply(color: Colors.grey),
|
||||||
children: document.documentType != null
|
children: document.documentType != null
|
||||||
? [
|
? [
|
||||||
const TextSpan(text: '\u30FB'),
|
const TextSpan(text: '\u30FB'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: labels.documentTypes[document.documentType]?.name,
|
text: labels.documentTypes[document.documentType]?.name,
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: onDocumentTypeSelected != null
|
||||||
..onTap = () => onDocumentTypeSelected?.call(document.documentType),
|
? (TapGestureRecognizer()
|
||||||
|
..onTap = () => onDocumentTypeSelected!(
|
||||||
|
document.documentType))
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Row(
|
|
||||||
// children: [
|
|
||||||
// Text(
|
|
||||||
// DateFormat.yMMMd().format(document.created),
|
|
||||||
// style: Theme.of(context)
|
|
||||||
// .textTheme
|
|
||||||
// .bodySmall
|
|
||||||
// ?.apply(color: Colors.grey),
|
|
||||||
// ),
|
|
||||||
// if (document.documentType != null) ...[
|
|
||||||
// Text("\u30FB"),
|
|
||||||
// DocumentTypeWidget(
|
|
||||||
// documentTypeId: document.documentType,
|
|
||||||
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
|
|
||||||
// color: Colors.grey,
|
|
||||||
// overflow: TextOverflow.ellipsis,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
),
|
),
|
||||||
isThreeLine: document.tags.isNotEmpty,
|
isThreeLine: document.tags.isNotEmpty,
|
||||||
leading: AspectRatio(
|
leading: AspectRatio(
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import 'package:responsive_builder/responsive_builder.dart';
|
|||||||
/// Performs initialization logic.
|
/// Performs initialization logic.
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
final int paperlessApiVersion;
|
final int paperlessApiVersion;
|
||||||
const HomePage({Key? key, required this.paperlessApiVersion}) : super(key: key);
|
const HomePage({Key? key, required this.paperlessApiVersion})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HomePageState createState() => _HomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
@@ -231,7 +232,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
listeners: [
|
listeners: [
|
||||||
BlocListener<ConnectivityCubit, ConnectivityState>(
|
BlocListener<ConnectivityCubit, ConnectivityState>(
|
||||||
// If app was started offline, load data once it comes back online.
|
// If app was started offline, load data once it comes back online.
|
||||||
listenWhen: (previous, current) => current == ConnectivityState.connected,
|
listenWhen: (previous, current) =>
|
||||||
|
current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
context.read<LabelRepository>().initialize();
|
context.read<LabelRepository>().initialize();
|
||||||
context.read<SavedViewRepository>().initialize();
|
context.read<SavedViewRepository>().initialize();
|
||||||
@@ -241,7 +243,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.task != null) {
|
if (state.task != null) {
|
||||||
// Handle local notifications on task change (only when app is running for now).
|
// Handle local notifications on task change (only when app is running for now).
|
||||||
context.read<LocalNotificationService>().notifyTaskChanged(state.task!);
|
context
|
||||||
|
.read<LocalNotificationService>()
|
||||||
|
.notifyTaskChanged(state.task!);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -254,7 +258,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
children: [
|
children: [
|
||||||
NavigationRail(
|
NavigationRail(
|
||||||
labelType: NavigationRailLabelType.all,
|
labelType: NavigationRailLabelType.all,
|
||||||
destinations: destinations.map((e) => e.toNavigationRailDestination()).toList(),
|
destinations: destinations
|
||||||
|
.map((e) => e.toNavigationRailDestination())
|
||||||
|
.toList(),
|
||||||
selectedIndex: _currentIndex,
|
selectedIndex: _currentIndex,
|
||||||
onDestinationSelected: _onNavigationChanged,
|
onDestinationSelected: _onNavigationChanged,
|
||||||
),
|
),
|
||||||
@@ -272,7 +278,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
elevation: 4.0,
|
elevation: 4.0,
|
||||||
selectedIndex: _currentIndex,
|
selectedIndex: _currentIndex,
|
||||||
onDestinationSelected: _onNavigationChanged,
|
onDestinationSelected: _onNavigationChanged,
|
||||||
destinations: destinations.map((e) => e.toNavigationDestination()).toList(),
|
destinations:
|
||||||
|
destinations.map((e) => e.toNavigationDestination()).toList(),
|
||||||
),
|
),
|
||||||
body: routes[_currentIndex],
|
body: routes[_currentIndex],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import 'package:paperless_mobile/features/paged_document_view/cubit/document_pag
|
|||||||
part 'inbox_cubit.g.dart';
|
part 'inbox_cubit.g.dart';
|
||||||
part 'inbox_state.dart';
|
part 'inbox_state.dart';
|
||||||
|
|
||||||
class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin {
|
class InboxCubit extends HydratedCubit<InboxState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
final LabelRepository _labelRepository;
|
final LabelRepository _labelRepository;
|
||||||
|
|
||||||
final PaperlessDocumentsApi _documentsApi;
|
final PaperlessDocumentsApi _documentsApi;
|
||||||
@@ -38,7 +39,10 @@ class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin
|
|||||||
this,
|
this,
|
||||||
onDeleted: remove,
|
onDeleted: remove,
|
||||||
onUpdated: (document) {
|
onUpdated: (document) {
|
||||||
if (document.tags.toSet().intersection(state.inboxTags.toSet()).isEmpty) {
|
if (document.tags
|
||||||
|
.toSet()
|
||||||
|
.intersection(state.inboxTags.toSet())
|
||||||
|
.isEmpty) {
|
||||||
remove(document);
|
remove(document);
|
||||||
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
|
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
|
||||||
} else {
|
} else {
|
||||||
@@ -139,7 +143,8 @@ class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin
|
|||||||
/// from the inbox.
|
/// from the inbox.
|
||||||
///
|
///
|
||||||
Future<Iterable<int>> removeFromInbox(DocumentModel document) async {
|
Future<Iterable<int>> removeFromInbox(DocumentModel document) async {
|
||||||
final tagsToRemove = document.tags.toSet().intersection(state.inboxTags.toSet());
|
final tagsToRemove =
|
||||||
|
document.tags.toSet().intersection(state.inboxTags.toSet());
|
||||||
|
|
||||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||||
final updatedDocument = await api.update(
|
final updatedDocument = await api.update(
|
||||||
@@ -193,8 +198,8 @@ class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin
|
|||||||
Future<void> assignAsn(DocumentModel document) async {
|
Future<void> assignAsn(DocumentModel document) async {
|
||||||
if (document.archiveSerialNumber == null) {
|
if (document.archiveSerialNumber == null) {
|
||||||
final int asn = await _documentsApi.findNextAsn();
|
final int asn = await _documentsApi.findNextAsn();
|
||||||
final updatedDocument =
|
final updatedDocument = await _documentsApi
|
||||||
await _documentsApi.update(document.copyWith(archiveSerialNumber: () => asn));
|
.update(document.copyWith(archiveSerialNumber: () => asn));
|
||||||
|
|
||||||
replace(updatedDocument);
|
replace(updatedDocument);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:paperless_mobile/features/document_search/view/sliver_search_bar
|
|||||||
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';
|
||||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_list_loading_widget.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';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
@@ -27,18 +26,15 @@ class InboxPage extends StatefulWidget {
|
|||||||
State<InboxPage> createState() => _InboxPageState();
|
State<InboxPage> createState() => _InboxPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin<InboxPage, InboxCubit> {
|
class _InboxPageState extends State<InboxPage>
|
||||||
final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
|
with DocumentPagingViewMixin<InboxPage, InboxCubit> {
|
||||||
|
final SliverOverlapAbsorberHandle searchBarHandle =
|
||||||
|
SliverOverlapAbsorberHandle();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final pagingScrollController = ScrollController();
|
final pagingScrollController = ScrollController();
|
||||||
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||||
|
final _scrollController = ScrollController();
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
context.read<InboxCubit>().reloadInbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -63,98 +59,104 @@ class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin<Inbo
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: BlocBuilder<InboxCubit, InboxState>(
|
body: SafeArea(
|
||||||
builder: (context, state) {
|
top: true,
|
||||||
return SafeArea(
|
child: NestedScrollView(
|
||||||
top: true,
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
child: NestedScrollView(
|
SliverOverlapAbsorber(
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
handle: searchBarHandle,
|
||||||
SliverOverlapAbsorber(
|
sliver: const SliverSearchBar(),
|
||||||
handle: searchBarHandle,
|
)
|
||||||
sliver: const SliverSearchBar(),
|
],
|
||||||
)
|
body: BlocBuilder<InboxCubit, InboxState>(
|
||||||
],
|
builder: (_, state) {
|
||||||
body: Builder(
|
if (state.documents.isEmpty && state.hasLoaded) {
|
||||||
builder: (context) {
|
return Center(
|
||||||
if (!state.hasLoaded) {
|
child: InboxEmptyWidget(
|
||||||
return const InboxListLoadingWidget();
|
emptyStateRefreshIndicatorKey:
|
||||||
} else if (state.documents.isEmpty) {
|
_emptyStateRefreshIndicatorKey,
|
||||||
return Center(
|
),
|
||||||
child: InboxEmptyWidget(
|
);
|
||||||
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
|
} else if (state.isLoading) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(top: 16, left: 16),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const InboxItemPlaceholder();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: context.read<InboxCubit>().reload,
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: HintCard(
|
||||||
|
show: !state.isHintAcknowledged,
|
||||||
|
hintText:
|
||||||
|
S.of(context)!.swipeLeftToMarkADocumentAsSeen,
|
||||||
|
onHintAcknowledged: () =>
|
||||||
|
context.read<InboxCubit>().acknowledgeHint(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
// Build a list of slivers alternating between SliverToBoxAdapter
|
||||||
} else {
|
// (group header) and a SliverList (inbox items).
|
||||||
return RefreshIndicator(
|
..._groupByDate(state.documents)
|
||||||
onRefresh: context.read<InboxCubit>().reload,
|
.entries
|
||||||
child: CustomScrollView(
|
.map(
|
||||||
slivers: [
|
(entry) => [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: HintCard(
|
child: Align(
|
||||||
show: !state.isHintAcknowledged,
|
alignment: Alignment.centerLeft,
|
||||||
hintText: S.of(context)!.swipeLeftToMarkADocumentAsSeen,
|
child: ClipRRect(
|
||||||
onHintAcknowledged: () =>
|
borderRadius: BorderRadius.circular(32.0),
|
||||||
context.read<InboxCubit>().acknowledgeHint(),
|
child: Text(
|
||||||
),
|
entry.key,
|
||||||
),
|
style:
|
||||||
// Build a list of slivers alternating between SliverToBoxAdapter
|
Theme.of(context).textTheme.bodySmall,
|
||||||
// (group header) and a SliverList (inbox items).
|
textAlign: TextAlign.center,
|
||||||
..._groupByDate(state.documents)
|
).padded(),
|
||||||
.entries
|
|
||||||
.map(
|
|
||||||
(entry) => [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(32.0),
|
|
||||||
child: Text(
|
|
||||||
entry.key,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).padded(),
|
|
||||||
),
|
|
||||||
).paddedOnly(top: 8.0),
|
|
||||||
),
|
),
|
||||||
SliverList(
|
).paddedOnly(top: 8.0),
|
||||||
delegate: SliverChildBuilderDelegate(
|
),
|
||||||
childCount: entry.value.length,
|
SliverList(
|
||||||
(context, index) {
|
delegate: SliverChildBuilderDelegate(
|
||||||
if (index < entry.value.length - 1) {
|
childCount: entry.value.length,
|
||||||
return Column(
|
(context, index) {
|
||||||
children: [
|
if (index < entry.value.length - 1) {
|
||||||
_buildListItem(
|
return Column(
|
||||||
entry.value[index],
|
children: [
|
||||||
),
|
_buildListItem(
|
||||||
const Divider(
|
entry.value[index],
|
||||||
indent: 16,
|
),
|
||||||
endIndent: 16,
|
const Divider(
|
||||||
),
|
indent: 16,
|
||||||
],
|
endIndent: 16,
|
||||||
);
|
),
|
||||||
}
|
],
|
||||||
return _buildListItem(
|
);
|
||||||
entry.value[index],
|
}
|
||||||
);
|
return _buildListItem(
|
||||||
},
|
entry.value[index],
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
],
|
),
|
||||||
)
|
),
|
||||||
.flattened
|
],
|
||||||
.toList(),
|
)
|
||||||
const SliverToBoxAdapter(
|
.flattened
|
||||||
child: SizedBox(height: 78),
|
.toList(),
|
||||||
),
|
const SliverToBoxAdapter(
|
||||||
],
|
child: SizedBox(height: 78),
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
}
|
),
|
||||||
},
|
);
|
||||||
),
|
}
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -239,7 +241,9 @@ class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin<Inbo
|
|||||||
Iterable<int> removedTags,
|
Iterable<int> removedTags,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await context.read<InboxCubit>().undoRemoveFromInbox(document, removedTags);
|
await context
|
||||||
|
.read<InboxCubit>()
|
||||||
|
.undoRemoveFromInbox(document, removedTags);
|
||||||
} on PaperlessServerException catch (error, stackTrace) {
|
} on PaperlessServerException catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,130 @@ 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/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
import 'package:paperless_mobile/core/workarounds/colored_chip.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/delete_document_confirmation_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.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/inbox/cubit/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class InboxItemPlaceholder extends StatelessWidget {
|
||||||
|
const InboxItemPlaceholder({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ShimmerPlaceholder(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const TextPlaceholder(length: 150, fontSize: 12),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 150,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 120,
|
||||||
|
width: 90,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: const ColoredBox(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Spacer(),
|
||||||
|
TextPlaceholder(length: 200, fontSize: 14),
|
||||||
|
Spacer(),
|
||||||
|
TextPlaceholder(length: 120, fontSize: 14),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
TextPlaceholder(length: 170, fontSize: 14),
|
||||||
|
Spacer(),
|
||||||
|
TagsPlaceholder(count: 3, dense: true),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 50,
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 50,
|
||||||
|
height: 40,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
).padded(),
|
||||||
|
const VerticalDivider(
|
||||||
|
indent: 12,
|
||||||
|
endIndent: 12,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 150,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Container(
|
||||||
|
width: 200,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class InboxItem extends StatefulWidget {
|
class InboxItem extends StatefulWidget {
|
||||||
static const a4AspectRatio = 1 / 1.4142;
|
static const a4AspectRatio = 1 / 1.4142;
|
||||||
|
|
||||||
final DocumentModel document;
|
final DocumentModel document;
|
||||||
const InboxItem({
|
const InboxItem({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -70,10 +182,14 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
_buildTextWithLeadingIcon(
|
_buildTextWithLeadingIcon(
|
||||||
Icon(
|
Icon(
|
||||||
Icons.person_outline,
|
Icons.person_outline,
|
||||||
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
size: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.fontSize,
|
||||||
),
|
),
|
||||||
LabelText<Correspondent>(
|
LabelText<Correspondent>(
|
||||||
label: state.labels.correspondents[widget.document.correspondent],
|
label: state.labels.correspondents[
|
||||||
|
widget.document.correspondent],
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
placeholder: "-",
|
placeholder: "-",
|
||||||
),
|
),
|
||||||
@@ -81,10 +197,14 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
_buildTextWithLeadingIcon(
|
_buildTextWithLeadingIcon(
|
||||||
Icon(
|
Icon(
|
||||||
Icons.description_outlined,
|
Icons.description_outlined,
|
||||||
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
size: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.fontSize,
|
||||||
),
|
),
|
||||||
LabelText<DocumentType>(
|
LabelText<DocumentType>(
|
||||||
label: state.labels.documentTypes[widget.document.documentType],
|
label: state.labels.documentTypes[
|
||||||
|
widget.document.documentType],
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
placeholder: "-",
|
placeholder: "-",
|
||||||
),
|
),
|
||||||
@@ -139,8 +259,8 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final shouldDelete = await showDialog<bool>(
|
final shouldDelete = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => DeleteDocumentConfirmationDialog(
|
||||||
DeleteDocumentConfirmationDialog(document: widget.document),
|
document: widget.document),
|
||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
@@ -217,7 +337,10 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
_isAsnAssignLoading = true;
|
_isAsnAssignLoading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
context.read<InboxCubit>().assignAsn(widget.document).whenComplete(
|
context
|
||||||
|
.read<InboxCubit>()
|
||||||
|
.assignAsn(widget.document)
|
||||||
|
.whenComplete(
|
||||||
() => setState(() => _isAsnAssignLoading = false),
|
() => setState(() => _isAsnAssignLoading = false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
import 'package:flutter/material.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/tags_placeholder.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
|
||||||
|
|
||||||
class InboxListLoadingWidget extends StatelessWidget {
|
|
||||||
const InboxListLoadingWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListView.separated(
|
|
||||||
itemCount: 20,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) => _buildInboxItem().padded(),
|
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 16),
|
|
||||||
).paddedOnly(top: 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInboxItem() {
|
|
||||||
return ShimmerPlaceholder(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const TextPlaceholder(length: 150, fontSize: 12),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 150,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 120,
|
|
||||||
width: 90,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: const ColoredBox(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
const Flexible(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Spacer(),
|
|
||||||
TextPlaceholder(length: 200, fontSize: 14),
|
|
||||||
Spacer(),
|
|
||||||
TextPlaceholder(length: 120, fontSize: 14),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
TextPlaceholder(length: 170, fontSize: 14),
|
|
||||||
Spacer(),
|
|
||||||
TagsPlaceholder(count: 3, dense: true),
|
|
||||||
Spacer(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 50,
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
width: 50,
|
|
||||||
height: 40,
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
).padded(),
|
|
||||||
const VerticalDivider(
|
|
||||||
indent: 12,
|
|
||||||
endIndent: 12,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 150,
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Container(
|
|
||||||
width: 200,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,8 @@ import 'paged_documents_state.dart';
|
|||||||
/// Mixin which can be used on cubits that handle documents.
|
/// Mixin which can be used on cubits that handle documents.
|
||||||
/// This implements all paging and filtering logic.
|
/// This implements all paging and filtering logic.
|
||||||
///
|
///
|
||||||
mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<State> {
|
mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||||
|
on BlocBase<State> {
|
||||||
PaperlessDocumentsApi get api;
|
PaperlessDocumentsApi get api;
|
||||||
DocumentChangedNotifier get notifier;
|
DocumentChangedNotifier get notifier;
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<Sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reload() async {
|
Future<void> reload() async {
|
||||||
emit(state.copyWithPaged(isLoading: true));
|
// emit(state.copyWithPaged(isLoading: true));
|
||||||
final filter = state.filter.copyWith(page: 1);
|
final filter = state.filter.copyWith(page: 1);
|
||||||
try {
|
try {
|
||||||
final result = await api.findAll(filter);
|
final result = await api.findAll(filter);
|
||||||
@@ -128,7 +129,8 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<Sta
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final foundPage = state.value[index];
|
final foundPage = state.value[index];
|
||||||
final replacementPage = foundPage.copyWith(
|
final replacementPage = foundPage.copyWith(
|
||||||
results: foundPage.results..removeWhere((element) => element.id == document.id),
|
results: foundPage.results
|
||||||
|
..removeWhere((element) => element.id == document.id),
|
||||||
);
|
);
|
||||||
final newCount = foundPage.count - 1;
|
final newCount = foundPage.count - 1;
|
||||||
emit(
|
emit(
|
||||||
@@ -136,7 +138,8 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<Sta
|
|||||||
value: state.value
|
value: state.value
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(currIndex, element) =>
|
(currIndex, element) =>
|
||||||
(currIndex == index ? replacementPage : element).copyWith(count: newCount),
|
(currIndex == index ? replacementPage : element)
|
||||||
|
.copyWith(count: newCount),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
@@ -159,11 +162,14 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState> on BlocBase<Sta
|
|||||||
if (pageIndex != -1) {
|
if (pageIndex != -1) {
|
||||||
final foundPage = state.value[pageIndex];
|
final foundPage = state.value[pageIndex];
|
||||||
final replacementPage = foundPage.copyWith(
|
final replacementPage = foundPage.copyWith(
|
||||||
results: foundPage.results.map((doc) => doc.id == document.id ? document : doc).toList(),
|
results: foundPage.results
|
||||||
|
.map((doc) => doc.id == document.id ? document : doc)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
final newState = state.copyWithPaged(
|
final newState = state.copyWithPaged(
|
||||||
value: state.value
|
value: state.value
|
||||||
.mapIndexed((currIndex, element) => currIndex == pageIndex ? replacementPage : element)
|
.mapIndexed((currIndex, element) =>
|
||||||
|
currIndex == pageIndex ? replacementPage : element)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
emit(newState);
|
emit(newState);
|
||||||
|
|||||||
44
packages/paperless_document_scanner/example/ios/Podfile
Normal file
44
packages/paperless_document_scanner/example/ios/Podfile
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '11.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user