Skip to content

Commit

Permalink
feat: add error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
p0dyakov committed Apr 2, 2023
1 parent 3f26255 commit b7e5e79
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 153 deletions.
9 changes: 6 additions & 3 deletions lib/src/feature/chat/bloc/chat_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

import 'package:dart_openai/openai.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:gpt_client/src/feature/chat/bloc/chat_dependencies.dart';
import 'package:gpt_client/src/feature/chat/model/chat_data.dart';
Expand Down Expand Up @@ -51,14 +52,16 @@ class ChatBloc extends StreamBloc<ChatEvent, ChatState> {
yield ChatState.updatedSuccessfully(
data: _dependencies.chatRepository.currentData(),
);
} on RequestFailedException catch (e) {
yield ChatState.error(
data: _data,
error: e.message,
);
} on Object catch (e) {
yield ChatState.error(
data: _data,
error: e.toString(),
);
rethrow;
} finally {
yield ChatState.idle(data: _data);
}
}
}
328 changes: 187 additions & 141 deletions lib/src/feature/chat/page/chat_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart' as ui;
Expand All @@ -17,168 +18,213 @@ class ChatPage extends StatefulWidget {
}

class _ChatPageState extends State<ChatPage> {
var _isDialogShowing = false;

@override
Widget build(BuildContext context) => ChatScope(
child: BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) => Scaffold(
appBar: ChatAppBar(
isTyping: state is ChatStateLoading,
),
extendBodyBehindAppBar: true,
body: SafeArea(
child: ui.Chat(
messages: state.data.messages,
inputOptions: ui.InputOptions(
sendButtonVisibilityMode: state is ChatStateLoading
? ui.SendButtonVisibilityMode.hidden
: ui.SendButtonVisibilityMode.editing,
child: BlocListener<ChatBloc, ChatState>(
listener: _blocListener,
child: BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) {
return Scaffold(
appBar: ChatAppBar(
isTyping: state is ChatStateLoading,
),
onSendPressed: (partialText) => ChatScope.sendMessage(
context,
partialText.text,
),
emptyState: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image(
image: const AssetImage('asset/image/ai_logo.png'),
width: 150,
height: 150,
color: Theme.of(context).primaryColor,
extendBodyBehindAppBar: true,
body: SafeArea(
child: ui.Chat(
messages: state.data.messages,
inputOptions: ui.InputOptions(
sendButtonVisibilityMode: state is ChatStateLoading
? ui.SendButtonVisibilityMode.hidden
: ui.SendButtonVisibilityMode.editing,
),
const Text(
'Ask your assistant something',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w400,
),
onSendPressed: (partialText) => ChatScope.sendMessage(
context,
partialText.text,
),
const SizedBox(height: 3),
InkWell(
onTap: () => launchUrl(
Uri.parse('https://openai.com/blog/chatgpt'),
),
child: Text(
'More about Chat GPT',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
emptyState: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image(
image: const AssetImage('asset/image/ai_logo.png'),
width: 150,
height: 150,
color: Theme.of(context).primaryColor,
),
),
const Text(
'Ask your assistant something',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 3),
InkWell(
onTap: () => launchUrl(
Uri.parse('https://openai.com/blog/chatgpt'),
),
child: Text(
'More about Chat GPT',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Theme.of(context).primaryColor,
),
),
),
],
),
],
),
user: state.data.user,
scrollPhysics: const BouncingScrollPhysics(),
dateHeaderBuilder: (header) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Center(
child: Text(
DateFormat('MMMM d, y').format(header.dateTime),
style: const TextStyle(
color: Color.fromARGB(255, 138, 138, 138),
fontSize: 12,
fontWeight: FontWeight.w500,
user: state.data.user,
scrollPhysics: const BouncingScrollPhysics(),
dateHeaderBuilder: (header) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Center(
child: Text(
DateFormat('MMMM d, y').format(header.dateTime),
style: const TextStyle(
color: Color.fromARGB(255, 138, 138, 138),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
theme: ui.DefaultChatTheme(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
theme: ui.DefaultChatTheme(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,

/// MESSAGES
messageInsetsHorizontal: 10,
messageInsetsVertical: 8,
messageBorderRadius: 14,
primaryColor: Theme.of(context).primaryColor,
secondaryColor: const Color.fromARGB(255, 239, 239, 239),
receivedMessageBodyTextStyle: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),
sentMessageBodyTextStyle: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),
/// MESSAGES
messageInsetsHorizontal: 10,
messageInsetsVertical: 8,
messageBorderRadius: 14,
primaryColor: Theme.of(context).primaryColor,
secondaryColor: const Color.fromARGB(255, 239, 239, 239),
receivedMessageBodyTextStyle: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),
sentMessageBodyTextStyle: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),

/// INPUT
inputBorderRadius: BorderRadius.zero,
inputBackgroundColor: Colors.white,
inputTextColor: Colors.black,
inputTextCursorColor: Theme.of(context).primaryColor,
inputPadding: EdgeInsets.only(
left: 10,
right: 10,
top: 7,
bottom:
MediaQuery.of(context).viewInsets.bottom != 0 ? 7 : 30,
),
inputTextStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),
inputContainerDecoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
/// INPUT
inputBorderRadius: BorderRadius.zero,
inputBackgroundColor: Colors.white,
inputTextColor: Colors.black,
inputTextCursorColor: Theme.of(context).primaryColor,
inputPadding: EdgeInsets.only(
left: 10,
right: 10,
top: 7,
bottom: MediaQuery.of(context).viewInsets.bottom != 0
? 7
: 30,
),
),
),
inputTextDecoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
inputTextStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.3,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
inputContainerDecoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
),
),
),
inputTextDecoration: const InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(
color: Color.fromARGB(255, 204, 204, 204),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 13,
vertical: 8,
),
isCollapsed: true,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 13,
vertical: 8,
),
isCollapsed: true,
),

/// SEND BUTTON
sendButtonMargin: EdgeInsets.zero,
sendButtonIcon: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(14)),
color: Theme.of(context).primaryColor,
),
padding: const EdgeInsets.all(2),
child: Transform.rotate(
angle: 1.5708,
child: const Icon(
Icons.arrow_back_rounded,
color: Colors.white,
size: 19,
/// SEND BUTTON
sendButtonMargin: EdgeInsets.zero,
sendButtonIcon: Container(
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(14)),
color: Theme.of(context).primaryColor,
),
padding: const EdgeInsets.all(2),
child: Transform.rotate(
angle: 1.5708,
child: const Icon(
Icons.arrow_back_rounded,
color: Colors.white,
size: 19,
),
),
),
),
),
),
),
),
);
},
),
),
);

void _blocListener(BuildContext context, ChatState state) {
if (!_isDialogShowing && state is ChatStateError) {
Future.delayed(
Duration.zero,
() => _showErrorDialog(context, state.error),
);
}
}

Future<void> _showErrorDialog(BuildContext context, String error) async {
_isDialogShowing = true;
await showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoAlertDialog(
title: const Text('An error has occurred'),
content: Text(error),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: Text(
'Okay',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 17,
color: Theme.of(context).primaryColor,
),
),
),
],
),
);
_isDialogShowing = false;
}
}
2 changes: 1 addition & 1 deletion lib/src/feature/chat/repository/chat_repository.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:dart_openai/openai.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:gpt_client/src/feature/chat/model/open_ai_message.dart';
import 'package:gpt_client/src/feature/chat/model/chat_data.dart';
import 'package:gpt_client/src/feature/chat/model/open_ai_message.dart';
import 'package:gpt_client/src/feature/chat/repository/chat_repository_dependencies.dart';
import 'package:gpt_client/src/feature/chat/repository/chat_repository_interface.dart';
import 'package:uuid/uuid.dart';
Expand Down
Loading

0 comments on commit b7e5e79

Please sign in to comment.