refactor home page
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:example/home_page.dart';
|
||||||
import 'package:example/routes/routes.dart';
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -306,7 +307,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
key: kStreamToken,
|
key: kStreamToken,
|
||||||
value: userToken,
|
value: userToken,
|
||||||
);
|
);
|
||||||
client.closeConnection();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
var errorText = 'Error connecting, retry';
|
var errorText = 'Error connecting, retry';
|
||||||
if (e is Map) {
|
if (e is Map) {
|
||||||
@@ -317,15 +317,14 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
_apiKeyError = errorText.toUpperCase();
|
_apiKeyError = errorText.toUpperCase();
|
||||||
});
|
});
|
||||||
loading = false;
|
loading = false;
|
||||||
client.closeConnection();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
await Navigator.pushNamedAndRemoveUntil(
|
await Navigator.pushNamedAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
Routes.APP,
|
Routes.HOME,
|
||||||
ModalRoute.withName(Routes.APP),
|
ModalRoute.withName(Routes.HOME),
|
||||||
arguments: client,
|
arguments: HomePageArgs(client),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:example/routes/routes.dart';
|
||||||
|
import 'package:example/search_text_field.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'channel_page.dart';
|
||||||
|
import 'chat_info_screen.dart';
|
||||||
|
import 'group_info_screen.dart';
|
||||||
|
|
||||||
|
class ChannelList extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ChannelList createState() => _ChannelList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelList extends State<ChannelList> {
|
||||||
|
TextEditingController? _controller;
|
||||||
|
|
||||||
|
String _channelQuery = '';
|
||||||
|
|
||||||
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
|
Timer? _debounce;
|
||||||
|
|
||||||
|
void _channelQueryListener() {
|
||||||
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||||
|
_debounce = Timer(const Duration(milliseconds: 350), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_channelQuery = _controller!.text;
|
||||||
|
_isSearchActive = _channelQuery.isNotEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TextEditingController()..addListener(_channelQueryListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller?.removeListener(_channelQueryListener);
|
||||||
|
_controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final user = StreamChat.of(context).user;
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (_isSearchActive) {
|
||||||
|
_controller!.clear();
|
||||||
|
setState(() => _isSearchActive = false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: ChannelsBloc(
|
||||||
|
child: MessageSearchBloc(
|
||||||
|
child: NestedScrollView(
|
||||||
|
floatHeaderSlivers: true,
|
||||||
|
headerSliverBuilder: (_, __) => [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SearchTextField(
|
||||||
|
controller: _controller,
|
||||||
|
showCloseButton: _isSearchActive,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
body: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 350),
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
||||||
|
child: _isSearchActive
|
||||||
|
? MessageSearchListView(
|
||||||
|
showErrorTile: true,
|
||||||
|
messageQuery: _channelQuery,
|
||||||
|
filters: Filter.in_('members', [user!.id]),
|
||||||
|
sortOptions: [
|
||||||
|
SortOption(
|
||||||
|
'created_at',
|
||||||
|
direction: SortOption.ASC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
pullToRefresh: false,
|
||||||
|
paginationParams: PaginationParams(limit: 20),
|
||||||
|
emptyBuilder: (_) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, viewportConstraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: viewportConstraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: StreamSvgIcon.search(
|
||||||
|
size: 96,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'No results...',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onItemTap: (messageResponse) async {
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
final message = messageResponse.message;
|
||||||
|
final channel = client.channel(
|
||||||
|
messageResponse.channel!.type,
|
||||||
|
id: messageResponse.channel!.id,
|
||||||
|
);
|
||||||
|
if (channel.state == null) {
|
||||||
|
await channel.watch();
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.CHANNEL_PAGE,
|
||||||
|
arguments: ChannelPageArgs(
|
||||||
|
channel: channel,
|
||||||
|
initialMessage: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: ChannelListView(
|
||||||
|
onStartChatPressed: () {
|
||||||
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
|
},
|
||||||
|
swipeToAction: true,
|
||||||
|
filter: Filter.in_('members', [user!.id]),
|
||||||
|
presence: true,
|
||||||
|
pagination: PaginationParams(
|
||||||
|
limit: 20,
|
||||||
|
),
|
||||||
|
channelWidget: ChannelPage(),
|
||||||
|
onViewInfoTap: (channel) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: ChatInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
user: channel.state!.members
|
||||||
|
.where((m) =>
|
||||||
|
m.userId !=
|
||||||
|
channel.client.state.user!.id)
|
||||||
|
.first
|
||||||
|
.user,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,195 +1,277 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:example/routes/routes.dart';
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:example/search_text_field.dart';
|
import 'package:example/user_mentions_page.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
|
||||||
|
|
||||||
import 'channel_page.dart';
|
import 'channel_list.dart';
|
||||||
import 'chat_info_screen.dart';
|
|
||||||
import 'group_info_screen.dart';
|
|
||||||
|
|
||||||
class ChannelListPage extends StatefulWidget {
|
class ChannelListPage extends StatefulWidget {
|
||||||
|
const ChannelListPage({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelListPageState createState() => _ChannelListPageState();
|
_ChannelListPageState createState() => _ChannelListPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListPageState extends State<ChannelListPage> {
|
class _ChannelListPageState extends State<ChannelListPage> {
|
||||||
TextEditingController? _controller;
|
int _currentIndex = 0;
|
||||||
|
|
||||||
String _channelQuery = '';
|
bool _isSelected(int index) => _currentIndex == index;
|
||||||
|
|
||||||
bool _isSearchActive = false;
|
List<BottomNavigationBarItem> get _navBarItems {
|
||||||
|
return <BottomNavigationBarItem>[
|
||||||
Timer? _debounce;
|
BottomNavigationBarItem(
|
||||||
|
icon: Stack(
|
||||||
void _channelQueryListener() {
|
clipBehavior: Clip.none,
|
||||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
children: [
|
||||||
_debounce = Timer(const Duration(milliseconds: 350), () {
|
StreamSvgIcon.message(
|
||||||
if (mounted) {
|
color: _isSelected(0)
|
||||||
setState(() {
|
? StreamChatTheme.of(context).colorTheme.black
|
||||||
_channelQuery = _controller!.text;
|
: Colors.grey,
|
||||||
_isSearchActive = _channelQuery.isNotEmpty;
|
),
|
||||||
});
|
Positioned(
|
||||||
}
|
top: -3,
|
||||||
});
|
right: -16,
|
||||||
}
|
child: UnreadIndicator(),
|
||||||
|
),
|
||||||
@override
|
],
|
||||||
void initState() {
|
),
|
||||||
super.initState();
|
label: 'Chats',
|
||||||
_controller = TextEditingController()..addListener(_channelQueryListener);
|
),
|
||||||
}
|
BottomNavigationBarItem(
|
||||||
|
icon: Stack(
|
||||||
@override
|
clipBehavior: Clip.none,
|
||||||
void dispose() {
|
children: [
|
||||||
_controller?.removeListener(_channelQueryListener);
|
StreamSvgIcon.mentions(
|
||||||
_controller?.dispose();
|
color: _isSelected(1)
|
||||||
super.dispose();
|
? StreamChatTheme.of(context).colorTheme.black
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
label: 'Mentions',
|
||||||
|
),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user;
|
||||||
return WillPopScope(
|
if (user == null) {
|
||||||
onWillPop: () async {
|
return Offstage();
|
||||||
if (_isSearchActive) {
|
}
|
||||||
_controller!.clear();
|
return Scaffold(
|
||||||
setState(() => _isSearchActive = false);
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
return false;
|
appBar: ChannelListHeader(
|
||||||
|
onNewChatButtonTap: () {
|
||||||
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
|
},
|
||||||
|
preNavigationCallback: () {
|
||||||
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
drawer: LeftDrawer(
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
drawerEdgeDragWidth: 50,
|
||||||
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
|
currentIndex: _currentIndex,
|
||||||
|
items: _navBarItems,
|
||||||
|
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
unselectedLabelStyle:
|
||||||
|
StreamChatTheme.of(context).textTheme.footnoteBold,
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
selectedItemColor: StreamChatTheme.of(context).colorTheme.black,
|
||||||
|
unselectedItemColor: Colors.grey,
|
||||||
|
onTap: (index) {
|
||||||
|
setState(() => _currentIndex = index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: IndexedStack(
|
||||||
|
index: _currentIndex,
|
||||||
|
children: [
|
||||||
|
ChannelList(),
|
||||||
|
UserMentionsPage(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<int>? badgeListener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
badgeListener = StreamChat.of(context)
|
||||||
|
.client
|
||||||
|
.state
|
||||||
|
.totalUnreadCountStream
|
||||||
|
.listen((count) {
|
||||||
|
if (count > 0) {
|
||||||
|
FlutterAppBadger.updateBadgeCount(count);
|
||||||
|
} else {
|
||||||
|
FlutterAppBadger.removeBadge();
|
||||||
}
|
}
|
||||||
return true;
|
});
|
||||||
},
|
}
|
||||||
child: ChannelsBloc(
|
super.initState();
|
||||||
child: MessageSearchBloc(
|
}
|
||||||
child: NestedScrollView(
|
|
||||||
floatHeaderSlivers: true,
|
@override
|
||||||
headerSliverBuilder: (_, __) => [
|
void dispose() {
|
||||||
SliverToBoxAdapter(
|
badgeListener?.cancel();
|
||||||
child: SearchTextField(
|
super.dispose();
|
||||||
controller: _controller,
|
}
|
||||||
showCloseButton: _isSearchActive,
|
}
|
||||||
),
|
|
||||||
),
|
class LeftDrawer extends StatelessWidget {
|
||||||
],
|
const LeftDrawer({
|
||||||
body: AnimatedSwitcher(
|
Key? key,
|
||||||
duration: const Duration(milliseconds: 350),
|
required this.user,
|
||||||
child: GestureDetector(
|
}) : super(key: key);
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onPanDown: (_) => FocusScope.of(context).unfocus(),
|
final User user;
|
||||||
child: _isSearchActive
|
|
||||||
? MessageSearchListView(
|
@override
|
||||||
showErrorTile: true,
|
Widget build(BuildContext context) {
|
||||||
messageQuery: _channelQuery,
|
return Drawer(
|
||||||
filters: Filter.in_('members', [user!.id]),
|
child: Container(
|
||||||
sortOptions: [
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
SortOption(
|
child: SafeArea(
|
||||||
'created_at',
|
child: Padding(
|
||||||
direction: SortOption.ASC,
|
padding: EdgeInsets.only(
|
||||||
|
top: MediaQuery.of(context).viewPadding.top + 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 20.0,
|
||||||
|
left: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
UserAvatar(
|
||||||
|
user: user,
|
||||||
|
showOnlineStatus: false,
|
||||||
|
constraints: BoxConstraints.tight(Size.fromRadius(20)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: Text(
|
||||||
|
user.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
pullToRefresh: false,
|
|
||||||
paginationParams: PaginationParams(limit: 20),
|
|
||||||
emptyBuilder: (_) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, viewportConstraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: viewportConstraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: StreamSvgIcon.search(
|
|
||||||
size: 96,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'No results...',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onItemTap: (messageResponse) async {
|
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
final message = messageResponse.message;
|
|
||||||
final channel = client.channel(
|
|
||||||
messageResponse.channel!.type,
|
|
||||||
id: messageResponse.channel!.id,
|
|
||||||
);
|
|
||||||
if (channel.state == null) {
|
|
||||||
await channel.watch();
|
|
||||||
}
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
Routes.CHANNEL_PAGE,
|
|
||||||
arguments: ChannelPageArgs(
|
|
||||||
channel: channel,
|
|
||||||
initialMessage: message,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: ChannelListView(
|
|
||||||
onStartChatPressed: () {
|
|
||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
|
||||||
},
|
|
||||||
swipeToAction: true,
|
|
||||||
filter: Filter.in_('members', [user!.id]),
|
|
||||||
presence: true,
|
|
||||||
pagination: PaginationParams(
|
|
||||||
limit: 20,
|
|
||||||
),
|
),
|
||||||
channelWidget: ChannelPage(),
|
),
|
||||||
onViewInfoTap: (channel) {
|
],
|
||||||
Navigator.pop(context);
|
),
|
||||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
),
|
||||||
Navigator.push(
|
ListTile(
|
||||||
context,
|
leading: StreamSvgIcon.penWrite(
|
||||||
MaterialPageRoute(
|
color: StreamChatTheme.of(context)
|
||||||
builder: (context) => StreamChannel(
|
.colorTheme
|
||||||
channel: channel,
|
.black
|
||||||
child: ChatInfoScreen(
|
.withOpacity(.5),
|
||||||
messageTheme: StreamChatTheme.of(context)
|
),
|
||||||
.ownMessageTheme,
|
onTap: () {
|
||||||
user: channel.state!.members
|
Navigator.popAndPushNamed(
|
||||||
.where((m) =>
|
context,
|
||||||
m.userId !=
|
Routes.NEW_CHAT,
|
||||||
channel.client.state.user!.id)
|
);
|
||||||
.first
|
},
|
||||||
.user,
|
title: Text(
|
||||||
),
|
'New direct message',
|
||||||
),
|
style: TextStyle(
|
||||||
),
|
fontSize: 14.5,
|
||||||
);
|
),
|
||||||
} else {
|
),
|
||||||
Navigator.push(
|
),
|
||||||
context,
|
ListTile(
|
||||||
MaterialPageRoute(
|
leading: StreamSvgIcon.contacts(
|
||||||
builder: (context) => StreamChannel(
|
color: StreamChatTheme.of(context)
|
||||||
channel: channel,
|
.colorTheme
|
||||||
child: GroupInfoScreen(
|
.black
|
||||||
messageTheme: StreamChatTheme.of(context)
|
.withOpacity(.5),
|
||||||
.ownMessageTheme,
|
),
|
||||||
),
|
onTap: () {
|
||||||
),
|
Navigator.popAndPushNamed(
|
||||||
),
|
context,
|
||||||
);
|
Routes.NEW_GROUP_CHAT,
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
'New group',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
|
||||||
|
if (!kIsWeb) {
|
||||||
|
final secureStorage = FlutterSecureStorage();
|
||||||
|
await secureStorage.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
final client = StreamChat.of(context).client;
|
||||||
|
client.disconnectUser();
|
||||||
|
await client.dispose();
|
||||||
|
|
||||||
|
await Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: true,
|
||||||
|
).pushNamedAndRemoveUntil(
|
||||||
|
Routes.CHOOSE_USER,
|
||||||
|
ModalRoute.withName(Routes.CHOOSE_USER),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
leading: StreamSvgIcon.user(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.black
|
||||||
|
.withOpacity(.5),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Sign out',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: StreamSvgIcon.iconMoon(
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
onPressed: () async {
|
||||||
|
final sp = await StreamingSharedPreferences.instance;
|
||||||
|
sp.setInt(
|
||||||
|
'theme',
|
||||||
|
Theme.of(context).brightness == Brightness.dark
|
||||||
|
? 1
|
||||||
|
: -1,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -71,21 +71,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Center(
|
leading: StreamBackButton(),
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
child: StreamSvgIcon.left(
|
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
body: _buildMediaGrid(),
|
body: _buildMediaGrid(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:example/app_config.dart';
|
import 'package:example/app_config.dart';
|
||||||
|
import 'package:example/home_page.dart';
|
||||||
import 'package:example/stream_version.dart';
|
import 'package:example/stream_version.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -6,6 +7,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
import 'main.dart';
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
const kStreamApiKey = 'STREAM_API_KEY';
|
const kStreamApiKey = 'STREAM_API_KEY';
|
||||||
@@ -89,8 +91,11 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
final client = StreamChatClient(
|
||||||
// client.apiKey = kDefaultStreamApiKey;
|
kDefaultStreamApiKey,
|
||||||
|
logLevel: Level.INFO,
|
||||||
|
)..chatPersistenceClient = chatPersistentClient;
|
||||||
|
|
||||||
await client.connectUser(
|
await client.connectUser(
|
||||||
user,
|
user,
|
||||||
token,
|
token,
|
||||||
@@ -115,6 +120,7 @@ class ChooseUserPage extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
Routes.HOME,
|
Routes.HOME,
|
||||||
ModalRoute.withName(Routes.HOME),
|
ModalRoute.withName(Routes.HOME),
|
||||||
|
arguments: HomePageArgs(client),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leading: UserAvatar(
|
leading: UserAvatar(
|
||||||
|
|||||||
@@ -1,240 +1,30 @@
|
|||||||
|
import 'package:example/routes/app_routes.dart';
|
||||||
import 'package:example/routes/routes.dart';
|
import 'package:example/routes/routes.dart';
|
||||||
import 'package:example/user_mentions_page.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
|
|
||||||
|
|
||||||
import 'channel_list_page.dart';
|
class HomePageArgs {
|
||||||
|
final StreamChatClient chatClient;
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
HomePageArgs(this.chatClient);
|
||||||
@override
|
|
||||||
_HomePageState createState() => _HomePageState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class HomePage extends StatelessWidget {
|
||||||
int _currentIndex = 0;
|
HomePage({
|
||||||
|
|
||||||
bool _isSelected(int index) => _currentIndex == index;
|
|
||||||
|
|
||||||
List<BottomNavigationBarItem> get _navBarItems {
|
|
||||||
return <BottomNavigationBarItem>[
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
StreamSvgIcon.message(
|
|
||||||
color: _isSelected(0)
|
|
||||||
? StreamChatTheme.of(context).colorTheme.black
|
|
||||||
: Colors.grey,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: -3,
|
|
||||||
right: -16,
|
|
||||||
child: UnreadIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
label: 'Chats',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
StreamSvgIcon.mentions(
|
|
||||||
color: _isSelected(1)
|
|
||||||
? StreamChatTheme.of(context).colorTheme.black
|
|
||||||
: Colors.grey,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
label: 'Mentions',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final user = StreamChat.of(context).user!;
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
|
||||||
appBar: ChannelListHeader(
|
|
||||||
onNewChatButtonTap: () {
|
|
||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
|
||||||
},
|
|
||||||
preNavigationCallback: () {
|
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
drawer: LeftDrawer(
|
|
||||||
user: user,
|
|
||||||
),
|
|
||||||
drawerEdgeDragWidth: 50,
|
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
|
||||||
currentIndex: _currentIndex,
|
|
||||||
items: _navBarItems,
|
|
||||||
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
|
|
||||||
unselectedLabelStyle:
|
|
||||||
StreamChatTheme.of(context).textTheme.footnoteBold,
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
selectedItemColor: StreamChatTheme.of(context).colorTheme.black,
|
|
||||||
unselectedItemColor: Colors.grey,
|
|
||||||
onTap: (index) {
|
|
||||||
setState(() => _currentIndex = index);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
body: IndexedStack(
|
|
||||||
index: _currentIndex,
|
|
||||||
children: [
|
|
||||||
ChannelListPage(),
|
|
||||||
UserMentionsPage(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LeftDrawer extends StatelessWidget {
|
|
||||||
const LeftDrawer({
|
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.user,
|
required this.chatClient,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final User user;
|
final StreamChatClient chatClient;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Drawer(
|
return StreamChat(
|
||||||
child: Container(
|
client: chatClient,
|
||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
child: Navigator(
|
||||||
child: SafeArea(
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
child: Padding(
|
initialRoute: Routes.CHANNEL_LIST_PAGE,
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: MediaQuery.of(context).viewPadding.top + 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 20.0,
|
|
||||||
left: 8,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
UserAvatar(
|
|
||||||
user: user,
|
|
||||||
showOnlineStatus: false,
|
|
||||||
constraints: BoxConstraints.tight(Size.fromRadius(20)),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
|
||||||
child: Text(
|
|
||||||
user.name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: StreamSvgIcon.penWrite(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.black
|
|
||||||
.withOpacity(.5),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.popAndPushNamed(
|
|
||||||
context,
|
|
||||||
Routes.NEW_CHAT,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
title: Text(
|
|
||||||
'New direct message',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: StreamSvgIcon.contacts(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.black
|
|
||||||
.withOpacity(.5),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.popAndPushNamed(
|
|
||||||
context,
|
|
||||||
Routes.NEW_GROUP_CHAT,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
title: Text(
|
|
||||||
'New group',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
|
|
||||||
if (!kIsWeb) {
|
|
||||||
final secureStorage = FlutterSecureStorage();
|
|
||||||
await secureStorage.deleteAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamChat.of(context).client.disconnectUser();
|
|
||||||
|
|
||||||
await Navigator.pushNamedAndRemoveUntil(
|
|
||||||
context,
|
|
||||||
Routes.APP,
|
|
||||||
ModalRoute.withName(Routes.APP),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
leading: StreamSvgIcon.user(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.black
|
|
||||||
.withOpacity(.5),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
'Sign out',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: StreamSvgIcon.iconMoon(
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
color: StreamChatTheme.of(context).colorTheme.grey,
|
|
||||||
onPressed: () async {
|
|
||||||
final sp = await StreamingSharedPreferences.instance;
|
|
||||||
sp.setInt(
|
|
||||||
'theme',
|
|
||||||
Theme.of(context).brightness == Brightness.dark
|
|
||||||
? 1
|
|
||||||
: -1,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:example/choose_user_page.dart';
|
import 'package:example/choose_user_page.dart';
|
||||||
|
import 'package:example/home_page.dart';
|
||||||
|
import 'package:example/splash_screen.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||||
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
|
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
|
||||||
|
|
||||||
import 'app_config.dart';
|
import 'app_config.dart';
|
||||||
import 'notifications_service.dart';
|
|
||||||
import 'routes/app_routes.dart';
|
import 'routes/app_routes.dart';
|
||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
@@ -24,8 +22,6 @@ final chatPersistentClient = StreamChatPersistenceClient(
|
|||||||
);
|
);
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,13 +30,9 @@ class MyApp extends StatefulWidget {
|
|||||||
_MyAppState createState() => _MyAppState();
|
_MyAppState createState() => _MyAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
class _MyAppState extends State<MyApp>
|
||||||
|
with SplashScreenStateMixin, TickerProviderStateMixin {
|
||||||
InitData? _initData;
|
InitData? _initData;
|
||||||
bool _animCompleted = false;
|
|
||||||
Animation<double>? _animation, _scaleAnimation;
|
|
||||||
AnimationController? _animationController, _scaleAnimationController;
|
|
||||||
Animation<Color?>? _colorAnimation;
|
|
||||||
late int timeOfStartMs;
|
|
||||||
|
|
||||||
Future<InitData> _initConnection() async {
|
Future<InitData> _initConnection() async {
|
||||||
String? apiKey, userId, token;
|
String? apiKey, userId, token;
|
||||||
@@ -69,56 +61,9 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
return InitData(client, prefs);
|
return InitData(client, prefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createAnimations() {
|
|
||||||
_scaleAnimationController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
value: 0,
|
|
||||||
duration: Duration(
|
|
||||||
milliseconds: 500,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_scaleAnimation = Tween(
|
|
||||||
begin: 1.0,
|
|
||||||
end: 1.5,
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: _scaleAnimationController!,
|
|
||||||
curve: Curves.easeInOutBack,
|
|
||||||
));
|
|
||||||
|
|
||||||
_animationController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: Duration(
|
|
||||||
milliseconds: 1000,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_animation = Tween(
|
|
||||||
begin: 0.0,
|
|
||||||
end: 1000.0,
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: _animationController!,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
));
|
|
||||||
_colorAnimation = ColorTween(
|
|
||||||
begin: Color(0xff005FFF),
|
|
||||||
end: Color(0xff005FFF),
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: _animationController!,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
));
|
|
||||||
_colorAnimation = ColorTween(
|
|
||||||
begin: Color(0xff005FFF),
|
|
||||||
end: Colors.transparent,
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: _animationController!,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
timeOfStartMs = DateTime.now().millisecondsSinceEpoch;
|
final timeOfStartMs = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
_createAnimations();
|
|
||||||
|
|
||||||
_initConnection().then(
|
_initConnection().then(
|
||||||
(initData) {
|
(initData) {
|
||||||
@@ -126,97 +71,23 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
_initData = initData;
|
_initData = initData;
|
||||||
});
|
});
|
||||||
|
|
||||||
var now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
if (now - timeOfStartMs > 1500) {
|
if (now - timeOfStartMs > 1500) {
|
||||||
SchedulerBinding.instance!.addPostFrameCallback((timeStamp) {
|
SchedulerBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_scaleAnimationController?.forward().whenComplete(() {
|
forwardAnimations();
|
||||||
_animationController?.forward();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Future.delayed(Duration(milliseconds: 1500)).then((value) {
|
Future.delayed(Duration(milliseconds: 1500)).then((value) {
|
||||||
_scaleAnimationController?.forward().whenComplete(() {
|
forwardAnimations();
|
||||||
_animationController?.forward();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kIsWeb) {
|
|
||||||
_initData!.client.state.totalUnreadCountStream.listen((count) {
|
|
||||||
if (count > 0) {
|
|
||||||
FlutterAppBadger.updateBadgeCount(count);
|
|
||||||
} else {
|
|
||||||
FlutterAppBadger.removeBadge();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_animationController?.addStatusListener((status) {
|
|
||||||
if (status == AnimationStatus.completed) {
|
|
||||||
setState(() {
|
|
||||||
_animCompleted = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAnimation() {
|
|
||||||
return MaterialApp(
|
|
||||||
home: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
AnimatedBuilder(
|
|
||||||
animation: _scaleAnimation!,
|
|
||||||
builder: (context, _) {
|
|
||||||
return Transform.scale(
|
|
||||||
scale: _scaleAnimation!.value,
|
|
||||||
child: AnimatedBuilder(
|
|
||||||
animation: _colorAnimation!,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
return Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
constraints: BoxConstraints.expand(),
|
|
||||||
color: _colorAnimation == null
|
|
||||||
? Color(0xff005FFF)
|
|
||||||
: _colorAnimation!.value,
|
|
||||||
child: !_animationController!.isAnimating
|
|
||||||
? Lottie.asset(
|
|
||||||
'assets/floating_boat.json',
|
|
||||||
alignment: Alignment.center,
|
|
||||||
)
|
|
||||||
: SizedBox(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AnimatedBuilder(
|
|
||||||
animation: _animation!,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
return Transform.scale(
|
|
||||||
scale: _animation!.value,
|
|
||||||
child: Container(
|
|
||||||
width: 1.0,
|
|
||||||
height: 1.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white
|
|
||||||
.withOpacity(1 - _animationController!.value),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
@@ -229,27 +100,6 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) => MaterialApp(
|
builder: (context, snapshot) => MaterialApp(
|
||||||
builder: (context, child) {
|
|
||||||
return StreamChat(
|
|
||||||
backgroundKeepAlive: Duration(seconds: 5),
|
|
||||||
client: _initData!.client,
|
|
||||||
onBackgroundEventReceived: (e) => showLocalNotification(
|
|
||||||
e, _initData!.client.state.user!.id),
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
|
|
||||||
child: child!,
|
|
||||||
value: SystemUiOverlayStyle(
|
|
||||||
systemNavigationBarColor:
|
|
||||||
StreamChatTheme.of(context).colorTheme.white,
|
|
||||||
systemNavigationBarIconBrightness:
|
|
||||||
Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Brightness.light
|
|
||||||
: Brightness.dark,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
theme: ThemeData.light(),
|
theme: ThemeData.light(),
|
||||||
darkTheme: ThemeData.dark(),
|
darkTheme: ThemeData.dark(),
|
||||||
themeMode: {
|
themeMode: {
|
||||||
@@ -257,13 +107,38 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
0: ThemeMode.system,
|
0: ThemeMode.system,
|
||||||
1: ThemeMode.light,
|
1: ThemeMode.light,
|
||||||
}[snapshot],
|
}[snapshot],
|
||||||
|
builder: (context, child) => StreamChatTheme(
|
||||||
|
data: StreamChatThemeData(
|
||||||
|
brightness: Theme.of(context).brightness,
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
|
),
|
||||||
onGenerateRoute: AppRoutes.generateRoute,
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
|
onGenerateInitialRoutes: (initialRouteName) {
|
||||||
|
if (initialRouteName == Routes.HOME) {
|
||||||
|
return [
|
||||||
|
AppRoutes.generateRoute(
|
||||||
|
RouteSettings(
|
||||||
|
name: Routes.HOME,
|
||||||
|
arguments: HomePageArgs(_initData!.client),
|
||||||
|
),
|
||||||
|
)!
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
AppRoutes.generateRoute(
|
||||||
|
RouteSettings(
|
||||||
|
name: Routes.CHOOSE_USER,
|
||||||
|
),
|
||||||
|
)!
|
||||||
|
];
|
||||||
|
},
|
||||||
initialRoute: _initData!.client.state.user == null
|
initialRoute: _initData!.client.state.user == null
|
||||||
? Routes.CHOOSE_USER
|
? Routes.CHOOSE_USER
|
||||||
: Routes.HOME,
|
: Routes.HOME,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!_animCompleted) _buildAnimation(),
|
if (!animationCompleted) buildAnimation(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -275,50 +150,3 @@ class InitData {
|
|||||||
|
|
||||||
InitData(this.client, this.preferences);
|
InitData(this.client, this.preferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
class HolePainter extends CustomPainter {
|
|
||||||
HolePainter({
|
|
||||||
required this.color,
|
|
||||||
required this.holeSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
Color color;
|
|
||||||
double holeSize;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
double radius = holeSize / 2;
|
|
||||||
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
|
|
||||||
Rect outerCircleRect = Rect.fromCircle(
|
|
||||||
center: Offset(size.width / 2, size.height / 2), radius: radius);
|
|
||||||
Rect innerCircleRect = Rect.fromCircle(
|
|
||||||
center: Offset(size.width / 2, size.height / 2), radius: radius / 2);
|
|
||||||
|
|
||||||
Path transparentHole = Path.combine(
|
|
||||||
PathOperation.difference,
|
|
||||||
Path()..addRect(rect),
|
|
||||||
Path()
|
|
||||||
..addOval(outerCircleRect)
|
|
||||||
..close(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Path halfTransparentRing = Path.combine(
|
|
||||||
PathOperation.difference,
|
|
||||||
Path()
|
|
||||||
..addOval(outerCircleRect)
|
|
||||||
..close(),
|
|
||||||
Path()
|
|
||||||
..addOval(innerCircleRect)
|
|
||||||
..close(),
|
|
||||||
);
|
|
||||||
|
|
||||||
canvas.drawPath(transparentHole, Paint()..color = color);
|
|
||||||
canvas.drawPath(
|
|
||||||
halfTransparentRing, Paint()..color = color.withOpacity(0.5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,21 +70,7 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
|
|||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Center(
|
leading: StreamBackButton(),
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 24.0,
|
|
||||||
height: 24.0,
|
|
||||||
child: StreamSvgIcon.left(
|
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
body: _buildMediaGrid(),
|
body: _buildMediaGrid(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:example/channel_list_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
@@ -28,7 +29,10 @@ class AppRoutes {
|
|||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.HOME),
|
settings: const RouteSettings(name: Routes.HOME),
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return HomePage();
|
final homePageArgs = args as HomePageArgs;
|
||||||
|
return HomePage(
|
||||||
|
chatClient: homePageArgs.chatClient,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
case Routes.CHOOSE_USER:
|
case Routes.CHOOSE_USER:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
@@ -92,6 +96,12 @@ class AppRoutes {
|
|||||||
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
case Routes.CHANNEL_LIST_PAGE:
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: Routes.CHANNEL_LIST_PAGE),
|
||||||
|
builder: (context) {
|
||||||
|
return ChannelListPage();
|
||||||
|
});
|
||||||
// Default case, should not reach here.
|
// Default case, should not reach here.
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ class Routes {
|
|||||||
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
|
||||||
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
|
||||||
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
static const String GROUP_INFO_SCREEN = '/group_info_screen';
|
||||||
|
static const String CHANNEL_LIST_PAGE = '/channel_list_page';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
|
mixin SplashScreenStateMixin<T extends StatefulWidget> on State<T>
|
||||||
|
implements TickerProvider {
|
||||||
|
late Animation<double> animation, scaleAnimation;
|
||||||
|
late AnimationController _animationController, _scaleAnimationController;
|
||||||
|
late Animation<Color?> colorAnimation;
|
||||||
|
bool animationCompleted = false;
|
||||||
|
|
||||||
|
void _createAnimations() {
|
||||||
|
_scaleAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
value: 0,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
scaleAnimation = Tween(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 1.5,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _scaleAnimationController,
|
||||||
|
curve: Curves.easeInOutBack,
|
||||||
|
));
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 1000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
animation = Tween(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1000.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
colorAnimation = ColorTween(
|
||||||
|
begin: Color(0xff005FFF),
|
||||||
|
end: Color(0xff005FFF),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
colorAnimation = ColorTween(
|
||||||
|
begin: Color(0xff005FFF),
|
||||||
|
end: Colors.transparent,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void forwardAnimations() {
|
||||||
|
_scaleAnimationController.forward().whenComplete(() {
|
||||||
|
_animationController.forward();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildAnimation() => Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: scaleAnimation,
|
||||||
|
builder: (context, _) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: scaleAnimation.value,
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: colorAnimation,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
constraints: BoxConstraints.expand(),
|
||||||
|
color: colorAnimation.value,
|
||||||
|
child: !_animationController.isAnimating
|
||||||
|
? Lottie.asset(
|
||||||
|
'assets/floating_boat.json',
|
||||||
|
alignment: Alignment.center,
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: animation,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: animation.value,
|
||||||
|
child: Container(
|
||||||
|
width: 1.0,
|
||||||
|
height: 1.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white
|
||||||
|
.withOpacity(1 - _animationController.value),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_createAnimations();
|
||||||
|
_animationController.addStatusListener((status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
setState(() {
|
||||||
|
animationCompleted = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,15 +11,9 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
git:
|
path: ../../../stream-chat-flutter/packages/stream_chat_flutter
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
|
||||||
ref: ref/segregate-api-layer
|
|
||||||
path: packages/stream_chat_flutter
|
|
||||||
stream_chat_persistence:
|
stream_chat_persistence:
|
||||||
git:
|
path: ../../../stream-chat-flutter/packages/stream_chat_persistence
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
|
||||||
ref: ref/segregate-api-layer
|
|
||||||
path: packages/stream_chat_persistence
|
|
||||||
flutter_local_notifications: ^5.0.0+4
|
flutter_local_notifications: ^5.0.0+4
|
||||||
flutter_svg: ^0.22.0
|
flutter_svg: ^0.22.0
|
||||||
flutter_secure_storage: ^4.2.0
|
flutter_secure_storage: ^4.2.0
|
||||||
@@ -31,15 +25,9 @@ dependencies:
|
|||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
stream_chat:
|
stream_chat:
|
||||||
git:
|
path: ../../../stream-chat-flutter/packages/stream_chat
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
|
||||||
ref: ref/segregate-api-layer
|
|
||||||
path: packages/stream_chat
|
|
||||||
stream_chat_flutter_core:
|
stream_chat_flutter_core:
|
||||||
git:
|
path: ../../../stream-chat-flutter/packages/stream_chat_flutter_core
|
||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
|
||||||
ref: ref/segregate-api-layer
|
|
||||||
path: packages/stream_chat_flutter_core
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.9.0
|
flutter_launcher_icons: ^0.9.0
|
||||||
|
|||||||
Reference in New Issue
Block a user