From fa00243c7ad22b37eef535898542cece8379af2a Mon Sep 17 00:00:00 2001 From: mediocre9 Date: Sun, 7 Jan 2024 01:07:52 +0500 Subject: [PATCH] fix: user feedback when loading state gets halted on submit button, when network state is offline --- .../feedback_screen/cubit/feedback_cubit.dart | 61 ++++--- .../feedback_screen/cubit/feedback_state.dart | 10 +- .../feedback_screen/feedback_screen.dart | 167 +++++++++++++++++- lib/services/feedback_service.dart | 24 ++- 4 files changed, 221 insertions(+), 41 deletions(-) diff --git a/lib/screens/feedback_screen/cubit/feedback_cubit.dart b/lib/screens/feedback_screen/cubit/feedback_cubit.dart index dada94d..3453ea8 100644 --- a/lib/screens/feedback_screen/cubit/feedback_cubit.dart +++ b/lib/screens/feedback_screen/cubit/feedback_cubit.dart @@ -1,21 +1,19 @@ 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 { 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 submitFeedback(String subject, String body) async { @@ -23,32 +21,53 @@ class FeedbackCubit extends Cubit { 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, + ); + } } diff --git a/lib/screens/feedback_screen/cubit/feedback_state.dart b/lib/screens/feedback_screen/cubit/feedback_state.dart index 076dd93..b531dc6 100644 --- a/lib/screens/feedback_screen/cubit/feedback_state.dart +++ b/lib/screens/feedback_screen/cubit/feedback_state.dart @@ -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 { diff --git a/lib/screens/feedback_screen/feedback_screen.dart b/lib/screens/feedback_screen/feedback_screen.dart index 7612c32..75e72fd 100644 --- a/lib/screens/feedback_screen/feedback_screen.dart +++ b/lib/screens/feedback_screen/feedback_screen.dart @@ -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}); @@ -10,7 +10,8 @@ class FeedbackScreen extends StatefulWidget { State createState() => _FeedbackScreenState(); } -class _FeedbackScreenState extends State with StandardAppWidgets { +class _FeedbackScreenState extends State + with StandardAppWidgets { final TextEditingController _subjectController = TextEditingController(); final TextEditingController _bodyController = TextEditingController(); @@ -27,6 +28,10 @@ class _FeedbackScreenState extends State with StandardAppWidgets if (state is Submitted) { showSnackBarWidget(context, state.message); } + + if (state is Error) { + showSnackBarWidget(context, state.message); + } }, builder: (context, state) { switch (state) { @@ -36,18 +41,24 @@ class _FeedbackScreenState extends State 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().submitFeedback(_subjectController.text, _bodyController.text); + await context.read().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(); @@ -61,19 +72,29 @@ class _FeedbackScreenState extends State 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().isRequiredFeedbackEmpty(_subjectController.text, _bodyController.text); + context.read().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().isRequiredFeedbackEmpty(_subjectController.text, _bodyController.text); + context.read().isRequiredFeedbackEmpty( + _subjectController.text, + _bodyController.text, + ); }, ), ], @@ -95,3 +116,131 @@ class _FeedbackScreenState extends State 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 createState() => _FeedbackScreenState(); +// } + +// class _FeedbackScreenState extends State +// 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( +// 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().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().isRequiredFeedbackEmpty( +// _subjectController.text, +// _bodyController.text, +// ); +// }, +// ), +// TextField( +// decoration: const InputDecoration(hintText: "Body (required)"), +// maxLines: 5, +// controller: _bodyController, +// onChanged: (_) { +// context.read().isRequiredFeedbackEmpty( +// _subjectController.text, +// _bodyController.text, +// ); +// }, +// ), +// ], +// ), +// ), +// ), +// ); +// } + +// validateRequiredFields(BuildContext context) { + +// } + +// @override +// void dispose() { +// super.dispose(); +// _subjectController.dispose(); +// _bodyController.dispose(); +// } + +// void _clearTextFields() { +// _subjectController.clear(); +// _bodyController.clear(); +// } +// } diff --git a/lib/services/feedback_service.dart b/lib/services/feedback_service.dart index 693980e..7bc5245 100644 --- a/lib/services/feedback_service.dart +++ b/lib/services/feedback_service.dart @@ -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 { Future post(T data); @@ -11,19 +10,26 @@ abstract interface class IFeedbackService { class FeedbackService extends IFeedbackService { final FirebaseFirestore firestore; final CollectionReference _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 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; } } }