Files
stream-chat-flutter/packages/stream_chat/example/lib/main.dart
T
2023-12-05 13:34:20 +02:00

334 lines
10 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
// DEBUG NOTE: made this global for easier access just for demo purposes
late StreamChatClient client;
Future<void> main() async {
/// Create a new instance of [StreamChatClient]
/// by passing the apikey obtained from your project dashboard.
client = StreamChatClient(
'kjt8387d892u',
logLevel: Level.INFO,
);
/// Set the current user. In a production scenario, this should be done using
/// a backend to generate a user token using our server SDK.
/// Please see the following for more information:
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
await client.connectUser(
User(
id: 'cool-shadow-7',
name: 'Cool Shadow',
// DEBUG NOTE: setting initial state to 0 (focus mode)
extraData: {'state': 0},
image:
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
),
"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2M5ZGUwMTctY2E5Mi00ODliLWFlY2MtZDEzYTQwMDUyYzk3In0.qPjnrWzyQBNF_tpFkVNayFmzArIXj42EVQKoo-sFmB4");
/// Creates a channel using the type `messaging` and `godevs`.
/// Channels are containers for holding messages between different members. To
/// learn more about channels and some of our predefined types, checkout our
/// our channel docs: https://getstream.io/chat/docs/initialize_channel/?language=dart
final channel = client.channel('messaging', id: 'godevs');
/// `.watch()` is used to create and listen to the channel for updates. If the
/// channel already exists, it will simply listen for new events.
await channel.watch();
runApp(
StreamExample(
client: client,
channel: channel,
),
);
}
/// Example using Stream's Low Level Dart client.
class StreamExample extends StatelessWidget {
/// To initialize this example, an instance of
/// [client] and [channel] is required.
const StreamExample({
super.key,
required this.client,
required this.channel,
});
/// Instance of [StreamChatClient] we created earlier.
/// This contains information about our application and connection state.
final StreamChatClient client;
/// The channel we'd like to observe and participate.
final Channel channel;
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Stream Chat Dart Example',
home: HomeScreen(channel: channel),
debugShowCheckedModeBanner: false,
);
}
/// Main screen of our application. The layout is comprised of an [AppBar]
/// containing the channel name and a [MessageView] displaying recent messages.
class HomeScreen extends StatefulWidget {
/// [HomeScreen] is constructed using the [Channel] we defined earlier.
const HomeScreen({
super.key,
required this.channel,
});
/// Channel object containing the [Channel.id] we'd like to observe.
final Channel channel;
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int? _state = null; // DEBUG NOTE: 0 => focus mode, 1 => ready
bool _isOnline = false;
StreamSubscription<OwnUser?>? _subscription;
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
@override
void initState() {
// DEBUG NOTE: I'm getting the initial local user info
final initialState = client.state.currentUser?.extraData['state'] as int?;
debugPrint('initialState: ${initialState.toString()}');
setState(() {
_state = initialState;
_isOnline = client.state.currentUser?.online ?? false;
});
// DEBUG NOTE: Im listening to changes to the local user
_subscription = client.state.currentUserStream.listen((user) {
debugPrint(
'is user online ? ${user?.online}'); // <----- DEBUG NOTE: this is what returns false after goReady()
final newState = user?.extraData['state'] as int?;
debugPrint('new user state : ${newState.toString()}');
setState(() {
_state = newState;
_isOnline = user?.online ?? false;
});
});
}
// DEBUG NOTE: test of switching user's state from focus mode to online
void goReady() {
client.updateUser(
client.state.currentUser!.copyWith(
// trying to force online state to stay true
// tried with this commented and without
online: true,
extraData: {
'state': 1,
},
),
);
}
Widget get userGlobalState {
if (_state == null) {
return Text("null");
}
if (_state == 1) {
return Text("status: ready | online: $_isOnline");
} else {
// DEBUG NOTE: this is what shows initially
return Text("status: focus mode | online: $_isOnline");
}
}
@override
Widget build(BuildContext context) {
final messages = widget.channel.state!.messagesStream;
return Scaffold(
appBar: AppBar(
title: Text('Channel: ${widget.channel.id}'),
leading: userGlobalState,
actions: [
if (_state == 0)
IconButton(
onPressed: goReady,
icon: Icon(Icons.circle, color: Colors.purple))
],
),
body: SafeArea(
child: StreamBuilder<List<Message>?>(
stream: messages,
builder: (
BuildContext context,
AsyncSnapshot<List<Message>?> snapshot,
) {
if (snapshot.hasData && snapshot.data != null) {
return MessageView(
messages: snapshot.data!.reversed.toList(),
channel: widget.channel,
);
} else if (snapshot.hasError) {
return const Center(
child: Text(
'There was an error loading messages. Please see logs.',
),
);
}
return const Center(
child: SizedBox(
width: 100,
height: 100,
child: CircularProgressIndicator(),
),
);
},
),
),
);
}
}
/// UI used to display a list of recent messages and a [TextField] for sending
/// new messages.
class MessageView extends StatefulWidget {
/// Message takes the latest list of messages and the current channel.
const MessageView({
super.key,
required this.messages,
required this.channel,
});
/// List of messages sent in the given channel.
final List<Message> messages;
/// Current channel being observed.
final Channel channel;
@override
_MessageViewState createState() => _MessageViewState();
}
class _MessageViewState extends State<MessageView> {
late final TextEditingController _controller;
late final ScrollController _scrollController;
List<Message> get _messages => widget.messages;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_scrollController = ScrollController();
}
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
/// Convenience method for scrolling the list view when a new message is sent.
void _updateList() {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
@override
Widget build(BuildContext context) => Column(
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
reverse: true,
itemBuilder: (BuildContext context, int index) {
final item = _messages[index];
if (item.user?.id == widget.channel.client.uid) {
return Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(item.text ?? ''),
),
);
} else {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(item.text ?? ''),
),
);
}
},
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Enter your message',
),
),
),
Material(
type: MaterialType.circle,
color: Colors.blue,
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () async {
// We can send a new message by calling `sendMessage` on
// the current channel. After sending a message, the
// TextField is cleared and the list view is scrolled
// to show the new item.
if (_controller.value.text.isNotEmpty) {
await widget.channel.sendMessage(
Message(text: _controller.value.text),
);
_controller.clear();
_updateList();
}
},
child: const Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Icon(
Icons.send,
color: Colors.white,
),
),
),
),
),
],
),
),
],
);
}
/// Helper extension for quickly retrieving
/// the current user id from a [StreamChatClient].
extension on StreamChatClient {
String get uid => state.currentUser!.id;
}