diff --git a/lib/interfaces/message_interface.dart b/lib/interfaces/message_interface.dart index c83d833..6a2c9ed 100644 --- a/lib/interfaces/message_interface.dart +++ b/lib/interfaces/message_interface.dart @@ -3,5 +3,5 @@ import 'package:chat_flutter/model/message.dart'; abstract class MessageInterface { Future sendMessage(String text, String roomId, String senderId); - Stream> getMessage(String roomId); + Stream> getMessage(String roomId, String userId); } diff --git a/lib/interfaces/user.dart b/lib/interfaces/user_interface.dart similarity index 100% rename from lib/interfaces/user.dart rename to lib/interfaces/user_interface.dart diff --git a/lib/main.dart b/lib/main.dart index dfd77dc..7e1cba2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,15 @@ -import 'package:chat_flutter/services/messgae_service.dart'; -import 'package:chat_flutter/ui/pages/create_room/select_member.dart'; +import 'package:chat_flutter/services/message_service.dart'; +import 'package:chat_flutter/ui/pages/create_room/select_member_page.dart'; import 'package:chat_flutter/services/auth/authenticator.dart'; import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; -import 'package:chat_flutter/ui/pages/sign_in/sign_in.dart'; -import 'package:chat_flutter/ui/pages/sign_up/sign_up.dart'; +import 'package:chat_flutter/ui/pages/room/room_edit_page.dart'; +import 'package:chat_flutter/ui/pages/sign_in/sign_in_page.dart'; +import 'package:chat_flutter/ui/pages/sign_up/sign_up_page.dart'; import 'package:flutter/material.dart'; -import 'package:chat_flutter/ui/pages/create_room/create_room.dart'; -import 'package:chat_flutter/ui/pages/home/home.dart'; -import 'package:chat_flutter/ui/pages/profile/profile_edit.dart'; -import 'package:chat_flutter/ui/pages/room/room.dart'; +import 'package:chat_flutter/ui/pages/create_room/create_room_page.dart'; +import 'package:chat_flutter/ui/pages/home/home_page.dart'; +import 'package:chat_flutter/ui/pages/profile/profile_edit_page.dart'; +import 'package:chat_flutter/ui/pages/room/room_page.dart'; import 'package:provider/provider.dart'; void main() { @@ -22,14 +23,13 @@ void main() { create: (_) => MessageService(), ), ], - child: MyApp(), + child: Wrapped(), )); } -class MyApp extends StatelessWidget { +class Wrapped extends StatelessWidget { @override Widget build(BuildContext context) { - // Authenticatorをコンストラクタで渡したかったので、MultiProviderではできなかった return ChangeNotifierProvider( create: (_) => ProfileController( Provider.of( @@ -37,29 +37,46 @@ class MyApp extends StatelessWidget { listen: false, ), ), - child: MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - initialRoute: - Provider.of(context, listen: false).isSignIn.value - ? '/homePage' - : '/signUpPage', - routes: { - '/homePage': HomePage.wrapped, - '/signUpPage': (context) => SignUpPage.wrapped( - Provider.of(context, listen: false)), - '/signInPage': (context) => SignInPage.wrapped( - Provider.of(context, listen: false)), - '/roomPage': RoomPage.wrapped, - '/profileEditPage': (context) => ProfileEditPage(), - '/selectMemberPage': (context) => SelectMemberPage.wrapped(), - '/createGroupPage': (context) => CreateRoomPage.wrapped( - Provider.of(context, listen: false)), - }, - ), + child: MyApp(), ); } } + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: Provider.of(context, listen: false) + .getUserById(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Container( + color: Colors.white, + ); + } + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + initialRoute: snapshot.data != '' ? '/homePage' : '/signUpPage', + routes: { + '/homePage': HomePage.wrapped, + '/signUpPage': (context) => SignUpPage.wrapped( + Provider.of(context, listen: false)), + '/signInPage': (context) => SignInPage.wrapped( + Provider.of(context, listen: false)), + '/roomPage': (context) => RoomPage.wrapped( + context, Provider.of(context, listen: false)), + '/profileEditPage': (context) => ProfileEditPage(), + '/roomEditPage': (context) => RoomEditPage(), + '/selectMemberPage': (context) => SelectMemberPage.wrapped( + Provider.of(context, listen: false)), + '/createGroupPage': (context) => CreateRoomPage.wrapped( + Provider.of(context, listen: false)), + }, + ); + }); + } +} diff --git a/lib/model/group.dart b/lib/model/group.dart deleted file mode 100644 index cd26d28..0000000 --- a/lib/model/group.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Group { - String name; - String imgUrl; - final bool isMe = false; - Group({ - this.name, - this.imgUrl, - }); -} diff --git a/lib/model/message.dart b/lib/model/message.dart index b2485ab..f548796 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -4,29 +4,26 @@ class Message { String text; DateTime sendTime; bool isMe; - bool isRead; String senderId; String roomId; Message({ this.text, this.sendTime, this.isMe, - this.isRead, this.senderId, this.roomId, }); - factory Message.fromJson(Map json) { + factory Message.fromJson(Map json, String userId) { bool judgeIsMe(String senderId) { // 自分のIDと比較 - return senderId == 'userId'; + return senderId == userId; } return Message( text: json['text'].toString(), sendTime: (json['createdAt'] as Timestamp).toDate(), isMe: judgeIsMe(json['from'].toString()), - isRead: true, senderId: json['from'].toString(), roomId: json['to'].toString(), ); diff --git a/lib/model/room.dart b/lib/model/room.dart index 25534c6..656ade2 100644 --- a/lib/model/room.dart +++ b/lib/model/room.dart @@ -4,14 +4,22 @@ class Room { String name; String imgUrl; List members; - String lastMessage; - String sendTime; + Map lastMessage; Room({ this.id, this.name, this.imgUrl, this.members, this.lastMessage, - this.sendTime, }); + + factory Room.fromJson(Map json, String roomId) { + return Room( + id: roomId, + name: json['name'].toString(), + imgUrl: json['imgUrl'].toString(), + members: json['members'].cast() as List, + lastMessage: json['lastMessage'] as Map, + ); + } } diff --git a/lib/providers/user.dart b/lib/providers/user.dart deleted file mode 100644 index cc4d2bc..0000000 --- a/lib/providers/user.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:chat_flutter/services/user.dart'; -import 'package:flutter/material.dart'; - -import 'package:chat_flutter/model/user.dart'; - -class UserProvider with ChangeNotifier { - UserProvider() { - _initialize(); - } - - UserService _userService; - - void _initialize() { - _userService = UserService(); - } - - Future getUserById(String userId) { - return _userService.getUserById(userId); - } -} diff --git a/lib/services/auth/authenticator.dart b/lib/services/auth/authenticator.dart index c62934c..1650349 100644 --- a/lib/services/auth/authenticator.dart +++ b/lib/services/auth/authenticator.dart @@ -3,10 +3,10 @@ import 'package:rxdart/rxdart.dart'; class Authenticator { Authenticator() { - _isSignIn.value = firebaseUser.value != null; + _isSignIn.value = _firebaseUser.value != null; _firebaseAuth.onAuthStateChanged.pipe(_firebaseUser); _firebaseAuth.onAuthStateChanged - .map((firebaseUser) => firebaseUser != null) + .map((firebaseUser) => _firebaseUser != null) .pipe(_isSignIn); } @@ -50,6 +50,9 @@ class Authenticator { Future getUid() async { final current = await fetchFirebaseUser(); + if (current == null) { + return ''; + } return current.uid; } diff --git a/lib/services/firebase_message_service.dart b/lib/services/firebase_message_service.dart index 1967edd..61b88ba 100644 --- a/lib/services/firebase_message_service.dart +++ b/lib/services/firebase_message_service.dart @@ -9,22 +9,27 @@ class FirebaseMessageService { 'from': message.senderId, 'to': message.roomId, 'text': message.text, - 'createdAt': message.sendTime, + 'createdAt': FieldValue.serverTimestamp(), }; + final updateData = {'lastMessage': messageData}; await _db .collection('message/v1/rooms/${message.roomId}/transcripts') .document() .setData(messageData); + await _db + .collection('message/v1/rooms') + .document('${message.roomId}') + .updateData(updateData); } - Stream> getMessageData(String roomId) { + Stream> getMessageData(String roomId, String userId) { final Stream querySnapshot = _db .collection('message/v1/rooms/$roomId/transcripts') .orderBy('createdAt', descending: false) .snapshots(); return querySnapshot.map((snapshot) { return snapshot.documents.map((doc) { - return Message.fromJson(doc.data); + return Message.fromJson(doc.data, userId); }).toList(); }); } diff --git a/lib/services/firebase_room_service.dart b/lib/services/firebase_room_service.dart index a8f510b..a666128 100644 --- a/lib/services/firebase_room_service.dart +++ b/lib/services/firebase_room_service.dart @@ -16,7 +16,7 @@ class FirebaseRoomService { return docRef.documentID; } - Future setRoomSetting(String userId, String roomId) async { + Future setMyRoomSetting(String userId, String roomId) async { final settingData = { 'allowNotification': false, }; @@ -26,4 +26,63 @@ class FirebaseRoomService { .document(roomId) .setData(settingData); } + + Future updateRoomData(Room room) async { + final Map roomData = { + 'name': room.name, + 'imgUrl': room.imgUrl, + }; + await _db + .collection('message/v1/rooms') + .document(room.id) + .updateData(roomData); + } + + Future setMyLastReadTime(String roomId, String userId) async { + final timeData = { + 'lastReadTime': FieldValue.serverTimestamp(), + }; + await _db + .collection('message/v1/rooms/$roomId/members') + .document('$userId') + .setData(timeData); + } + + Stream> getLastReadTimeList(String roomId) { + final Stream querySnapshot = _db + .collection('message/v1/rooms/$roomId/members') + .orderBy('lastReadTime', descending: false) + .snapshots(); + return querySnapshot.map((snapshot) { + return snapshot.documents.map((doc) { + return (doc.data['lastReadTime'] as Timestamp).toDate(); + }).toList(); + }); + } + + Future> getMyRoomList(String uid) async { + final QuerySnapshot querySnapshot = await _db + .collection('message/v1/users/$uid/room_setting') + .getDocuments(); + final List docList = querySnapshot.documents; + final List roomIdList = + docList.map((doc) => doc.documentID).toList(); + final List roomList = []; + for (final String roomId in roomIdList) { + final DocumentSnapshot roomData = + await _db.collection('message/v1/rooms').document('$roomId').get(); + roomList.add(Room.fromJson(roomData.data, roomId)); + } + //時間順に並び替え + roomList.sort((a, b) { + final aTime = a.lastMessage['createdAt'] as Timestamp; + final bTime = b.lastMessage['createdAt'] as Timestamp; + if (aTime.compareTo(bTime) > 0) { + return -1; + } else { + return 1; + } + }); + return roomList; + } } diff --git a/lib/services/firebase_storage_service.dart b/lib/services/firebase_storage_service.dart index 1567801..1ad7985 100644 --- a/lib/services/firebase_storage_service.dart +++ b/lib/services/firebase_storage_service.dart @@ -6,9 +6,9 @@ class FirebaseStorageService { final _storage = FirebaseStorage.instance; Future uploadImage( - File file, String uid, StorageType storageType) async { + File file, String id, StorageType storageType) async { final StorageReference ref = - _storage.ref().child('${storageTypeToString(storageType)}/$uid'); + _storage.ref().child('${storageTypeToString(storageType)}/$id'); final StorageUploadTask uploadTask = ref.putFile(file); await uploadTask.onComplete; diff --git a/lib/services/firebase_user_service.dart b/lib/services/firebase_user_service.dart index 6720401..90d12d6 100644 --- a/lib/services/firebase_user_service.dart +++ b/lib/services/firebase_user_service.dart @@ -29,7 +29,13 @@ class FirebaseUserService { return User.fromJson(user, uid); } - Future> getAllUser() async { + Future getUserImgUrl(String uid) async { + final DocumentSnapshot result = + await _db.collection('message/v1/users').document('$uid').get(); + return result.data['profileImageURL'].toString(); + } + + /*Future> getAllUser() async { final QuerySnapshot result = await _db.collection('message/v1/users').getDocuments(); @@ -37,5 +43,20 @@ class FirebaseUserService { final Map user = doc.data; return User.fromJson(user, doc.documentID); }).toList(); + }*/ + + Future> getSearchedUser(String name) async { + final startAt = [name]; + final endAt = ['$name\uf8ff']; + final QuerySnapshot result = await _db + .collection('message/v1/users') + .orderBy('name') + .startAt(startAt) + .endAt(endAt) + .getDocuments(); + return result.documents.map((doc) { + final Map user = doc.data; + return User.fromJson(user, doc.documentID); + }).toList(); } } diff --git a/lib/services/messgae_service.dart b/lib/services/message_service.dart similarity index 78% rename from lib/services/messgae_service.dart rename to lib/services/message_service.dart index bf657c7..5134040 100644 --- a/lib/services/messgae_service.dart +++ b/lib/services/message_service.dart @@ -9,13 +9,12 @@ class MessageService implements MessageInterface { text: text, senderId: senderId, roomId: roomId, - sendTime: DateTime.now(), ); await FirebaseMessageService().setMessageData(message); } @override - Stream> getMessage(String roomId) { - return FirebaseMessageService().getMessageData(roomId); + Stream> getMessage(String roomId, String userId) { + return FirebaseMessageService().getMessageData(roomId, userId); } } diff --git a/lib/services/user.dart b/lib/services/user.dart deleted file mode 100644 index 1b6de0e..0000000 --- a/lib/services/user.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:chat_flutter/interfaces/user.dart'; -import 'package:chat_flutter/model/user.dart'; - -class UserService implements UserInterface { - UserService(); - - @override - Future getUserById(String userId) async { - final User user = User(name: 'test', imgUrl: ''); - - await Future.delayed(const Duration(seconds: 1)); - return Future.value(user); - } -} diff --git a/lib/ui/atoms/circular_image.dart b/lib/ui/atoms/circular_image.dart new file mode 100644 index 0000000..31ee492 --- /dev/null +++ b/lib/ui/atoms/circular_image.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class CircularImage extends StatelessWidget { + const CircularImage({Key key, @required this.size, this.imgUrl}) + : super(key: key); + final double size; + final String imgUrl; + + @override + Widget build(BuildContext context) { + if (imgUrl != null && imgUrl.isNotEmpty) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(imgUrl), + ), + ), + ); + } else { + return Container( + width: size, + height: size, + decoration: const BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage('assets/images/avatar.JPG'), + ), + ), + ); + } + } +} diff --git a/lib/ui/atoms/error_dialog.dart b/lib/ui/atoms/error_dialog.dart new file mode 100644 index 0000000..5e4a27b --- /dev/null +++ b/lib/ui/atoms/error_dialog.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class ErrorDialog extends StatelessWidget { + const ErrorDialog(this.errorMessage); + final String errorMessage; + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('ERROR'), + content: Text(errorMessage), + actions: [ + FlatButton( + child: const Text('OK'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ); + } +} diff --git a/lib/ui/atoms/input_text_field.dart b/lib/ui/atoms/input_text_field.dart index 0c118d5..14b3774 100644 --- a/lib/ui/atoms/input_text_field.dart +++ b/lib/ui/atoms/input_text_field.dart @@ -22,7 +22,7 @@ class InputTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return SizedBox( height: 60, child: TextField( controller: controller, @@ -33,7 +33,7 @@ class InputTextField extends StatelessWidget { maxLines: maxLines, decoration: InputDecoration( hintText: hintText, - hintStyle: TextStyle( + hintStyle: const TextStyle( color: Colors.grey, ), border: OutlineInputBorder( diff --git a/lib/ui/atoms/profile_image.dart b/lib/ui/atoms/my_profile_image.dart similarity index 91% rename from lib/ui/atoms/profile_image.dart rename to lib/ui/atoms/my_profile_image.dart index 56133c8..9cbc3fd 100644 --- a/lib/ui/atoms/profile_image.dart +++ b/lib/ui/atoms/my_profile_image.dart @@ -2,8 +2,8 @@ import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class ProfileImage extends StatelessWidget { - const ProfileImage({Key key, this.size}) : super(key: key); +class MyProfileImage extends StatelessWidget { + const MyProfileImage({Key key, this.size}) : super(key: key); final double size; @override diff --git a/lib/ui/atoms/my_room_image.dart b/lib/ui/atoms/my_room_image.dart new file mode 100644 index 0000000..8ce14ab --- /dev/null +++ b/lib/ui/atoms/my_room_image.dart @@ -0,0 +1,51 @@ +import 'package:chat_flutter/ui/pages/room/room_controller.dart'; +import 'package:chat_flutter/ui/pages/room/room_edit_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class MyRoomImage extends StatelessWidget { + const MyRoomImage({Key key, this.size, this.roomController}) + : super(key: key); + final double size; + final RoomController roomController; + + @override + Widget build(BuildContext context) { + final RoomEditController roomEditController = + Provider.of(context); + if (roomEditController.selectedImageFile != null) { + return SizedBox( + width: size, + height: size, + child: CircleAvatar( + backgroundImage: FileImage(roomEditController.selectedImageFile), + ), + ); + } else if (roomController.room.imgUrl != '' && + roomController.room.imgUrl != null) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(roomController.room.imgUrl), + ), + ), + ); + } else { + return Container( + width: size, + height: size, + decoration: const BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage('assets/images/avatar.JPG'), + ), + ), + ); + } + } +} diff --git a/lib/ui/molecules/create_room/create_room_list_tile.dart b/lib/ui/molecules/create_room/create_room_list_tile.dart index 92df638..36e65ac 100644 --- a/lib/ui/molecules/create_room/create_room_list_tile.dart +++ b/lib/ui/molecules/create_room/create_room_list_tile.dart @@ -1,5 +1,6 @@ import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/model/user.dart'; +import 'package:chat_flutter/ui/atoms/circular_image.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/config/app_text_size.dart'; @@ -17,13 +18,9 @@ class CreateRoomListTile extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: 50, - width: 50, - child: CircleAvatar( - radius: double.infinity, - backgroundImage: NetworkImage(user.imgUrl), - ), + CircularImage( + size: 50, + imgUrl: user.imgUrl, ), const SizedBox( width: AppSpace.midium, @@ -31,7 +28,7 @@ class CreateRoomListTile extends StatelessWidget { Expanded( child: Text( user.name, - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.midium, fontWeight: FontWeight.bold, ), diff --git a/lib/ui/molecules/create_room/member_list.dart b/lib/ui/molecules/create_room/member_list.dart index 2bb08e4..2c39b56 100644 --- a/lib/ui/molecules/create_room/member_list.dart +++ b/lib/ui/molecules/create_room/member_list.dart @@ -15,7 +15,7 @@ class MemberList extends StatelessWidget { itemBuilder: (BuildContext context, int index) { return Container( height: 40, - margin: EdgeInsets.all(AppSpace.small), + margin: const EdgeInsets.all(AppSpace.small), child: FlatButton( onPressed: () { controller.removeMember(controller.members[index]); @@ -26,7 +26,7 @@ class MemberList extends StatelessWidget { color: Colors.blue, child: Text( controller.members[index].name, - style: TextStyle( + style: const TextStyle( color: Colors.white, ), ), diff --git a/lib/ui/molecules/message/list.dart b/lib/ui/molecules/message/message_list.dart similarity index 74% rename from lib/ui/molecules/message/list.dart rename to lib/ui/molecules/message/message_list.dart index f189cbb..a133717 100644 --- a/lib/ui/molecules/message/list.dart +++ b/lib/ui/molecules/message/message_list.dart @@ -1,12 +1,15 @@ import 'package:chat_flutter/model/message.dart'; import 'package:chat_flutter/ui/molecules/message/message_list_item.dart'; +import 'package:chat_flutter/ui/pages/room/room_controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class MessageList extends StatelessWidget { @override Widget build(BuildContext context) { - final messageList = Provider.of>(context); + final List messageList = Provider.of>(context); + final ScrollController scrollController = + Provider.of(context).scrollController; if (messageList == null) { return Container(); } else if (messageList.isEmpty) { @@ -14,6 +17,7 @@ class MessageList extends StatelessWidget { } else { return Expanded( child: ListView.builder( + controller: scrollController, physics: const ScrollPhysics(), scrollDirection: Axis.vertical, shrinkWrap: true, diff --git a/lib/ui/molecules/message/message_list_item.dart b/lib/ui/molecules/message/message_list_item.dart index d8d5399..08b246d 100644 --- a/lib/ui/molecules/message/message_list_item.dart +++ b/lib/ui/molecules/message/message_list_item.dart @@ -1,21 +1,35 @@ import 'package:chat_flutter/config/app_radius.dart'; import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/model/message.dart'; -import 'package:chat_flutter/ui/atoms/profile_image.dart'; +import 'package:chat_flutter/services/firebase_user_service.dart'; +import 'package:chat_flutter/ui/atoms/circular_image.dart'; import 'package:chat_flutter/util/common_func_util.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/config/app_text_size.dart'; +import 'package:provider/provider.dart'; class MessageListItem extends StatelessWidget { const MessageListItem(this.message); final Message message; + int readPerson(List timeList) { + int count = 0; + final DateTime sendTime = message.sendTime; + timeList.forEach((time) { + if (time.compareTo(sendTime) >= 0) { + count++; + } + }); + return count; + } + @override Widget build(BuildContext context) { - //isMeは頻繁に使うし変化しないから、ここで定義するのは? - //あとmessage.messageに少し違和感 => message.textとか? + final timeList = Provider.of>(context); + final FirebaseUserService firebaseUserService = FirebaseUserService(); + final int readPersonNum = readPerson(timeList); return Padding( - padding: EdgeInsets.all(AppSpace.xsmall), + padding: const EdgeInsets.all(AppSpace.xsmall), child: Row( mainAxisAlignment: message.isMe ? MainAxisAlignment.end : MainAxisAlignment.start, @@ -24,29 +38,44 @@ class MessageListItem extends StatelessWidget { if (message.isMe) Column( children: [ - Text( - (message.isRead) ? '既読' : '', - style: TextStyle( - fontSize: AppTextSize.xsmall, + FutureBuilder( + future: + Future.delayed(const Duration(milliseconds: 100)), + builder: (context, snapshot) => Text( + (readPersonNum > 1) ? '既読${readPersonNum - 1}' : '', + style: const TextStyle( + fontSize: AppTextSize.xsmall, + ), ), ), Text( CommonFuncUtil.dateTimeToString(message.sendTime), - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.xsmall, ), ), ], ), if (!message.isMe) - const ProfileImage( - size: 40, + FutureBuilder( + future: firebaseUserService.getUserImgUrl(message.senderId), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const CircularImage( + size: 40, + ); + } + return CircularImage( + size: 40, + imgUrl: snapshot.data, + ); + }, ), Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width / 2, ), - margin: EdgeInsets.symmetric( + margin: const EdgeInsets.symmetric( horizontal: AppSpace.xsmall, ), decoration: BoxDecoration( @@ -66,7 +95,7 @@ class MessageListItem extends StatelessWidget { if (!message.isMe) Text( CommonFuncUtil.dateTimeToString(message.sendTime), - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.xsmall, ), ), diff --git a/lib/ui/molecules/profile/app_bar.dart b/lib/ui/molecules/profile/profile_page_app_bar.dart similarity index 100% rename from lib/ui/molecules/profile/app_bar.dart rename to lib/ui/molecules/profile/profile_page_app_bar.dart diff --git a/lib/ui/molecules/room/input_message_text_field.dart b/lib/ui/molecules/room/input_message_text_field.dart index 154d45e..d27c3de 100644 --- a/lib/ui/molecules/room/input_message_text_field.dart +++ b/lib/ui/molecules/room/input_message_text_field.dart @@ -12,7 +12,6 @@ class InputMessageTextField extends StatelessWidget { return Expanded( child: TextField( controller: roomTextController, - keyboardType: TextInputType.multiline, minLines: 1, maxLines: 4, decoration: InputDecoration( @@ -24,7 +23,7 @@ class InputMessageTextField extends StatelessWidget { AppSpace.small, ), ), - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.midium, ), ), diff --git a/lib/ui/molecules/room/room_app_bar.dart b/lib/ui/molecules/room/room_app_bar.dart new file mode 100644 index 0000000..74738e1 --- /dev/null +++ b/lib/ui/molecules/room/room_app_bar.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:chat_flutter/model/room.dart'; +import 'package:provider/provider.dart'; +import 'package:chat_flutter/ui/pages/room/room_controller.dart'; + +class RoomAppBar extends StatelessWidget with PreferredSizeWidget { + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + final roomController = Provider.of(context); + final Room room = roomController.room; + return AppBar( + elevation: 1, + backgroundColor: Colors.white, + iconTheme: const IconThemeData( + color: Color(0xff707070), + ), + title: Text( + room.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xff707070), + ), + ), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () async { + roomController.dispose(); + Navigator.pop(context); + }, + ), + actions: [ + IconButton( + onPressed: () async { + await Provider.of(context, listen: false) + .scrollPage(); + }, + icon: const Icon( + Icons.arrow_downward, + ), + ), + IconButton( + onPressed: () { + Navigator.pushNamed( + context, + '/roomEditPage', + arguments: roomController, + ); + }, + icon: const Icon( + Icons.more_vert, + ), + ), + ], + ); + } +} diff --git a/lib/ui/molecules/talk/app_bar.dart b/lib/ui/molecules/talk/talk_page_app_bar.dart similarity index 57% rename from lib/ui/molecules/talk/app_bar.dart rename to lib/ui/molecules/talk/talk_page_app_bar.dart index d45e9a3..fe63216 100644 --- a/lib/ui/molecules/talk/app_bar.dart +++ b/lib/ui/molecules/talk/talk_page_app_bar.dart @@ -1,25 +1,36 @@ +import 'package:chat_flutter/ui/pages/talk/talk_controller.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class TalkPageAppBar extends StatelessWidget with PreferredSizeWidget { @override - Size get preferredSize => Size.fromHeight(kToolbarHeight); + Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override Widget build(BuildContext context) { return AppBar( elevation: 1, backgroundColor: Colors.white, - title: Text( + title: const Text( 'Talk', style: TextStyle( - color: const Color(0xff707070), + color: Color(0xff707070), fontWeight: FontWeight.bold, ), ), leading: Container(), actions: [ IconButton( - icon: Icon( + icon: const Icon( + Icons.refresh, + ), + onPressed: () async { + await Provider.of(context, listen: false) + .getMyRoomList(); + }, + ), + IconButton( + icon: const Icon( Icons.add_comment, ), onPressed: () { diff --git a/lib/ui/molecules/talk/list_tile.dart b/lib/ui/molecules/talk/talk_page_list_tile.dart similarity index 78% rename from lib/ui/molecules/talk/list_tile.dart rename to lib/ui/molecules/talk/talk_page_list_tile.dart index 5a5b8f1..45a7ea2 100644 --- a/lib/ui/molecules/talk/list_tile.dart +++ b/lib/ui/molecules/talk/talk_page_list_tile.dart @@ -1,5 +1,8 @@ import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/config/app_text_size.dart'; +import 'package:chat_flutter/ui/atoms/circular_image.dart'; +import 'package:chat_flutter/util/common_func_util.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/model/room.dart'; @@ -24,14 +27,7 @@ class TalkPageListTile extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: 60, - width: 60, - child: CircleAvatar( - radius: double.infinity, - backgroundImage: NetworkImage(room.imgUrl), - ), - ), + CircularImage(size: 60, imgUrl: room.imgUrl), const SizedBox( width: AppSpace.midium, ), @@ -49,7 +45,7 @@ class TalkPageListTile extends StatelessWidget { Flexible( child: Text( room.name, - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.midium, fontWeight: FontWeight.bold, ), @@ -57,8 +53,11 @@ class TalkPageListTile extends StatelessWidget { ), ), Text( - room.sendTime, - style: TextStyle( + CommonFuncUtil.dateTimeToString( + (room.lastMessage['createdAt'] as Timestamp) + .toDate(), + ), + style: const TextStyle( fontSize: AppTextSize.xsmall, color: Colors.grey, ), @@ -67,8 +66,8 @@ class TalkPageListTile extends StatelessWidget { ), Flexible( child: Text( - room.lastMessage, - style: TextStyle( + room.lastMessage['text'].toString(), + style: const TextStyle( fontSize: AppTextSize.small, color: Colors.grey, ), diff --git a/lib/ui/pages/create_room/create_room_controller.dart b/lib/ui/pages/create_room/create_room_controller.dart index 295310d..57ac7ab 100644 --- a/lib/ui/pages/create_room/create_room_controller.dart +++ b/lib/ui/pages/create_room/create_room_controller.dart @@ -5,6 +5,7 @@ import 'package:chat_flutter/model/storage_type.dart'; import 'package:chat_flutter/services/auth/authenticator.dart'; import 'package:chat_flutter/services/firebase_room_service.dart'; import 'package:chat_flutter/services/firebase_storage_service.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/model/user.dart'; import 'package:image_picker/image_picker.dart'; @@ -18,11 +19,6 @@ class CreateRoomController with ChangeNotifier { final FirebaseRoomService _firebaseRoomService = FirebaseRoomService(); Future createRoom(List members, String roomName) async { - final imagePath = selectedImageFile != null - ? await FirebaseStorageService() - .uploadImage(selectedImageFile, 'sss', StorageType.room) - : ''; - final List memberIdList = members.map((member) { return member.id; }).toList(); @@ -32,18 +28,29 @@ class CreateRoomController with ChangeNotifier { memberIdList.add(myId); room = Room( + id: '', name: roomName, members: memberIdList, - imgUrl: imagePath, // 初めはブランクで入れておく - lastMessage: '', + imgUrl: '', + lastMessage: { + 'text': '', + 'createdAt': FieldValue.serverTimestamp(), + }, ); - final String roomId = await _firebaseRoomService.setRoomData(room); + room.id = await _firebaseRoomService.setRoomData(room); + + if (selectedImageFile != null) { + //roomIdで画像URLを作成する + room.imgUrl = await FirebaseStorageService() + .uploadImage(selectedImageFile, room.id, StorageType.room); + await FirebaseRoomService().updateRoomData(room); + } // メンバー全員のroom_settingを作成(Cloud Functionsで実装する?) memberIdList.forEach((userId) async { - await _firebaseRoomService.setRoomSetting(userId, roomId); + await _firebaseRoomService.setMyRoomSetting(userId, room.id); }); } diff --git a/lib/ui/pages/create_room/create_room.dart b/lib/ui/pages/create_room/create_room_page.dart similarity index 95% rename from lib/ui/pages/create_room/create_room.dart rename to lib/ui/pages/create_room/create_room_page.dart index 8c6849b..337e57d 100644 --- a/lib/ui/pages/create_room/create_room.dart +++ b/lib/ui/pages/create_room/create_room_page.dart @@ -47,7 +47,7 @@ class CreateRoomPage extends StatelessWidget { ).createRoom(members, _textEditingController.text); await Navigator.pushReplacementNamed(context, '/homePage'); }, - icon: Icon( + icon: const Icon( Icons.check, ), ), @@ -73,20 +73,20 @@ class CreateRoomPage extends StatelessWidget { width: 250, child: TextField( controller: _textEditingController, - style: TextStyle( + style: const TextStyle( fontSize: AppTextSize.xlarge, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), - SizedBox( + const SizedBox( height: AppSpace.big, ), - Align( + const Align( alignment: Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( left: AppSpace.midium, ), child: Text( diff --git a/lib/ui/pages/create_room/select_member_controller.dart b/lib/ui/pages/create_room/select_member_controller.dart index f70db16..1a3f5af 100644 --- a/lib/ui/pages/create_room/select_member_controller.dart +++ b/lib/ui/pages/create_room/select_member_controller.dart @@ -4,9 +4,10 @@ import 'package:chat_flutter/services/firebase_user_service.dart'; import 'package:flutter/material.dart'; class SelectMemberController extends ChangeNotifier { - SelectMemberController(this.authenticator) { + SelectMemberController(this.authenticator); + /*{ getAllUserExceptMe(); - } + }*/ List members = []; List searchedUserList = []; @@ -27,12 +28,19 @@ class SelectMemberController extends ChangeNotifier { notifyListeners(); } - Future getAllUserExceptMe() async { + /*Future getAllUserExceptMe() async { final List allUserList = await FirebaseUserService().getAllUser(); // 自分のIDを除く final String myId = await authenticator.getUid(); searchedUserList = allUserList.where((user) => user.id != myId).toList(); + notifyListeners(); + }*/ + Future getSearchedUserExceptMe(String name) async { + final List userList = + await FirebaseUserService().getSearchedUser(name); + final String myId = await authenticator.getUid(); + searchedUserList = userList.where((user) => user.id != myId).toList(); notifyListeners(); } diff --git a/lib/ui/pages/create_room/select_member.dart b/lib/ui/pages/create_room/select_member_page.dart similarity index 86% rename from lib/ui/pages/create_room/select_member.dart rename to lib/ui/pages/create_room/select_member_page.dart index a8b77f7..ca16761 100644 --- a/lib/ui/pages/create_room/select_member.dart +++ b/lib/ui/pages/create_room/select_member_page.dart @@ -16,8 +16,7 @@ class SelectMemberPage extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = - Provider.of(context, listen: false); + final controller = Provider.of(context); final textController = TextEditingController(); return Scaffold( appBar: AppBar( @@ -47,11 +46,12 @@ class SelectMemberPage extends StatelessWidget { child: TextField( controller: textController, decoration: InputDecoration( - hintText: 'Enter a message', + hintText: 'Enter a name', suffixIcon: IconButton( - icon: Icon(Icons.search), - onPressed: () { - controller.searchUser('id'); + icon: const Icon(Icons.search), + onPressed: () async { + await controller + .getSearchedUserExceptMe(textController.text); }, ), ), @@ -63,9 +63,9 @@ class SelectMemberPage extends StatelessWidget { const SizedBox( height: 20, ), - Align( + const Align( alignment: Alignment.centerLeft, - child: const Padding( + child: Padding( padding: EdgeInsets.only(left: 15), child: Text( 'Add Members', diff --git a/lib/ui/pages/home/home_controller.dart b/lib/ui/pages/home/home_controller.dart index 8624d71..6ca685d 100644 --- a/lib/ui/pages/home/home_controller.dart +++ b/lib/ui/pages/home/home_controller.dart @@ -4,13 +4,11 @@ import 'package:flutter/material.dart'; class HomeController with ChangeNotifier { HomeController(this.authenticator) { currentIndex = 0; - authenticator.isSignIn.listen((value) { _isSignIn = value; }); } - //TODO: AuthControllerを作るか迷い中 final Authenticator authenticator; int currentIndex; diff --git a/lib/ui/pages/home/home.dart b/lib/ui/pages/home/home_page.dart similarity index 80% rename from lib/ui/pages/home/home.dart rename to lib/ui/pages/home/home_page.dart index 69c0afe..7d4556d 100644 --- a/lib/ui/pages/home/home.dart +++ b/lib/ui/pages/home/home_page.dart @@ -1,9 +1,9 @@ import 'package:chat_flutter/services/auth/authenticator.dart'; -import 'package:chat_flutter/ui/molecules/profile/app_bar.dart'; -import 'package:chat_flutter/ui/molecules/talk/app_bar.dart'; +import 'package:chat_flutter/ui/molecules/profile/profile_page_app_bar.dart'; +import 'package:chat_flutter/ui/molecules/talk/talk_page_app_bar.dart'; import 'package:chat_flutter/ui/pages/home/home_controller.dart'; -import 'package:chat_flutter/ui/pages/profile/profile.dart'; -import 'package:chat_flutter/ui/pages/talk/talk.dart'; +import 'package:chat_flutter/ui/pages/profile/profile_page.dart'; +import 'package:chat_flutter/ui/pages/talk/talk_page.dart'; import 'package:chat_flutter/ui/pages/talk/talk_controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,7 +23,12 @@ class HomePage extends StatelessWidget { ), ), ChangeNotifierProvider( - create: (_) => TalkController(), + create: (_) => TalkController( + Provider.of( + context, + listen: false, + ), + ), ), ], child: const HomePage._(), @@ -65,7 +70,7 @@ class HomePage extends StatelessWidget { Icons.message, ), title: Text( - '', + 'talk', ), ), BottomNavigationBarItem( @@ -73,7 +78,7 @@ class HomePage extends StatelessWidget { Icons.person, ), title: Text( - '', + 'profile', ), ), ], diff --git a/lib/ui/pages/profile/profile_controller.dart b/lib/ui/pages/profile/profile_controller.dart index b47105d..5a5272a 100644 --- a/lib/ui/pages/profile/profile_controller.dart +++ b/lib/ui/pages/profile/profile_controller.dart @@ -6,31 +6,22 @@ import 'package:chat_flutter/services/firebase_storage_service.dart'; import 'package:chat_flutter/services/firebase_user_service.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/model/user.dart'; -import 'package:image_picker/image_picker.dart'; class ProfileController with ChangeNotifier { - ProfileController(this.authenticator) { - getUserById(); - } + ProfileController(this.authenticator); User user; File selectedImageFile; - Authenticator authenticator; - FirebaseUserService firebaseUserService = FirebaseUserService(); + final Authenticator authenticator; + final FirebaseUserService firebaseUserService = FirebaseUserService(); - Future getUserById() async { + Future getUserById() async { final String userId = await authenticator.getUid(); + if (userId.isEmpty) { + return ''; + } user = await firebaseUserService.getUserData(userId); notifyListeners(); - } - - Future selectProfileImage() async { - final imagePicker = ImagePicker(); - final selectedImage = - await imagePicker.getImage(source: ImageSource.gallery); - if (selectedImage != null) { - selectedImageFile = File(selectedImage.path); - notifyListeners(); - } + return 'success'; } void notifySelectImage(String imagePath) { diff --git a/lib/ui/pages/profile/profile_edit.dart b/lib/ui/pages/profile/profile_edit_page.dart similarity index 95% rename from lib/ui/pages/profile/profile_edit.dart rename to lib/ui/pages/profile/profile_edit_page.dart index 9738c2a..8fa53ee 100644 --- a/lib/ui/pages/profile/profile_edit.dart +++ b/lib/ui/pages/profile/profile_edit_page.dart @@ -1,9 +1,9 @@ import 'package:chat_flutter/model/user.dart'; +import 'package:chat_flutter/ui/atoms/my_profile_image.dart'; import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; -import 'package:chat_flutter/ui/atoms/profile_image.dart'; import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/config/app_text_size.dart'; @@ -68,7 +68,7 @@ class _ProfileEditPage extends StatelessWidget { ); } }, - child: const ProfileImage( + child: const MyProfileImage( size: 150, ), ), @@ -84,6 +84,7 @@ class _ProfileEditPage extends StatelessWidget { minLines: 1, maxLines: 1, controller: nameController, + textInputAction: TextInputAction.done, textAlign: TextAlign.center, style: const TextStyle( fontSize: AppTextSize.xlarge, diff --git a/lib/ui/pages/profile/profile.dart b/lib/ui/pages/profile/profile_page.dart similarity index 96% rename from lib/ui/pages/profile/profile.dart rename to lib/ui/pages/profile/profile_page.dart index 3a45b95..723bdec 100644 --- a/lib/ui/pages/profile/profile.dart +++ b/lib/ui/pages/profile/profile_page.dart @@ -1,6 +1,6 @@ import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/model/user.dart'; -import 'package:chat_flutter/ui/atoms/profile_image.dart'; +import 'package:chat_flutter/ui/atoms/my_profile_image.dart'; import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -32,7 +32,7 @@ class _ProfilePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - const ProfileImage( + const MyProfileImage( size: 150, ), const SizedBox( diff --git a/lib/ui/pages/room/room_controller.dart b/lib/ui/pages/room/room_controller.dart index 4d8df70..f3f1547 100644 --- a/lib/ui/pages/room/room_controller.dart +++ b/lib/ui/pages/room/room_controller.dart @@ -1,20 +1,78 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:chat_flutter/model/message.dart'; -import 'package:chat_flutter/services/messgae_service.dart'; +import 'package:chat_flutter/model/room.dart'; +import 'package:chat_flutter/model/storage_type.dart'; +import 'package:chat_flutter/services/auth/authenticator.dart'; +import 'package:chat_flutter/services/firebase_room_service.dart'; +import 'package:chat_flutter/services/firebase_storage_service.dart'; +import 'package:chat_flutter/services/message_service.dart'; import 'package:flutter/material.dart'; class RoomController extends ChangeNotifier { - RoomController({this.messageService}); + RoomController({this.messageService, this.authenticator, this.room}) { + Future(() async { + userId = await authenticator.getUid(); + notifyListeners(); + }); + } + final Room room; + final ScrollController scrollController = ScrollController(); + final FirebaseRoomService firebaseRoomService = FirebaseRoomService(); final MessageService messageService; + final Authenticator authenticator; + String userId; + bool isReading = true; Future sendMessage(String message, String roomId) async { - // ローカルに保存してそこから取得する? - const String userId = 'userId'; - await messageService.sendMessage(message, roomId, userId); notifyListeners(); } Stream> messageList(String roomId) { - return messageService.getMessage(roomId); + return messageService.getMessage(roomId, userId); + } + + Stream> lastReadTimeList(String roomId) { + return FirebaseRoomService().getLastReadTimeList(roomId); + } + + StreamSubscription> listenStream() { + return messageList(room.id).listen((event) async { + if (isReading) { + //読んだ時間更新 + await FirebaseRoomService().setMyLastReadTime(room.id, userId); + } + }); + } + + Future changeRoomInfo(String name, File selectedImageFile) async { + room.name = name; + if (selectedImageFile != null) { + room.imgUrl = await FirebaseStorageService().uploadImage( + selectedImageFile, + room.id, + StorageType.room, + ); + } + await firebaseRoomService.updateRoomData(room); + notifyListeners(); + } + + Future scrollPage() async { + await scrollController.animateTo( + scrollController.position.maxScrollExtent, + //スクロール時間 + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + + @override + void dispose() { + isReading = false; + notifyListeners(); + super.dispose(); } } diff --git a/lib/ui/pages/room/room_edit_controller.dart b/lib/ui/pages/room/room_edit_controller.dart new file mode 100644 index 0000000..bd1a4ac --- /dev/null +++ b/lib/ui/pages/room/room_edit_controller.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +class RoomEditController with ChangeNotifier { + File selectedImageFile; + void notifySelectImage(String imagePath) { + selectedImageFile = File(imagePath); + notifyListeners(); + } +} diff --git a/lib/ui/pages/room/room_edit_page.dart b/lib/ui/pages/room/room_edit_page.dart new file mode 100644 index 0000000..1aa043f --- /dev/null +++ b/lib/ui/pages/room/room_edit_page.dart @@ -0,0 +1,126 @@ +import 'package:chat_flutter/model/room.dart'; +import 'package:chat_flutter/ui/atoms/my_room_image.dart'; +import 'package:chat_flutter/ui/pages/room/room_controller.dart'; +import 'package:chat_flutter/ui/pages/room/room_edit_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:chat_flutter/config/app_space.dart'; +import 'package:chat_flutter/config/app_text_size.dart'; +import 'package:provider/provider.dart'; + +class RoomEditPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final RoomController roomController = + ModalRoute.of(context).settings.arguments as RoomController; + final room = roomController.room; + return ChangeNotifierProvider( + create: (_) => RoomEditController(), + child: Scaffold( + appBar: AppBar( + title: const Text( + 'Room', + style: TextStyle( + color: Color(0xff707070), + ), + ), + backgroundColor: Colors.white, + iconTheme: const IconThemeData( + color: Color(0xff707070), + ), + ), + backgroundColor: Colors.white, + body: (room == null) + ? const Center( + child: CircularProgressIndicator(), + ) + : _RoomEditPage( + roomController: roomController, + ), + ), + ); + } +} + +class _RoomEditPage extends StatelessWidget { + final RoomController roomController; + + const _RoomEditPage({Key key, this.roomController}) : super(key: key); + + @override + Widget build(BuildContext context) { + final Room room = roomController.room; + final RoomEditController roomEditController = + Provider.of(context); + final TextEditingController nameController = TextEditingController( + text: room.name, + ); + return SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlatButton( + onPressed: () async { + final imagePicker = ImagePicker(); + final selectImage = await imagePicker.getImage( + source: ImageSource.gallery, + ); + if (selectImage != null) { + roomEditController.notifySelectImage( + selectImage.path, + ); + } + }, + child: MyRoomImage( + size: 150, + roomController: roomController, + ), + ), + const SizedBox( + height: AppSpace.small, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: AppSpace.midium, + ), + child: TextField( + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: 1, + controller: nameController, + textInputAction: TextInputAction.done, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: AppTextSize.xlarge, + fontWeight: FontWeight.w700, + ), + ), + ), + const SizedBox( + height: AppSpace.large, + ), + Center( + child: SizedBox( + width: 150, + child: RaisedButton.icon( + icon: const Icon( + Icons.arrow_upward, + color: Colors.white, + ), + label: const Text('更新する'), + onPressed: () async { + await roomController.changeRoomInfo(nameController.text, + roomEditController.selectedImageFile); + Navigator.of(context).pop(); + }, + color: Colors.redAccent, + textColor: Colors.white, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/pages/room/room.dart b/lib/ui/pages/room/room_page.dart similarity index 62% rename from lib/ui/pages/room/room.dart rename to lib/ui/pages/room/room_page.dart index 2e08f62..24d70c4 100644 --- a/lib/ui/pages/room/room.dart +++ b/lib/ui/pages/room/room_page.dart @@ -1,10 +1,12 @@ import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/model/message.dart'; import 'package:chat_flutter/model/room.dart'; -import 'package:chat_flutter/services/messgae_service.dart'; -import 'package:chat_flutter/ui/molecules/message/list.dart'; +import 'package:chat_flutter/services/auth/authenticator.dart'; +import 'package:chat_flutter/services/message_service.dart'; +import 'package:chat_flutter/ui/molecules/message/message_list.dart'; import 'package:chat_flutter/ui/molecules/room/input_message_text_field.dart'; import 'package:chat_flutter/ui/pages/room/room_controller.dart'; +import 'package:chat_flutter/ui/molecules/room/room_app_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:provider/provider.dart'; @@ -12,52 +14,48 @@ import 'package:provider/provider.dart'; class RoomPage extends StatelessWidget { const RoomPage._({Key key}) : super(key: key); - static Widget wrapped(BuildContext context) { + static Widget wrapped(BuildContext context, Authenticator authenticator) { return ChangeNotifierProvider( create: (_) => RoomController( - messageService: Provider.of(context, listen: false)), + messageService: Provider.of(context, listen: false), + authenticator: authenticator, + room: ModalRoute.of(context).settings.arguments as Room, + ), child: const RoomPage._(), ); } @override Widget build(BuildContext context) { - final Room room = ModalRoute.of(context).settings.arguments as Room; - final roomController = Provider.of(context, listen: false); + final roomController = Provider.of(context); + final Room room = roomController.room; final TextEditingController textController = TextEditingController(); - return Scaffold( - appBar: AppBar( - elevation: 1, - backgroundColor: Colors.white, - iconTheme: const IconThemeData( - color: Color(0xff707070), - ), - title: Text( - room.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Color(0xff707070), - ), + if (roomController.userId == null) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), ), - actions: [ - IconButton( - onPressed: () {}, - icon: Icon( - Icons.more_vert, - ), - ), - ], - ), + ); + } + return Scaffold( + appBar: RoomAppBar(), body: Stack( children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ - StreamProvider>( - create: (_) => roomController.messageList(room.id), + StreamProvider>( + create: (_) => roomController.lastReadTimeList(room.id), initialData: const [], - child: MessageList(), + child: StreamProvider>( + create: (_) { + roomController.listenStream(); + return roomController.messageList(room.id); + }, + initialData: const [], + child: MessageList(), + ), ), const SizedBox( height: AppSpace.xBig, @@ -79,18 +77,20 @@ class RoomPage extends StatelessWidget { InputMessageTextField( roomTextController: textController, ), - SizedBox( + const SizedBox( width: AppSpace.xsmall, ), IconButton( onPressed: () async { + // + FocusScope.of(context).unfocus(); await roomController.sendMessage( textController.text, - 'roomId', + room.id, ); textController.clear(); }, - icon: Icon( + icon: const Icon( Icons.send, ), ), diff --git a/lib/ui/pages/sign_in/sign_in.dart b/lib/ui/pages/sign_in/sign_in_page.dart similarity index 72% rename from lib/ui/pages/sign_in/sign_in.dart rename to lib/ui/pages/sign_in/sign_in_page.dart index e8d1d9a..e0f0c6a 100644 --- a/lib/ui/pages/sign_in/sign_in.dart +++ b/lib/ui/pages/sign_in/sign_in_page.dart @@ -1,7 +1,9 @@ import 'package:chat_flutter/config/app_radius.dart'; import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/services/auth/authenticator.dart'; +import 'package:chat_flutter/ui/atoms/error_dialog.dart'; import 'package:chat_flutter/ui/atoms/input_text_field.dart'; +import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; import 'package:chat_flutter/ui/pages/sign_in/sign_in_controller.dart'; import 'package:flutter/material.dart'; @@ -28,11 +30,11 @@ class SignInPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: AppSpace.midium, top: AppSpace.big, ), - child: Text( + child: const Text( 'SIGN IN', style: TextStyle( fontSize: AppTextSize.big, @@ -40,12 +42,12 @@ class SignInPage extends StatelessWidget { ), ), ), - SizedBox( + const SizedBox( height: AppSpace.big, ), Expanded( child: Container( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: AppSpace.midium, top: AppSpace.xlarge, right: AppSpace.midium, @@ -72,20 +74,20 @@ class SignInPage extends StatelessWidget { obscureText: true, controller: controller.passwordTextController, ), - SizedBox( + const SizedBox( height: AppSpace.big, ), ], ), - SizedBox( + const SizedBox( height: AppSpace.large, ), RaisedButton( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: AppSpace.big, vertical: AppSpace.small, ), - child: Text( + child: const Text( 'Start', style: TextStyle( fontSize: AppTextSize.xlarge, @@ -96,25 +98,43 @@ class SignInPage extends StatelessWidget { color: Colors.white, elevation: 3, shape: RoundedRectangleBorder( - side: BorderSide( + side: const BorderSide( style: BorderStyle.solid, color: Colors.grey, ), borderRadius: BorderRadius.circular(AppRadius.xlarge), ), onPressed: () async { - await controller.signIn(); - await Navigator.pushNamed(context, '/homePage'); + if (controller.email == null || + controller.password == null) { + const String message = + 'Emails and passwords should not be empty.'; + await showDialog( + context: context, + builder: (context) => const ErrorDialog(message)); + } else { + try { + await controller.signIn(); + await Provider.of(context, + listen: false) + .getUserById(); + await Navigator.pushNamed(context, '/homePage'); + } on Exception catch (e) { + await showDialog( + context: context, + builder: (context) => ErrorDialog(e.toString())); + } + } }, ), - SizedBox( + const SizedBox( height: AppSpace.large, ), FlatButton( onPressed: () { Navigator.pushNamed(context, '/signUpPage'); }, - child: Text( + child: const Text( 'SIGN UP', style: TextStyle( fontSize: AppTextSize.large, diff --git a/lib/ui/pages/sign_up/sign_up.dart b/lib/ui/pages/sign_up/sign_up_page.dart similarity index 78% rename from lib/ui/pages/sign_up/sign_up.dart rename to lib/ui/pages/sign_up/sign_up_page.dart index 59c09ba..0a4d618 100644 --- a/lib/ui/pages/sign_up/sign_up.dart +++ b/lib/ui/pages/sign_up/sign_up_page.dart @@ -1,7 +1,9 @@ import 'package:chat_flutter/config/app_radius.dart'; import 'package:chat_flutter/config/app_space.dart'; import 'package:chat_flutter/services/auth/authenticator.dart'; +import 'package:chat_flutter/ui/atoms/error_dialog.dart'; import 'package:chat_flutter/ui/atoms/input_text_field.dart'; +import 'package:chat_flutter/ui/pages/profile/profile_controller.dart'; import 'package:chat_flutter/ui/pages/sign_up/sign_up_controller.dart'; import 'package:flutter/material.dart'; @@ -20,7 +22,7 @@ class SignUpPage extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = Provider.of(context, listen: false); + final controller = Provider.of(context); return Scaffold( backgroundColor: Colors.white70, body: Column( @@ -104,12 +106,26 @@ class SignUpPage extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.xlarge), ), onPressed: () async { - try { - await controller.signUp(); - await Navigator.pushNamed(context, '/homePage'); - } on Exception catch (e) { - // TODO: error handling - debugPrint(e.toString()); + if (controller.name == null || + controller.email == null || + controller.password == null) { + const String message = + 'Name,Email and password should not be empty.'; + await showDialog( + context: context, + builder: (context) => const ErrorDialog(message)); + } else { + try { + await controller.signUp(); + await Provider.of(context, + listen: false) + .getUserById(); + await Navigator.pushNamed(context, '/homePage'); + } on Exception catch (e) { + await showDialog( + context: context, + builder: (context) => ErrorDialog(e.toString())); + } } }, ), diff --git a/lib/ui/pages/talk/talk.dart b/lib/ui/pages/talk/talk.dart deleted file mode 100644 index 257f355..0000000 --- a/lib/ui/pages/talk/talk.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:chat_flutter/ui/molecules/talk/list_tile.dart'; -import 'package:chat_flutter/ui/pages/talk/talk_controller.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class TalkPage extends StatelessWidget { - @override - Widget build(BuildContext context) { - final roomList = Provider.of(context).roomList; - if (roomList == null) { - return const Center( - child: CircularProgressIndicator(), - ); - } else { - return ListView.builder( - physics: const ScrollPhysics(), - scrollDirection: Axis.vertical, - shrinkWrap: true, - itemCount: roomList.length, - itemBuilder: ( - BuildContext context, - int index, - ) { - return TalkPageListTile(roomList[index]); - }, - ); - } - } -} diff --git a/lib/ui/pages/talk/talk_controller.dart b/lib/ui/pages/talk/talk_controller.dart index 1186abb..70787fa 100644 --- a/lib/ui/pages/talk/talk_controller.dart +++ b/lib/ui/pages/talk/talk_controller.dart @@ -1,36 +1,23 @@ +import 'package:chat_flutter/services/auth/authenticator.dart'; +import 'package:chat_flutter/services/firebase_room_service.dart'; import 'package:flutter/material.dart'; import 'package:chat_flutter/model/room.dart'; class TalkController with ChangeNotifier { - TalkController() { - getRoomList(); + TalkController(this.authenticator) { + Future(() async { + uid = await authenticator.getUid(); + await getMyRoomList(); + }); } + + final FirebaseRoomService firebaseRoomService = FirebaseRoomService(); + Authenticator authenticator; + String uid; List roomList; - Future getRoomList() async { - roomList = [ - Room( - id: 'roomId', - name: 'Sport', - imgUrl: 'https://prtimes.jp/i/24101/70/resize/d24101-70-320114-0.jpg', - lastMessage: 'hello,group!', - sendTime: '23:04', - ), - Room( - id: 'roomId', - name: 'Study', - imgUrl: 'https://prtimes.jp/i/24101/70/resize/d24101-70-320114-0.jpg', - lastMessage: 'hey!', - sendTime: '21:04', - ), - Room( - id: 'roomId', - name: 'Hobby', - imgUrl: 'https://prtimes.jp/i/24101/70/resize/d24101-70-320114-0.jpg', - lastMessage: 'amusement Park!!!!', - sendTime: '20:04', - ), - ]; - await Future.delayed(const Duration(seconds: 1)); + + Future getMyRoomList() async { + roomList = await firebaseRoomService.getMyRoomList(uid); notifyListeners(); } } diff --git a/lib/ui/pages/talk/talk_page.dart b/lib/ui/pages/talk/talk_page.dart new file mode 100644 index 0000000..19dd129 --- /dev/null +++ b/lib/ui/pages/talk/talk_page.dart @@ -0,0 +1,29 @@ +import 'package:chat_flutter/model/room.dart'; +import 'package:chat_flutter/ui/molecules/talk/talk_page_list_tile.dart'; +import 'package:chat_flutter/ui/pages/talk/talk_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class TalkPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final List roomList = Provider.of(context).roomList; + if (roomList == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return ListView.builder( + physics: const ScrollPhysics(), + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: roomList.length, + itemBuilder: ( + BuildContext context, + int index, + ) { + return TalkPageListTile(roomList[index]); + }, + ); + } +} diff --git a/lib/util/common_func_util.dart b/lib/util/common_func_util.dart index a685c07..d8088f8 100644 --- a/lib/util/common_func_util.dart +++ b/lib/util/common_func_util.dart @@ -4,7 +4,7 @@ import 'package:intl/date_symbol_data_local.dart'; class CommonFuncUtil { static String dateTimeToString(DateTime date) { initializeDateFormatting('ja_JP'); - final formatter = DateFormat('HH:ss', 'ja_JP'); + final formatter = DateFormat('HH:mm', 'ja_JP'); final formatted = formatter.format(date); return formatted; }