feat(order info): added order info screen

This commit is contained in:
Username1548
2025-04-04 20:21:31 +07:00
parent 9715f4cac9
commit 748484cf8f
387 changed files with 16171 additions and 18199 deletions

View File

@@ -61,7 +61,7 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
EasyDebounce.debounce(
'like_plan_$slug',
Duration(seconds: 1),
const Duration(seconds: 1),
() async {
try {
await planRepository.likePlan(slug);
@@ -86,7 +86,7 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
}
EasyDebounce.debounce(
'add_or_remove_plan_to_favorites',
Duration(seconds: 1),
const Duration(seconds: 1),
() async {
try {
await planRepository.addToFavorite(slug);
@@ -112,7 +112,7 @@ class AboutPlanCubit extends Cubit<AboutPlanState> {
}
EasyDebounce.debounce(
'add_or_remove_plan_to_favorites',
Duration(seconds: 1),
const Duration(seconds: 1),
() async {
try {
await planRepository.removeFromFavorite(slug);

View File

@@ -12,8 +12,7 @@ part of 'about_plan_state.dart';
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$AboutPlanState {
@@ -32,16 +31,14 @@ mixin _$AboutPlanState {
/// @nodoc
abstract class $AboutPlanStateCopyWith<$Res> {
factory $AboutPlanStateCopyWith(
AboutPlanState value,
$Res Function(AboutPlanState) then,
) = _$AboutPlanStateCopyWithImpl<$Res, AboutPlanState>;
AboutPlanState value, $Res Function(AboutPlanState) then) =
_$AboutPlanStateCopyWithImpl<$Res, AboutPlanState>;
@useResult
$Res call({
bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex,
});
$Res call(
{bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex});
$PlanModelCopyWith<$Res>? get planModel;
}
@@ -66,31 +63,24 @@ class _$AboutPlanStateCopyWithImpl<$Res, $Val extends AboutPlanState>
Object? planModel = freezed,
Object? chosenOptionIndex = null,
}) {
return _then(
_value.copyWith(
isLoading:
null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
error:
freezed == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String?,
planModel:
freezed == planModel
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex:
null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
)
as $Val,
);
return _then(_value.copyWith(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
error: freezed == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String?,
planModel: freezed == planModel
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex: null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
/// Create a copy of AboutPlanState
@@ -111,18 +101,16 @@ class _$AboutPlanStateCopyWithImpl<$Res, $Val extends AboutPlanState>
/// @nodoc
abstract class _$$AboutPlanStateImplCopyWith<$Res>
implements $AboutPlanStateCopyWith<$Res> {
factory _$$AboutPlanStateImplCopyWith(
_$AboutPlanStateImpl value,
$Res Function(_$AboutPlanStateImpl) then,
) = __$$AboutPlanStateImplCopyWithImpl<$Res>;
factory _$$AboutPlanStateImplCopyWith(_$AboutPlanStateImpl value,
$Res Function(_$AboutPlanStateImpl) then) =
__$$AboutPlanStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex,
});
$Res call(
{bool isLoading,
String? error,
PlanModel? planModel,
int chosenOptionIndex});
@override
$PlanModelCopyWith<$Res>? get planModel;
@@ -133,9 +121,8 @@ class __$$AboutPlanStateImplCopyWithImpl<$Res>
extends _$AboutPlanStateCopyWithImpl<$Res, _$AboutPlanStateImpl>
implements _$$AboutPlanStateImplCopyWith<$Res> {
__$$AboutPlanStateImplCopyWithImpl(
_$AboutPlanStateImpl _value,
$Res Function(_$AboutPlanStateImpl) _then,
) : super(_value, _then);
_$AboutPlanStateImpl _value, $Res Function(_$AboutPlanStateImpl) _then)
: super(_value, _then);
/// Create a copy of AboutPlanState
/// with the given fields replaced by the non-null parameter values.
@@ -147,42 +134,35 @@ class __$$AboutPlanStateImplCopyWithImpl<$Res>
Object? planModel = freezed,
Object? chosenOptionIndex = null,
}) {
return _then(
_$AboutPlanStateImpl(
isLoading:
null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
error:
freezed == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String?,
planModel:
freezed == planModel
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex:
null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
),
);
return _then(_$AboutPlanStateImpl(
isLoading: null == isLoading
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
error: freezed == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String?,
planModel: freezed == planModel
? _value.planModel
: planModel // ignore: cast_nullable_to_non_nullable
as PlanModel?,
chosenOptionIndex: null == chosenOptionIndex
? _value.chosenOptionIndex
: chosenOptionIndex // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$AboutPlanStateImpl implements _AboutPlanState {
const _$AboutPlanStateImpl({
required this.isLoading,
required this.error,
required this.planModel,
required this.chosenOptionIndex,
});
const _$AboutPlanStateImpl(
{required this.isLoading,
required this.error,
required this.planModel,
required this.chosenOptionIndex});
@override
final bool isLoading;
@@ -223,18 +203,15 @@ class _$AboutPlanStateImpl implements _AboutPlanState {
@pragma('vm:prefer-inline')
_$$AboutPlanStateImplCopyWith<_$AboutPlanStateImpl> get copyWith =>
__$$AboutPlanStateImplCopyWithImpl<_$AboutPlanStateImpl>(
this,
_$identity,
);
this, _$identity);
}
abstract class _AboutPlanState implements AboutPlanState {
const factory _AboutPlanState({
required final bool isLoading,
required final String? error,
required final PlanModel? planModel,
required final int chosenOptionIndex,
}) = _$AboutPlanStateImpl;
const factory _AboutPlanState(
{required final bool isLoading,
required final String? error,
required final PlanModel? planModel,
required final int chosenOptionIndex}) = _$AboutPlanStateImpl;
@override
bool get isLoading;

View File

@@ -32,10 +32,10 @@ import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import '../widgets/option_widget.dart';
import '../../../../core/widgets/option_widget.dart';
class AboutPlanScreen extends StatefulWidget {
AboutPlanScreen({super.key, required this.plan, required this.navigateToComments, required this.index});
const AboutPlanScreen({super.key, required this.plan, required this.navigateToComments, required this.index});
final PlanModel plan;
final bool navigateToComments;
final int? index;
@@ -97,7 +97,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
builder: (BuildContext context, CommentsState commentsState) {
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
if (state.isLoading)
if (state.isLoading) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
@@ -117,7 +117,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
path: 'plan/${widget.plan.id}',
slug: widget.plan.slug,
),
anchor: Aligned(
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
@@ -137,8 +137,9 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
],
),
body: Loader(),
body: const Loader(),
);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
@@ -164,7 +165,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
path: 'plan/${widget.plan.id}',
slug: widget.plan.slug,
),
anchor: Aligned(
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
@@ -191,7 +192,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
backgroundColor: appTheme.gray100,
resizeToAvoidBottomInset: true,
bottomSheet: Container(
padding: EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
@@ -221,9 +222,11 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
onPressed: () async {
final chosenPlan = await context.read<AboutPlanCubit>().chosePlan();
if (chosenPlan == null) {
if (!context.mounted) return;
showErrorDialog(context, title: 'Не удалось заказать план');
return;
}
if (!context.mounted) return;
GoRouter.of(context).pushReplacementNamed(
'chatScreen',
extra: {
@@ -238,7 +241,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
),
),
SizedBox(width: 8),
const SizedBox(width: 8),
BookmarkButton(
isFavorite: state.planModel?.isFavorite ?? false,
onTap: () {
@@ -258,7 +261,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
],
),
SizedBox(height: 8),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -279,17 +282,17 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
height: 32,
width: 32,
child: Icon(
child: const Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) SizedBox(width: 8),
if (replyId != null) const SizedBox(width: 8),
Expanded(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: 52),
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
@@ -299,7 +302,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
style: TextStyle(
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black,
@@ -307,9 +310,9 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
decoration: InputDecoration(
isDense: false, // убираем true, чтобы padding учитывался корректно
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен
hintText: 'Комментарий',
hintStyle: TextStyle(
hintStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
height: 1.2,
@@ -318,21 +321,21 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: Color(0xFFD1D1D6), width: 1),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: Color(0xFFD1D1D6), width: 1),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: Color(0xFFD1D1D6), width: 1),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
SizedBox(width: 8),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
if (textEditingController.text.length > 2) {
@@ -348,8 +351,10 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
replyId = null;
});
Future.delayed(Duration(milliseconds: 500), () {
context.read<AboutPlanCubit>().getPlan(widget.plan.slug);
Future.delayed(const Duration(milliseconds: 500), () {
if (context.mounted) {
context.read<AboutPlanCubit>().getPlan(widget.plan.slug);
}
});
FocusManager.instance.primaryFocus?.unfocus();
}
@@ -392,7 +397,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
path: 'plan/${widget.plan.id}',
slug: widget.plan.slug,
),
anchor: Aligned(
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
@@ -414,11 +419,11 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
body: SingleChildScrollView(
controller: scrollController,
padding: EdgeInsets.only(bottom: 160),
padding: const EdgeInsets.only(bottom: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 8),
const SizedBox(height: 8),
MainBackgroundBouble(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -426,7 +431,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
Stack(
children: [
CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + widget.plan.image,
imageUrl: '$baseUrl/storage/${widget.plan.image}',
height: 300,
width: MediaQuery.sizeOf(context).width,
fit: BoxFit.fill,
@@ -439,22 +444,23 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
SizedBox(width: 8),
const SizedBox(width: 8),
Text(
widget.plan.totalViews.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
SizedBox(width: 12),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
context.read<AboutPlanCubit>().likePlan(slug: widget.plan.slug, index: widget.index);
},
child: InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
@@ -462,33 +468,32 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
height: 12,
colorFilter: state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
),
SizedBox(width: 8),
const SizedBox(width: 8),
Text(
state.planModel?.totalLikes.toString() ?? '0',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
),
SizedBox(width: 12),
const SizedBox(width: 12),
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
SizedBox(width: 8),
const SizedBox(width: 8),
Text(
widget.plan.totalComments.toString(),
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
backgroundColor: appTheme.whiteA700,
),
],
),
),
],
),
SizedBox(height: 16),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
@@ -496,12 +501,12 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
SizedBox(height: 12),
const SizedBox(height: 12),
InfoRow(plan: state.planModel, type: ProductType.plan),
ExpandableText(
text: widget.plan.details,
),
SizedBox(height: 20),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
@@ -509,11 +514,11 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
title: s.plan_author,
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
],
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
if (state.planModel?.packetsCount == 3) ...[
OptionsTab(
cost: [
@@ -522,7 +527,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
state.planModel?.packet3Options?.price ?? 0,
],
),
SizedBox(height: 12),
const SizedBox(height: 12),
],
switch (state.chosenOptionIndex) {
1 => OptionWidget(
@@ -535,15 +540,15 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
option: state.planModel?.packet1Options,
),
},
SizedBox(
const SizedBox(
height: 16,
),
MainBackgroundBouble(
padding: EdgeInsets.symmetric(vertical: 16),
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text(
'${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}',
@@ -552,7 +557,7 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
),
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
if (commentsState is CommentsStateLoaded)
CommentsWidget(

View File

@@ -1,45 +0,0 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
class OptionDurationWidget extends StatelessWidget {
const OptionDurationWidget({super.key, required this.duration});
final int duration;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4), color: appTheme.cardBackground),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Container(
width: 4,
height: 20,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.only(
topRight: Radius.circular(100),
bottomRight: Radius.circular(100),
),
),
),
),
SizedBox(
width: 12,
),
Text(
'${s.option_duration} $duration ${getDayForm(duration, s)}',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(
width: 12,
),
],
),
);
}
}

View File

@@ -1,49 +0,0 @@
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/core/widgets/item_widget.dart';
import 'package:eatvio_app/features/plans/presentation/widgets/option_duration_widget.dart';
import 'package:flutter/material.dart';
class OptionWidget extends StatelessWidget {
const OptionWidget({super.key, required this.option});
final PacketOptions? option;
@override
Widget build(BuildContext context) {
if (option == null) {
return SizedBox.shrink();
}
return MainBackgroundBouble(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16),
Text(
option?.short ?? '',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
SizedBox(height: 12),
Text(
option?.description ?? '',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
),
SizedBox(height: 12),
OptionDurationWidget(duration: 7),
SizedBox(height: 16),
CustomDivider(),
SizedBox(height: 16),
ListView.separated(
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 12),
itemBuilder: (context, index) => ItemWidget(text: option?.items[index] ?? ''),
itemCount: option?.items.length ?? 0,
shrinkWrap: true,
),
],
),
);
}
}

View File

@@ -14,7 +14,7 @@ class OptionsTab extends StatelessWidget {
builder: (context, state) {
final selectedOption = state.chosenOptionIndex;
return Container(
margin: EdgeInsets.symmetric(horizontal: 4),
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(100),