520 lines
16 KiB
Dart
520 lines
16 KiB
Dart
import 'package:didvan/config/theme_data.dart';
|
|
import 'package:didvan/constants/app_icons.dart';
|
|
import 'package:didvan/models/home_page_content/home_page_list.dart';
|
|
import 'package:didvan/models/home_page_content/swot.dart';
|
|
import 'package:didvan/models/new_statistic/new_statistics_model.dart';
|
|
import 'package:didvan/providers/user.dart';
|
|
import 'package:didvan/routes/routes.dart';
|
|
import 'package:didvan/views/home/main/main_page_state.dart';
|
|
import 'package:didvan/views/home/main/widgets/main_content.dart';
|
|
import 'package:didvan/views/home/main/widgets/story_section.dart';
|
|
import 'package:didvan/views/home/main/widgets/simple_explore_card.dart';
|
|
import 'package:didvan/views/home/new_statistic/new_statistics_state.dart';
|
|
import 'package:didvan/views/widgets/didvan/card.dart';
|
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
|
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
|
import 'package:didvan/views/widgets/carousel_3d.dart';
|
|
import 'package:didvan/views/home/home_state.dart';
|
|
import 'package:didvan/views/widgets/text_divider.dart';
|
|
import 'package:didvan/views/widgets/mini_chart.dart';
|
|
import 'package:didvan/views/widgets/home_app_bar.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
|
|
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
|
|
|
|
import 'package:universal_html/html.dart' as html;
|
|
|
|
bool isAnyMobile() {
|
|
if (kIsWeb) {
|
|
final userAgent = html.window.navigator.userAgent.toLowerCase();
|
|
return userAgent.contains('mobile') ||
|
|
userAgent.contains('android') ||
|
|
userAgent.contains('ios');
|
|
}
|
|
|
|
return defaultTargetPlatform == TargetPlatform.android ||
|
|
defaultTargetPlatform == TargetPlatform.iOS;
|
|
}
|
|
|
|
class MainPage extends StatefulWidget {
|
|
const MainPage({
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
State<MainPage> createState() => _MainPageState();
|
|
}
|
|
|
|
class _MainPageState extends State<MainPage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.read<MainPageState>().init();
|
|
context.read<NewStatisticState>().init();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return StateHandler<MainPageState>(
|
|
onRetry: () => context.read<MainPageState>().init(),
|
|
state: context.watch<MainPageState>(),
|
|
builder: (context, state) {
|
|
return Column(
|
|
children: [
|
|
const HomeAppBar(
|
|
showBackButton: false,
|
|
showSearchField: true,
|
|
),
|
|
Expanded(
|
|
child: ListView(
|
|
padding: const EdgeInsets.only(top: 0, bottom: 16),
|
|
children: [
|
|
if (state.stories.isNotEmpty) ...[
|
|
const TextDivider(text: 'دیدهبان')
|
|
.animate()
|
|
.fadeIn(delay: 400.ms, duration: 500.ms),
|
|
const _DidvanSignalsTitle()
|
|
.animate()
|
|
.fadeIn(delay: 500.ms, duration: 500.ms),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: StorySection(stories: state.stories),
|
|
).animate().fadeIn(delay: 600.ms, duration: 500.ms),
|
|
],
|
|
const SizedBox(height: 12),
|
|
const TextDivider(text: 'پیشخوان استراتژیک')
|
|
.animate()
|
|
.fadeIn(delay: 700.ms, duration: 500.ms),
|
|
const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: MainPageMainContent(),
|
|
).animate().fadeIn(delay: 800.ms, duration: 500.ms),
|
|
if (state.content != null &&
|
|
state.content!.lists.isNotEmpty) ...[
|
|
const _ExploreLatestTitle()
|
|
.animate()
|
|
.fadeIn(delay: 900.ms, duration: 500.ms),
|
|
_ExploreLatestSlider(
|
|
lists: state.content!.lists,
|
|
swotItems: state.swotItems,
|
|
).animate().fadeIn(delay: 1000.ms, duration: 500.ms),
|
|
],
|
|
const _IndustryPulseTitle()
|
|
.animate()
|
|
.fadeIn(delay: 1100.ms, duration: 500.ms),
|
|
const _IndustryPulseCards()
|
|
.animate()
|
|
.fadeIn(delay: 1200.ms, duration: 500.ms),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DidvanSignalsTitle extends StatelessWidget {
|
|
const _DidvanSignalsTitle();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
bottom: 16,
|
|
top: 0,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
'lib/assets/icons/voice-square.svg',
|
|
color: Theme.of(context).colorScheme.title,
|
|
width: 30,
|
|
height: 30,
|
|
),
|
|
const SizedBox(width: 5),
|
|
DidvanText(
|
|
"سیگنالهای دیدوان",
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
color: const Color.fromARGB(255, 0, 89, 119),
|
|
fontSize: 13,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ExploreLatestTitle extends StatelessWidget {
|
|
const _ExploreLatestTitle();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
bottom: 16,
|
|
top: 0,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
'lib/assets/icons/discover.svg',
|
|
color: Theme.of(context).colorScheme.title,
|
|
width: 30,
|
|
height: 30,
|
|
),
|
|
const SizedBox(width: 5),
|
|
DidvanText(
|
|
"تازهترینهای کاوش",
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
color: const Color.fromARGB(255, 0, 89, 119),
|
|
fontSize: 13,
|
|
),
|
|
],
|
|
),
|
|
GestureDetector(
|
|
onTap: () {
|
|
context.read<HomeState>().tabController.animateTo(3);
|
|
},
|
|
child: const Row(
|
|
children: [
|
|
DidvanText(
|
|
"مشاهده همه",
|
|
color: Color.fromARGB(255, 0, 126, 167),
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 12,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ExploreLatestSlider extends StatelessWidget {
|
|
final List<MainPageList> lists;
|
|
final List<SwotItem> swotItems;
|
|
|
|
const _ExploreLatestSlider({required this.lists, required this.swotItems});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final List<Widget> items = [];
|
|
for (var list in lists) {
|
|
if (list.type == 'video' ||
|
|
list.type == 'podcast' ||
|
|
list.type == 'news' ||
|
|
list.type == 'radar') {
|
|
continue;
|
|
}
|
|
if (list.contents.isNotEmpty) {
|
|
final newestContent = list.contents.first;
|
|
items.add(
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
child: SimpleExploreCard(
|
|
content: newestContent,
|
|
type: list.type,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (swotItems.isNotEmpty) {
|
|
items.add(
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
|
child: SimpleExploreCard(
|
|
swotItem: swotItems.first,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (items.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Carousel3D(
|
|
items: items,
|
|
height: 220,
|
|
autoPlayDuration: const Duration(seconds: 5),
|
|
showControls: true,
|
|
onItemChanged: (index) {
|
|
// Optional: Handle item change if needed
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _IndustryPulseTitle extends StatelessWidget {
|
|
const _IndustryPulseTitle();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
bottom: 16,
|
|
top: 16,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
SvgPicture.asset(
|
|
'lib/assets/icons/chart 2.svg',
|
|
color: Theme.of(context).colorScheme.title,
|
|
width: 30,
|
|
height: 30,
|
|
),
|
|
const SizedBox(width: 5),
|
|
DidvanText(
|
|
"نبض صنعت",
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
color: const Color.fromARGB(255, 0, 89, 119),
|
|
fontSize: 13,
|
|
),
|
|
],
|
|
),
|
|
GestureDetector(
|
|
onTap: () {
|
|
context.read<HomeState>().tabController.animateTo(2);
|
|
},
|
|
child: const Row(
|
|
children: [
|
|
DidvanText(
|
|
"مشاهده همه",
|
|
color: Color.fromARGB(255, 0, 126, 167),
|
|
fontWeight: FontWeight.normal,
|
|
fontSize: 12,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _IndustryPulseCards extends StatefulWidget {
|
|
const _IndustryPulseCards();
|
|
|
|
@override
|
|
State<_IndustryPulseCards> createState() => _IndustryPulseCardsState();
|
|
}
|
|
|
|
class _IndustryPulseCardsState extends State<_IndustryPulseCards> {
|
|
late PageController _pageController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_pageController = PageController(viewportFraction: 0.45);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return StateHandler<NewStatisticState>(
|
|
state: context.watch<NewStatisticState>(),
|
|
placeholder: const Center(child: CircularProgressIndicator()),
|
|
onRetry: () => context.read<NewStatisticState>().init(),
|
|
builder: (context, statisticState) {
|
|
if (statisticState.contents.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final List<Content> allItems = [];
|
|
statisticState.contents.forEach((category) {
|
|
allItems.addAll(category.contents);
|
|
});
|
|
|
|
final List<String> desiredTitles = [
|
|
'دلار',
|
|
'بیت کوین',
|
|
'نیکل',
|
|
'نفت خام'
|
|
];
|
|
final List<Content> itemsToShow = allItems
|
|
.where((item) => desiredTitles.contains(item.title))
|
|
.toList();
|
|
|
|
if (itemsToShow.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 16.0),
|
|
child: Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: SizedBox(
|
|
height: 165,
|
|
child: PageView.builder(
|
|
padEnds: false,
|
|
controller: _pageController,
|
|
itemCount: itemsToShow.length,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 8.0),
|
|
child: AspectRatio(
|
|
aspectRatio: 1,
|
|
child: _IndustryPulseCard(statistic: itemsToShow[index]),
|
|
),
|
|
)
|
|
.animate()
|
|
.fadeIn(delay: (200 * index).ms, duration: 500.ms)
|
|
.slideX(
|
|
begin: -0.5, duration: 500.ms, curve: Curves.easeOut);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _IndustryPulseCard extends StatelessWidget {
|
|
final Content statistic;
|
|
|
|
const _IndustryPulseCard({required this.statistic});
|
|
|
|
Color _diffColor(BuildContext context) => statistic.data.dt == 'high'
|
|
? Theme.of(context).colorScheme.success
|
|
: Theme.of(context).colorScheme.error;
|
|
|
|
bool get _hasDiff => statistic.data.dp != 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final state = context.read<NewStatisticState>();
|
|
return GestureDetector(
|
|
onTap: () =>
|
|
Navigator.of(context).pushNamed(Routes.statisticDetails, arguments: {
|
|
'onMarkChanged': (value) => onMarkChanged(statistic.id, value),
|
|
'label': statistic.label,
|
|
'title': statistic.title,
|
|
'marked': statistic.marked,
|
|
}).then(
|
|
(value) => state.getStatistic(),
|
|
),
|
|
child: DidvanCard(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 9),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: MiniChart(
|
|
label: statistic.label,
|
|
width: double.infinity,
|
|
height: 38,
|
|
lineColor: _hasDiff
|
|
? _diffColor(context)
|
|
: Theme.of(context).colorScheme.primary,
|
|
changePercent: statistic.data.dp.toDouble(),
|
|
trend: statistic.data.dt,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
if (statistic.marked)
|
|
Icon(
|
|
Icons.star,
|
|
color: Theme.of(context).colorScheme.yellow,
|
|
size: 16,
|
|
),
|
|
if (statistic.marked) const SizedBox(width: 4),
|
|
DidvanText(
|
|
statistic.title,
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 6),
|
|
DidvanText(
|
|
statistic.data.p,
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
if (_hasDiff) const SizedBox(height: 6),
|
|
if (_hasDiff)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: statistic.data.dt == 'high'
|
|
? const Color.fromRGBO(245, 255, 252, 1)
|
|
: const Color.fromRGBO(255, 248, 248, 1),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
statistic.data.dt == 'high'
|
|
? DidvanIcons.angle_up_regular
|
|
: DidvanIcons.angle_down_regular,
|
|
size: 16,
|
|
color: _diffColor(context),
|
|
),
|
|
const SizedBox(width: 4),
|
|
DidvanText(
|
|
'${statistic.data.dp}%',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
color: _diffColor(context),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void onMarkChanged(int id, bool value) {
|
|
UserProvider.changeStatisticMark(id, value);
|
|
}
|
|
} |