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

674 lines
27 KiB
Dart

// ignore_for_file: unused_element_parameter
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:
const Color.fromARGB(255, 102, 102, 102),
),
),
),
),
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:
const Color.fromARGB(255, 102, 102, 102),
),
),
),
),
],
),
],
),
),
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: const Color.fromARGB(255, 224, 224, 224),
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: const Color.fromARGB(255, 230, 243, 250),
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: DidvanText(
'نشان‌شده‌ها',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
color: 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: const DidvanText('رادارهای استراتژیک',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('دنیای فولاد',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('پویش افق',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('ویدیو‌کست',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('پادکست',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('سها',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
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: const DidvanText('اینفوگرافی',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
),
),
const _FadeInSlide(
delay: Duration(milliseconds: 750),
child: DidvanDivider(),
),
_FadeInSlide(
delay: const Duration(milliseconds: 800),
child: MenuOption(
onTap: () => _onCategorySelected(8),
titleWidget: const DidvanText(
'ماژول بایدها و نبایدها',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
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: const DidvanText('ماهنامه تحلیلی',
style: TextStyle(
color: Color.fromARGB(255, 102, 102, 102))),
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,
),
);
}
}