Flutter
[Flutter] 채팅앱 만들기 #10
상도동 카르마
2023. 3. 11. 19:54
What to do?
홈화면 UI 구현
데모영상
Reference
구현해야 할 내용
1. 상단 탭바
2. 채팅방 List
3. 활동중인 유저
State 관리
상단 탭바에서 활동중인 유저 숫자를 보여줘야 함.
또한 활동중 유저 탭을 누르면 보여줄 유저 목록이 필요함.
이를 위해 홈화면에서 사용할 State를 정의
- State
abstract class HomeState extends Equatable {}
class HomeInitial extends HomeState {
@override
List<Object> get props => [];
}
class HomeLoading extends HomeState {
@override
List<Object> get props => [];
}
class HomeSuccess extends HomeState {
final List<User> activeUsers;
HomeSuccess(this.activeUsers);
@override
List<Object> get props => [activeUsers];
}
- Cubit
class HomeCubit extends Cubit<HomeState> {
final IUserService _userService;
HomeCubit(this._userService) : super(HomeInitial());
Future<void> activeUsers() async {
// satte를 로딩중으로 변경하기
emit(HomeLoading());
// 활동중인 users 가져오기
final users = await _userService.online();
// state를 성공으로 변경하기
emit(HomeSuccess(users));
}
}
- compositonRoot
- Home 위젯에서 위에서 정의한 Cubit을 사용할 수 있도록 함
class CompositionRoot {
...
static Widget composeHomeUi() {
HomeCubit homeCubit = HomeCubit(_userService);
return MultiBlocProvider(
providers: [BlocProvider(create: (BuildContext context) => homeCubit)],
child: const Home());
}
}
- main.dart
- 이전 코드에서는 홈화면을 회원가입화면으로 만들었는데, 이를 홈화면으로 수정함
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await CompositionRoot.configure();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Chat App",
debugShowCheckedModeBanner: false,
theme: lightTheme(context),
darkTheme: darkTheme(context),
// 기존코드 : home: CompositionRoot.composeOnBoardingUi());
// 수정한 코드 ▽
home: CompositionRoot.composeHomeUi());
}
const MyApp({key}) : super(key: key);
}
Widgets
홈화면에서 상단 탭바를 누르면 보여줄 ⓐ 채팅방 목록 ⓑ 활동중 유저 목록을 각각 위젯으로 만듬
- 채팅방 List
- 실제 데이터 베이스에서 채팅방 정보를 가져오는 로직은 작성하지 않음
- 일단 3개의 가짜 채팅방 목록만 보이도록 함
class Chats extends StatefulWidget {
const Chats();
@override
State<Chats> createState() => _ChatsState();
}
class _ChatsState extends State<Chats> {
@override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (_, idx) => _chatItem(),
separatorBuilder: (_, __) => Divider(),
// TODO : item 개수
itemCount: 3);
}
_chatItem() => ListTile(
/// profile image
leading: const ProfileImage(
// TODO : 프로필 이미지 가져오는 경로
imageUrl: "https://picsum.photos/seed/picsum/200/300",
isInternetConnected: true,
),
/// 유저명
title: Text(
"유저명을 넣을 곳",
style: Theme.of(context).textTheme.bodyMedium.copyWith(
fontWeight: FontWeight.bold,
color: isLightTheme(context) ? Colors.black : Colors.white),
),
/// message
subtitle: Text(
"메세지를 넣을 곳",
overflow: TextOverflow.ellipsis,
softWrap: true,
style: Theme.of(context).textTheme.bodySmall.copyWith(
color: isLightTheme(context) ? Colors.black54 : Colors.white70),
),
trailing: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
/// 메세지 보낸 시간
Text(
"메세지 보낸 시간을 넣을 곳",
style: Theme.of(context).textTheme.labelSmall.copyWith(
color: isLightTheme(context)
? Colors.black54
: Colors.white70),
),
/// 읽지 않은 메세지 수
ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Container(
height: 15.0,
width: 15.0,
color: kPrimary,
alignment: Alignment.center,
child: Text(
"3+",
style: Theme.of(context).textTheme.labelSmall.copyWith(
fontWeight: FontWeight.bold, color: Colors.white70),
),
),
)
],
),
),
);
}
- 활동중인 유저 List
- state = HomeLoading → 로딩중 화면
- state = HomeSuccess → 활동중인 유저 목록를 ListView로 렌더링
class ActiveUsers extends StatefulWidget {
const ActiveUsers({Key key}) : super(key: key);
@override
State<ActiveUsers> createState() => _ActiveUsersState();
}
class _ActiveUsersState extends State<ActiveUsers> {
@override
Widget build(BuildContext context) => BlocBuilder<HomeCubit, HomeState>(
builder: (_, state) {
if (state is HomeLoading) return _loading();
if (state is HomeSuccess) return _activeUserList(state.activeUsers);
return Container();
},
);
ListTile _activeUserItem(User user) => ListTile(
leading: ProfileImage(
imageUrl: user.photoUrl,
isInternetConnected: true,
),
title: Text(
user.username,
style: Theme.of(context)
.textTheme
.bodyMedium
.copyWith(fontSize: 14.0, fontWeight: FontWeight.bold),
),
);
_activeUserList(List<User> users) => ListView.separated(
itemBuilder: (BuildContext context, idx) => _activeUserItem(users[idx]),
separatorBuilder: (_, __) => const Divider(),
itemCount: users.length);
Widget _loading() => const Center(
child: CircularProgressIndicator(),
);
}
홈화면
class Home extends StatefulWidget {
const Home();
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
/// 처음에 활동중 유저 목록 가져오기
context.read<HomeCubit>().activeUsers();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
/// 로그인 유저 프로필
title: _loginUserProfile(),
/// 탭바
bottom: TabBar(
indicatorPadding: const EdgeInsets.only(top: 10, bottom: 10),
tabs: [
_messageTab(),
_activeUserTab(),
],
),
),
/// 홈화면에서 보여줄 화면 (채팅방 목록, 활동 중 유저 목록)
body: const TabBarView(
children: [Chats(), ActiveUsers()],
),
));
}
/// 로그인한 유저의 프로필
Widget _loginUserProfile() => Container(
width: double.maxFinite,
child: Row(
children: [
// TODO : 썸네일 이미지 주소
const ProfileImage(
imageUrl: "https://picsum.photos/seed/picsum/200/300"),
Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Text(
"TEST",
style: Theme.of(context)
.textTheme
.caption
.copyWith(fontSize: 14.0, fontWeight: FontWeight.bold),
),
),
Padding(
padding: const EdgeInsets.only(left: 12.0),
child:
Text("TEST", style: Theme.of(context).textTheme.caption),
),
],
)
],
),
);
/// 메세지 탭
Tab _messageTab() => Tab(
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(50)),
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.message_rounded),
Container(
margin: EdgeInsets.only(left: 10.0), child: Text("메세지"))
],
),
),
),
);
/// 활동중 유저 탭
Tab _activeUserTab() => Tab(
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(50)),
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.people_outline_rounded),
BlocBuilder<HomeCubit, HomeState>(
builder: (_, state) => state is HomeSuccess
? Text('활동 중 유저(${state.activeUsers.length})')
: Text('활동 중 유저(0)')),
],
)),
),
);
}