Skip to content

Commit

Permalink
fix: user feedback when loading state gets halted on submit button, w…
Browse files Browse the repository at this point in the history
…hen network state is offline
  • Loading branch information
mediocre9 committed Jan 6, 2024
1 parent 9f5d41b commit fa00243
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 41 deletions.
61 changes: 40 additions & 21 deletions lib/screens/feedback_screen/cubit/feedback_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,54 +1,73 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:smart_link/config/strings/app_strings.dart';
import 'package:smart_link/services/auth_service.dart';
import 'package:smart_link/services/feedback_service.dart';

import '../../../models/user_feedback_model.dart';
import 'package:smart_link/config/config.dart';
import 'package:smart_link/models/models.dart';
import 'package:smart_link/services/services.dart';

part 'feedback_state.dart';

class FeedbackCubit extends Cubit<FeedbackState> {
final FeedbackService feedbackService;
final AuthenticationService service;
final GoogleAuthService authService;

FeedbackCubit({
required this.feedbackService,
required this.service,
required this.authService,
}) : super(const FeedbackInitial(color: Colors.grey));

Future<void> submitFeedback(String subject, String body) async {
if (isRequiredFeedbackEmpty(subject, body)) return;

emit(const Loading(color: Colors.blue));

final DateTime(:day, :month, :year) = DateTime.now();

final currentDate = DateTime(day, month, year);

final feedback = UserFeedback(
id: service.getCurrentUser!.uid,
email: service.getCurrentUser!.email!,
username: service.getCurrentUser!.displayName!,
final feedback = _createUserFeedback(
subject: subject,
body: body,
submittedDate: currentDate,
service: authService,
);

await feedbackService.post(feedback);

emit(const Submitted(message: AppStrings.feedbackPosted));
emit(const FeedbackInitial(color: Colors.grey));
try {
await feedbackService.post(feedback);
emit(const Submitted(message: AppStrings.feedbackPosted));
} on NetworkException {
emit(const Error(message: AppStrings.noInternet));
} catch (e) {
emit(const Error(message: 'Something went wrong!'));
} finally {
emit(const FeedbackInitial(color: Colors.grey));
}
}

bool isRequiredFeedbackEmpty(String subject, String body) {
if (subject.isNotEmpty && body.isNotEmpty) {
emit(const NonEmptyState(color: Colors.blue));
emit(const EmptyFieldsState(color: Colors.blue));
return false;
}

emit(const FeedbackInitial(color: Colors.grey));
return true;
}

DateTime _getCurrentDate() {
final DateTime(:day, :month, :year) = DateTime.now();
return DateTime(day, month, year);
}

UserFeedback _createUserFeedback({
required String subject,
required String body,
required GoogleAuthService service,
}) {
final currentDate = _getCurrentDate();

return UserFeedback(
id: service.getCurrentUser!.uid,
email: service.getCurrentUser!.email!,
username: service.getCurrentUser!.displayName!,
subject: subject,
body: body,
submittedDate: currentDate,
);
}
}
10 changes: 8 additions & 2 deletions lib/screens/feedback_screen/cubit/feedback_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ final class Loading extends FeedbackState {
const Loading({required this.color});
}

final class NonEmptyState extends FeedbackState {
final class Error extends FeedbackState {
final String message;

const Error({required this.message});
}

final class EmptyFieldsState extends FeedbackState {
final Color color;

const NonEmptyState({required this.color});
const EmptyFieldsState({required this.color});
}

final class Submitted extends FeedbackState {
Expand Down
167 changes: 158 additions & 9 deletions lib/screens/feedback_screen/feedback_screen.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smart_link/common/common.dart';
import 'package:smart_link/screens/feedback_screen/cubit/feedback_cubit.dart';
import 'package:smart_link/common/standard_app_widgets.dart';

class FeedbackScreen extends StatefulWidget {
const FeedbackScreen({super.key});
Expand All @@ -10,7 +10,8 @@ class FeedbackScreen extends StatefulWidget {
State<FeedbackScreen> createState() => _FeedbackScreenState();
}

class _FeedbackScreenState extends State<FeedbackScreen> with StandardAppWidgets {
class _FeedbackScreenState extends State<FeedbackScreen>
with StandardAppWidgets {
final TextEditingController _subjectController = TextEditingController();
final TextEditingController _bodyController = TextEditingController();

Expand All @@ -27,6 +28,10 @@ class _FeedbackScreenState extends State<FeedbackScreen> with StandardAppWidgets
if (state is Submitted) {
showSnackBarWidget(context, state.message);
}

if (state is Error) {
showSnackBarWidget(context, state.message);
}
},
builder: (context, state) {
switch (state) {
Expand All @@ -36,18 +41,24 @@ class _FeedbackScreenState extends State<FeedbackScreen> with StandardAppWidgets
icon: Icon(Icons.send_rounded, color: state.color),
);

case NonEmptyState():
case EmptyFieldsState():
return IconButton(
icon: Icon(Icons.send_rounded, color: state.color),
onPressed: () async {
FocusManager.instance.primaryFocus?.unfocus();
await context.read<FeedbackCubit>().submitFeedback(_subjectController.text, _bodyController.text);
await context.read<FeedbackCubit>().submitFeedback(
_subjectController.text,
_bodyController.text,
);
_clearTextFields();
},
);

case Loading():
return Transform.scale(scale: 0.7, child: CircularProgressIndicator(color: state.color));
return Transform.scale(
scale: 0.7,
child: CircularProgressIndicator(color: state.color),
);

default:
return Container();
Expand All @@ -61,19 +72,29 @@ class _FeedbackScreenState extends State<FeedbackScreen> with StandardAppWidgets
child: Column(
children: [
TextField(
decoration: const InputDecoration(hintText: "Subject (required)"),
decoration: const InputDecoration(
hintText: "Subject (required)",
),
maxLength: 70,
controller: _subjectController,
onChanged: (_) {
context.read<FeedbackCubit>().isRequiredFeedbackEmpty(_subjectController.text, _bodyController.text);
context.read<FeedbackCubit>().isRequiredFeedbackEmpty(
_subjectController.text,
_bodyController.text,
);
},
),
TextField(
decoration: const InputDecoration(hintText: "Body (required)"),
decoration: const InputDecoration(
hintText: "Body (required)",
),
maxLines: 5,
controller: _bodyController,
onChanged: (_) {
context.read<FeedbackCubit>().isRequiredFeedbackEmpty(_subjectController.text, _bodyController.text);
context.read<FeedbackCubit>().isRequiredFeedbackEmpty(
_subjectController.text,
_bodyController.text,
);
},
),
],
Expand All @@ -95,3 +116,131 @@ class _FeedbackScreenState extends State<FeedbackScreen> with StandardAppWidgets
_bodyController.clear();
}
}













// import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:smart_link/common/common.dart';
// import 'package:smart_link/screens/feedback_screen/cubit/feedback_cubit.dart';

// class FeedbackScreen extends StatefulWidget {
// const FeedbackScreen({super.key});

// @override
// State<FeedbackScreen> createState() => _FeedbackScreenState();
// }

// class _FeedbackScreenState extends State<FeedbackScreen>
// with StandardAppWidgets {
// final TextEditingController _subjectController = TextEditingController();
// final TextEditingController _bodyController = TextEditingController();

// @override
// Widget build(BuildContext context) {
// return GestureDetector(
// onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
// child: Scaffold(
// appBar: AppBar(
// title: const Text("Feedback"),
// actions: [
// BlocConsumer<FeedbackCubit, FeedbackState>(
// listener: (context, state) {
// if (state is Submitted) {
// showSnackBarWidget(context, state.message);
// }
// },
// builder: (context, state) {
// switch (state) {
// case FeedbackInitial():
// return IconButton(
// onPressed: null,
// icon: Icon(Icons.send_rounded, color: state.color),
// );

// case NonEmptyState():
// return IconButton(
// icon: Icon(Icons.send_rounded, color: state.color),
// onPressed: () async {
// FocusManager.instance.primaryFocus?.unfocus();
// await context.read<FeedbackCubit>().submitFeedback(
// _subjectController.text,
// _bodyController.text,
// );
// _clearTextFields();
// },
// );

// case Loading():
// return Transform.scale(
// scale: 0.7,
// child: CircularProgressIndicator(color: state.color),
// );

// default:
// return Container();
// }
// },
// )
// ],
// ),
// body: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Column(
// children: [
// TextField(
// decoration:
// const InputDecoration(hintText: "Subject (required)"),
// maxLength: 70,
// controller: _subjectController,
// onChanged: (_) {
// context.read<FeedbackCubit>().isRequiredFeedbackEmpty(
// _subjectController.text,
// _bodyController.text,
// );
// },
// ),
// TextField(
// decoration: const InputDecoration(hintText: "Body (required)"),
// maxLines: 5,
// controller: _bodyController,
// onChanged: (_) {
// context.read<FeedbackCubit>().isRequiredFeedbackEmpty(
// _subjectController.text,
// _bodyController.text,
// );
// },
// ),
// ],
// ),
// ),
// ),
// );
// }

// validateRequiredFields(BuildContext context) {

// }

// @override
// void dispose() {
// super.dispose();
// _subjectController.dispose();
// _bodyController.dispose();
// }

// void _clearTextFields() {
// _subjectController.clear();
// _bodyController.clear();
// }
// }
24 changes: 15 additions & 9 deletions lib/services/feedback_service.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'dart:developer';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:smart_link/models/user_feedback_model.dart';

import '../models/model.dart';
import 'package:smart_link/models/models.dart';
import 'package:smart_link/services/services.dart';

abstract interface class IFeedbackService<T extends Model> {
Future<void> post(T data);
Expand All @@ -11,19 +10,26 @@ abstract interface class IFeedbackService<T extends Model> {
class FeedbackService extends IFeedbackService<UserFeedback> {
final FirebaseFirestore firestore;
final CollectionReference<UserFeedback> _feedbackCollections;
final IConnectivityService connectivityService;

FeedbackService({required this.firestore})
: _feedbackCollections = firestore.collection('feedback-reports').withConverter(
fromFirestore: (snapshot, _) => UserFeedback.fromJSON(snapshot.data()!),
toFirestore: (UserFeedback feedback, _) => feedback.toJSON(),
);
FeedbackService({
required this.connectivityService,
required this.firestore,
}) : _feedbackCollections =
firestore.collection('feedback-reports').withConverter(
fromFirestore: (snapshot, _) =>
UserFeedback.fromJSON(snapshot.data()!),
toFirestore: (UserFeedback feedback, _) => feedback.toJSON(),
);

@override
Future<void> post(UserFeedback data) async {
try {
if (await connectivityService.isOffline()) throw NetworkException();
await _feedbackCollections.doc().set(data);
} catch (e) {
log("FirebaseFirestore Exception: $e");
log("Feedback Exception: $e");
rethrow;
}
}
}

0 comments on commit fa00243

Please sign in to comment.