334 lines
10 KiB
Dart
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&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;
|
|
}
|