component updates
This commit is contained in:
parent
a6607fd5af
commit
61aea52161
|
|
@ -29,6 +29,7 @@ class _UsernameInputState extends State<UsernameInput> {
|
||||||
title: 'نام کاربری یا شماره موبایل',
|
title: 'نام کاربری یا شماره موبایل',
|
||||||
hintText: 'نام کاربری یا شماره موبایل',
|
hintText: 'نام کاربری یا شماره موبایل',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
acceptSpace: false,
|
||||||
onSubmitted: (value) => state.confirmUsername(),
|
onSubmitted: (value) => state.confirmUsername(),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.contains(' ')) {
|
if (value.contains(' ')) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:didvan/config/theme_data.dart';
|
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/pages/authentication/authentication_state.dart';
|
||||||
import 'package:didvan/widgets/didvan/icon_button.dart';
|
import 'package:didvan/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
|
|
@ -15,7 +16,7 @@ class AuthenticationAppBar extends StatelessWidget {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
DidvanIconButton(
|
DidvanIconButton(
|
||||||
icon: Icons.arrow_back,
|
icon: DidvanIcons.back_regular,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (state.currentPageIndex == 0) {
|
if (state.currentPageIndex == 0) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:didvan/config/theme_data.dart';
|
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/models/view/app_bar_data.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -16,7 +17,7 @@ class DidvanAppBar extends StatelessWidget {
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
color: Theme.of(context).colorScheme.title,
|
color: Theme.of(context).colorScheme.title,
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(DidvanIcons.back_regular),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,15 @@ import 'package:flutter/material.dart';
|
||||||
class DidvanPageView extends StatelessWidget {
|
class DidvanPageView extends StatelessWidget {
|
||||||
final List<Widget> pages;
|
final List<Widget> pages;
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
const DidvanPageView({Key? key, required this.pages, this.scrollController})
|
final int initialPage;
|
||||||
: super(key: key);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -14,6 +21,7 @@ class DidvanPageView extends StatelessWidget {
|
||||||
return CarouselSlider.builder(
|
return CarouselSlider.builder(
|
||||||
itemCount: pages.length,
|
itemCount: pages.length,
|
||||||
options: CarouselOptions(
|
options: CarouselOptions(
|
||||||
|
onPageChanged: (index, reason) => onPageChanged(index),
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
initialPage: 2,
|
initialPage: 2,
|
||||||
viewportFraction: 0.94,
|
viewportFraction: 0.94,
|
||||||
|
|
@ -22,7 +30,7 @@ class DidvanPageView extends StatelessWidget {
|
||||||
itemBuilder: (context, index, realIndex) => SizedBox(
|
itemBuilder: (context, index, realIndex) => SizedBox(
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: index == 2 ? scrollController : null,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 4,
|
left: 4,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/widgets/animated_visibility.dart';
|
import 'package:didvan/widgets/animated_visibility.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||||
|
|
||||||
class DidvanTextField extends StatefulWidget {
|
class DidvanTextField extends StatefulWidget {
|
||||||
|
|
@ -16,6 +17,7 @@ class DidvanTextField extends StatefulWidget {
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
final dynamic initialValue;
|
final dynamic initialValue;
|
||||||
final bool obsecureText;
|
final bool obsecureText;
|
||||||
|
final bool acceptSpace;
|
||||||
final Function(String value)? validator;
|
final Function(String value)? validator;
|
||||||
final TextInputType? textInputType;
|
final TextInputType? textInputType;
|
||||||
const DidvanTextField({
|
const DidvanTextField({
|
||||||
|
|
@ -31,6 +33,7 @@ class DidvanTextField extends StatefulWidget {
|
||||||
this.obsecureText = false,
|
this.obsecureText = false,
|
||||||
this.autoFocus = false,
|
this.autoFocus = false,
|
||||||
this.onSubmitted,
|
this.onSubmitted,
|
||||||
|
this.acceptSpace = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -79,6 +82,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
||||||
border: Border.all(color: _borderColor()),
|
border: Border.all(color: _borderColor()),
|
||||||
),
|
),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
inputFormatters: <TextInputFormatter>[
|
||||||
|
if (!widget.acceptSpace)
|
||||||
|
FilteringTextInputFormatter.allow(RegExp("[0-9a-zA-Z]")),
|
||||||
|
],
|
||||||
autofocus: widget.autoFocus,
|
autofocus: widget.autoFocus,
|
||||||
obscureText: _hideContent,
|
obscureText: _hideContent,
|
||||||
textAlign: widget.textAlign,
|
textAlign: widget.textAlign,
|
||||||
|
|
@ -89,7 +96,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
||||||
onChanged: _onChanged,
|
onChanged: _onChanged,
|
||||||
validator: _validator,
|
validator: _validator,
|
||||||
obscuringCharacter: '*',
|
obscuringCharacter: '*',
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText2!
|
||||||
|
.copyWith(fontFamily: 'Dana'),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
suffixIcon: _suffixBuilder(),
|
suffixIcon: _suffixBuilder(),
|
||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
|
|
@ -181,9 +191,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
||||||
_error = null;
|
_error = null;
|
||||||
});
|
});
|
||||||
value = value.toEnglishDigit();
|
value = value.toEnglishDigit();
|
||||||
if (widget.onChanged != null) {
|
widget.onChanged?.call(value);
|
||||||
widget.onChanged!(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _validator(String? value) {
|
String? _validator(String? value) {
|
||||||
|
|
@ -193,6 +201,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = error;
|
_error = error;
|
||||||
});
|
});
|
||||||
|
return '';
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = null;
|
_error = null;
|
||||||
|
|
@ -200,4 +209,10 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/models/news_details_data.dart';
|
import 'package:didvan/models/news_details_data.dart';
|
||||||
import 'package:didvan/models/radar_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/icon_button.dart';
|
||||||
import 'package:didvan/widgets/didvan/text.dart';
|
import 'package:didvan/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -11,13 +17,15 @@ class FloatingNavigationBar extends StatefulWidget {
|
||||||
final RadarDetailsData? radar;
|
final RadarDetailsData? radar;
|
||||||
final NewsDetailsData? news;
|
final NewsDetailsData? news;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
final VoidCallback bookmarkCallback;
|
final VoidCallback onMark;
|
||||||
|
final VoidCallback onUnmark;
|
||||||
const FloatingNavigationBar({
|
const FloatingNavigationBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.radar,
|
this.radar,
|
||||||
this.news,
|
this.news,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.bookmarkCallback,
|
required this.onMark,
|
||||||
|
required this.onUnmark,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -25,8 +33,6 @@ class FloatingNavigationBar extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
bool _isMarked = false;
|
|
||||||
|
|
||||||
bool get _isRadar => widget.radar != null;
|
bool get _isRadar => widget.radar != null;
|
||||||
bool _isScrolled = false;
|
bool _isScrolled = false;
|
||||||
|
|
||||||
|
|
@ -34,7 +40,6 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_isMarked = _item.marked;
|
|
||||||
widget.scrollController.addListener(() {
|
widget.scrollController.addListener(() {
|
||||||
final position = widget.scrollController.position.pixels;
|
final position = widget.scrollController.position.pixels;
|
||||||
if (position > 300 && !_isScrolled) {
|
if (position > 300 && !_isScrolled) {
|
||||||
|
|
@ -57,9 +62,9 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
: Theme.of(context).colorScheme.focused;
|
: Theme.of(context).colorScheme.focused;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(left: 32, right: 32, bottom: 20),
|
margin: const EdgeInsets.only(left: 32, right: 32, bottom: 20),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 48,
|
height: 48,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.navigation,
|
color: Theme.of(context).colorScheme.navigation,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
|
@ -75,7 +80,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
DidvanIconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_isScrolled) {
|
if (_isScrolled) {
|
||||||
widget.scrollController.animateTo(
|
widget.scrollController.animateTo(
|
||||||
|
|
@ -87,63 +92,86 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
||||||
}
|
}
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: _isScrolled
|
||||||
_isScrolled ? Icons.arrow_upward : Icons.arrow_back,
|
? DidvanIcons.arrow_up_regular
|
||||||
),
|
: DidvanIcons.back_regular,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (_isRadar)
|
if (_isRadar)
|
||||||
DidvanIconButton(
|
BookmarkButton(
|
||||||
onPressed: () {
|
value: _item.marked,
|
||||||
setState(() {
|
onMark: widget.onMark,
|
||||||
_isMarked = !_isMarked;
|
onUnmark: widget.onUnmark,
|
||||||
});
|
|
||||||
widget.bookmarkCallback();
|
|
||||||
},
|
|
||||||
icon: _isMarked
|
|
||||||
? DidvanIcons.bookmark_solid
|
|
||||||
: DidvanIcons.bookmark_regular,
|
|
||||||
),
|
|
||||||
if (_isRadar)
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
DidvanIcons.evaluation_regular,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
// if (_isRadar)
|
||||||
|
// IconButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// icon: const Icon(
|
||||||
|
// DidvanIcons.evaluation_regular,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 48,
|
width: 48,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (_item.comments != 0)
|
||||||
DidvanText(
|
DidvanText(
|
||||||
'2',
|
_item.comments.toString(),
|
||||||
color: foregroundColor,
|
color: foregroundColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
const Icon(
|
DidvanIconButton(
|
||||||
DidvanIcons.chats_regular,
|
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)
|
if (!_isRadar)
|
||||||
IconButton(
|
BookmarkButton(
|
||||||
onPressed: () {},
|
value: _item.marked,
|
||||||
icon: const Icon(
|
onMark: widget.onMark,
|
||||||
DidvanIcons.bookmark_regular,
|
onUnmark: widget.onUnmark,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (_isRadar)
|
if (_isRadar)
|
||||||
IconButton(
|
DidvanIconButton(
|
||||||
onPressed: () {},
|
gestureSize: 32,
|
||||||
icon: const Icon(
|
onPressed: _showMoreOptions,
|
||||||
Icons.more_horiz,
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class _SearchFieldState extends State<SearchField> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _fillColor(),
|
color: _fillColor(),
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextFormField(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
textAlignVertical: TextAlignVertical.center,
|
textAlignVertical: TextAlignVertical.center,
|
||||||
|
|
@ -122,4 +122,10 @@ class _SearchFieldState extends State<SearchField> {
|
||||||
}
|
}
|
||||||
return Theme.of(context).colorScheme.surface;
|
return Theme.of(context).colorScheme.surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue