Skip to content

Commit

Permalink
feature: Add feedback modals (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
0niel authored Feb 25, 2023
1 parent fdef892 commit 0342ad0
Show file tree
Hide file tree
Showing 3 changed files with 351 additions and 0 deletions.
23 changes: 23 additions & 0 deletions lib/presentation/pages/profile/about_app_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:rtu_mirea_app/presentation/bloc/about_app_bloc/about_app_bloc.dart';
import 'package:rtu_mirea_app/presentation/bloc/user_bloc/user_bloc.dart';
import 'package:rtu_mirea_app/presentation/widgets/buttons/colorful_button.dart';
import 'package:rtu_mirea_app/presentation/widgets/buttons/icon_button.dart';
import 'package:rtu_mirea_app/presentation/widgets/feedback_modal.dart';
import 'package:rtu_mirea_app/service_locator.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:rtu_mirea_app/presentation/typography.dart';
Expand Down Expand Up @@ -203,6 +206,26 @@ class AboutAppPage extends StatelessWidget {
),
],
),
const SizedBox(height: 16),
SizedBox(
height: 40,
width: double.infinity,
child: ColorfulButton(
text: 'Сообщить об ошибке',
backgroundColor: AppTheme.colors.colorful07.withBlue(180),
onClick: () {
final userBloc = context.read<UserBloc>();

userBloc.state.maybeMap(
logInSuccess: (value) => FeedbackBottomModalSheet.show(
context,
defaultEmail: value.user.email,
),
orElse: () => FeedbackBottomModalSheet.show(context),
);
},
),
),
const SizedBox(height: 24),
Text('Контрибьюторы', style: AppTextStyle.h4),
const SizedBox(height: 16),
Expand Down
56 changes: 56 additions & 0 deletions lib/presentation/pages/schedule/schedule_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:rtu_mirea_app/domain/entities/schedule.dart';
import 'package:rtu_mirea_app/presentation/bloc/schedule_bloc/schedule_bloc.dart';
import 'package:rtu_mirea_app/presentation/bloc/user_bloc/user_bloc.dart';
import 'package:rtu_mirea_app/presentation/core/routes/routes.gr.dart';
import 'package:rtu_mirea_app/presentation/pages/schedule/widgets/schedule_settings_drawer.dart';
import 'package:rtu_mirea_app/presentation/pages/schedule/widgets/schedule_settings_modal.dart';
import 'package:rtu_mirea_app/presentation/theme.dart';
import 'package:rtu_mirea_app/presentation/widgets/buttons/colorful_button.dart';
import 'package:rtu_mirea_app/presentation/widgets/settings_switch_button.dart';
import '../../widgets/feedback_modal.dart';
import 'widgets/schedule_page_view.dart';
import 'package:rtu_mirea_app/presentation/typography.dart';

Expand Down Expand Up @@ -263,6 +265,60 @@ class _SchedulePageState extends State<SchedulePage> {
context.router.push(const GroupsSelectRoute()),
),
),
Material(
color: Colors.transparent,
child: InkWell(
child: Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 20),
child: Row(
children: [
SvgPicture.asset(
'assets/icons/social-sharing.svg',
height: 16,
width: 16,
),
const SizedBox(width: 20),
Text("Проблемы с расписанием",
style: AppTextStyle.buttonL),
],
),
),
Opacity(
opacity: 0.05,
child: Container(
width: double.infinity,
height: 1,
color: Colors.white,
),
),
],
),
onTap: () {
final defaultText = state is ScheduleLoaded
? 'Возникла проблема с расписанием группы ${state.activeGroup}:\n\n'
: null;

final userBloc = context.read<UserBloc>();

userBloc.state.maybeMap(
logInSuccess: (value) =>
FeedbackBottomModalSheet.show(
context,
defaultText: defaultText,
defaultEmail: value.user.email,
),
orElse: () => FeedbackBottomModalSheet.show(
context,
defaultText: defaultText,
),
);
},
),
),

if (state is ScheduleLoaded)
Expanded(
child: Column(
Expand Down
272 changes: 272 additions & 0 deletions lib/presentation/widgets/feedback_modal.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import 'package:flutter/material.dart';
import 'package:sentry/sentry.dart';

import '../theme.dart';
import '../typography.dart';
import 'buttons/primary_button.dart';

class FeedbackBottomModalSheet extends StatefulWidget {
const FeedbackBottomModalSheet({
Key? key,
this.onConfirm,
this.defaultText,
this.defaultEmail,
}) : super(key: key);

final String? defaultEmail;
final String? defaultText;
final VoidCallback? onConfirm;

static void show(
BuildContext context, {
String? defaultEmail,
String? defaultText,
VoidCallback? onConfirm,
}) {
showModalBottomSheet(
isDismissible: true,
isScrollControlled: true,
backgroundColor: AppTheme.colors.background02,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(24),
),
),
context: context,
builder: (context) => SafeArea(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: FeedbackBottomModalSheet(
defaultEmail: defaultEmail,
defaultText: defaultText,
onConfirm: () {
onConfirm?.call();
Navigator.pop(context);
},
),
),
),
);
}

@override
State<FeedbackBottomModalSheet> createState() =>
_FeedbackBottomModalSheetState();
}

class _FeedbackBottomModalSheetState extends State<FeedbackBottomModalSheet> {
@override
void initState() {
super.initState();
_emailController.text = widget.defaultEmail ?? '';
_textController.text = widget.defaultText ?? '';
}

final _emailController = TextEditingController();
final _textController = TextEditingController();

String? _emailErrorText;
String? _textErrorText;

final _reEmail = RegExp(
r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
);

void _sendFeedback() async {
final email = _emailController.text;
final text = _textController.text;

if (email == null) {
setState(() {
_emailErrorText = 'Введите email';
});
return;
}

if (text == null) {
setState(() {
_textErrorText = 'Введите текст';
});
return;
}

if (!_reEmail.hasMatch(email)) {
setState(() {
_emailErrorText = 'Некорректный email';
});
return;
}

if (text.isEmpty) {
setState(() {
_textErrorText = 'Введите текст';
});
return;
}

setState(() {
_emailErrorText = null;
_textErrorText = null;
});

final SentryId sentryId = await Sentry.captureMessage(text);

final userFeedback = SentryUserFeedback(
eventId: sentryId,
email: email,
comments: text,
);

Sentry.captureUserFeedback(userFeedback).then((value) {
final message = 'Отзыв отправлен. Код ошибки: $sentryId';

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: AppTheme.colors.primary.withOpacity(0.8),
content: Text(message, style: AppTextStyle.captionL),
duration: const Duration(seconds: 3),
),
);
});
widget.onConfirm?.call();
}

@override
Widget build(BuildContext context) {
final border = OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Colors.transparent,
width: 0,
),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
Text(
'Оставить отзыв',
style: AppTextStyle.h5,
),
const SizedBox(height: 8),
Text(
'Кажется, у вас что-то пошло не так. Пожалуйста, напишите нам, и мы постараемся исправить это. Мы свяжемся по указанному email адресу для уточнения деталей.',
style: AppTextStyle.captionL.copyWith(
color: AppTheme.colors.deactive,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
]),
Text(
'Email',
style: AppTextStyle.chip.copyWith(
color: AppTheme.colors.deactive,
),
),
const SizedBox(height: 8),
TextField(
decoration: InputDecoration(
errorText: _emailErrorText,
errorStyle: AppTextStyle.captionL.copyWith(
color: AppTheme.colors.colorful07,
),
hintText: 'Введите email',
hintStyle: AppTextStyle.titleS.copyWith(
color: AppTheme.colors.deactive,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.primary,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.colorful07,
),
),
disabledBorder: border,
enabledBorder: border,
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.colorful07,
),
),
fillColor: AppTheme.colors.background01,
filled: true,
),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
style: AppTextStyle.titleS,
controller: _emailController,
),
const SizedBox(height: 24),
Text(
'Что случилось?',
style: AppTextStyle.chip.copyWith(
color: AppTheme.colors.deactive,
),
),
const SizedBox(height: 8),
TextField(
keyboardType: TextInputType.multiline,
maxLines: 5,
controller: _textController,
decoration: InputDecoration(
hintText: 'Когда я нажимаю "Х" происходит "У"',
hintStyle: AppTextStyle.bodyL.copyWith(
color: AppTheme.colors.deactive,
),
errorText: _textErrorText,
errorStyle: AppTextStyle.captionS.copyWith(
color: AppTheme.colors.colorful07,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.primary,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.colorful07,
),
),
disabledBorder: border,
enabledBorder: border,
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppTheme.colors.colorful07,
),
),
fillColor: AppTheme.colors.background01,
filled: true,
),
textInputAction: TextInputAction.done,
style: AppTextStyle.bodyL,
),
const SizedBox(height: 24),
PrimaryButton(
text: 'Отправить',
onClick: _sendFeedback,
),
],
),
);
}
}

0 comments on commit 0342ad0

Please sign in to comment.