didvan-app/lib/views/home/bookmarks/bookmarks.dart

682 lines
27 KiB
Dart

// ignore_for_file: unused_element_parameter, deprecated_member_use
import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/bookmarks/bookmark_state.dart';
import 'package:didvan/views/home/main/widgets/swot_bookmark.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/date_picker_button.dart';
import 'package:didvan/views/widgets/item_title.dart';
import 'package:didvan/views/widgets/menu_item.dart';
import 'package:didvan/views/widgets/overview/multitype.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/state_handlers/empty_result.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
import 'package:didvan/views/widgets/search_field.dart';
import 'package:didvan/views/widgets/ai_chat_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
class Bookmarks extends StatefulWidget {
const Bookmarks({Key? key}) : super(key: key);
@override
State<Bookmarks> createState() => _BookmarksState();
}
class _BookmarksState extends State<Bookmarks> {
final _focuseNode = FocusNode();
Timer? _timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<BookmarksState>().loadInitialData();
});
}
// ignore: unused_element
void _onSearchChanged(String value) {
final state = context.read<BookmarksState>();
if (value.length < 3 && value.isNotEmpty) {
if (state.search.isNotEmpty && value.isEmpty) {
state.search = value;
state.loadInitialData();
}
return;
}
if (state.lastSearch == value && value.isNotEmpty) return;
_timer?.cancel();
_timer = Timer(const Duration(milliseconds: 700), () {
state.search = value;
state.searchAndLoadData(page: 1);
});
}
Future<void> _showFilterBottomSheet() async {
final state = context.read<BookmarksState>();
final categories = [
{'id': 1, 'label': 'پویش افق'},
{'id': 2, 'label': 'دنیای فولاد'},
{'id': 3, 'label': 'استودیو آینده'},
{'id': 5, 'label': 'رادارهای استراتژیک'},
{'id': 6, 'label': 'سها'},
{'id': 7, 'label': 'اینفوگرافی'},
];
await ActionSheetUtils(context).showBottomSheet(
data: ActionSheetData(
title: 'فیلتر جستجو',
titleIconWidget: SvgPicture.asset(
'lib/assets/icons/document-filter.svg',
width: 24,
height: 24,
),
dismissTitle: 'حذف فیلتر',
confrimTitle: 'نمایش نتایج',
onDismissed: () => state.resetFilters(false),
onConfirmed: () => state.loadInitialData(),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ItemTitle(
title: 'تاریخ',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
iconWidget: 'lib/assets/icons/calendar.svg',
color: const Color.fromARGB(255, 27, 60, 89),
),
const SizedBox(height: 8),
StatefulBuilder(
builder: (context, setState) => Row(
children: [
DatePickerButton(
initialValue:
state.startDate?.toIso8601String().split('T').first,
emptyText: 'از تاریخ',
onPicked: (date) => setState(() {
state.startDate =
date != null ? DateTime.parse(date) : null;
}),
lastDate: state.endDate?.toIso8601String().split('T').first,
),
const SizedBox(width: 8),
DatePickerButton(
initialValue:
state.endDate?.toIso8601String().split('T').first,
emptyText: 'تا تاریخ',
onPicked: (date) => setState(() {
state.endDate =
date != null ? DateTime.parse(date) : null;
}),
firstDate:
state.startDate?.toIso8601String().split('T').first,
),
],
),
),
const SizedBox(height: 28),
ItemTitle(
title: 'دسته بندی',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
iconWidget: 'lib/assets/icons/ion_extension-puzzle-outline.svg',
color: const Color.fromARGB(255, 27, 60, 89),
),
const SizedBox(height: 12),
Wrap(
children: [
for (var category in categories)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: category['label'] as String,
value: state.selectedTypes.contains(category['id']),
onChanged: (value) {
if (value) {
state.selectedTypes.add(category['id'] as int);
} else {
state.selectedTypes.remove(category['id']);
}
},
color: const Color.fromARGB(255, 61, 61, 61),
),
),
],
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final state = context.watch<BookmarksState>();
return DidvanScaffold(
appBarData: null,
showSliversFirst: true,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(8.0),
hidePlayer: true,
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 140,
backgroundColor: Theme.of(context).colorScheme.surface,
automaticallyImplyLeading: false,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(
Assets.horizontalLogoWithText,
height: 60,
color: Theme.of(context).colorScheme.title,
),
Row(
children: [
GestureDetector(
onTap: () {
Navigator.pushNamed(context, Routes.profile);
},
child: SizedBox(
width: 44,
height: 44,
child: Center(
child: SvgPicture.asset(
'lib/assets/icons/New_Profile.svg',
width: 30,
height: 30,
color: Theme.of(context).colorScheme.caption,
),
),
),
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: SizedBox(
width: 44,
height: 44,
child: Center(
child: SvgPicture.asset(
'lib/assets/icons/arrow-left.svg',
width: 25,
height: 25,
color: Theme.of(context).colorScheme.caption,
),
),
),
),
],
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: SearchField(
title: 'دیدوان',
focusNode: _focuseNode,
onChanged: _onSearchChanged,
onFilterButtonPressed: _showFilterBottomSheet,
isFiltered: state.isFiltered,
extraIconPath: 'lib/assets/icons/live ai.svg',
onExtraIconPressed: () {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => const AiChatDialog(),
);
},
),
),
],
),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverToBoxAdapter(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: !state.searching,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(context).colorScheme.focused,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.focused,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: DidvanText(
'نشان‌شده‌ها',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
color: DesignConfig.isDark
? Colors.white
: Colors.black,
fontWeight: FontWeight.normal,
fontSize: 15),
),
),
),
const SizedBox(height: 15),
_FadeInSlide(
delay: const Duration(milliseconds: 100),
child: MenuOption(
onTap: () => _onCategorySelected(5),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Stratzhic Radar.svg",
width: 20,
),
titleWidget: DidvanText('رادارهای استراتژیک',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 150),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 200),
child: MenuOption(
onTap: () => _onCategorySelected(2),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Donye_Foolad.svg",
width: 20,
),
titleWidget: DidvanText('دنیای فولاد',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 250),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 300),
child: MenuOption(
onTap: () => _onCategorySelected(1),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Pouyesh_Ofogh_New.svg",
width: 20,
),
titleWidget: DidvanText('پویش افق',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 350),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 400),
child: MenuOption(
onTap: () => _onCategorySelected(3),
iconWidget: SvgPicture.asset(
"lib/assets/icons/video-play.svg",
color: const Color.fromARGB(255, 0, 126, 167),
width: 20,
),
titleWidget: DidvanText('ویدیو‌کست',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 450),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 500),
child: MenuOption(
onTap: () => _onCategorySelected(4),
iconWidget: SvgPicture.asset(
"lib/assets/icons/microphone-2.svg",
color: const Color.fromARGB(255, 0, 126, 167),
width: 20,
),
titleWidget: DidvanText('پادکست',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 550),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 600),
child: MenuOption(
onTap: () => _onCategorySelected(6),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Saha.svg",
width: 20,
),
titleWidget: DidvanText('سها',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 650),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 700),
child: MenuOption(
onTap: () => _onCategorySelected(7),
iconWidget: SvgPicture.asset(
"lib/assets/icons/hugeicons_chart-02.svg",
color: const Color.fromARGB(255, 0, 126, 167),
width: 20,
),
titleWidget: DidvanText('اینفوگرافی',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 750),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 800),
child: MenuOption(
onTap: () => _onCategorySelected(8),
titleWidget: DidvanText('ماژول بایدها و نبایدها',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Swot_New.svg",
width: 24),
iconSize: 24,
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 850),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 900),
child: MenuOption(
onTap: () => _onCategorySelected(9),
titleWidget: DidvanText('ماهنامه تحلیلی',
style: TextStyle(
color:
Theme.of(context).colorScheme.caption)),
iconWidget: SvgPicture.asset(
"lib/assets/icons/Monthly.svg",
width: 24),
iconSize: 24,
),
),
const SizedBox(height: 7),
],
),
),
),
const ItemTitle(title: 'آخرین نشان شده‌ها')
],
),
),
),
SliverStateHandler<BookmarksState>(
state: state,
enableEmptyState: state.bookmarks.isEmpty &&
state.bookmarkedSwotItems.isEmpty &&
!state.searching &&
!state.swotItemsLoading,
emptyState: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
DidvanText(
'در قسمت رصدخانه من، تمامی مطالبی که در قسمت‌های مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشان‌دار) کرده‌اید، به تفکیک نمایش داده می‌شوند. هم‌چنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.',
fontSize: 14,
color: Theme.of(context).colorScheme.title,
textAlign: TextAlign.justify,
),
Image.asset(
Assets.bookmarkAnimation,
width: MediaQuery.sizeOf(context).width,
height: 180,
),
],
),
placeholder: state.searching &&
state.bookmarks.isEmpty &&
state.bookmarkedSwotItems.isEmpty &&
!state.swotItemsLoading
? EmptyResult(onNewSearch: _focuseNode.requestFocus)
: MultitypeOverview.placeholder,
builder: (context, state, index) {
if (index >= state.bookmarks.length) {
return const Center(child: CircularProgressIndicator());
}
return _FadeInSlide(
delay: Duration(milliseconds: (index % 10) * 100),
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color.fromRGBO(184, 184, 184, 1),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: MultitypeOverview(
item: state.bookmarks[index],
onMarkChanged: state.onMarkChanged,
hasUnmarkConfirmation: true,
enableCaption: true,
enableBookmark: true,
showDivider: false,
),
),
),
),
);
},
itemPadding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
childCount: state.bookmarks.length +
(state.page != state.lastPage && state.bookmarks.isNotEmpty
? 1
: 0),
onRetry: () => state.loadInitialData(),
),
if (state.appState == AppState.idle &&
state.bookmarkedSwotItems.isNotEmpty)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final item = state.bookmarkedSwotItems[index];
return _FadeInSlide(
delay: Duration(milliseconds: (index % 10) * 100),
child: Padding(
padding:
const EdgeInsets.only(bottom: 8, left: 16, right: 16),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color.fromRGBO(184, 184, 184, 1),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SwotBookmark(
item: item,
onSwotUnbookmarked: (postId) {
state.onSwotMarkChanged(postId);
},
),
),
),
),
);
},
childCount: state.bookmarkedSwotItems.length,
),
)
else if (state.swotItemsLoading)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: MultitypeOverview.placeholder),
),
)
],
);
}
void _onCategorySelected(int type) {
FocusScope.of(context).unfocus();
final state = context.read<BookmarksState>();
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: {
'type': type,
'onDeleted': (int id) {
state.bookmarks.removeWhere(
(element) => element.id == id && element.typeInteger == type);
if (type == 8) {
state.bookmarkedSwotItems.removeWhere((element) => element.id == id);
}
state.update();
},
}).then((_) {
state.loadInitialData();
});
}
}
// void _onChanged(String value) {
// final state = context.read<BookmarksState>();
// if (value.length < 3 && value.isNotEmpty || state.lastSearch == value) {
// return;
// }
// _timer?.cancel();
// _timer = Timer(const Duration(seconds: 1), () {
// state.search = value;
// state.getBookmarks(page: 1);
// });
// }
// }
class _FadeInSlide extends StatefulWidget {
final Widget child;
final Duration delay;
final Duration duration;
final double slideOffset;
const _FadeInSlide({
Key? key,
required this.child,
this.delay = Duration.zero,
// ignore: unused_element_parameter
this.duration = const Duration(milliseconds: 400),
this.slideOffset = 50.0,
}) : super(key: key);
@override
State<_FadeInSlide> createState() => _FadeInSlideState();
}
class _FadeInSlideState extends State<_FadeInSlide> {
bool _isVisible = false;
@override
void initState() {
super.initState();
Timer(widget.delay, () {
if (mounted) {
setState(() {
_isVisible = true;
});
}
});
}
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
opacity: _isVisible ? 1.0 : 0.0,
duration: widget.duration,
curve: Curves.easeOut,
child: AnimatedContainer(
duration: widget.duration,
curve: Curves.easeOut,
transform: Matrix4.translationValues(
0, _isVisible ? 0 : widget.slideOffset, 0),
child: widget.child,
),
);
}
}