component updates

This commit is contained in:
MohammadTaha Basiri 2022-01-18 18:16:25 +03:30
parent a6607fd5af
commit 61aea52161
10 changed files with 290 additions and 152 deletions

View File

@ -29,6 +29,7 @@ class _UsernameInputState extends State<UsernameInput> {
title: 'نام کاربری یا شماره موبایل',
hintText: 'نام کاربری یا شماره موبایل',
textAlign: TextAlign.center,
acceptSpace: false,
onSubmitted: (value) => state.confirmUsername(),
validator: (value) {
if (value.contains(' ')) {

View File

@ -1,4 +1,5 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/pages/authentication/authentication_state.dart';
import 'package:didvan/widgets/didvan/icon_button.dart';
import 'package:didvan/widgets/didvan/text.dart';
@ -15,7 +16,7 @@ class AuthenticationAppBar extends StatelessWidget {
return Row(
children: [
DidvanIconButton(
icon: Icons.arrow_back,
icon: DidvanIcons.back_regular,
onPressed: () {
if (state.currentPageIndex == 0) {
Navigator.of(context).pop();

View File

@ -0,0 +1,42 @@
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/widgets/didvan/icon_button.dart';
import 'package:flutter/material.dart';
class BookmarkButton extends StatefulWidget {
final bool value;
final VoidCallback onMark;
final VoidCallback onUnmark;
const BookmarkButton(
{Key? key,
required this.value,
required this.onMark,
required this.onUnmark})
: super(key: key);
@override
State<BookmarkButton> createState() => _BookmarkButtonState();
}
class _BookmarkButtonState extends State<BookmarkButton> {
bool _value = false;
@override
void initState() {
_value = widget.value;
super.initState();
}
@override
Widget build(BuildContext context) {
return DidvanIconButton(
gestureSize: 24,
icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () {
setState(() {
_value = !_value;
});
_value ? widget.onMark() : widget.onUnmark();
},
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
@ -16,7 +17,7 @@ class DidvanAppBar extends StatelessWidget {
IconButton(
onPressed: () => Navigator.of(context).pop(),
color: Theme.of(context).colorScheme.title,
icon: const Icon(Icons.arrow_back),
icon: const Icon(DidvanIcons.back_regular),
),
const SizedBox(width: 16),
Expanded(

View File

@ -5,8 +5,15 @@ import 'package:flutter/material.dart';
class DidvanPageView extends StatelessWidget {
final List<Widget> pages;
final ScrollController? scrollController;
const DidvanPageView({Key? key, required this.pages, this.scrollController})
: super(key: key);
final int initialPage;
final void Function(int index) onPageChanged;
const DidvanPageView({
Key? key,
required this.pages,
this.scrollController,
required this.onPageChanged,
this.initialPage = 2,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -14,6 +21,7 @@ class DidvanPageView extends StatelessWidget {
return CarouselSlider.builder(
itemCount: pages.length,
options: CarouselOptions(
onPageChanged: (index, reason) => onPageChanged(index),
height: double.infinity,
initialPage: 2,
viewportFraction: 0.94,
@ -22,7 +30,7 @@ class DidvanPageView extends StatelessWidget {
itemBuilder: (context, index, realIndex) => SizedBox(
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
controller: scrollController,
controller: index == 2 ? scrollController : null,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.only(
left: 4,

View File

@ -4,6 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/widgets/animated_visibility.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
class DidvanTextField extends StatefulWidget {
@ -16,6 +17,7 @@ class DidvanTextField extends StatefulWidget {
final String? hintText;
final dynamic initialValue;
final bool obsecureText;
final bool acceptSpace;
final Function(String value)? validator;
final TextInputType? textInputType;
const DidvanTextField({
@ -31,6 +33,7 @@ class DidvanTextField extends StatefulWidget {
this.obsecureText = false,
this.autoFocus = false,
this.onSubmitted,
this.acceptSpace = true,
}) : super(key: key);
@override
@ -79,6 +82,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
border: Border.all(color: _borderColor()),
),
child: TextFormField(
inputFormatters: <TextInputFormatter>[
if (!widget.acceptSpace)
FilteringTextInputFormatter.allow(RegExp("[0-9a-zA-Z]")),
],
autofocus: widget.autoFocus,
obscureText: _hideContent,
textAlign: widget.textAlign,
@ -89,7 +96,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
onChanged: _onChanged,
validator: _validator,
obscuringCharacter: '*',
style: Theme.of(context).textTheme.bodyText1,
style: Theme.of(context)
.textTheme
.bodyText2!
.copyWith(fontFamily: 'Dana'),
decoration: InputDecoration(
suffixIcon: _suffixBuilder(),
enabled: widget.enabled,
@ -181,9 +191,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
_error = null;
});
value = value.toEnglishDigit();
if (widget.onChanged != null) {
widget.onChanged!(value);
}
widget.onChanged?.call(value);
}
String? _validator(String? value) {
@ -193,6 +201,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
setState(() {
_error = error;
});
return '';
} else {
setState(() {
_error = null;
@ -200,4 +209,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -3,6 +3,12 @@ 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/models/view/action_sheet_data.dart';
import 'package:didvan/pages/home/profile/widgets/menu_item.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/widgets/bookmark_button.dart';
import 'package:didvan/widgets/didvan/divider.dart';
import 'package:didvan/widgets/didvan/icon_button.dart';
import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
@ -11,13 +17,15 @@ class FloatingNavigationBar extends StatefulWidget {
final RadarDetailsData? radar;
final NewsDetailsData? news;
final ScrollController scrollController;
final VoidCallback bookmarkCallback;
final VoidCallback onMark;
final VoidCallback onUnmark;
const FloatingNavigationBar({
Key? key,
this.radar,
this.news,
required this.scrollController,
required this.bookmarkCallback,
required this.onMark,
required this.onUnmark,
}) : super(key: key);
@override
@ -25,8 +33,6 @@ class FloatingNavigationBar extends StatefulWidget {
}
class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
bool _isMarked = false;
bool get _isRadar => widget.radar != null;
bool _isScrolled = false;
@ -34,7 +40,6 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
@override
void initState() {
_isMarked = _item.marked;
widget.scrollController.addListener(() {
final position = widget.scrollController.position.pixels;
if (position > 300 && !_isScrolled) {
@ -57,9 +62,9 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
: Theme.of(context).colorScheme.focused;
return Container(
margin: const EdgeInsets.only(left: 32, right: 32, bottom: 20),
padding: const EdgeInsets.symmetric(horizontal: 12),
width: double.infinity,
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.navigation,
borderRadius: BorderRadius.circular(24),
@ -75,7 +80,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
),
child: Row(
children: [
IconButton(
DidvanIconButton(
onPressed: () {
if (_isScrolled) {
widget.scrollController.animateTo(
@ -87,63 +92,86 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
}
Navigator.of(context).pop();
},
icon: Icon(
_isScrolled ? Icons.arrow_upward : Icons.arrow_back,
),
icon: _isScrolled
? DidvanIcons.arrow_up_regular
: DidvanIcons.back_regular,
),
const Spacer(),
if (_isRadar)
DidvanIconButton(
onPressed: () {
setState(() {
_isMarked = !_isMarked;
});
widget.bookmarkCallback();
},
icon: _isMarked
? DidvanIcons.bookmark_solid
: DidvanIcons.bookmark_regular,
),
if (_isRadar)
IconButton(
onPressed: () {},
icon: const Icon(
DidvanIcons.evaluation_regular,
),
BookmarkButton(
value: _item.marked,
onMark: widget.onMark,
onUnmark: widget.onUnmark,
),
// if (_isRadar)
// IconButton(
// onPressed: () {},
// icon: const Icon(
// DidvanIcons.evaluation_regular,
// ),
// ),
SizedBox(
width: 48,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DidvanText(
'2',
color: foregroundColor,
),
if (_item.comments != 0)
DidvanText(
_item.comments.toString(),
color: foregroundColor,
),
const SizedBox(width: 4),
const Icon(
DidvanIcons.chats_regular,
DidvanIconButton(
gestureSize: 32,
onPressed: () => Navigator.of(context).pushNamed(
Routes.comments,
arguments: {'id': _item.id, 'isRadar': _isRadar},
),
icon: DidvanIcons.chats_regular,
),
],
),
),
if (!_isRadar) const SizedBox(width: 12),
if (!_isRadar)
IconButton(
onPressed: () {},
icon: const Icon(
DidvanIcons.bookmark_regular,
),
BookmarkButton(
value: _item.marked,
onMark: widget.onMark,
onUnmark: widget.onUnmark,
),
if (_isRadar)
IconButton(
onPressed: () {},
icon: const Icon(
Icons.more_horiz,
),
DidvanIconButton(
gestureSize: 32,
onPressed: _showMoreOptions,
icon: DidvanIcons.menu_regular,
),
],
),
),
);
}
void _showMoreOptions() {
ActionSheetUtils.showBottomSheet(
data: ActionSheetData(
content: Column(
children: [
MenuItem(
title: 'ارتباط با سردبیر',
onTap: () {},
icon: DidvanIcons.profile_regular,
),
const DidvanDivider(),
MenuItem(
title: 'گزارش اشکال',
onTap: () {},
icon: DidvanIcons.description_regular,
),
],
),
title: 'موارد بیشتر',
withoutButtonMode: true,
),
);
}
}

View File

@ -43,7 +43,7 @@ class _SearchFieldState extends State<SearchField> {
decoration: BoxDecoration(
color: _fillColor(),
),
child: TextField(
child: TextFormField(
focusNode: _focusNode,
style: Theme.of(context).textTheme.bodyText1,
textAlignVertical: TextAlignVertical.center,
@ -122,4 +122,10 @@ class _SearchFieldState extends State<SearchField> {
}
return Theme.of(context).colorScheme.surface;
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,135 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:didvan/services/storage/storage.dart';
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 StatefulWidget {
final String imageUrl;
final double width;
final double height;
final BorderRadius? borderRadius;
final double? aspectRatio;
const SkeletonImage({
Key? key,
required this.imageUrl,
this.width = 300,
this.height = 140,
this.borderRadius = DesignConfig.lowBorderRadius,
this.aspectRatio,
}) : super(key: key);
@override
State<SkeletonImage> createState() => _SkeletonImageState();
}
class _SkeletonImageState extends State<SkeletonImage> {
Uint8List? _bytes;
@override
void initState() {
if (kIsWeb) _getImage();
super.initState();
}
Future<void> _getImage() async {
final url = RequestHelper.baseUrl + widget.imageUrl;
final storage = StorageService.webStorage;
String? imageCache = storage['image-cache'];
final Map data = imageCache == null ? {} : jsonDecode(imageCache);
if (data.containsKey(url)) {
_bytes = Uint8List.fromList(
List<int>.from(data[url]),
);
} else {
_bytes = (await http.get(
Uri.parse(url),
headers: {'Authorization': 'Bearer ${RequestService.token}'},
))
.bodyBytes;
addImageToStorage();
}
if (mounted) {
setState(() {});
}
}
void addImageToStorage() {
final storage = StorageService.webStorage;
String? imageCache = storage['image-cache'];
final Map data = imageCache == null ? {} : Map.from(jsonDecode(imageCache));
data.addAll({RequestHelper.baseUrl + widget.imageUrl: _bytes});
StorageService.webStorage.addAll({'image-cache': jsonEncode(data)});
}
@override
Widget build(BuildContext context) {
return _aspectRatioGenerator(
child: Builder(builder: (context) {
if (kIsWeb) {
return Builder(
builder: (context) {
if (_bytes == null || _bytes!.isEmpty) {
return ShimmerPlaceholder(
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: widget.width,
height: widget.height,
imageUrl: RequestHelper.baseUrl + widget.imageUrl,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
progressIndicatorBuilder: (context, url, progress) =>
SkeletonAnimation(
shimmerColor: Theme.of(context).colorScheme.border,
borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.disabledBackground,
borderRadius: widget.borderRadius,
),
),
),
);
}),
);
}
Widget _aspectRatioGenerator({required Widget child}) =>
widget.aspectRatio == null
? SizedBox(child: child)
: AspectRatio(
aspectRatio: widget.aspectRatio!,
child: child,
);
}

View File

@ -1,99 +0,0 @@
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 StatefulWidget {
final String imageUrl;
final double width;
final double height;
final BorderRadius? borderRadius;
const SkeletonImage({
Key? key,
required this.imageUrl,
required this.width,
required this.height,
this.borderRadius = DesignConfig.lowBorderRadius,
}) : super(key: key);
@override
State<SkeletonImage> createState() => _SkeletonImageState();
}
class _SkeletonImageState extends State<SkeletonImage> {
Uint8List? _bytes;
@override
void initState() {
if (kIsWeb) _getImage();
super.initState();
}
Future<void> _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: widget.width,
height: widget.height,
imageUrl: RequestHelper.baseUrl + widget.imageUrl,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
progressIndicatorBuilder: (context, url, progress) => SkeletonAnimation(
shimmerColor: Theme.of(context).colorScheme.border,
borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.disabledBackground,
borderRadius: widget.borderRadius,
),
height: widget.height,
width: widget.width,
),
),
);
}
}