didvan-app/lib/views/home/main/main_page.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);
}
}