D1APP-67 bookmarks

This commit is contained in:
MohammadTaha Basiri 2022-02-05 12:56:08 +03:30
parent 3b3cdd20d0
commit afc9c7bafe
23 changed files with 535 additions and 476 deletions

View File

@ -1,62 +1,26 @@
import 'package:didvan/models/category.dart';
class ItemOverview {
class OverviewData {
final int id;
final String title;
final String image;
final String description;
final int? timeToRead;
final String? reference;
final bool? forManagers;
final String createdAt;
final String? type;
final List<Category>? categories;
int? comments;
ItemOverview({
const OverviewData({
required this.id,
required this.title,
required this.image,
required this.description,
required this.timeToRead,
required this.reference,
required this.forManagers,
required this.createdAt,
required this.type,
required this.categories,
required this.comments,
});
factory ItemOverview.fromJson(Map<String, dynamic> json) => ItemOverview(
factory OverviewData.fromJson(Map<String, dynamic> json) => OverviewData(
id: json['id'],
title: json['title'],
image: json['image'],
description: json['description'],
timeToRead: json['timeToRead'],
reference: json['reference'],
forManagers: json['forManagers'],
createdAt: json['createdAt'],
type: json['type'],
comments: json['comments'],
categories: json['categories'] == null
? null
: List<Category>.from(
json['categories'].map(
(cat) => Category.fromJson(cat),
),
),
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'image': image,
'description': description,
'timeToRead': timeToRead,
'reference': reference,
'forManagers': forManagers,
'createdAt': createdAt,
'type': type,
'categories': categories?.map((e) => e.toJson()).toList(),
};
}

View File

@ -1,21 +1,25 @@
class NewsOverviewData {
final int id;
final String title;
import 'package:didvan/models/item_overview.dart';
class NewsOverviewData extends OverviewData {
final String reference;
final String description;
final String image;
final String createdAt;
bool marked;
NewsOverviewData({
required this.id,
required this.title,
required this.reference,
required this.description,
required this.image,
required this.createdAt,
required this.marked,
});
required id,
required createdAt,
required description,
required title,
required image,
}) : super(
createdAt: createdAt,
description: description,
id: id,
image: image,
title: title,
type: 'news',
);
factory NewsOverviewData.fromJson(Map<String, dynamic> json) =>
NewsOverviewData(
@ -25,16 +29,6 @@ class NewsOverviewData {
description: json['description'],
image: json['image'],
createdAt: json['createdAt'],
marked: json['marked'],
marked: json['marked'] ?? true,
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'reference': reference,
'description': description,
'image': image,
'createdAt': createdAt,
'marked': marked,
};
}

View File

@ -1,29 +1,33 @@
import 'package:didvan/models/item_overview.dart';
import 'category.dart';
class RadarOverviewData {
final int id;
final String image;
final String title;
final String description;
final int timeToRead;
final String createdAt;
class RadarOverviewData extends OverviewData {
final bool forManagers;
bool marked;
final List<Category> categories;
final int timeToRead;
int comments;
bool marked;
RadarOverviewData({
required this.id,
required this.image,
required this.title,
required this.description,
required this.timeToRead,
required this.createdAt,
required this.forManagers,
required this.marked,
required this.categories,
required this.comments,
});
required this.timeToRead,
required this.marked,
required createdAt,
required description,
required id,
required image,
required title,
}) : super(
createdAt: createdAt,
description: description,
id: id,
image: image,
title: title,
type: 'radar',
);
factory RadarOverviewData.fromJson(Map<String, dynamic> json) =>
RadarOverviewData(
@ -42,17 +46,4 @@ class RadarOverviewData {
),
),
);
Map<String, dynamic> toJson() => {
'id': id,
'image': image,
'title': title,
'description': description,
'timeToRead': timeToRead,
'createdAt': createdAt,
'forManagers': forManagers,
'marked': marked,
'comment': comments,
'categories': categories.map((e) => e.toJson()).toList(),
};
}

View File

@ -13,7 +13,7 @@ class ActionSheetData {
final bool withoutButtonMode;
final bool smallDismissButton;
ActionSheetData({
const ActionSheetData({
required this.content,
required this.title,
this.confrimTitle,

View File

@ -11,9 +11,6 @@ import 'package:didvan/pages/home/widgets/date_picker_button.dart';
import 'package:didvan/widgets/item_title.dart';
import 'package:didvan/pages/home/widgets/search_field.dart';
import 'package:didvan/pages/home/widgets/logo_app_bar.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/state_handlers/empty_result.dart';
import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart';
@ -76,7 +73,7 @@ class _NewsState extends State<News> {
),
childCount: state.news.length,
itemPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
placeholder: const _NewsOverviewPlaceholder(),
placeholder: NewsOverview.placeholder,
),
],
);
@ -141,56 +138,3 @@ class _NewsState extends State<News> {
);
}
}
class _NewsOverviewPlaceholder extends StatelessWidget {
const _NewsOverviewPlaceholder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(height: 64, width: 64),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
ShimmerPlaceholder(height: 18, width: 200),
SizedBox(height: 8),
ShimmerPlaceholder(height: 18, width: 100),
],
),
],
),
const SizedBox(height: 12),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: 100,
),
const DidvanDivider(verticalPadding: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(height: 12, width: 150),
ShimmerPlaceholder(height: 24, width: 24),
],
),
],
),
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/pages/home/news/news_details/news_details_state.dart';
import 'package:didvan/widgets/didvan/page_view.dart';
import 'package:didvan/pages/home/widgets/floating_navigation_bar.dart';
@ -19,7 +20,7 @@ class _NewsDetailsState extends State<NewsDetails> {
@override
void initState() {
final state = context.read<NewsDetailsState>();
state.args = widget.pageData['args'];
state.args = widget.pageData['args'] ?? const NewsRequestArgs(page: 0);
Future.delayed(Duration.zero, () {
state.getNewsDetails(widget.pageData['id']);
});
@ -49,6 +50,8 @@ class _NewsDetailsState extends State<NewsDetails> {
left: 0,
right: 0,
child: FloatingNavigationBar(
hasUnmarkConfirmation:
widget.pageData['hasUnmarkConfirmation'],
scrollController: _scrollController,
comments: state.currentNews.comments,
id: state.currentNews.id,

View File

@ -29,11 +29,17 @@ class NewsDetailsState extends CoreProvier {
_handleTracking(sendRequest: isForward != null);
if (service.isSuccess) {
final result = service.result;
final newsItem = NewsDetailsData.fromJson(result['news']);
if (args.page == 0) {
news.add(newsItem);
initialIndex = 0;
appState = AppState.idle;
return;
}
NewsDetailsData? prevNews;
if (result['prevNews'].isNotEmpty) {
prevNews = NewsDetailsData.fromJson(result['prevNews']);
}
final newsItem = NewsDetailsData.fromJson(result['news']);
NewsDetailsData? nextNews;
if (result['nextNews'].isNotEmpty) {
nextNews = NewsDetailsData.fromJson(result['nextNews']);

View File

@ -18,12 +18,9 @@ import 'package:didvan/pages/home/widgets/search_field.dart';
import 'package:didvan/pages/home/widgets/logo_app_bar.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/pages/home/widgets/date_picker_button.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/checkbox.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/item_title.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/state_handlers/empty_result.dart';
import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart';
@ -129,7 +126,7 @@ class _RadarState extends State<Radar> {
child:
EmptyResult(onNewSearch: () => _focusNode.requestFocus()),
),
placeholder: const _RadarOverviewPlaceholder(),
placeholder: RadarOverview.placeholder,
builder: (context, state, index) {
final radar = state.radars[index];
return RadarOverview(
@ -286,71 +283,3 @@ class _RadarState extends State<Radar> {
super.dispose();
}
}
class _RadarOverviewPlaceholder extends StatelessWidget {
const _RadarOverviewPlaceholder({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(
width: 200,
height: 16,
),
const SizedBox(height: 8),
const AspectRatio(aspectRatio: 16 / 9, child: ShimmerPlaceholder()),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(
height: 12,
width: 70,
),
ShimmerPlaceholder(
height: 12,
width: 70,
),
],
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const DidvanDivider(),
Row(
children: const [
ShimmerPlaceholder(
height: 32,
width: 32,
),
Spacer(),
ShimmerPlaceholder(
height: 32,
width: 32,
),
SizedBox(width: 16),
ShimmerPlaceholder(
height: 32,
width: 32,
),
],
),
],
),
);
}
}

View File

@ -32,7 +32,7 @@ class _RadarDetailsState extends State<RadarDetails> {
return Scaffold(
body: Consumer<RadarDetailsState>(
builder: (context, state, child) => StateHandler<RadarDetailsState>(
onRetry: () => state.getRadarDetails(state.currentRadar.id),
onRetry: () => state.getRadarDetails(widget.pageData['id']),
state: state,
builder: (context, state) => Stack(
children: [
@ -50,12 +50,15 @@ class _RadarDetailsState extends State<RadarDetails> {
left: 0,
right: 0,
child: FloatingNavigationBar(
hasUnmarkConfirmation:
widget.pageData['hasUnmarkConfirmation'],
comments: state.currentRadar.comments,
id: state.currentRadar.id,
isRadar: true,
marked: state.currentRadar.marked,
title: state.currentRadar.title,
onMarkChanged: (value) => widget.pageData['onMarkChanged'](
onMarkChanged: (value) =>
widget.pageData['onMarkChanged']?.call(
state.currentRadar.id,
value,
),
@ -63,7 +66,7 @@ class _RadarDetailsState extends State<RadarDetails> {
scrollController: _scrollController,
onCommentsChanged: (count) {
state.onCommentsChanged(count);
widget.pageData['onCommentsChanged'](
widget.pageData['onCommentsChanged']?.call(
state.currentRadar.id,
count,
);

View File

@ -35,7 +35,6 @@ class RadarDetailsState extends CoreProvier {
_handleTracking(sendRequest: isForward != null);
if (service.isSuccess) {
final result = service.result;
RadarDetailsData? prevRadar;
final radar = RadarDetailsData.fromJson(result['radar']);
if (args.page == 0) {
radars.add(radar);
@ -44,6 +43,7 @@ class RadarDetailsState extends CoreProvier {
return;
}
RadarDetailsData? prevRadar;
if (result['prevRadar'].isNotEmpty) {
prevRadar = RadarDetailsData.fromJson(result['prevRadar']);
}

View File

@ -1,17 +1,15 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/item_overview.dart';
import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
class BookmarksState extends CoreProvier {
final List<ItemOverview> bookmarks = [];
final List<dynamic> filterdBookmarks = [];
final List<OverviewData> bookmarks = [];
String search = '';
String lastSearch = '';
String? groupName;
bool get searching => search != '';
Future<void> getBookmarks() async {
@ -19,50 +17,34 @@ class BookmarksState extends CoreProvier {
lastSearch = search;
}
appState = AppState.busy;
final service = RequestService(RequestHelper.bookmarks(groupName));
final service = RequestService(RequestHelper.bookmarks());
await service.httpGet();
String resultKey;
switch (groupName) {
case 'radar':
resultKey = 'radars';
break;
case 'news':
resultKey = groupName!;
break;
default:
resultKey = 'contents';
}
if (service.isSuccess) {
final marks = service.result[resultKey];
if (groupName == null) {
final marks = service.result['contents'];
bookmarks.clear();
for (var i = 0; i < marks.length; i++) {
bookmarks.add(ItemOverview.fromJson(marks[i]));
bookmarks.add(OverviewData.fromJson(marks[i]));
}
} else {
filterdBookmarks.clear();
for (var i = 0; i < marks.length; i++) {
filterdBookmarks.add(ItemOverview.fromJson(marks[i]));
}
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
void unMark(int id) {
void onMarkChanged(int id, bool value) {
if (value) return;
final type = bookmarks.firstWhere((element) => element.id == id).type;
switch (type) {
case 'radars':
UserProvider.changeRadarMark(id, value);
break;
case 'news':
UserProvider.changeNewsMark(id, value);
break;
default:
}
bookmarks.removeWhere((element) => element.id == id);
filterdBookmarks.removeWhere((element) => element.id == id);
notifyListeners();
final service = RequestService(RequestHelper.markRadar(id));
service.delete();
}
void onCommentsChanged(int id, int value) {
bookmarks.firstWhere((radar) => radar.id == id).comments = value;
filterdBookmarks.firstWhere((radar) => radar.id == id).comments = value;
notifyListeners();
}
}

View File

@ -1,13 +1,18 @@
import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/pages/home/settings/bookmarks/bookmark_state.dart';
import 'package:didvan/pages/home/widgets/menu_item.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/widgets/animated_visibility.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/scaffold.dart';
import 'package:didvan/pages/home/widgets/multitype_item.dart';
import 'package:didvan/pages/home/widgets/multitype_overview.dart';
import 'package:didvan/pages/home/widgets/search_field.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/item_title.dart';
import 'package:didvan/widgets/state_handlers/empty_list.dart';
import 'package:didvan/widgets/state_handlers/empty_result.dart';
import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart';
@ -48,60 +53,62 @@ class _BookmarksState extends State<Bookmarks> {
focusNode: _focuseNode,
),
const SizedBox(height: 16),
// AnimatedVisibility(
// duration: DesignConfig.lowAnimationDuration,
// isVisible: !state.searching,
// child: DidvanCard(
// child: Column(
// children: [
// MenuItem(
// onTap: () => _onCategorySelected('radar'),
// title: 'تحلیل‌های رادار',
// icon: DidvanIcons.radar_regular,
// iconSize: 24,
// ),
// const DidvanDivider(),
// MenuItem(
// onTap: () => _onCategorySelected('news'),
// title: 'اخبار',
// icon: DidvanIcons.news_regular,
// iconSize: 24,
// ),
// const DidvanDivider(),
// MenuItem(
// onTap: () => _onCategorySelected('video'),
// title: 'ویدئو‌ها',
// icon: DidvanIcons.video_regular,
// iconSize: 24,
// ),
// const DidvanDivider(),
// MenuItem(
// onTap: () => _onCategorySelected('podcast'),
// title: 'پادکست‌ها',
// icon: DidvanIcons.podcast_regular,
// iconSize: 24,
// ),
// ],
// ),
// ),
// ),
// Align(
// alignment: Alignment.centerRight,
// child: AnimatedVisibility(
// duration: DesignConfig.lowAnimationDuration,
// isVisible: !state.searching,
// child: const ItemTitle(title: 'آخرین نشان نشده‌ها'),
// ),
// ),
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: !state.searching,
child: DidvanCard(
child: Column(
children: [
MenuItem(
onTap: () => _onCategorySelected('radars'),
title: 'تحلیل‌های رادار',
icon: DidvanIcons.radar_regular,
iconSize: 24,
),
const DidvanDivider(),
MenuItem(
onTap: () => _onCategorySelected('news'),
title: 'اخبار',
icon: DidvanIcons.news_regular,
iconSize: 24,
),
const DidvanDivider(),
MenuItem(
onTap: () => _onCategorySelected('videos'),
title: 'ویدئو‌ها',
icon: DidvanIcons.video_regular,
iconSize: 24,
),
const DidvanDivider(),
MenuItem(
onTap: () => _onCategorySelected('podcasts'),
title: 'پادکست‌ها',
icon: DidvanIcons.podcast_regular,
iconSize: 24,
),
],
),
),
),
Align(
alignment: Alignment.centerRight,
child: AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: !state.searching,
child: const ItemTitle(title: 'آخرین نشان شده‌ها'),
),
),
],
slivers: [
SliverStateHandler<BookmarksState>(
state: state,
// centerEmptyState: state.searching,
builder: (context, state, index) => MultitypeItem(
centerEmptyState: state.searching,
builder: (context, state, index) => MultitypeOverview(
item: state.bookmarks[index],
onMarkChanged: state.onMarkChanged,
hasUnmarkConfirmation: true,
),
placeholder: const _NewsOverviewPlaceholder(),
placeholder: MultitypeOverview.placeholder,
itemPadding: const EdgeInsets.only(bottom: 8),
emptyState: state.searching
? EmptyResult(onNewSearch: _focuseNode.requestFocus)
@ -114,12 +121,11 @@ class _BookmarksState extends State<Bookmarks> {
);
}
// void _onCategorySelected(String? type) {
// FocusScope.of(context).unfocus();
// final state = context.read<BookmarksState>();
// state.groupName = type;
// Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: state);
// }
void _onCategorySelected(String type) {
if (type != 'radars' && type != 'news') return;
FocusScope.of(context).unfocus();
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type);
}
void _onChanged(String value) {
final state = context.read<BookmarksState>();
@ -133,56 +139,3 @@ class _BookmarksState extends State<Bookmarks> {
});
}
}
class _NewsOverviewPlaceholder extends StatelessWidget {
const _NewsOverviewPlaceholder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(height: 64, width: 64),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
ShimmerPlaceholder(height: 18, width: 200),
SizedBox(height: 8),
ShimmerPlaceholder(height: 18, width: 100),
],
),
],
),
const SizedBox(height: 12),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: 100,
),
const DidvanDivider(verticalPadding: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(height: 12, width: 150),
ShimmerPlaceholder(height: 24, width: 24),
],
),
],
),
);
}
}

View File

@ -1,10 +1,10 @@
import 'package:didvan/models/news_overview.dart';
import 'package:didvan/models/radar_overview.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
import 'package:didvan/pages/home/widgets/news_overview.dart';
import 'package:didvan/pages/home/widgets/radar_overview.dart';
import 'package:didvan/pages/home/settings/bookmarks/bookmark_state.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/scaffold.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/state_handlers/empty_list.dart';
import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart';
@ -20,19 +20,22 @@ class FilteredBookmarks extends StatefulWidget {
class _FilteredBookmarksState extends State<FilteredBookmarks> {
@override
void initState() {
Future.delayed(Duration.zero, context.read<BookmarksState>().getBookmarks);
Future.delayed(
Duration.zero,
context.read<FilteredBookmarksState>().getBookmarks,
);
super.initState();
}
String get _appBarTitle {
switch (context.read<BookmarksState>().groupName) {
case 'radar':
switch (context.read<FilteredBookmarksState>().type) {
case 'radars':
return 'تحلیل‌های رادار';
case 'news':
return 'اخبار';
case 'video':
case 'videos':
return 'ویدئو‌ها';
case 'podcast':
case 'podcasts':
return 'پادکست‌ها';
default:
return 'پادکست‌ها';
@ -44,93 +47,40 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
return DidvanScaffold(
appBarData: AppBarData(title: _appBarTitle),
slivers: [
Consumer<BookmarksState>(
Consumer<FilteredBookmarksState>(
builder: (context, state, child) =>
SliverStateHandler<BookmarksState>(
SliverStateHandler<FilteredBookmarksState>(
state: state,
enableEmptyState: state.filterdBookmarks.isEmpty,
enableEmptyState: state.bookmarks.isEmpty,
itemPadding: const EdgeInsets.only(bottom: 8),
placeholder: const _RadarOverviewPlaceholder(),
placeholder: RadarOverview.placeholder,
emptyState: const EmptyList(),
builder: (context, state, index) => RadarOverview(
radar: state.filterdBookmarks[index],
onMarkChanged: (id, value) {},
onCommentsChanged: (id, count) =>
state.onCommentsChanged(id, count),
),
childCount: state.filterdBookmarks.length,
onRetry: state.getBookmarks,
builder: (context, state, index) {
if (state.type == 'radars') {
return RadarOverview(
radar: state.bookmarks[index] as RadarOverviewData,
onMarkChanged: _onBookmarkChanged,
onCommentsChanged: state.onCommentsChanged,
hasUnmarkConfirmation: true,
);
}
return NewsOverview(
news: state.bookmarks[index] as NewsOverviewData,
onMarkChanged: _onBookmarkChanged,
hasUnmarkConfirmation: true,
);
},
childCount: state.bookmarks.length,
onRetry: () => state.getBookmarks(),
),
),
],
);
}
}
class _RadarOverviewPlaceholder extends StatelessWidget {
const _RadarOverviewPlaceholder({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(
width: 200,
height: 16,
),
const SizedBox(height: 8),
const AspectRatio(aspectRatio: 16 / 9, child: ShimmerPlaceholder()),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(
height: 12,
width: 70,
),
ShimmerPlaceholder(
height: 12,
width: 70,
),
],
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const DidvanDivider(),
Row(
children: const [
ShimmerPlaceholder(
height: 32,
width: 32,
),
Spacer(),
ShimmerPlaceholder(
height: 32,
width: 32,
),
SizedBox(width: 16),
ShimmerPlaceholder(
height: 32,
width: 32,
),
],
),
],
),
);
Future<void> _onBookmarkChanged(int id, bool value) async {
if (value) return;
final state = context.read<FilteredBookmarksState>();
state.onMarkChanged(id, false);
}
}

View File

@ -0,0 +1,65 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/item_overview.dart';
import 'package:didvan/models/news_overview.dart';
import 'package:didvan/models/radar_overview.dart';
import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
class FilteredBookmarksState extends CoreProvier {
String search = '';
String lastSearch = '';
final List<OverviewData> bookmarks = [];
final String type;
FilteredBookmarksState(this.type);
bool get searching => search != '';
Future<void> getBookmarks() async {
if (search != '') {
lastSearch = search;
}
appState = AppState.busy;
final service = RequestService(
RequestHelper.bookmarks(type: type == 'radars' ? 'radar' : type));
await service.httpGet();
if (service.isSuccess) {
final marks = service.result[type];
bookmarks.clear();
for (var i = 0; i < marks.length; i++) {
if (type == 'radars') {
bookmarks.add(RadarOverviewData.fromJson(marks[i]));
}
if (type == 'news') {
bookmarks.add(NewsOverviewData.fromJson(marks[i]));
}
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
void onMarkChanged(int id, bool value) {
switch (type) {
case 'radars':
UserProvider.changeRadarMark(id, value);
break;
case 'news':
UserProvider.changeNewsMark(id, value);
break;
default:
}
bookmarks.removeWhere((element) => element.id == id);
notifyListeners();
}
void onCommentsChanged(int id, int value) {
(bookmarks.firstWhere((radar) => radar.id == id) as RadarOverviewData)
.comments = value;
notifyListeners();
}
}

View File

@ -1,16 +1,21 @@
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/widgets/didvan/icon_button.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
class BookmarkButton extends StatefulWidget {
final bool value;
final void Function(bool value) onMarkChanged;
final bool bigGestureSize;
final bool askForConfirmation;
const BookmarkButton({
Key? key,
required this.value,
this.bigGestureSize = false,
required this.onMarkChanged,
this.askForConfirmation = false,
}) : super(key: key);
@override
@ -37,11 +42,27 @@ class _BookmarkButtonState extends State<BookmarkButton> {
return DidvanIconButton(
gestureSize: widget.bigGestureSize ? 32 : 24,
icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () {
onPressed: () async {
bool confirm = false;
if (widget.askForConfirmation) {
await ActionSheetUtils.openDialog(
data: ActionSheetData(
content: const DidvanText(
'آیا می‌خواهید این محتوا از نشان‌ شده‌ها حذف شود؟',
),
titleIcon: DidvanIcons.bookmark_regular,
titleColor: Theme.of(context).colorScheme.secondary,
title: 'تایید عملیات',
onConfirmed: () => confirm = true,
),
);
}
if (!widget.askForConfirmation || confirm) {
setState(() {
_value = !_value;
});
widget.onMarkChanged(_value);
}
},
);
}

View File

@ -16,6 +16,7 @@ import 'package:flutter/material.dart';
class FloatingNavigationBar extends StatefulWidget {
final ScrollController scrollController;
final bool hasUnmarkConfirmation;
final void Function(int count) onCommentsChanged;
final bool isRadar;
final bool marked;
@ -36,6 +37,7 @@ class FloatingNavigationBar extends StatefulWidget {
required this.id,
required this.title,
this.categories,
this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
@ -107,8 +109,14 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
const Spacer(),
if (widget.isRadar)
BookmarkButton(
askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.marked,
onMarkChanged: widget.onMarkChanged,
onMarkChanged: (value) {
widget.onMarkChanged(value);
if (widget.hasUnmarkConfirmation && !value) {
Navigator.of(context).pop();
}
},
bigGestureSize: true,
),
SizedBox(
@ -140,8 +148,14 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
if (!widget.isRadar) const SizedBox(width: 12),
if (!widget.isRadar)
BookmarkButton(
askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.marked,
onMarkChanged: widget.onMarkChanged,
onMarkChanged: (value) {
widget.onMarkChanged(value);
if (widget.hasUnmarkConfirmation && !value) {
Navigator.of(context).pop();
}
},
bigGestureSize: true,
),
if (widget.isRadar)

View File

@ -1,19 +1,42 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/item_overview.dart';
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
class MultitypeItem extends StatelessWidget {
final ItemOverview item;
const MultitypeItem({Key? key, required this.item}) : super(key: key);
class MultitypeOverview extends StatelessWidget {
final OverviewData item;
final bool hasUnmarkConfirmation;
final void Function(int id, bool value) onMarkChanged;
const MultitypeOverview({
Key? key,
required this.item,
required this.onMarkChanged,
this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
onTap: () => Navigator.of(context).pushNamed(
item.type == 'radar' ? Routes.radarDetails : Routes.newsDetails,
arguments: {
'onMarkChanged': onMarkChanged,
'id': item.id,
'args': item.type == 'radar'
? const RadarRequestArgs(page: 0)
: const NewsRequestArgs(page: 0),
'hasUnmarkConfirmation': hasUnmarkConfirmation,
},
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -72,4 +95,27 @@ class MultitypeItem extends StatelessWidget {
),
);
}
static Widget get placeholder => DidvanCard(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(height: 80, width: 80),
const SizedBox(width: 8),
SizedBox(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
ShimmerPlaceholder(height: 18, width: 200),
SizedBox(height: 8),
ShimmerPlaceholder(height: 18, width: 100),
Spacer(),
ShimmerPlaceholder(height: 14, width: 80),
],
),
),
],
),
);
}

View File

@ -6,6 +6,7 @@ import 'package:didvan/pages/home/widgets/bookmark_button.dart';
import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
@ -13,11 +14,13 @@ class NewsOverview extends StatelessWidget {
final NewsOverviewData news;
final NewsRequestArgs? newsRequestArgs;
final void Function(int id, bool value) onMarkChanged;
final bool hasUnmarkConfirmation;
const NewsOverview({
Key? key,
required this.news,
required this.onMarkChanged,
this.newsRequestArgs,
this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
@ -29,6 +32,7 @@ class NewsOverview extends StatelessWidget {
'onMarkChanged': onMarkChanged,
'id': news.id,
'args': newsRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
},
),
child: Column(
@ -77,6 +81,7 @@ class NewsOverview extends StatelessWidget {
BookmarkButton(
value: news.marked,
onMarkChanged: (value) => onMarkChanged(news.id, value),
askForConfirmation: hasUnmarkConfirmation,
),
],
),
@ -84,4 +89,50 @@ class NewsOverview extends StatelessWidget {
),
);
}
static Widget get placeholder => DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(height: 64, width: 64),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
ShimmerPlaceholder(height: 18, width: 200),
SizedBox(height: 8),
ShimmerPlaceholder(height: 18, width: 100),
],
),
],
),
const SizedBox(height: 12),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: 100,
),
const DidvanDivider(verticalPadding: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(height: 12, width: 150),
ShimmerPlaceholder(height: 24, width: 24),
],
),
],
),
);
}

View File

@ -9,6 +9,7 @@ import 'package:didvan/widgets/didvan/card.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/icon_button.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/shimmer_placeholder.dart';
import 'package:didvan/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
@ -16,6 +17,7 @@ class RadarOverview extends StatelessWidget {
final RadarOverviewData radar;
final void Function(int id, int count) onCommentsChanged;
final void Function(int id, bool value) onMarkChanged;
final bool hasUnmarkConfirmation;
final RadarRequestArgs? radarRequestArgs;
const RadarOverview({
Key? key,
@ -23,6 +25,7 @@ class RadarOverview extends StatelessWidget {
required this.onCommentsChanged,
required this.onMarkChanged,
this.radarRequestArgs,
this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
@ -35,6 +38,7 @@ class RadarOverview extends StatelessWidget {
'onCommentsChanged': onCommentsChanged,
'id': radar.id,
'args': radarRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
},
),
child: Column(
@ -101,6 +105,7 @@ class RadarOverview extends StatelessWidget {
BookmarkButton(
value: radar.marked,
onMarkChanged: (value) => onMarkChanged(radar.id, value),
askForConfirmation: hasUnmarkConfirmation,
),
const Spacer(),
if (radar.comments != 0) DidvanText(radar.comments.toString()),
@ -129,4 +134,63 @@ class RadarOverview extends StatelessWidget {
),
);
}
static Widget get placeholder => DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(
width: 200,
height: 16,
),
const SizedBox(height: 8),
const AspectRatio(aspectRatio: 16 / 9, child: ShimmerPlaceholder()),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerPlaceholder(
height: 12,
width: 70,
),
ShimmerPlaceholder(
height: 12,
width: 70,
),
],
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
),
const DidvanDivider(),
Row(
children: const [
ShimmerPlaceholder(
height: 32,
width: 32,
),
Spacer(),
ShimmerPlaceholder(
height: 32,
width: 32,
),
SizedBox(width: 16),
ShimmerPlaceholder(
height: 32,
width: 32,
),
],
),
],
),
);
}

View File

@ -16,6 +16,7 @@ import 'package:didvan/pages/home/settings/about_us/about_us.dart';
import 'package:didvan/pages/home/settings/bookmarks/bookmarks.dart';
import 'package:didvan/pages/home/settings/bookmarks/bookmark_state.dart';
import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart';
import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
import 'package:didvan/pages/home/settings/direct_list/direct_list.dart';
import 'package:didvan/pages/home/settings/direct_list/direct_list_state.dart';
import 'package:didvan/pages/home/settings/general_settings/settings.dart';
@ -129,8 +130,10 @@ class RouteGenerator {
);
case Routes.filteredBookmarks:
return _createRoute(
ChangeNotifierProvider<BookmarksState>.value(
value: settings.arguments as BookmarksState,
ChangeNotifierProvider<FilteredBookmarksState>(
create: (context) => FilteredBookmarksState(
settings.arguments as String,
),
child: const FilteredBookmarks(),
),
);

View File

@ -14,7 +14,7 @@ class RequestHelper {
static const String updateUserProfile = _baseUserUrl + '/profile/photo';
static const String checkUsername = _baseUserUrl + '/CheckUsername';
static const String updateProfile = _baseUserUrl + '/profile/edit';
static String bookmarks(String? type) =>
static String bookmarks({String? type}) =>
_baseUserUrl + '/marked/${type ?? ''}';
static const String directTypes = baseUrl + '/direct/types';

View File

@ -156,6 +156,82 @@ class ActionSheetUtils {
);
}
static Future<void> openDialog({required ActionSheetData data}) async {
await showDialog(
context: context,
builder: (context) => Dialog(
shape: const RoundedRectangleBorder(
borderRadius: DesignConfig.mediumBorderRadius,
),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Icon(
data.titleIcon,
size: 20,
color: data.titleColor,
),
),
const SizedBox(
width: 8,
),
Expanded(
child: DidvanText(
data.title,
style: Theme.of(context).textTheme.headline3,
color: data.titleColor,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(
height: 12,
),
data.content,
const SizedBox(
height: 12,
),
Row(
children: [
if (data.hasDismissButton)
Expanded(
child: DidvanButton(
onPressed: data.onDismissed ?? () => pop(),
title: data.dismissTitle ?? 'بازگشت',
style: ButtonStyleMode.flat,
),
),
if (data.hasDismissButton)
const SizedBox(
width: 20,
),
Expanded(
child: DidvanButton(
onPressed: () {
pop();
data.onConfirmed?.call();
},
title: data.confrimTitle ?? 'تایید',
),
),
],
),
],
),
),
),
);
}
static void pop() {
DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop();

View File

@ -43,7 +43,7 @@ class StateHandler<T extends CoreProvier> extends StatelessWidget {
color: Theme.of(context).colorScheme.primary,
);
case AppState.failed:
return EmptyConnection(onRetry: onRetry);
return Center(child: EmptyConnection(onRetry: onRetry));
default:
return Container();
}