statistics base version
This commit is contained in:
parent
4d77442337
commit
06e828b8c6
|
|
@ -192,6 +192,7 @@ extension DidvanColorScheme on ColorScheme {
|
|||
Color get overlay => brightness == Brightness.dark
|
||||
? const Color(0xFF0F1011)
|
||||
: const Color(0xFF292929);
|
||||
Color get yellow => const Color(0XFFEAA92A);
|
||||
|
||||
// Error and success colors
|
||||
Color get errorBack => brightness == Brightness.dark
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
class Data {
|
||||
final String p;
|
||||
final String h;
|
||||
final String l;
|
||||
final String d;
|
||||
final double dp;
|
||||
final String dt;
|
||||
final String t;
|
||||
final String? tEn;
|
||||
final String tG;
|
||||
final String ts;
|
||||
|
||||
Data({
|
||||
required this.p,
|
||||
required this.h,
|
||||
required this.l,
|
||||
required this.d,
|
||||
required this.dp,
|
||||
required this.dt,
|
||||
required this.t,
|
||||
required this.tEn,
|
||||
required this.tG,
|
||||
required this.ts,
|
||||
});
|
||||
|
||||
factory Data.fromJson(Map<String, dynamic> json) => Data(
|
||||
p: json['p'],
|
||||
h: json['h'],
|
||||
l: json['l'],
|
||||
d: json['d'],
|
||||
dp: double.parse(json['dp'].toString()),
|
||||
dt: json['dt'],
|
||||
t: json['t'],
|
||||
tEn: json['t_en'],
|
||||
tG: json['t-g'],
|
||||
ts: json['ts'],
|
||||
);
|
||||
|
||||
factory Data.fromList(List list) => Data(
|
||||
p: list[0],
|
||||
h: list[1],
|
||||
l: list[2],
|
||||
d: list[3],
|
||||
dp: double.parse(list[4].toString().replaceAll('-', '0')),
|
||||
dt: list[5],
|
||||
t: list[6],
|
||||
tEn: list[7],
|
||||
tG: '',
|
||||
ts: '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'p': p,
|
||||
'h': h,
|
||||
'l': l,
|
||||
'd': d,
|
||||
'dp': dp,
|
||||
'dt': dt,
|
||||
't': t,
|
||||
't_en': tEn,
|
||||
't-g': tG,
|
||||
'ts': ts,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import 'data.dart';
|
||||
|
||||
class StatisticData {
|
||||
final int id;
|
||||
final String label;
|
||||
final String title;
|
||||
final Data data;
|
||||
|
||||
StatisticData({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.title,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
factory StatisticData.fromJson(Map<String, dynamic> json) => StatisticData(
|
||||
id: json['id'],
|
||||
label: json['label'],
|
||||
title: json['title'],
|
||||
data: Data.fromJson(json['data']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'label': label,
|
||||
'title': title,
|
||||
'data': data.toJson(),
|
||||
};
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ class UserProvider extends CoreProvier {
|
|||
static final List<MapEntry> _radarMarkQueue = [];
|
||||
static final List<MapEntry> _newsMarkQueue = [];
|
||||
static final List<MapEntry> _studioMarkQueue = [];
|
||||
static final List<MapEntry> _statisticMarkQueue = [];
|
||||
|
||||
Future<String?> setAndGetToken({String? newToken}) async {
|
||||
if (newToken == null) {
|
||||
|
|
@ -184,4 +185,20 @@ class UserProvider extends CoreProvier {
|
|||
_newsMarkQueue.removeWhere((element) => element.key == id);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> changeStatisticMark(int id, bool value) async {
|
||||
_statisticMarkQueue.add(MapEntry(id, value));
|
||||
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||
final MapEntry? lastChange =
|
||||
_statisticMarkQueue.lastWhereOrNull((item) => item.key == id);
|
||||
if (lastChange == null) return;
|
||||
final service = RequestService(RequestHelper.mark(id, 'statistic'));
|
||||
if (lastChange.value) {
|
||||
await service.post();
|
||||
} else {
|
||||
await service.delete();
|
||||
}
|
||||
_statisticMarkQueue.removeWhere((element) => element.key == id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart';
|
|||
import 'package:didvan/views/home/settings/general_settings/settings.dart';
|
||||
import 'package:didvan/views/home/settings/general_settings/settings_state.dart';
|
||||
import 'package:didvan/views/home/settings/profile/profile.dart';
|
||||
import 'package:didvan/views/home/statistics/statistics_state.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic_details/statistic_details.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic_details/statistic_details_state.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic_state.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart'
|
||||
if (dart.library.io) 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart'
|
||||
if (dart.library.html) 'package:didvan/views/home/studio/studio_details/studio_details.web.dart';
|
||||
|
|
@ -65,8 +67,8 @@ class RouteGenerator {
|
|||
ChangeNotifierProvider<StudioState>(
|
||||
create: (context) => StudioState(),
|
||||
),
|
||||
ChangeNotifierProvider<StatisticsState>(
|
||||
create: (context) => StatisticsState(),
|
||||
ChangeNotifierProvider<StatisticState>(
|
||||
create: (context) => StatisticState(),
|
||||
),
|
||||
],
|
||||
child: const Home(),
|
||||
|
|
@ -111,6 +113,15 @@ class RouteGenerator {
|
|||
pageData: settings.arguments as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
case Routes.statisticDetails:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<StatisticDetailsState>(
|
||||
create: (context) => StatisticDetailsState(),
|
||||
child: StatisticDetails(
|
||||
pageData: settings.arguments as Map<String, dynamic>,
|
||||
),
|
||||
),
|
||||
);
|
||||
case Routes.directList:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<DirectListState>(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class Routes {
|
|||
static const String radarDetails = '/radar-details';
|
||||
static const String newsDetails = '/news-details';
|
||||
static const String studioDetails = '/studio-details';
|
||||
static const String statisticDetails = '/statistic-details';
|
||||
static const String directList = '/direct-list';
|
||||
static const String direct = '/direct';
|
||||
static const String comments = '/comments';
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class RequestHelper {
|
|||
static const String _baseRadarUrl = baseUrl + '/radar';
|
||||
static const String _baseNewsUrl = baseUrl + '/news';
|
||||
static const String _baseStudioUrl = baseUrl + '/studio';
|
||||
static const String _baseStatisticUrl = baseUrl + '/statistic';
|
||||
static const String _baseDirectUrl = _baseUserUrl + '/direct';
|
||||
|
||||
static const String confirmUsername = _baseUserUrl + '/confirmUsername';
|
||||
|
|
@ -118,6 +119,21 @@ class RequestHelper {
|
|||
MapEntry('asc', args.asc),
|
||||
]);
|
||||
|
||||
static String statisticOverviews(int? category) =>
|
||||
_baseStatisticUrl +
|
||||
_urlConcatGenerator(
|
||||
[MapEntry('category', category)],
|
||||
);
|
||||
static String statisticDetails(
|
||||
String label,
|
||||
String period,
|
||||
) =>
|
||||
_baseStatisticUrl +
|
||||
'/$label' +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('period', period),
|
||||
]);
|
||||
|
||||
static String mark(int id, String type) => baseUrl + '/$type/$id/mark';
|
||||
static String tracking(int id, String type) =>
|
||||
baseUrl + '/$type/$id/tracking';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:didvan/views/home/home_state.dart';
|
|||
import 'package:didvan/views/home/news/news.dart';
|
||||
import 'package:didvan/views/home/radar/radar.dart';
|
||||
import 'package:didvan/views/home/settings/settings.dart';
|
||||
import 'package:didvan/views/home/statistics/statistics.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic.dart';
|
||||
import 'package:didvan/views/home/studio/studio.dart';
|
||||
import 'package:didvan/views/widgets/didvan/bnb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -37,7 +37,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
controller: _tabController,
|
||||
children: const [
|
||||
News(),
|
||||
Statistics(),
|
||||
Statistic(),
|
||||
Radar(),
|
||||
Studio(),
|
||||
Settings(),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class _NewsState extends State<News> {
|
|||
onRetry: () => state.getNews(page: state.page),
|
||||
state: state,
|
||||
builder: (context, state) => ListView.builder(
|
||||
cacheExtent: 1000,
|
||||
cacheExtent: 1500,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const LogoAppBar();
|
||||
|
|
@ -68,7 +68,13 @@ class _NewsState extends State<News> {
|
|||
return NewsOverview.placeholder;
|
||||
}
|
||||
final news = state.news[index];
|
||||
return NewsOverview(
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
child: NewsOverview(
|
||||
news: news,
|
||||
onMarkChanged: state.onMarkChanged,
|
||||
newsRequestArgs: NewsRequestArgs(
|
||||
|
|
@ -77,6 +83,7 @@ class _NewsState extends State<News> {
|
|||
startDate: state.startDate,
|
||||
search: state.search,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: state.news.length + 2,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ class _RadarState extends State<Radar> {
|
|||
if (state.appState != AppState.failed)
|
||||
CategoriesRow1(
|
||||
topPadding: 300,
|
||||
rightPadding: 124,
|
||||
onSelected: _onCategorySelected,
|
||||
categories: state.categories,
|
||||
isColapsed:
|
||||
|
|
@ -180,11 +181,10 @@ class _RadarState extends State<Radar> {
|
|||
!state.searching &&
|
||||
!state.filtering)
|
||||
CategoriesList(
|
||||
isRadar: true,
|
||||
categories: state.categories,
|
||||
isColapsed:
|
||||
state.isColapsed || state.searching || state.filtering,
|
||||
onSelected: () => state.getRadars(page: 1),
|
||||
onSelected: (_) => state.getRadars(page: 1),
|
||||
selectedCats: state.selectedCats,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,30 +2,29 @@ import 'dart:math';
|
|||
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:didvan/models/category.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/requests/radar.dart';
|
||||
import 'package:didvan/views/home/statistics/statistics_state.dart';
|
||||
import 'package:didvan/models/statistic_data/statistic_data.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic_state.dart';
|
||||
import 'package:didvan/views/home/statistic/widgets/statistic_overview.dart';
|
||||
import 'package:didvan/views/home/widgets/categories_gird.dart';
|
||||
import 'package:didvan/views/home/widgets/categories_list.dart';
|
||||
import 'package:didvan/views/home/widgets/logo_app_bar.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/radar.dart';
|
||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Statistics extends StatefulWidget {
|
||||
const Statistics({Key? key}) : super(key: key);
|
||||
class Statistic extends StatefulWidget {
|
||||
const Statistic({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<Statistics> createState() => _RadarState();
|
||||
State<Statistic> createState() => _StatisticState();
|
||||
}
|
||||
|
||||
class _RadarState extends State<Statistics> {
|
||||
class _StatisticState extends State<Statistic> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
bool _isAnimating = false;
|
||||
|
|
@ -35,7 +34,7 @@ class _RadarState extends State<Statistics> {
|
|||
_scrollController.addListener(() {
|
||||
_handleAnimations();
|
||||
});
|
||||
final state = context.read<StatisticsState>();
|
||||
final state = context.read<StatisticState>();
|
||||
state.addListener(() {
|
||||
if (state.shouldColapse && mounted) {
|
||||
_handleAnimations(true);
|
||||
|
|
@ -48,7 +47,7 @@ class _RadarState extends State<Statistics> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<StatisticsState>(
|
||||
return Consumer<StatisticState>(
|
||||
builder: (context, state, child) => Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
|
|
@ -60,7 +59,7 @@ class _RadarState extends State<Statistics> {
|
|||
const SliverToBoxAdapter(child: LogoAppBar()),
|
||||
if (state.appState != AppState.failed)
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 156),
|
||||
child: SizedBox(height: 180),
|
||||
),
|
||||
if (state.appState != AppState.failed)
|
||||
SliverPadding(
|
||||
|
|
@ -80,85 +79,89 @@ class _RadarState extends State<Statistics> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SliverStateHandler<StatisticsState>(
|
||||
onRetry: () => state.getStatistics(page: state.page),
|
||||
SliverStateHandler<StatisticState>(
|
||||
onRetry: () => state.getStatistic(page: state.page),
|
||||
state: state,
|
||||
itemPadding: const EdgeInsets.only(
|
||||
bottom: 20,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
enableEmptyState: state.statistics.isEmpty,
|
||||
emptyState: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 120),
|
||||
child: EmptyState(
|
||||
asset: Assets.emptyResult,
|
||||
title: 'موردی برای نمایش وجود ندارد.',
|
||||
),
|
||||
),
|
||||
placeholder: RadarOverview.placeholder,
|
||||
emptyState: const EmptyList(),
|
||||
enableEmptyState: _itemCount(state) == 0,
|
||||
placeholder: StatisticOverview.placeHolder,
|
||||
builder: (context, state, index) {
|
||||
index += 2;
|
||||
if (index % 15 == 0 && state.lastPage != state.page) {
|
||||
state.getStatistics(page: state.page + 1);
|
||||
bool isMarked = false;
|
||||
StatisticData statistic;
|
||||
if (index < state.markedStatistics.length) {
|
||||
isMarked = true;
|
||||
statistic = state.markedStatistics[index];
|
||||
} else {
|
||||
statistic =
|
||||
state.statistics[index - state.markedStatistics.length];
|
||||
}
|
||||
index -= 2;
|
||||
if (index >= state.statistics.length) {
|
||||
return RadarOverview.placeholder;
|
||||
}
|
||||
final radar = state.statistics[index];
|
||||
return RadarOverview(
|
||||
radar: radar,
|
||||
return StatisticOverview(
|
||||
statistic: statistic,
|
||||
isMarked: isMarked,
|
||||
onMarkChanged: state.changeMark,
|
||||
onCommentsChanged: (id, count) => {},
|
||||
radarRequestArgs: RadarRequestArgs(
|
||||
page: state.page,
|
||||
categories:
|
||||
List.from(state.selectedCats.map((cat) => cat.id)),
|
||||
isSingleItem: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: state.statistics.length +
|
||||
(state.lastPage == state.page ? 0 : 3),
|
||||
childCount: _itemCount(state),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: state.appState == AppState.busy
|
||||
? 300
|
||||
: _itemCount(state) == 0
|
||||
? 150
|
||||
: max(
|
||||
MediaQuery.of(context).size.height -
|
||||
_itemCount(state) * 120,
|
||||
0),
|
||||
),
|
||||
if (state.statistics.length == 1)
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 320),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state.appState != AppState.failed)
|
||||
CategoriesRow1(
|
||||
onSelected: _onCategorySelected,
|
||||
categories: state.categories,
|
||||
categories: List.from(state.categories)..removeAt(0),
|
||||
isColapsed: state.isColapsed,
|
||||
topPadding: 120,
|
||||
topPadding: 144,
|
||||
rightPadding: 300,
|
||||
),
|
||||
if (state.appState != AppState.failed)
|
||||
CategoriesList(
|
||||
isRadar: false,
|
||||
categories: state.categories,
|
||||
isColapsed: state.isColapsed,
|
||||
onSelected: () => state.getStatistics(page: 1),
|
||||
selectedCats: state.selectedCats,
|
||||
onSelected: (id) {
|
||||
state.selectedCategoryId = id;
|
||||
state.getStatistic(page: 1);
|
||||
},
|
||||
selectedCats: state.selectedCategory == null
|
||||
? []
|
||||
: [state.selectedCategory!],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int _itemCount(state) =>
|
||||
state.markedStatistics.length +
|
||||
(state.selectedCategoryId == 1 ? 0 : state.statistics.length);
|
||||
|
||||
void _onCategorySelected(CategoryData category) {
|
||||
final state = context.read<StatisticsState>();
|
||||
state.selectedCats.clear();
|
||||
final state = context.read<StatisticState>();
|
||||
state.selectedCategoryId = 0;
|
||||
if (category.id != 0) {
|
||||
state.selectedCats.add(category);
|
||||
state.selectedCategoryId = category.id;
|
||||
}
|
||||
state.getStatistics(page: 1);
|
||||
state.getStatistic(page: 1);
|
||||
}
|
||||
|
||||
void _handleAnimations([bool forceAnimate = false]) async {
|
||||
final state = context.read<StatisticsState>();
|
||||
final state = context.read<StatisticState>();
|
||||
if (_isAnimating) return;
|
||||
final double position = _scrollController.offset;
|
||||
if (position > 5 && !state.isColapsed || forceAnimate) {
|
||||
|
|
@ -166,14 +169,14 @@ class _RadarState extends State<Statistics> {
|
|||
_isAnimating = true;
|
||||
setState(() {});
|
||||
await _scrollController.animateTo(
|
||||
200,
|
||||
228,
|
||||
duration: DesignConfig.mediumAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
_isAnimating = false;
|
||||
setState(() {});
|
||||
} else if (position <
|
||||
min(_scrollController.position.maxScrollExtent, 200) &&
|
||||
min(_scrollController.position.maxScrollExtent, 228) &&
|
||||
state.isColapsed) {
|
||||
state.isScrolled = false;
|
||||
_isAnimating = true;
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/view/app_bar_data.dart';
|
||||
import 'package:didvan/views/home/statistic/statistic_details/statistic_details_state.dart';
|
||||
import 'package:didvan/views/home/widgets/categories_list.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/multitype.dart';
|
||||
import 'package:didvan/views/home/widgets/tag_item.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
class StatisticDetails extends StatefulWidget {
|
||||
final Map<String, dynamic> pageData;
|
||||
const StatisticDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatisticDetails> createState() => _StatisticDetailsState();
|
||||
}
|
||||
|
||||
class _StatisticDetailsState extends State<StatisticDetails> {
|
||||
@override
|
||||
void initState() {
|
||||
final state = context.read<StatisticDetailsState>();
|
||||
state.label = widget.pageData['label'];
|
||||
state.marked = widget.pageData['marked'];
|
||||
state.currentDateRangeId = 0;
|
||||
Future.delayed(Duration.zero, state.getStatisticDetails);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<StatisticDetailsState>(
|
||||
builder: (context, state, child) => DidvanScaffold(
|
||||
padding: EdgeInsets.zero,
|
||||
appBarData: AppBarData(
|
||||
title: widget.pageData['title'],
|
||||
hasBack: true,
|
||||
subtitle: 'رادار قیمتها',
|
||||
trailing: DidvanIconButton(
|
||||
icon: state.marked ? Icons.star : Icons.star_border,
|
||||
color: state.marked
|
||||
? Theme.of(context).colorScheme.yellow
|
||||
: Theme.of(context).colorScheme.focusedBorder,
|
||||
size: 32,
|
||||
onPressed: () {
|
||||
state.marked = !state.marked;
|
||||
state.update();
|
||||
widget.pageData['onMarkChanged'](state.marked);
|
||||
},
|
||||
),
|
||||
),
|
||||
children: [
|
||||
StateHandler<StatisticDetailsState>(
|
||||
topPadding: MediaQuery.of(context).size.height / 3,
|
||||
builder: (context, state) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DidvanText('نمودار تغییرات'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 120,
|
||||
child: state.chartState == AppState.busy
|
||||
? SpinKitThreeBounce(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 24,
|
||||
)
|
||||
: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
tooltipBgColor:
|
||||
Theme.of(context).colorScheme.navigation,
|
||||
getTooltipItems: (data) => [
|
||||
LineTooltipItem(
|
||||
state.datas[data.first.spotIndex].tEn! +
|
||||
'\n' +
|
||||
intl.NumberFormat("###,000", "en_US")
|
||||
.format(
|
||||
data.first.bar
|
||||
.spots[data.first.spotIndex].y,
|
||||
),
|
||||
Theme.of(context)
|
||||
.textTheme
|
||||
.caption!
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
minX: 0,
|
||||
maxX: state.datas.length.toDouble() - 1,
|
||||
maxY: state.maxValue * 1.001,
|
||||
minY: state.minValue,
|
||||
gridData: FlGridData(show: false),
|
||||
borderData: FlBorderData(show: false),
|
||||
titlesData: FlTitlesData(show: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: [
|
||||
for (var i = 0; i < state.datas.length; i++)
|
||||
FlSpot(
|
||||
i.toDouble(),
|
||||
_stringToDouble(state.datas[i].p),
|
||||
)
|
||||
],
|
||||
barWidth: 2,
|
||||
dotData: FlDotData(
|
||||
getDotPainter: (p0, p1, p2, p3) =>
|
||||
FlDotCirclePainter(
|
||||
color: Colors.transparent,
|
||||
strokeWidth: 1,
|
||||
strokeColor:
|
||||
Theme.of(context).colorScheme.success,
|
||||
),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.success,
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
tileMode: TileMode.decal,
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background,
|
||||
const Color(0XFFF5B763)
|
||||
.withOpacity(0.2),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DidvanText('بازه نمایش:'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CategoriesList(
|
||||
isColapsed: false,
|
||||
isAppBar: false,
|
||||
selectedCats: [state.currentDateRange],
|
||||
categories: state.dateRanges,
|
||||
onSelected: (id) {
|
||||
state.currentDateRangeId = id;
|
||||
state.getStatisticDetails();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DidvanCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDataItem('قیمت لحظهای', state.data!.p),
|
||||
const DidvanDivider(verticalPadding: 8),
|
||||
_buildDataItem('بالاترین قیمت روز', state.data!.h),
|
||||
const SizedBox(height: 8),
|
||||
_buildDataItem('پایینترین قیمت روز', state.data!.l),
|
||||
const SizedBox(height: 8),
|
||||
_buildDataItem(
|
||||
'درصد تغییر نسبت به دیروز',
|
||||
'${state.data!.dp}%',
|
||||
icon: _diffIcon(state),
|
||||
color: _diffColor(state),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDataItem(
|
||||
'میزان تغییر نسبت به دیروز',
|
||||
state.data!.d,
|
||||
icon: _diffIcon(state),
|
||||
color: _diffColor(state),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (var i = 0; i < state.tags.length; i++)
|
||||
TagItem(
|
||||
tag: state.tags[i],
|
||||
onMarkChanged: (_, __) {},
|
||||
type: 'statistic',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DidvanCard(
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.relatedContents.isEmpty)
|
||||
for (var i = 0; i < 3; i++) ...[
|
||||
MultitypeOverview.placeholder,
|
||||
if (i != 2) const SizedBox(height: 16)
|
||||
],
|
||||
for (var i = 0;
|
||||
i < state.relatedContents.length;
|
||||
i++) ...[
|
||||
MultitypeOverview(
|
||||
item: state.relatedContents[i],
|
||||
onMarkChanged: (id, value) {},
|
||||
),
|
||||
if (i != state.relatedContents.length - 1)
|
||||
const SizedBox(height: 16)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
onRetry: state.getStatisticDetails,
|
||||
state: state,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _stringToDouble(String value) =>
|
||||
double.parse(value.replaceAll(',', ''));
|
||||
|
||||
Color? _diffColor(StatisticDetailsState state) {
|
||||
if (state.data!.dp == 0) {
|
||||
return null;
|
||||
}
|
||||
if (state.data!.dt == 'low') {
|
||||
return Theme.of(context).colorScheme.success;
|
||||
} else {
|
||||
return Theme.of(context).colorScheme.error;
|
||||
}
|
||||
}
|
||||
|
||||
IconData? _diffIcon(StatisticDetailsState state) {
|
||||
if (state.data!.dp == 0) {
|
||||
return null;
|
||||
}
|
||||
if (state.data!.dt == 'low') {
|
||||
return DidvanIcons.angle_up_regular;
|
||||
} else {
|
||||
return DidvanIcons.angle_down_regular;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDataItem(
|
||||
String title,
|
||||
String value, {
|
||||
IconData? icon,
|
||||
bool isBold = false,
|
||||
Color? color,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
DidvanText(
|
||||
title,
|
||||
style: isBold
|
||||
? Theme.of(context).textTheme.bodyText1
|
||||
: Theme.of(context).textTheme.bodyText2,
|
||||
),
|
||||
const Spacer(),
|
||||
if (icon != null) Icon(icon, color: color),
|
||||
DidvanText(value, color: color),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
import 'package:didvan/models/category.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/overview_data.dart';
|
||||
import 'package:didvan/models/statistic_data/data.dart';
|
||||
import 'package:didvan/models/tag.dart';
|
||||
import 'package:didvan/providers/core.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class StatisticDetailsState extends CoreProvier {
|
||||
late bool marked;
|
||||
late String label;
|
||||
String? startDate;
|
||||
String? endDate;
|
||||
int currentDateRangeId = 0;
|
||||
final List<Data> datas = [];
|
||||
final List<OverviewData> relatedContents = [];
|
||||
final List<Tag> tags = [];
|
||||
Data? data;
|
||||
double maxValue = 0;
|
||||
double? minValue;
|
||||
|
||||
AppState chartState = AppState.idle;
|
||||
|
||||
final dateRanges = [
|
||||
CategoryData(
|
||||
id: 0,
|
||||
label: 'هفتگی',
|
||||
asset: 'weekly',
|
||||
),
|
||||
CategoryData(
|
||||
id: 1,
|
||||
label: 'ماهانه',
|
||||
asset: 'monthly',
|
||||
),
|
||||
CategoryData(
|
||||
id: 2,
|
||||
label: 'شش ماهه',
|
||||
asset: 'semiyearly',
|
||||
),
|
||||
CategoryData(
|
||||
id: 3,
|
||||
label: 'سالانه',
|
||||
asset: 'yearly',
|
||||
),
|
||||
];
|
||||
|
||||
CategoryData get currentDateRange => dateRanges.firstWhere(
|
||||
(element) => element.id == currentDateRangeId,
|
||||
);
|
||||
|
||||
Future<void> getStatisticDetails() async {
|
||||
if (data == null) {
|
||||
final result = await getStatisticCurrentDetails();
|
||||
if (!result) {
|
||||
appState = AppState.failed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
minValue = null;
|
||||
maxValue = 0;
|
||||
if (datas.isEmpty) {
|
||||
appState = AppState.busy;
|
||||
} else {
|
||||
chartState = AppState.busy;
|
||||
notifyListeners();
|
||||
}
|
||||
datas.clear();
|
||||
final service = RequestService(
|
||||
RequestHelper.statisticDetails(
|
||||
label,
|
||||
dateRanges[currentDateRangeId].asset!,
|
||||
),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
final result = service.result['data'];
|
||||
tags.clear();
|
||||
for (var i = 0; i < service.result['tags'].length; i++) {
|
||||
tags.add(Tag.fromJson(service.result['tags'][i]));
|
||||
}
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
datas.add(Data.fromList(result[i]));
|
||||
final highest = _stringToDouble(datas.last.h);
|
||||
final lowest = _stringToDouble(datas.last.l);
|
||||
if (highest > maxValue) {
|
||||
maxValue = highest;
|
||||
}
|
||||
if (lowest < (minValue ?? _stringToDouble(datas.last.p))) {
|
||||
minValue = lowest;
|
||||
}
|
||||
}
|
||||
if (currentDateRangeId != 0 && currentDateRangeId != 1) {
|
||||
final grouped =
|
||||
datas.groupListsBy((element) => element.tEn!.split('/')[1]);
|
||||
datas.clear();
|
||||
grouped.forEach((key, value) {
|
||||
datas.add(
|
||||
Data(
|
||||
p: _average(value),
|
||||
h: maxValue.toString(),
|
||||
l: minValue.toString(),
|
||||
d: '',
|
||||
dp: 0,
|
||||
dt: '',
|
||||
t: '',
|
||||
tEn: value.first.tEn!.substring(0, 7),
|
||||
tG: '',
|
||||
ts: '',
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
getRelatedContents();
|
||||
datas.replaceRange(0, datas.length, datas.reversed);
|
||||
chartState = AppState.idle;
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
if (datas.isEmpty) {
|
||||
appState = AppState.failed;
|
||||
} else {
|
||||
chartState = AppState.failed;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> getStatisticCurrentDetails() async {
|
||||
final service = RequestService(
|
||||
RequestHelper.statisticDetails(
|
||||
label,
|
||||
'current',
|
||||
),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
data = Data.fromJson(service.result['data']);
|
||||
}
|
||||
return service.isSuccess;
|
||||
}
|
||||
|
||||
String _average(List<Data> inputs) {
|
||||
double sum = 0;
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
sum += _stringToDouble(inputs[i].p);
|
||||
}
|
||||
return (sum / inputs.length).toString();
|
||||
}
|
||||
|
||||
double _stringToDouble(String value) =>
|
||||
double.parse(value.replaceAll(',', ''));
|
||||
|
||||
Future<void> getRelatedContents() async {
|
||||
if (relatedContents.isNotEmpty) return;
|
||||
final service = RequestService(RequestHelper.tag(
|
||||
ids: tags.map((tag) => tag.id).toList(),
|
||||
));
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
final relateds = service.result['contents'];
|
||||
for (var i = 0; i < relateds.length; i++) {
|
||||
relatedContents.add(OverviewData.fromJson(relateds[i]));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:didvan/models/category.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/statistic_data/statistic_data.dart';
|
||||
import 'package:didvan/providers/core.dart';
|
||||
import 'package:didvan/providers/user.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
|
||||
class StatisticState extends CoreProvier {
|
||||
int page = 1;
|
||||
bool isScrolled = false;
|
||||
bool shouldColapse = false;
|
||||
int selectedCategoryId = -1;
|
||||
List<CategoryData> categories = [];
|
||||
final List<StatisticData> statistics = [];
|
||||
final List<StatisticData> markedStatistics = [];
|
||||
|
||||
bool get isColapsed => (isCategorySelected && isScrolled) || isScrolled;
|
||||
|
||||
CategoryData? get selectedCategory => categories.firstWhereOrNull(
|
||||
(element) => element.id == selectedCategoryId,
|
||||
);
|
||||
|
||||
bool get isCategorySelected => selectedCategoryId != 0;
|
||||
|
||||
void resetFilters(bool isInit) {
|
||||
selectedCategoryId = 0;
|
||||
isScrolled = false;
|
||||
if (!isInit) {
|
||||
getStatistic(page: 1);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getStatistic({
|
||||
required int page,
|
||||
}) async {
|
||||
this.page = page;
|
||||
if (this.page == page) {
|
||||
statistics.clear();
|
||||
}
|
||||
if (page == 1) {
|
||||
appState = AppState.busy;
|
||||
}
|
||||
final RequestService service = RequestService(
|
||||
RequestHelper.statisticOverviews(
|
||||
selectedCategoryId == 0 || selectedCategoryId == 1
|
||||
? null
|
||||
: selectedCategoryId - 1,
|
||||
),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
final others = service.result['others'];
|
||||
for (var i = 0; i < others.length; i++) {
|
||||
statistics.add(StatisticData.fromJson(others[i]));
|
||||
}
|
||||
final marked = service.result['marked'];
|
||||
for (var i = 0; i < marked.length; i++) {
|
||||
statistics.add(StatisticData.fromJson(marked[i]));
|
||||
}
|
||||
if (isColapsed || isCategorySelected) {
|
||||
shouldColapse = true;
|
||||
}
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
|
||||
appState = AppState.failed;
|
||||
}
|
||||
|
||||
Future<void> changeMark(int id, bool value) async {
|
||||
final item = statistics.firstWhereOrNull((element) => element.id == id) ??
|
||||
markedStatistics.firstWhere((element) => element.id == id);
|
||||
if (value) {
|
||||
markedStatistics.add(item);
|
||||
statistics.remove(item);
|
||||
} else {
|
||||
markedStatistics.remove(item);
|
||||
statistics.add(item);
|
||||
}
|
||||
UserProvider.changeStatisticMark(id, value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void init() {
|
||||
resetFilters(true);
|
||||
Future.delayed(Duration.zero, () {
|
||||
getStatistic(page: 1);
|
||||
});
|
||||
categories = [
|
||||
CategoryData(
|
||||
id: 1,
|
||||
label: 'منتخب',
|
||||
asset: Assets.economicCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 2,
|
||||
label: 'اقتصاد کلان',
|
||||
asset: Assets.economicCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 3,
|
||||
label: 'صنعت فولاد',
|
||||
asset: Assets.politicalCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 4,
|
||||
label: 'بازار سرمایه',
|
||||
asset: Assets.techCategoryIcon,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/statistic_data/statistic_data.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StatisticOverview extends StatelessWidget {
|
||||
final StatisticData statistic;
|
||||
final bool isMarked;
|
||||
final void Function(int id, bool value) onMarkChanged;
|
||||
const StatisticOverview({
|
||||
Key? key,
|
||||
required this.statistic,
|
||||
required this.isMarked,
|
||||
required this.onMarkChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
Color _diffColor(context) => statistic.data.dt == 'low'
|
||||
? Theme.of(context).colorScheme.success
|
||||
: Theme.of(context).colorScheme.error;
|
||||
|
||||
bool get _hasDiff => statistic.data.d != '0';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: {
|
||||
'onMarkChanged': (value) => onMarkChanged(statistic.id, value),
|
||||
'label': statistic.label,
|
||||
'title': statistic.title,
|
||||
'marked': isMarked,
|
||||
}),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (isMarked)
|
||||
Icon(
|
||||
Icons.star,
|
||||
color: Theme.of(context).colorScheme.yellow,
|
||||
size: 18,
|
||||
),
|
||||
DidvanText(
|
||||
statistic.title,
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
),
|
||||
const Spacer(),
|
||||
if (_hasDiff)
|
||||
DidvanText(
|
||||
'(${statistic.data.d})',
|
||||
color: _diffColor(context),
|
||||
),
|
||||
if (_hasDiff) const SizedBox(width: 8),
|
||||
DidvanText(
|
||||
statistic.data.p,
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.trending_down,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.hint,
|
||||
),
|
||||
DidvanText(
|
||||
statistic.data.l,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
color: Theme.of(context).colorScheme.hint,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.trending_up,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.hint,
|
||||
),
|
||||
DidvanText(
|
||||
statistic.data.h,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
color: Theme.of(context).colorScheme.hint,
|
||||
),
|
||||
const Spacer(),
|
||||
if (_hasDiff)
|
||||
Icon(
|
||||
statistic.data.dt == 'low'
|
||||
? DidvanIcons.angle_up_regular
|
||||
: DidvanIcons.angle_down_regular,
|
||||
size: 18,
|
||||
color: _diffColor(context),
|
||||
),
|
||||
if (_hasDiff) const SizedBox(width: 4),
|
||||
if (_hasDiff)
|
||||
DidvanText(
|
||||
statistic.data.dp.toString() + '%',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
color: _diffColor(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget get placeHolder => DidvanCard(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: const [
|
||||
ShimmerPlaceholder(width: 80, height: 16),
|
||||
Spacer(),
|
||||
ShimmerPlaceholder(width: 50, height: 14),
|
||||
SizedBox(width: 8),
|
||||
ShimmerPlaceholder(width: 50, height: 16),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: const [
|
||||
ShimmerPlaceholder(width: 150, height: 12),
|
||||
Spacer(),
|
||||
ShimmerPlaceholder(width: 80, height: 12),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:didvan/models/category.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/overview_data.dart';
|
||||
import 'package:didvan/models/requests/radar.dart';
|
||||
import 'package:didvan/providers/core.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
|
||||
class StatisticsState extends CoreProvier {
|
||||
int page = 1;
|
||||
int lastPage = 1;
|
||||
bool isScrolled = false;
|
||||
bool shouldColapse = false;
|
||||
final List<CategoryData> selectedCats = [];
|
||||
List<CategoryData> categories = [];
|
||||
final List<OverviewData> statistics = [];
|
||||
|
||||
bool get isColapsed => (isCategorySelected && isScrolled) || isScrolled;
|
||||
|
||||
bool get isCategorySelected => selectedCats.length == 1;
|
||||
|
||||
void resetFilters(bool isInit) {
|
||||
selectedCats.clear();
|
||||
isScrolled = false;
|
||||
if (!isInit) {
|
||||
getStatistics(page: 1);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getStatistics({
|
||||
required int page,
|
||||
}) async {
|
||||
this.page = page;
|
||||
if (this.page == page) {
|
||||
statistics.clear();
|
||||
}
|
||||
if (page == 1) {
|
||||
appState = AppState.busy;
|
||||
}
|
||||
final RequestService service = RequestService(
|
||||
RequestHelper.radarOverviews(
|
||||
args: RadarRequestArgs(
|
||||
page: page,
|
||||
categories: selectedCats.map((e) => e.id).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
lastPage = service.result['lastPage'];
|
||||
final radarsList = service.result['radars'];
|
||||
for (var i = 0; i < radarsList.length; i++) {
|
||||
statistics.add(OverviewData.fromJson(radarsList[i]));
|
||||
}
|
||||
if (isColapsed || isCategorySelected) {
|
||||
shouldColapse = true;
|
||||
}
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
|
||||
appState = AppState.failed;
|
||||
}
|
||||
|
||||
Future<void> changeMark(int id, bool value, bool shouldUpdate) async {
|
||||
statistics.firstWhere((element) => element.id == id).marked = value;
|
||||
if (shouldUpdate) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
resetFilters(true);
|
||||
Future.delayed(Duration.zero, () {
|
||||
getStatistics(page: 1);
|
||||
});
|
||||
categories = [
|
||||
CategoryData(
|
||||
id: 1,
|
||||
label: 'اقتصاد کلان',
|
||||
asset: Assets.economicCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 2,
|
||||
label: 'صنعت فولاد',
|
||||
asset: Assets.politicalCategoryIcon,
|
||||
),
|
||||
CategoryData(
|
||||
id: 3,
|
||||
label: 'بازار سرمایه',
|
||||
asset: Assets.techCategoryIcon,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ class CategoriesRow1 extends StatelessWidget {
|
|||
final List<CategoryData> categories;
|
||||
final bool isColapsed;
|
||||
final double topPadding;
|
||||
final double rightPadding;
|
||||
final void Function(CategoryData data) onSelected;
|
||||
const CategoriesRow1({
|
||||
Key? key,
|
||||
|
|
@ -14,6 +15,7 @@ class CategoriesRow1 extends StatelessWidget {
|
|||
required this.isColapsed,
|
||||
required this.onSelected,
|
||||
required this.topPadding,
|
||||
required this.rightPadding,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -23,8 +25,8 @@ class CategoriesRow1 extends StatelessWidget {
|
|||
curve: Curves.easeIn,
|
||||
duration: DesignConfig.mediumAnimationDuration,
|
||||
top: isColapsed ? -60 : topPadding + d.padding.top,
|
||||
left: isColapsed ? -80 : 0,
|
||||
right: isColapsed ? 124 : 0,
|
||||
left: isColapsed ? -rightPadding : 0,
|
||||
right: isColapsed ? rightPadding : 0,
|
||||
child: Row(
|
||||
children: categories
|
||||
.sublist(0, 3)
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class CategoriesList extends StatefulWidget {
|
||||
final bool isColapsed;
|
||||
final bool isAppBar;
|
||||
final List<CategoryData> selectedCats;
|
||||
final List<CategoryData> categories;
|
||||
final VoidCallback onSelected;
|
||||
final bool isRadar;
|
||||
final void Function(int id) onSelected;
|
||||
const CategoriesList({
|
||||
Key? key,
|
||||
required this.isColapsed,
|
||||
required this.selectedCats,
|
||||
required this.categories,
|
||||
required this.onSelected,
|
||||
required this.isRadar,
|
||||
this.isAppBar = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -46,6 +46,32 @@ class _CategoriesListState extends State<CategoriesList> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MediaQueryData d = MediaQuery.of(context);
|
||||
final child = SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: widget.isAppBar
|
||||
? EdgeInsets.only(
|
||||
top: d.padding.top + 12,
|
||||
bottom: 12,
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
if (widget.isAppBar)
|
||||
_itemBuilder(
|
||||
CategoryData(
|
||||
label: 'همه',
|
||||
id: 0,
|
||||
),
|
||||
context,
|
||||
),
|
||||
for (var i = 0; i < widget.categories.length; i++)
|
||||
_itemBuilder(widget.categories[i], context),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (widget.isAppBar) {
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
|
|
@ -67,34 +93,14 @@ class _CategoriesListState extends State<CategoriesList> {
|
|||
child: AnimatedVisibility(
|
||||
isVisible: widget.isColapsed,
|
||||
duration: DesignConfig.mediumAnimationDuration,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
// physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(
|
||||
top: d.padding.top + 12,
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_itemBuilder(
|
||||
CategoryData(
|
||||
label: widget.isRadar ? 'همه' : 'منتخب',
|
||||
id: 0,
|
||||
),
|
||||
context,
|
||||
),
|
||||
for (var i = 0; i < widget.categories.length; i++)
|
||||
_itemBuilder(widget.categories[i], context),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _itemBuilder(CategoryData category, BuildContext context) {
|
||||
return GestureDetector(
|
||||
|
|
@ -108,7 +114,7 @@ class _CategoriesListState extends State<CategoriesList> {
|
|||
duration: DesignConfig.lowAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
widget.onSelected();
|
||||
widget.onSelected(category.id);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 12),
|
||||
|
|
@ -127,7 +133,7 @@ class _CategoriesListState extends State<CategoriesList> {
|
|||
color: widget.selectedCats.length == 1 &&
|
||||
widget.selectedCats.contains(category) ||
|
||||
category.id == 0 && widget.selectedCats.isEmpty
|
||||
? Theme.of(context).colorScheme.focused
|
||||
? Theme.of(context).colorScheme.splash
|
||||
: null,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.focusedBorder,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,12 @@ class StateHandler<T extends CoreProvier> extends StatelessWidget {
|
|||
case AppState.idle:
|
||||
return builder(context, state);
|
||||
case AppState.busy:
|
||||
return placeholder ??
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: topPadding),
|
||||
child: placeholder ??
|
||||
SpinKitSpinningLines(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
);
|
||||
case AppState.failed:
|
||||
return Center(child: EmptyConnection(onRetry: onRetry));
|
||||
|
|
|
|||
14
pubspec.lock
14
pubspec.lock
|
|
@ -141,6 +141,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
expandable_bottom_sheet:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -211,6 +218,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.10"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.50.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ dependencies:
|
|||
permission_handler: ^9.2.0
|
||||
better_player: ^0.0.81
|
||||
assets_audio_player: ^3.0.4+1
|
||||
fl_chart: ^0.50.1
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue