This commit is contained in:
MohammadTaha Basiri 2023-10-11 23:42:38 +03:30
parent a5ac5b8c82
commit db4b1f8e00
20 changed files with 195 additions and 126 deletions

View File

@ -12,7 +12,12 @@ class LightThemeConfig {
textTheme: _TextThemeData.data,
cardColor: _colorScheme.surface,
checkboxTheme: CheckboxThemeData(
fillColor: MaterialStateProperty.all<Color>(_colorScheme.primary),
fillColor: MaterialStateProperty.resolveWith((states) {
if (!states.contains(MaterialState.selected)) {
return Colors.transparent;
}
return _colorScheme.primary;
}),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
@ -26,7 +31,7 @@ class LightThemeConfig {
static const ColorScheme _colorScheme = ColorScheme(
primary: _primary,
primaryContainer: _white,
secondary: Color(0xFFD61515),
secondary: Color(0xFFB20436),
secondaryContainer: _white,
surface: _white,
background: _background,
@ -53,7 +58,12 @@ class DarkThemeConfig {
),
checkboxTheme: CheckboxThemeData(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
fillColor: MaterialStateProperty.all<Color>(_colorScheme.primary),
fillColor: MaterialStateProperty.resolveWith((states) {
if (!states.contains(MaterialState.selected)) {
return Colors.transparent;
}
return _colorScheme.primary;
}),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
@ -65,7 +75,7 @@ class DarkThemeConfig {
static const ColorScheme _colorScheme = ColorScheme(
primary: _primary,
primaryContainer: _white,
secondary: Color(0xFFE53939),
secondary: Color(0xFFB21542),
secondaryContainer: _white,
surface: Color(0xFF181B1F),
background: _background,
@ -138,8 +148,8 @@ class _TextThemeData {
extension DidvanColorScheme on ColorScheme {
// Secondary colors
Color get secondaryDisabled => brightness == Brightness.dark
? const Color(0xFF703838)
: const Color(0xFFFFC8C8);
? const Color(0xFF703848)
: const Color(0xFFFFC8D7);
Color get white => const Color(0xFFFFFFFF);
Color get focused => brightness == Brightness.dark

View File

@ -3,7 +3,7 @@ import 'package:didvan/models/home_page_content/banner.dart';
import 'home_page_list.dart';
class MainPageContent {
final List<MainPageBannerType> banners;
final List<List<MainPageBannerType>> banners;
final List<MainPageList> lists;
final int unread;
@ -12,9 +12,13 @@ class MainPageContent {
factory MainPageContent.fromJson(Map<String, dynamic> json) {
return MainPageContent(
banners: List<MainPageBannerType>.from(json['banners'].map(
(x) => MainPageBannerType.fromJson(x),
)),
banners: List<List<MainPageBannerType>>.from(
json['banners'].map(
(list) => List<MainPageBannerType>.from(
list.map((e) => MainPageBannerType.fromJson(e)).toList(),
),
),
),
lists: List<MainPageList>.from(
json['lists'].map(
(x) => MainPageList.fromJson(x),

View File

@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
class MainCategoryType {
final int id;
final String label;
final IconData icon;
MainCategoryType({required this.id, required this.label, required this.icon});
}

View File

@ -59,6 +59,7 @@ class _CommentsState extends State<Comments> {
child: Stack(
children: [
DidvanScaffold(
hidePlayer: true,
physics: const BouncingScrollPhysics(),
backgroundColor: Theme.of(context).colorScheme.surface,
appBarData: _isPage

View File

@ -56,6 +56,7 @@ class _DirectState extends State<Direct> {
left: 0,
right: 0,
child: DidvanScaffold(
hidePlayer: true,
padding: EdgeInsets.zero,
reverse: true,
backgroundColor: Theme.of(context).colorScheme.surface,

View File

@ -44,7 +44,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: LogoAppBar(),
appBar: const LogoAppBar(),
body: Consumer<HomeState>(
builder: (context, state, child) => AnimatedCrossFade(
duration: DesignConfig.lowAnimationDuration,

View File

@ -26,7 +26,6 @@ class HomeState extends CoreProvier {
int _currentPageIndex = 0;
String search = '';
String lastSearch = '';
bool _showSearchPage = false;
Timer? timer;
String? startDate;
String? endDate;
@ -48,13 +47,6 @@ class HomeState extends CoreProvier {
}
}
set showSearchPage(bool value) {
_showSearchPage = value;
notifyListeners();
}
bool get showSearchPage => _showSearchPage;
set currentPageIndex(int value) {
_currentPageIndex = value;
notifyListeners();
@ -123,7 +115,7 @@ class HomeState extends CoreProvier {
await service.httpGet();
if (service.isSuccess) {
lastPage = service.result['lastPage'];
unreadCount = service.result['unread'];
unreadCount = service.result['unread'] ?? unreadCount;
results.addAll(
List<OverviewData>.from(
service.result['contents'].map(
@ -138,10 +130,19 @@ class HomeState extends CoreProvier {
appState = AppState.failed;
}
final categoryFilters = [
CategoryData(id: 1, label: 'پویش افق'),
CategoryData(id: 2, label: 'دنیای فولاد'),
CategoryData(id: 3, label: 'ویدئوکست'),
CategoryData(id: 4, label: 'پادکست'),
CategoryData(id: 5, label: 'تحلیل‌های راداری'),
CategoryData(id: 6, label: 'سها'),
];
void refresh() {
menuItems.clear();
categories.clear();
menuItems.addAll([
menuItems = [
MenuItemType(
label: 'دنیای فولاد',
asset: Assets.fooladWorld,
@ -160,7 +161,7 @@ class HomeState extends CoreProvier {
MenuItemType(
label: 'سها',
asset: Assets.saha,
link: 'https://didvan.app',
link: 'https://saha.didvan.app',
),
MenuItemType(
label: 'رادار استارتاپ',
@ -192,9 +193,9 @@ class HomeState extends CoreProvier {
asset: Assets.podcast,
link: Routes.podcasts,
),
]);
];
categories.addAll([
categories = [
CategoryData(
id: 1,
label: 'اقتصادی',
@ -245,7 +246,6 @@ class HomeState extends CoreProvier {
label: 'کسب و کار',
asset: Assets.businessCategoryIcon,
),
]);
Future.delayed(Duration.zero, notifyListeners);
];
}
}

View File

@ -82,7 +82,10 @@ class MainPageState extends CoreProvier {
return;
}
if (link.startsWith('http')) {
launchUrlString('$link?accessToken=${RequestService.token}');
launchUrlString(
'$link?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView,
);
return;
}
Navigator.of(ActionSheetUtils.context).pushNamed(link, arguments: args);

View File

@ -3,6 +3,7 @@ import 'package:didvan/views/widgets/didvan/slider.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
class MainPageBanner extends StatelessWidget {
final bool isFirst;
@ -12,15 +13,18 @@ class MainPageBanner extends StatelessWidget {
Widget build(BuildContext context) {
final state = context.read<MainPageState>();
return DidvanSlider(
itemBuilder: (context, index, realIndex) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: GestureDetector(
onTap: () => state.content.banners[index].link,
child: SkeletonImage(
imageUrl: state.content.banners[index].image,
itemBuilder: (context, index, realIndex) {
final item = state.content.banners[isFirst ? 0 : 1][index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: GestureDetector(
onTap: item.link == null ? null : () => launchUrlString(item.link!),
child: SkeletonImage(
imageUrl: item.image,
),
),
),
),
);
},
itemCount: state.content.banners.length,
viewportFraction: 1,
enableIndicator: true,

View File

@ -1,6 +1,8 @@
import 'package:collection/collection.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/home/search/widgets/search_result_item.dart';
import 'package:didvan/views/widgets/categories_list.dart';
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart';
@ -15,22 +17,42 @@ class SearchPage extends StatelessWidget {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: StateHandler<HomeState>(
state: state,
enableEmptyState:
state.appState == AppState.idle && state.results.isEmpty,
emptyState: const EmptyList(),
onRetry: () => state.searchAll(page: state.page),
builder: (context, state) => ListView.builder(
padding: const EdgeInsets.all(16),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: SearchResultItem(
item: state.results[index],
child: Stack(
children: [
StateHandler<HomeState>(
state: state,
enableEmptyState:
state.appState == AppState.idle && state.results.isEmpty,
emptyState: const EmptyList(),
onRetry: () => state.searchAll(page: state.page),
builder: (context, state) => ListView.builder(
padding: const EdgeInsets.all(16)
.copyWith(top: state.selectedCats.length <= 1 ? 72 : 16),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: SearchResultItem(
item: state.results[index],
),
),
itemCount: state.results.length,
),
),
itemCount: state.results.length,
),
CategoriesList(
isColapsed: state.selectedCats.length <= 1,
selectedCats: state.selectedCats,
categories: state.categoryFilters,
onSelected: (id) {
state.selectedCats.clear();
final cat = state.categoryFilters
.firstWhereOrNull((element) => element.id == id);
if (cat != null) {
state.selectedCats.add(cat);
}
state.searchAll(page: 1);
},
top: 0,
),
],
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
@ -12,7 +13,10 @@ class MainCategories extends StatelessWidget {
void _onTap(String link, BuildContext context) {
if (link.startsWith('http')) {
launchUrlString(link);
launchUrlString(
'$link?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView,
);
} else if (link.startsWith('tab-')) {
final state = context.read<HomeState>();
state.currentPageIndex = 1;

View File

@ -31,6 +31,7 @@ class _DirectListState extends State<DirectList> {
Widget build(BuildContext context) {
return Consumer<DirectListState>(
builder: (context, state, child) => DidvanScaffold(
padding: const EdgeInsets.symmetric(vertical: 16),
appBarData: AppBarData(
hasBack: true,
title: 'پیام‌ها',

View File

@ -51,7 +51,12 @@ class _EditProfileState extends State<EditProfile> {
Form(
key: _formKey,
child: DidvanScaffold(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
padding: const EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: 92,
),
appBarData: AppBarData(title: 'ویرایش پروفایل'),
children: [
const ProfilePhoto(),

View File

@ -48,6 +48,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
onRetry: () {},
state: context.read<GeneralSettingsState>(),
builder: (context, state) => DidvanScaffold(
padding: const EdgeInsets.all(16),
appBarData: AppBarData(hasBack: true, title: 'تنظیمات'),
children: [
DidvanCard(

View File

@ -19,6 +19,7 @@ class ProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DidvanScaffold(
padding: const EdgeInsets.all(16),
appBarData: AppBarData(
title: 'تنظیمات',
),

View File

@ -193,7 +193,7 @@ class _RadarState extends State<Radar> {
child: SearchField(
focusNode: _focusNode,
isFiltered: state.filtering,
title: 'رادار',
title: 'افق',
onChanged: _onChanged,
onFilterButtonPressed: _showFilterBottomSheet,
),

View File

@ -88,9 +88,17 @@ class _CategoriesListState extends State<CategoriesList> {
firstChild: const SizedBox(),
secondChild: Container(
height: 60 + d.padding.top,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: DesignConfig.defaultShadow,
boxShadow: [
BoxShadow(
color: const Color(0XFF1B3C59).withOpacity(0.15),
blurRadius: 8,
spreadRadius: 0,
offset: const Offset(0, 8),
)
],
),
child: AnimatedVisibility(
isVisible: widget.isColapsed,

View File

@ -32,6 +32,7 @@ class DidvanScaffold extends StatefulWidget {
final ScrollPhysics? physics;
final ScrollController? scrollController;
final bool showSliversFirst;
final bool hidePlayer;
const DidvanScaffold({
Key? key,
@ -44,6 +45,7 @@ class DidvanScaffold extends StatefulWidget {
this.reverse = false,
this.scrollController,
this.showSliversFirst = false,
this.hidePlayer = false,
}) : super(key: key);
@override
@ -138,12 +140,13 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
appBarData: widget.appBarData!,
scrollController: _scrollController,
),
const Positioned(
bottom: 20,
left: 0,
right: 0,
child: _PlayerNavBar(),
),
if (!widget.hidePlayer)
const Positioned(
bottom: 20,
left: 0,
right: 0,
child: _PlayerNavBar(),
),
],
),
),

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/category.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
@ -12,14 +11,12 @@ import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/item_title.dart';
import 'package:didvan/views/widgets/search_field.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/logos/didvan_vertical_logo.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
LogoAppBar({Key? key}) : super(key: key);
const LogoAppBar({Key? key}) : super(key: key);
@override
Size get preferredSize => const Size(double.infinity, 144);
@ -33,14 +30,16 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20)),
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: const Color(0XFF1B3C59).withOpacity(0.15),
blurRadius: 8,
spreadRadius: 0,
offset: const Offset(0, 8),
)
],
boxShadow: state.currentPageIndex == 1 || state.filtering
? null
: [
BoxShadow(
color: const Color(0XFF1B3C59).withOpacity(0.15),
blurRadius: 8,
spreadRadius: 0,
offset: const Offset(0, 8),
)
],
),
margin: EdgeInsets.only(top: d.padding.top),
padding: const EdgeInsets.all(16),
@ -125,15 +124,6 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
});
}
final categoryFilters = [
CategoryData(id: 1, label: 'پویش افق'),
CategoryData(id: 2, label: 'دنیای فولاد'),
CategoryData(id: 3, label: 'ویدئوکست'),
CategoryData(id: 4, label: 'پادکست'),
CategoryData(id: 1, label: 'تحلیل‌های راداری'),
CategoryData(id: 1, label: 'سها'),
];
Future<void> _showFilterBottomSheet(BuildContext context) async {
final state = context.read<HomeState>();
await ActionSheetUtils.showBottomSheet(
@ -185,18 +175,19 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
const SizedBox(height: 12),
Wrap(
children: [
for (var i = 0; i < categoryFilters.length; i++)
for (var i = 0; i < state.categoryFilters.length; i++)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: categoryFilters[i].label,
value: state.selectedCats.contains(state.categories[i]),
title: state.categoryFilters[i].label,
value:
state.selectedCats.contains(state.categoryFilters[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categories[i]);
state.selectedCats.add(state.categoryFilters[i]);
return;
}
state.selectedCats.remove(state.categories[i]);
state.selectedCats.remove(state.categoryFilters[i]);
},
),
),
@ -273,45 +264,45 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
// }
}
class _BottomSheetItem extends StatelessWidget {
final String icon;
final String title;
final bool enabled;
final VoidCallback onTap;
const _BottomSheetItem({
required this.icon,
required this.title,
this.enabled = false,
required this.onTap,
});
// class _BottomSheetItem extends StatelessWidget {
// final String icon;
// final String title;
// final bool enabled;
// final VoidCallback onTap;
// const _BottomSheetItem({
// required this.icon,
// required this.title,
// this.enabled = false,
// required this.onTap,
// });
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: enabled ? onTap : null,
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgPicture.asset(
icon,
theme: SvgTheme(
currentColor: enabled
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.disabledText,
),
),
const SizedBox(width: 8),
DidvanText(
title,
color: enabled
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.disabledText,
),
],
),
),
);
}
}
// @override
// Widget build(BuildContext context) {
// return GestureDetector(
// onTap: enabled ? onTap : null,
// child: Container(
// color: Colors.transparent,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// icon,
// theme: SvgTheme(
// currentColor: enabled
// ? Theme.of(context).colorScheme.title
// : Theme.of(context).colorScheme.disabledText,
// ),
// ),
// const SizedBox(width: 8),
// DidvanText(
// title,
// color: enabled
// ? Theme.of(context).colorScheme.title
// : Theme.of(context).colorScheme.disabledText,
// ),
// ],
// ),
// ),
// );
// }
// }

View File

@ -75,6 +75,7 @@ class MultitypeOverview extends StatelessWidget {
item.id,
args: const StudioRequestArgs(page: 0, type: 'podcast'),
);
MediaService.currentPodcast = state.studio;
MediaService.handleAudioPlayback(
audioSource: item.link,
id: item.id,