forked from eatvio/eatvio_app
feat(profile): Replaced the diet and calendar icons, reduced the height of the bottom nav bar, added the first version of the measurements graph, added the time of the chat message and changed the design slightly
This commit is contained in:
@@ -30,4 +30,20 @@ class ProfileCubit extends Cubit<ProfileState> {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
addCircumference({
|
||||
required int chestCircumference,
|
||||
required int waistCircumference,
|
||||
required int hipCircumference,
|
||||
}) async {
|
||||
try {
|
||||
await profileRepository.addCircumference(
|
||||
chestCircumference: chestCircumference,
|
||||
waistCircumference: waistCircumference,
|
||||
hipCircumference: hipCircumference,
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ProfileState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,84 @@
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart';
|
||||
import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
class ProfileScreen extends StatelessWidget {
|
||||
final Author author;
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
final Author? author;
|
||||
|
||||
const ProfileScreen({super.key, required this.author});
|
||||
const ProfileScreen({super.key, this.author});
|
||||
|
||||
@override
|
||||
State<ProfileScreen> createState() => _ProfileScreenState();
|
||||
}
|
||||
|
||||
class _ProfileScreenState extends State<ProfileScreen> {
|
||||
List<Circumference> chartData = [];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ProfileCubit>(
|
||||
create: (BuildContext context) => sl<ProfileCubit>()..getProfileBySlug(author.authorSlug),
|
||||
create: (BuildContext context) {
|
||||
if (widget.author == null)
|
||||
return sl<ProfileCubit>()..getMyProfile();
|
||||
else
|
||||
return sl<ProfileCubit>()..getProfileBySlug(widget.author!.authorSlug);
|
||||
},
|
||||
child: BlocBuilder<ProfileCubit, ProfileState>(
|
||||
builder: (BuildContext context, ProfileState state) {
|
||||
if (state is ProfileStateLoaded) {
|
||||
chartData = state.profile.circumferences;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
state.profile.user.name,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
title: Text(state.profile.user.name),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 32),
|
||||
child: CircleAvatar(
|
||||
maxRadius: 128,
|
||||
minRadius: 128,
|
||||
radius: 32,
|
||||
backgroundImage: NetworkImage(state.profile.user.profilePhotoUrl),
|
||||
),
|
||||
),
|
||||
SfCartesianChart(
|
||||
margin: EdgeInsets.zero,
|
||||
enableAxisAnimation: true,
|
||||
enableSideBySideSeriesPlacement: true,
|
||||
legend: const Legend(
|
||||
isVisible: true,
|
||||
opacity: 0.7,
|
||||
position: LegendPosition.bottom,
|
||||
),
|
||||
plotAreaBorderWidth: 0,
|
||||
trackballBehavior: TrackballBehavior(
|
||||
enable: true,
|
||||
activationMode: ActivationMode.singleTap,
|
||||
),
|
||||
primaryXAxis: DateTimeAxis(
|
||||
dateFormat: DateFormat.MMMd('ru'),
|
||||
axisLine: AxisLine(
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
primaryYAxis: NumericAxis(
|
||||
labelFormat: '{value} см',
|
||||
axisLine: AxisLine(
|
||||
width: 1,
|
||||
),
|
||||
majorTickLines: MajorTickLines(size: 0),
|
||||
),
|
||||
series: _getSplineAreaSeries(),
|
||||
tooltipBehavior: TooltipBehavior(enable: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -67,4 +105,64 @@ class ProfileScreen extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<CartesianSeries<Circumference, DateTime>> _getSplineAreaSeries() {
|
||||
final Color seriesColor1 = Colors.red;
|
||||
final Color seriesColor2 = Colors.blue;
|
||||
final Color seriesColor3 = Colors.green;
|
||||
|
||||
return <CartesianSeries<Circumference, DateTime>>[
|
||||
SplineSeries<Circumference, DateTime>(
|
||||
markerSettings: MarkerSettings(
|
||||
isVisible: true,
|
||||
color: seriesColor1,
|
||||
height: 12,
|
||||
width: 12,
|
||||
),
|
||||
dataLabelSettings: DataLabelSettings(
|
||||
labelAlignment: ChartDataLabelAlignment.top,
|
||||
isVisible: true,
|
||||
),
|
||||
dataSource: chartData,
|
||||
color: seriesColor1,
|
||||
name: 'Грудь',
|
||||
xValueMapper: (Circumference value, int index) => value.createdAt,
|
||||
yValueMapper: (Circumference value, int index) => value.chestCircumference,
|
||||
),
|
||||
SplineSeries<Circumference, DateTime>(
|
||||
markerSettings: MarkerSettings(
|
||||
isVisible: true,
|
||||
color: seriesColor2,
|
||||
height: 12,
|
||||
width: 12,
|
||||
),
|
||||
dataLabelSettings: DataLabelSettings(
|
||||
labelAlignment: ChartDataLabelAlignment.top,
|
||||
isVisible: true,
|
||||
),
|
||||
dataSource: chartData,
|
||||
color: seriesColor2,
|
||||
name: 'Талия',
|
||||
xValueMapper: (Circumference value, int index) => value.createdAt,
|
||||
yValueMapper: (Circumference value, int index) => value.waistCircumference,
|
||||
),
|
||||
SplineSeries<Circumference, DateTime>(
|
||||
markerSettings: MarkerSettings(
|
||||
isVisible: true,
|
||||
color: seriesColor3,
|
||||
height: 12,
|
||||
width: 12,
|
||||
),
|
||||
dataLabelSettings: DataLabelSettings(
|
||||
labelAlignment: ChartDataLabelAlignment.top,
|
||||
isVisible: true,
|
||||
),
|
||||
dataSource: chartData,
|
||||
color: seriesColor3,
|
||||
name: 'Бёдра',
|
||||
xValueMapper: (Circumference value, int index) => value.createdAt,
|
||||
yValueMapper: (Circumference value, int index) => value.hipCircumference,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,22 @@ class ProfileRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Circumference> addCircumference({
|
||||
required int chestCircumference,
|
||||
required int waistCircumference,
|
||||
required int hipCircumference,
|
||||
}) async {
|
||||
try {
|
||||
return (await profileService.addCircumference(
|
||||
chestCircumference: chestCircumference,
|
||||
waistCircumference: waistCircumference,
|
||||
hipCircumference: hipCircumference,
|
||||
))
|
||||
.values
|
||||
.first;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,11 @@ abstract class ProfileService {
|
||||
|
||||
@GET('/api/profile/index')
|
||||
Future<ProfileModel> getMyProfile();
|
||||
|
||||
@POST('/api/profile/addCircumference')
|
||||
Future<Map<String, Circumference>> addCircumference({
|
||||
@Part(name: 'chest_circumference') required int chestCircumference,
|
||||
@Part(name: 'waist_circumference') required int waistCircumference,
|
||||
@Part(name: 'hip_circumference') required int hipCircumference,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,6 +87,56 @@ class _ProfileService implements ProfileService {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, Circumference>> addCircumference({
|
||||
required int chestCircumference,
|
||||
required int waistCircumference,
|
||||
required int hipCircumference,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = FormData();
|
||||
_data.fields.add(MapEntry(
|
||||
'chest_circumference',
|
||||
chestCircumference.toString(),
|
||||
));
|
||||
_data.fields.add(MapEntry(
|
||||
'waist_circumference',
|
||||
waistCircumference.toString(),
|
||||
));
|
||||
_data.fields.add(MapEntry(
|
||||
'hip_circumference',
|
||||
hipCircumference.toString(),
|
||||
));
|
||||
final _options = _setStreamType<Map<String, Circumference>>(Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/addCircumference',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late Map<String, Circumference> _value;
|
||||
try {
|
||||
_value = _result.data!.map((k, dynamic v) =>
|
||||
MapEntry(k, Circumference.fromJson(v as Map<String, dynamic>)));
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
Reference in New Issue
Block a user