diff --git a/lib/widgets/audio_visualizer.dart b/lib/widgets/audio_visualizer.dart index f4fdc3d..94fc277 100644 --- a/lib/widgets/audio_visualizer.dart +++ b/lib/widgets/audio_visualizer.dart @@ -49,7 +49,7 @@ class _AudioVisualizerState extends State { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.dark + color: DesignConfig.isDark ? Theme.of(context).colorScheme.black : Theme.of(context).colorScheme.background, borderRadius: DesignConfig.mediumBorderRadius, diff --git a/lib/widgets/didvan/button.dart b/lib/widgets/didvan/button.dart index a3baeba..7b3122b 100644 --- a/lib/widgets/didvan/button.dart +++ b/lib/widgets/didvan/button.dart @@ -31,8 +31,13 @@ class DidvanButton extends StatelessWidget { foregroundColor = Theme.of(context).colorScheme.text; break; case ButtonStyleMode.flat: - backgroundColor = Theme.of(context).colorScheme.surface; - foregroundColor = Theme.of(context).colorScheme.white; + if (DesignConfig.isDark) { + backgroundColor = Theme.of(context).colorScheme.surface; + foregroundColor = Theme.of(context).colorScheme.white; + } else { + backgroundColor = Theme.of(context).colorScheme.surface; + foregroundColor = Theme.of(context).colorScheme.primary; + } break; default: } diff --git a/lib/widgets/didvan/page_view.dart b/lib/widgets/didvan/page_view.dart index 6ecc97f..a1abbdc 100644 --- a/lib/widgets/didvan/page_view.dart +++ b/lib/widgets/didvan/page_view.dart @@ -4,13 +4,15 @@ import 'package:flutter/material.dart'; class DidvanPageView extends StatelessWidget { final List pages; - const DidvanPageView({Key? key, required this.pages}) : super(key: key); + final ScrollController? scrollController; + const DidvanPageView({Key? key, required this.pages, this.scrollController}) + : super(key: key); @override Widget build(BuildContext context) { final double deviceTopPadding = MediaQuery.of(context).padding.top; return CarouselSlider.builder( - itemCount: 5, + itemCount: pages.length, options: CarouselOptions( height: double.infinity, initialPage: 2, @@ -20,16 +22,18 @@ class DidvanPageView extends StatelessWidget { itemBuilder: (context, index, realIndex) => SizedBox( height: MediaQuery.of(context).size.height, child: SingleChildScrollView( + controller: scrollController, physics: const BouncingScrollPhysics(), padding: EdgeInsets.only( left: 4, right: 4, top: 16 + deviceTopPadding, + bottom: 92, ), child: DidvanCard( padding: EdgeInsets.zero, enableBorder: false, - child: pages[0], + child: pages[index], ), ), ), diff --git a/lib/widgets/didvan/text_field.dart b/lib/widgets/didvan/text_field.dart index cd13c85..9186744 100644 --- a/lib/widgets/didvan/text_field.dart +++ b/lib/widgets/didvan/text_field.dart @@ -8,6 +8,7 @@ import 'package:persian_number_utility/persian_number_utility.dart'; class DidvanTextField extends StatefulWidget { final void Function(String value)? onChanged; + final void Function(String value)? onSubmitted; final bool enabled; final bool autoFocus; final TextAlign textAlign; @@ -29,6 +30,7 @@ class DidvanTextField extends StatefulWidget { this.textAlign = TextAlign.right, this.obsecureText = false, this.autoFocus = false, + this.onSubmitted, }) : super(key: key); @override @@ -83,10 +85,9 @@ class _DidvanTextFieldState extends State { keyboardType: widget.textInputType, focusNode: _focusNode, controller: _controller, + onFieldSubmitted: widget.onSubmitted, onChanged: _onChanged, - validator: (value) { - _validator(value); - }, + validator: _validator, obscuringCharacter: '*', style: Theme.of(context).textTheme.bodyText1, decoration: InputDecoration( @@ -144,7 +145,9 @@ class _DidvanTextFieldState extends State { Color _fillColor() { if (!widget.enabled) { - return Theme.of(context).colorScheme.disabledBackground; + return DesignConfig.isDark + ? Theme.of(context).colorScheme.disabledBackground + : Theme.of(context).colorScheme.secondCTA; } if (_focusNode.hasFocus) { return Theme.of(context).colorScheme.focused; @@ -183,14 +186,13 @@ class _DidvanTextFieldState extends State { } } - Future _validator(String? value) async { + String? _validator(String? value) { if (widget.validator != null) { - final String? error = await widget.validator!(value!); + final String? error = widget.validator!(value!); if (error != null) { setState(() { _error = error; }); - return ''; } else { setState(() { _error = null; diff --git a/lib/widgets/floating_navigation_bar.dart b/lib/widgets/floating_navigation_bar.dart index 8f74f82..e5b348d 100644 --- a/lib/widgets/floating_navigation_bar.dart +++ b/lib/widgets/floating_navigation_bar.dart @@ -1,19 +1,60 @@ +import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/news_details_data.dart'; +import 'package:didvan/models/radar_details_data.dart'; +import 'package:didvan/widgets/didvan/icon_button.dart'; import 'package:didvan/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; -class FloatingNavigationBar extends StatelessWidget { - final bool isRadar; - const FloatingNavigationBar({Key? key, required this.isRadar}) - : super(key: key); +class FloatingNavigationBar extends StatefulWidget { + final RadarDetailsData? radar; + final NewsDetailsData? news; + final ScrollController scrollController; + final VoidCallback bookmarkCallback; + const FloatingNavigationBar({ + Key? key, + this.radar, + this.news, + required this.scrollController, + required this.bookmarkCallback, + }) : super(key: key); + + @override + State createState() => _FloatingNavigationBarState(); +} + +class _FloatingNavigationBarState extends State { + bool _isMarked = false; + + bool get _isRadar => widget.radar != null; + bool _isScrolled = false; + + get _item => widget.radar ?? widget.news; + + @override + void initState() { + _isMarked = _item.marked; + widget.scrollController.addListener(() { + final position = widget.scrollController.position.pixels; + if (position > 300 && !_isScrolled) { + setState(() { + _isScrolled = true; + }); + } else if (position < 300 && _isScrolled) { + setState(() { + _isScrolled = false; + }); + } + }); + super.initState(); + } @override Widget build(BuildContext context) { - final Color foregroundColor = - Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).colorScheme.focusedBorder - : Theme.of(context).colorScheme.focused; + final Color foregroundColor = DesignConfig.isDark + ? Theme.of(context).colorScheme.focusedBorder + : Theme.of(context).colorScheme.focused; return Container( margin: const EdgeInsets.only(left: 32, right: 32, bottom: 20), width: double.infinity, @@ -35,20 +76,35 @@ class FloatingNavigationBar extends StatelessWidget { child: Row( children: [ IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: const Icon( - Icons.arrow_back, + onPressed: () { + if (_isScrolled) { + widget.scrollController.animateTo( + 0, + duration: DesignConfig.lowAnimationDuration, + curve: Curves.easeIn, + ); + return; + } + Navigator.of(context).pop(); + }, + icon: Icon( + _isScrolled ? Icons.arrow_upward : Icons.arrow_back, ), ), const Spacer(), - if (isRadar) - IconButton( - onPressed: () {}, - icon: const Icon( - DidvanIcons.bookmark_regular, - ), + if (_isRadar) + DidvanIconButton( + onPressed: () { + setState(() { + _isMarked = !_isMarked; + }); + widget.bookmarkCallback(); + }, + icon: _isMarked + ? DidvanIcons.bookmark_solid + : DidvanIcons.bookmark_regular, ), - if (isRadar) + if (_isRadar) IconButton( onPressed: () {}, icon: const Icon( @@ -66,19 +122,19 @@ class FloatingNavigationBar extends StatelessWidget { ), const SizedBox(width: 4), const Icon( - DidvanIcons.directs_regular, + DidvanIcons.chats_regular, ), ], ), ), - if (!isRadar) + if (!_isRadar) IconButton( onPressed: () {}, icon: const Icon( DidvanIcons.bookmark_regular, ), ), - if (isRadar) + if (_isRadar) IconButton( onPressed: () {}, icon: const Icon( diff --git a/lib/widgets/shimmer_placeholder.dart b/lib/widgets/shimmer_placeholder.dart index 502c094..fd39851 100644 --- a/lib/widgets/shimmer_placeholder.dart +++ b/lib/widgets/shimmer_placeholder.dart @@ -6,7 +6,7 @@ import 'package:skeleton_text/skeleton_text.dart'; class ShimmerPlaceholder extends StatelessWidget { final double? height; final double? width; - final BorderRadius borderRadius; + final BorderRadius? borderRadius; const ShimmerPlaceholder({ Key? key, @@ -18,14 +18,16 @@ class ShimmerPlaceholder extends StatelessWidget { @override Widget build(BuildContext context) { return SkeletonAnimation( - borderRadius: borderRadius, + borderRadius: borderRadius!, shimmerColor: Theme.of(context).colorScheme.secondCTA, gradientColor: Theme.of(context).colorScheme.cardBorder, child: Container( height: height, width: width, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.disabledBackground, + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.focused + : Theme.of(context).colorScheme.disabledBackground, borderRadius: borderRadius, ), ), diff --git a/lib/widgets/skeletun_image.dart b/lib/widgets/skeletun_image.dart index c5dd3eb..c1fe353 100644 --- a/lib/widgets/skeletun_image.dart +++ b/lib/widgets/skeletun_image.dart @@ -1,11 +1,17 @@ +import 'dart:typed_data'; + +import 'package:didvan/widgets/shimmer_placeholder.dart'; +import 'package:http/http.dart' as http; import 'package:cached_network_image/cached_network_image.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:skeleton_text/skeleton_text.dart'; -class SkeletonImage extends StatelessWidget { +class SkeletonImage extends StatefulWidget { final String imageUrl; final double width; final double height; @@ -15,19 +21,61 @@ class SkeletonImage extends StatelessWidget { required this.imageUrl, required this.width, required this.height, - this.borderRadius, + this.borderRadius = DesignConfig.lowBorderRadius, }) : super(key: key); + @override + State createState() => _SkeletonImageState(); +} + +class _SkeletonImageState extends State { + Uint8List? _bytes; + + @override + void initState() { + if (kIsWeb) _getImage(); + super.initState(); + } + + Future _getImage() async { + _bytes = (await http.get( + Uri.parse(RequestHelper.baseUrl + widget.imageUrl), + headers: {'Authorization': 'Bearer ${RequestService.token}'}, + )) + .bodyBytes; + if (mounted) { + setState(() {}); + } + } + @override Widget build(BuildContext context) { + if (kIsWeb) { + if (_bytes == null || _bytes!.isEmpty) { + return ShimmerPlaceholder( + width: widget.width, + height: widget.height, + borderRadius: widget.borderRadius, + ); + } + return ClipRRect( + borderRadius: widget.borderRadius, + child: Image.memory( + _bytes!, + width: widget.width, + height: widget.height, + fit: BoxFit.cover, + ), + ); + } return CachedNetworkImage( httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'}, - width: width, - height: height, - imageUrl: imageUrl, + width: widget.width, + height: widget.height, + imageUrl: RequestHelper.baseUrl + widget.imageUrl, imageBuilder: (context, imageProvider) => Container( decoration: BoxDecoration( - borderRadius: borderRadius ?? DesignConfig.lowBorderRadius, + borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius, image: DecorationImage( image: imageProvider, fit: BoxFit.cover, @@ -36,14 +84,14 @@ class SkeletonImage extends StatelessWidget { ), progressIndicatorBuilder: (context, url, progress) => SkeletonAnimation( shimmerColor: Theme.of(context).colorScheme.border, - borderRadius: borderRadius ?? DesignConfig.lowBorderRadius, + borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius, child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.disabledBackground, - borderRadius: borderRadius, + borderRadius: widget.borderRadius, ), - height: height, - width: width, + height: widget.height, + width: widget.width, ), ), ); diff --git a/lib/widgets/state_handler.dart b/lib/widgets/state_handler.dart index 0fc9a6f..16ad8ea 100644 --- a/lib/widgets/state_handler.dart +++ b/lib/widgets/state_handler.dart @@ -1,26 +1,25 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; class StateHandler extends StatelessWidget { final T state; final Widget Function(BuildContext context, T state) builder; final VoidCallback? onRefresh; - final bool enableCustomLoadingIndicator; - final Color? customLoadingIndicatorColor; final bool enableEmptyState; + final Widget? placeholder; final Widget? emptyState; final double topPadding; const StateHandler({ Key? key, required this.builder, this.emptyState, - this.enableCustomLoadingIndicator = false, this.enableEmptyState = false, this.onRefresh, this.topPadding = 0, - this.customLoadingIndicatorColor, required this.state, + this.placeholder, }) : super(key: key); @override @@ -38,9 +37,12 @@ class StateHandler extends StatelessWidget { case AppState.idle: return builder(context, state); case AppState.busy: - return const CircularProgressIndicator(); + return placeholder ?? + SpinKitSpinningLines( + color: Theme.of(context).colorScheme.primary, + ); case AppState.failed: - return enableCustomLoadingIndicator ? emptyState! : Container(); + return Container(); default: return Container(); }