forked from eatvio/eatvio_app
feat(chat): hero animation + chat select scren
This commit is contained in:
@@ -202,16 +202,14 @@ class AppRouter {
|
||||
path: '/chat-select-screen',
|
||||
name: 'chatSelectScreen',
|
||||
builder: (context, state) => ChatSelectScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) => ChatScreen(chatId: state.extra as String),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) => ChatScreen(chatId: state.extra as String),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/my-settings-screen',
|
||||
name: 'mySettingsScreen',
|
||||
|
||||
@@ -38,6 +38,9 @@ class InitializationCubit extends Cubit<InitializationState> {
|
||||
|
||||
await pusher.connect();
|
||||
|
||||
// Channel channel = pusher.subscribe('private-direct-chat.');
|
||||
// channel.registerListener(classId, method);
|
||||
|
||||
emit(const InitializationState.success());
|
||||
} catch (e) {
|
||||
emit(InitializationState.error(errorMessage: e.toString()));
|
||||
|
||||
@@ -81,119 +81,121 @@ class ChatScreen extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Color(0xFFF0F2F5),
|
||||
width: double.maxFinite,
|
||||
child: CustomScrollView(
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => ChatBubbleWidget(
|
||||
text: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].data,
|
||||
isCurrentUser: state.chatRoomModel.users['to_chat_user']!.id !=
|
||||
state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].authorId,
|
||||
mediaData: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].mediaData,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Color(0xFFF0F2F5),
|
||||
width: double.maxFinite,
|
||||
child: CustomScrollView(
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => ChatBubbleWidget(
|
||||
text: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].data,
|
||||
isCurrentUser: state.chatRoomModel.users['to_chat_user']!.id !=
|
||||
state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].authorId,
|
||||
mediaData: state.chatRoomModel.messages[state.chatRoomModel.messages.length - index - 1].mediaData,
|
||||
),
|
||||
childCount: state.chatRoomModel.messages.length,
|
||||
),
|
||||
childCount: state.chatRoomModel.messages.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
child: Ink(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Icon(
|
||||
Icons.attach_file_rounded,
|
||||
size: 28,
|
||||
color: Color(0xFF858E99),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
child: Ink(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Icon(
|
||||
Icons.attach_file_rounded,
|
||||
size: 28,
|
||||
color: Color(0xFF858E99),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: mediaQueryData.size.width - 96,
|
||||
child: TextFormField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Mulish',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: TextStyle(
|
||||
SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: mediaQueryData.size.width - 96,
|
||||
child: TextFormField(
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Mulish',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFFAEAEB2),
|
||||
color: Colors.black,
|
||||
),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Mulish',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Color(0xFFAEAEB2),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
InkWell(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
onTap: () {},
|
||||
child: Ink(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Icon(
|
||||
Icons.send_outlined,
|
||||
size: 28,
|
||||
color: Color(0xFF858E99),
|
||||
SizedBox(width: 8),
|
||||
InkWell(
|
||||
borderRadius: BorderRadiusStyle.circleBorder17,
|
||||
onTap: () {},
|
||||
child: Ink(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: Icon(
|
||||
Icons.send_outlined,
|
||||
size: 28,
|
||||
color: Color(0xFF858E99),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,17 +27,22 @@ class ChatSelectBloc extends Bloc<ChatSelectEvent, ChatSelectState> {
|
||||
List<ChatItemModel> chats = [];
|
||||
for (int i = 0; i < chatRooms.length; i++) {
|
||||
String text = chatRooms[i]['last_message']['data'];
|
||||
if (chatRooms[i]['last_message']['is_author_from_user']) {
|
||||
if (chatRooms[i]['last_message']['is_author_message_auth_user'] != null) {
|
||||
text = "Вы: " + text;
|
||||
}
|
||||
chats.add(ChatItemModel(
|
||||
chats.add(
|
||||
ChatItemModel(
|
||||
id: chatRooms[i]['id'],
|
||||
userImage: chatRooms[i]['users']['to_chat_user']
|
||||
['profile_photo_path'],
|
||||
userImage: chatRooms[i]['users']['to_chat_user']['profile_photo_path'],
|
||||
username: chatRooms[i]['users']['to_chat_user']['name'],
|
||||
dateText: DateFormat('dd.MM').format(
|
||||
DateTime.parse(chatRooms[i]['last_message']['updated_at'])),
|
||||
greetingText: text));
|
||||
DateTime.parse(
|
||||
chatRooms[i]['last_message']['updated_at'],
|
||||
),
|
||||
),
|
||||
greetingText: text,
|
||||
),
|
||||
);
|
||||
}
|
||||
return chats;
|
||||
}
|
||||
@@ -75,8 +80,7 @@ class ChatSelectBloc extends Bloc<ChatSelectEvent, ChatSelectState> {
|
||||
}
|
||||
} else {
|
||||
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
|
||||
emit(ChatSelectErrorState(
|
||||
'Некоторые необходимые данные отсутствуют.'));
|
||||
emit(ChatSelectErrorState('Некоторые необходимые данные отсутствуют.'));
|
||||
}
|
||||
} else {
|
||||
// Статус ответа не равен 200, обрабатываем ошибку
|
||||
|
||||
@@ -64,7 +64,10 @@ class ChatSelectScreen extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
} else if (state is ChatSelectErrorState) {
|
||||
return Center(child: Text(state.errorMessage));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(child: Text(state.errorMessage)),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
|
||||
@@ -4,26 +4,23 @@ import 'package:eatvio_app/core/app_export.dart';
|
||||
// ignore_for_file: must_be_immutable
|
||||
|
||||
class ChatItemModel {
|
||||
ChatItemModel(
|
||||
{this.userImage,
|
||||
this.username,
|
||||
this.dateText,
|
||||
this.greetingText,
|
||||
required this.id}) {
|
||||
String? userImage;
|
||||
String? username;
|
||||
String? dateText;
|
||||
String? greetingText;
|
||||
String id;
|
||||
|
||||
ChatItemModel({
|
||||
this.userImage,
|
||||
this.username,
|
||||
this.dateText,
|
||||
this.greetingText,
|
||||
required this.id,
|
||||
}) {
|
||||
userImage = userImage ?? ImageConstant.imageNotFound;
|
||||
username = username;
|
||||
dateText = dateText;
|
||||
greetingText = greetingText;
|
||||
id = id;
|
||||
}
|
||||
|
||||
String? userImage;
|
||||
|
||||
String? username;
|
||||
|
||||
String? dateText;
|
||||
|
||||
String? greetingText;
|
||||
|
||||
String id;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import 'package:equatable/equatable.dart';
|
||||
|
||||
/// This class defines the variables used in the [chat_select_screen],
|
||||
/// and is typically used to hold data that is passed between different parts of the application.
|
||||
|
||||
class ChatSelectModel extends Equatable {
|
||||
ChatSelectModel({this.chatList = const []});
|
||||
List<ChatItemModel> chatList;
|
||||
|
||||
ChatSelectModel copyWith({List<ChatItemModel>? chatList}) {
|
||||
return ChatSelectModel(
|
||||
chatList: chatList ?? this.chatList,
|
||||
|
||||
@@ -15,12 +15,13 @@ class ChatEntryWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => GoRouter.of(context).goNamed(
|
||||
return InkWell(
|
||||
borderRadius: BorderRadiusStyle.circleBorder8,
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: chatEntryObj.id.toString(),
|
||||
),
|
||||
child: Container(
|
||||
child: Ink(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadiusStyle.circleBorder8,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
@@ -34,37 +35,32 @@ class _PhotoViewScreenState extends State<PhotoViewScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.black,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
return DismissiblePage(
|
||||
minScale: 1,
|
||||
onDismissed: () => GoRouter.of(context).pop(),
|
||||
direction: DismissiblePageDismissDirection.vertical,
|
||||
isFullScreen: true,
|
||||
child: PhotoViewGallery.builder(
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
title: Text(
|
||||
(imageIndex + 1).toString() + ' из ' + widget.imageUrls.length.toString(),
|
||||
),
|
||||
),
|
||||
body: Dismissible(
|
||||
direction: DismissDirection.vertical,
|
||||
key: const Key('key'),
|
||||
onDismissed: (_) => GoRouter.of(context).pop(),
|
||||
resizeDuration: Duration(microseconds: 1),
|
||||
child: Container(
|
||||
child: PhotoViewGallery.builder(
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
builder: (BuildContext context, int index) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: NetworkImage(widget.imageUrls[index]),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: widget.imageUrls[index]),
|
||||
errorBuilder: (context, object, stackTrace) {
|
||||
return Column(
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
builder: (BuildContext context, int index) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: CachedNetworkImageProvider(
|
||||
widget.imageUrls[index],
|
||||
),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: widget.imageUrls[index],
|
||||
transitionOnUserGestures: true,
|
||||
),
|
||||
errorBuilder: (context, object, stackTrace) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 32),
|
||||
Text('Файл'),
|
||||
@@ -96,28 +92,28 @@ class _PhotoViewScreenState extends State<PhotoViewScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: widget.imageUrls.length,
|
||||
loadingBuilder: (context, event) => Center(
|
||||
child: Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
value: event == null ? 0 : event.cumulativeBytesLoaded / event.expectedTotalBytes!,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: widget.imageUrls.length,
|
||||
loadingBuilder: (context, event) => Center(
|
||||
child: Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
value: event == null ? 0 : event.cumulativeBytesLoaded / event.expectedTotalBytes!,
|
||||
),
|
||||
pageController: pageController,
|
||||
onPageChanged: (page) {
|
||||
setState(() {
|
||||
imageIndex = page;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
pageController: pageController,
|
||||
onPageChanged: (page) {
|
||||
setState(() {
|
||||
imageIndex = page;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/theme/app_decoration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ChatBubbleWidget extends StatelessWidget {
|
||||
@@ -71,18 +72,21 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'photoViewScreen',
|
||||
extra: mediaData!.map((e) => dotenv.env['STORAGE_URL']! + e.toString()).toList(),
|
||||
pathParameters: {
|
||||
'index': index.toString(),
|
||||
},
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: mediaData!.map((e) => dotenv.env['STORAGE_URL']! + e.toString()).toList(),
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadiusStyle.roundedBorder8,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: dotenv.env['STORAGE_URL']! + '${mediaData![index]}',
|
||||
child: Hero(
|
||||
tag: dotenv.env['STORAGE_URL']! + '${mediaData![index]}',
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadiusStyle.roundedBorder8,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: dotenv.env['STORAGE_URL']! + '${mediaData![index]}',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -175,13 +175,13 @@ class MyProfileScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
addImage(BuildContext context) async {
|
||||
final picker = ImagePicker();
|
||||
List<XFile>? images = await picker.pickMultiImage();
|
||||
if (images.isNotEmpty) {
|
||||
for (var image in images) {
|
||||
context.read<MyProfileBloc>().add(UploadAvatar(image));
|
||||
}
|
||||
final picker = ImagePicker();
|
||||
List<XFile>? images = await picker.pickMultiImage();
|
||||
if (images.isNotEmpty) {
|
||||
for (var image in images) {
|
||||
context.read<MyProfileBloc>().add(UploadAvatar(image));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTapSettings(BuildContext context) {
|
||||
@@ -192,7 +192,7 @@ class MyProfileScreen extends StatelessWidget {
|
||||
|
||||
_buildCarousel(BuildContext contex, MyProfileLoadedState state) {
|
||||
return CarouselSlider(
|
||||
options: CarouselOptions(height: 320.0),
|
||||
options: CarouselOptions(height: 340),
|
||||
items: [1, 2, 3].map((chartType) {
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -3,7 +3,7 @@ description: A new Flutter project.
|
||||
version: 0.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.4 <4.0.0'
|
||||
sdk: ">=3.3.4 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -60,8 +60,9 @@ dependencies:
|
||||
dio: ^5.4.3+1
|
||||
# websocket
|
||||
pusher_client: ^2.0.0
|
||||
# photo gallery viewer
|
||||
# photo gallery viewer + dismissible page
|
||||
photo_view: ^0.15.0
|
||||
dismissible_page: ^1.0.2
|
||||
#url launcher
|
||||
url_launcher: ^6.3.0
|
||||
|
||||
@@ -94,4 +95,4 @@ flutter:
|
||||
- asset: assets/fonts/MulishRegular.ttf
|
||||
weight: 400
|
||||
- asset: assets/fonts/MulishSemiBold.ttf
|
||||
weight: 600
|
||||
weight: 600
|
||||
|
||||
Reference in New Issue
Block a user