代码更新即时通讯

This commit is contained in:
apple 2022-08-05 20:18:30 +08:00
parent 08c5947e63
commit ba45554dfa
15 changed files with 2465 additions and 291 deletions

BIN
images/palRoom/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

6
lib/api/part.dart Normal file
View File

@ -0,0 +1,6 @@
import '../../utils/http_util.dart';
//
getList(data) {
return MyHttpUtil().get("/chat/api/room/page", data: data);
}

View File

@ -14,8 +14,10 @@ class MyHttpUtil {
// "Authorization": getToken() ? 'Bearer $getToken()' : null
};
options = dioplugn.BaseOptions(
connectTimeout: 15000,
receiveTimeout: 3000,
connectTimeout: 15000, //
receiveTimeout: 15000,
// baseUrl: 'http://192.118.2.93:9093',
// baseUrl: 'http://HousedeMacBook-Air.local:9093',
baseUrl: 'http://101.35.117.69:9093',
headers: header ?? defaultHeader,
);

View File

@ -1,3 +1,3 @@
const APP_ID = '809529cf18814549a0249802512a7508';
const Token =
'006809529cf18814549a0249802512a7508IAAPcoSQKL/h/B6xy0PPG/XdoZUWas07EdCBtxLe7HH4Hz4vjswAAAAAEABsSV6NzT/fYgEAAQDKP99i';
'006809529cf18814549a0249802512a7508IAAV27Y5MjNYz2233R1af76+12kaNyjb5VI8eBXNHY9fED4vjswAAAAAEACOhaHH2fLpYgEAAQDW8uli';

View File

View File

@ -1,11 +1,13 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/utils/auth.dart';
import 'package:will_play/views/palRoom/PalRoom.dart';
import '../../utils/http_util.dart';
import '../../utils/settings.dart';
class CreateRoomPage extends StatefulWidget {
CreateRoomPage({Key? key}) : super(key: key);
@ -17,6 +19,8 @@ class CreateRoomPage extends StatefulWidget {
class _CreateRoomPageState extends State<CreateRoomPage> {
final _formKey = GlobalKey<FormState>();
final _roomName = TextEditingController();
late RtcEngine _engine; //
bool flag = false;
List patternList = [
@ -57,59 +61,61 @@ class _CreateRoomPageState extends State<CreateRoomPage> {
),
];
var Timer;
late Timer timer; //
late String roomName = _roomName.text; //
late String roomId; // ID
void _create(roomName) {
MyHttpUtil().post(
"/chat/api/room/create",
data: {"name": roomName},
).then((res) {
var roomId = res.data;
void _create() {
MyHttpUtil()
.post("/chat/api/room/create", data: {"name": roomName}).then((res) {
roomId = res.data;
// print(roomId);
Timer = Timer.periodic(Duration(seconds: 1), (timer) async {
_lodingCreate(roomId);
});
// Timer = Timer.periodic(Duration(seconds: 1), (timer) async {
// var resp = await MyHttpUtil().get(
// "/chat/api/room/create/$roomId",
// );
// String userName = await Storage.get('userName');
// print(userName);
// print(resp.data);
// if (resp.data == true) {
// // onJoin(userName, roomName, ClientRole.Broadcaster); // 1 2 3
// Timer?.cancel();
// Timer = null;
// }
// });
_agoraToken(roomId);
});
}
void _lodingCreate(roomId) async {
var resp = await MyHttpUtil().get(
"/chat/api/room/create/$roomId",
);
String userName = await Storage.get('userName');
print(userName);
print(resp.data);
if (resp.data == true) {
// onJoin(userName, roomName, ClientRole.Broadcaster); // 1 2 3
Timer?.cancel();
Timer = null;
void _agoraToken(roomId) async {
var res = await MyHttpUtil().get("/chat/api/auth/agora/token",
data: {'channelName': roomId, 'role': 1});
var agoraToken = res.data["token"];
String uid = await Storage.get('id');
String userName = await Storage.get('userName'); //
_onJoin(agoraToken, userName, roomId, ClientRole.Broadcaster);
// Future.delayed(Duration(seconds: 3), () {
// _onJoin(agoraToken, userName, roomId, ClientRole.Broadcaster);
// });
// if (agoraToken != null && agoraToken.isNotEmpty) {
// _engine = await RtcEngine.create(APP_ID); //
// _engine.joinChannel(agoraToken, roomId, null, int.parse(uid)); //
// timer = Timer.periodic(Duration(seconds: 1), (timer) async {
// _lodingCreate(agoraToken);
// });
// }
}
void _lodingCreate(agoraToken) async {
var res = await MyHttpUtil().get("/chat/api/room/create/$roomId");
String userName = await Storage.get('userName'); //
print('_lodingCreate${res.data}');
if (res.data == true) {
_onJoin(agoraToken, userName, roomId, ClientRole.Broadcaster);
timer.cancel();
}
}
@override
void dispose() {
super.dispose();
Timer?.cancel();
Timer = null;
timer.cancel();
}
// 1 2 3
Future<void> onJoin(userName, channelName, role) async {
// 1token2 3 4
Future<void> _onJoin(agoraToken, userName, channelName, role) async {
await _handleCameraAndMic(Permission.camera);
await _handleCameraAndMic(Permission.microphone);
@ -117,6 +123,7 @@ class _CreateRoomPageState extends State<CreateRoomPage> {
context,
MaterialPageRoute(
builder: (context) => PalRoomPage(
agoraToken: agoraToken,
userName: userName,
channelName: channelName,
role: role,
@ -374,8 +381,8 @@ class _CreateRoomPageState extends State<CreateRoomPage> {
),
onPressed: () {
if (_formKey.currentState!.validate()) {
_create(_roomName.text);
print('表单验证通过!');
_create();
}
},
),

View File

@ -1,17 +1,95 @@
// import 'package:flutter/material.dart';
// class InformationPage extends StatefulWidget {
// InformationPage({Key? key}) : super(key: key);
// @override
// State<InformationPage> createState() => _InformationPageState();
// }
// class _InformationPageState extends State<InformationPage> {
// @override
// Widget build(BuildContext context) {
// return Container(
// child: Text('aa'),
// );
// }
// }
import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
class InformationPage extends StatefulWidget {
InformationPage({Key? key}) : super(key: key);
@override
State<InformationPage> createState() => _InformationPageState();
_InformationPageState createState() => _InformationPageState();
}
class _InformationPageState extends State<InformationPage> {
TextEditingController _controller = TextEditingController();
late IOWebSocketChannel channel;
String _text = "";
@override
void initState() {
// websocket连接
channel = IOWebSocketChannel.connect(
'ws://101.35.117.69:9093/chat/api/chat/room/${1}');
}
@override
Widget build(BuildContext context) {
return Container(
child: Text("消息"),
return Scaffold(
appBar: AppBar(
title: Text("发现页面 WebSocket"),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: TextField(
controller: _controller,
decoration: InputDecoration(labelText: '发送消息'),
),
),
StreamBuilder(
stream: channel.stream,
builder: (context, snapshot) {
//
if (snapshot.hasError) {
_text = "网络不通...";
} else if (snapshot.hasData) {
_text = "echo: ${snapshot.data}";
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Text(_text),
);
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: Icon(Icons.send),
),
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
channel.sink.add(_controller.text);
}
}
@override
void dispose() {
channel.sink.close();
super.dispose();
}
}

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:will_play/utils/auth.dart';
// import '../../utils/dioHttp.dart';
import '../../utils/http_util.dart';
import '../../utils/auth.dart';
import '../../utils/showToast.dart';
@ -36,7 +35,7 @@ class _LoginPageState extends State<LoginPage> {
var setToken = await Storage.set('token', '${res.data["token"]}');
var setId = await Storage.set('id', '${res.data["id"]}');
var setUserName = await Storage.set('userName', '${res.data["userName"]}');
var setUserName = await Storage.set('userName', '${res.data["username"]}');
Navigator.of(context).pushReplacementNamed('/');
}
@ -44,6 +43,7 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0, // z轴阴影
@ -61,7 +61,7 @@ class _LoginPageState extends State<LoginPage> {
fontWeight: FontWeight.bold),
),
),
body: Padding(
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Form(
key: _formKey,

View File

@ -1,77 +1,106 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../utils/settings.dart';
import 'package:will_play/utils/auth.dart';
import 'package:web_socket_channel/io.dart';
//
//
//
//
// (线)
//
// 退()
import '../../utils/settings.dart';
import '../../utils/http_util.dart';
import '../../utils/showToast.dart';
// RtcEngine
// 1.
// 2.
// 3.
// 4.(线)
// 5.
// 6.退()
// 使 WebSocket
// 1.WebSocket服务器
// 2.
// 3.
// 4.WebSocket连接
class PalRoomPage extends StatefulWidget {
final String agoraToken;
final String userName;
final String channelName;
final ClientRole? role;
PalRoomPage({Key? key, this.userName = '', this.channelName = '', this.role})
: super(key: key);
PalRoomPage({
Key? key,
this.agoraToken = '',
this.userName = '',
this.channelName = '',
this.role,
}) : super(key: key);
@override
State<PalRoomPage> createState() => _PalRoomPageState();
}
class _PalRoomPageState extends State<PalRoomPage> {
final _users = <int>[]; // id数组
// final _users = <int>[]; // id数组
final _infoStrings = []; //
bool viewPanel = true; //
bool muted = false; //
bool volume = true; //
late RtcEngine _engine; //
TextEditingController _controller = TextEditingController(); //
late RtcEngine _engine; // RtcEngine
late IOWebSocketChannel _wsChannel; // webSocket
//
List list = [
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
List _list = [
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
];
//
List newUserList = [];
@override
void initState() {
super.initState();
_infoStrings.insert(0, {
'type': 'system',
'content': '信息-${widget.userName}-${widget.channelName}-${widget.role}'
});
initialize();
if (widget.role == ClientRole.Broadcaster) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
} else if (widget.role == ClientRole.Audience) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为观众,点击麦克风图标上麦'});
}
// if (widget.role == ClientRole.Broadcaster) {
// _infoStrings.insert(0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
// } else if (widget.role == ClientRole.Audience) {
// _infoStrings.insert(0, {'type': 'system', 'content': '您身份为观众,点击麦克风图标上麦'});
// }
wsInitalize();
}
@override
void dispose() {
super.dispose();
_users.clear();
// _users.clear();
_engine.leaveChannel();
_engine.destroy();
_wsChannel.sink.close();
}
Future<void> initialize() async {
_infoStrings.insert(0, {
'type': 'system',
'content':
'|令牌:${widget.agoraToken}|用户名:${widget.userName}|房间:${widget.channelName}|身份:${widget.role}'
});
// appid是否存在
if (APP_ID.isEmpty) {
setState(() {
@ -92,12 +121,79 @@ class _PalRoomPageState extends State<PalRoomPage> {
AudioScenario.ChatRoomEntertainment); //
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
_addAgoraEventHandlers(); //
if (widget.role == ClientRole.Broadcaster) {
await _engine.setClientRole(ClientRole.Broadcaster);
}
_engine.joinChannel(Token, widget.channelName, null, 0); //
_addAgoraEventHandlers(); //
String uid = await Storage.get('id');
await _engine.joinChannel(
widget.agoraToken, widget.channelName, null, int.parse(uid)); //
_detail();
}
wsInitalize() async {
String uid = await Storage.get('id');
_wsChannel = IOWebSocketChannel.connect(
'ws://101.35.117.69:9093/chat/api/chat/room/${int.parse(uid)}'); //
print('object');
_wsChannel.stream.listen((data) {
var aa = jsonDecode(data);
print('收到服务器数据:${aa}');
setState(() {
_infoStrings.insert(0, {
'type': 'speak',
'content': '${aa['message']}',
'name': '${aa['username']}'
});
});
}, onDone: () {
print('连接关闭时响应');
}, onError: (error) {
print('发生错误');
}, cancelOnError: true); //
}
//
void _detail() async {
var res =
await MyHttpUtil().get("/chat/api/room/detail/${widget.channelName}");
// print('${res}');
var _userList = res.data['userList'];
// image字段头像
_userList.forEach((item) {
item['image'] = 'images/palRoom/user.png';
item['flag'] = true;
});
//
newUserList =
_userList.where((item) => item['type'] != 'audience').toList();
print('-----------------筛选出身份类型为主播的用户:${newUserList}-----------------');
for (var i = 0; i < _list.length; i++) {
if (newUserList.length < (i + 1)) {
setState(() {
_list[i] = {
'nickname': '',
'flag': false,
'image': 'images/palRoom/voice2.png'
};
});
} else {
setState(() {
_list[i] = newUserList[i];
});
}
}
print('-----------------${_list}-----------------');
}
void _addAgoraEventHandlers() {
@ -109,57 +205,80 @@ class _PalRoomPageState extends State<PalRoomPage> {
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
userJoined: (uid, elapsed) {
_detail();
//
setState(() {
final info = '用户加入: uid $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
joinChannelSuccess: (channel, uid, elapsed) {
_detail();
//
setState(() {
final info = '加入频道: $channel, uid: $uid';
final info = '加入频道成功: $channel, uid: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
leaveChannel: (stats) {
_detail();
//
setState(() {
_infoStrings.insert(0, {'type': 'system', 'content': '离开频道'});
_users.clear();
});
},
userJoined: (uid, elapsed) {
//
setState(() {
final info = '用户加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.add(uid);
// _users.clear();
});
},
userOffline: (uid, reason) {
// 线
_detail();
//
setState(() {
final info = '用户离线: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.remove(uid);
});
},
firstRemoteVideoFrame: (uid, width, height, elapsed) {
//
setState(() {
final info = 'First Remote Video Frame: $uid';
final info = '用户离开当前频道: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.remove(uid);
});
},
clientRoleChanged: (oldRole, newRole) {
_detail();
//
setState(() {
final info = '用户旧身份$oldRole,用户新身份$newRole';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
rejoinChannelSuccess: (channel, uid, elapsed) {
_detail();
//
setState(() {
final info = '用户成功重新加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
warning: (warn) {
// RtcChannel
print(warn);
},
tokenPrivilegeWillExpire: (token) {
// Token 30s内过期回调
// app Token _engine.renewToken(token) Token SDK
print('即将服务失效的 Token:${token}');
},
requestToken: () {
// Token
setState(() {
final info = 'Token 已过期';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
));
}
//
Widget _head() {
//
_modelBottomSheet(index) async {
_modelBottomSheet(index) {
showModalBottomSheet(
context: context,
builder: (context) {
@ -167,33 +286,34 @@ class _PalRoomPageState extends State<PalRoomPage> {
height: 150.0,
child: Column(
children: [
list[index]['content'] == false
_list[index]['flag'] == false
? ListTile(
title: Text(
"连线上麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
for (var i = 0; i < list.length; i++) {
if (i == index) {
list[index]['content'] = true;
list[index]['name'] = '_users';
list[index]['image'] =
'images/palRoom/photo.png';
} else {
list[i]['content'] = false;
list[i]['name'] = '';
list[i]['image'] = 'images/palRoom/voice2.png';
}
}
});
// setState(() {
// for (var i = 0; i < _list.length; i++) {
// if (i == index) {
// _list[index]['flag'] = true;
// _list[index]['nickname'] = '_users';
// _list[index]['image'] =
// 'images/palRoom/user.png';
// } else {
// _list[i]['flag'] = false;
// _list[i]['name'] = '';
// _list[i]['image'] = 'images/palRoom/voice2.png';
// }
// }
// });
//
_engine.setClientRole(ClientRole.Broadcaster);
setState(() {
_list[index]['flag'] = true;
});
Navigator.pop(context);
print("连线上麦:${list[index]['content']}");
},
)
: ListTile(
@ -203,16 +323,13 @@ class _PalRoomPageState extends State<PalRoomPage> {
),
onTap: () {
setState(() {
list[index]['content'] = false;
list[index]['name'] = '';
list[index]['image'] = 'images/palRoom/voice2.png';
_list[index]['flag'] = false;
_list[index]['nickname'] = '';
_list[index]['image'] = 'images/palRoom/voice2.png';
});
//
_engine.setClientRole(ClientRole.Audience);
Navigator.pop(context);
print("下麦:${list[index]['content']}");
},
),
ListTile(
@ -233,11 +350,12 @@ class _PalRoomPageState extends State<PalRoomPage> {
return Container(
child: ListView(
children: [
// -- start --
Container(
padding: EdgeInsets.fromLTRB(0, 27, 0, 5),
alignment: Alignment.topCenter,
child: Image.asset(
'images/palRoom/photo.png',
'images/palRoom/user.png',
width: 70,
height: 70,
),
@ -269,9 +387,12 @@ class _PalRoomPageState extends State<PalRoomPage> {
],
),
),
// -- end --
// -- start --
Container(
// padding: EdgeInsets.only(left: 15, right: 15),
child: GridView.builder(
padding: EdgeInsets.all(15),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 40,
@ -279,7 +400,7 @@ class _PalRoomPageState extends State<PalRoomPage> {
crossAxisCount: 4,
childAspectRatio: 1 / 1.25,
),
itemCount: list.length,
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
@ -304,25 +425,27 @@ class _PalRoomPageState extends State<PalRoomPage> {
borderRadius: BorderRadius.circular(50),
),
child: Center(
child: Image.asset(
list[index]['image'],
width: list[index]['content'] ? 55 : 14,
height: list[index]['content'] ? 55 : 21,
child: ClipOval(
child: Image.asset(
_list[index]['image'],
width: _list[index]['flag'] ? 55 : 14,
height: _list[index]['flag'] ? 55 : 21,
),
),
),
),
Text(
list[index]['name'],
_list[index]['nickname'],
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
;
},
),
),
// -- end --
],
),
);
@ -330,7 +453,7 @@ class _PalRoomPageState extends State<PalRoomPage> {
//
Widget _planel() {
_filtering(type, content) {
_filtering(type, content, {name}) {
switch (type) {
case 'system':
return Container(
@ -342,7 +465,7 @@ class _PalRoomPageState extends State<PalRoomPage> {
case 'speak':
return Container(
child: Text(
'聊天消息:$content',
'${name}聊天消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
@ -364,9 +487,9 @@ class _PalRoomPageState extends State<PalRoomPage> {
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
decoration: BoxDecoration(
color: Colors.black,
),
// decoration: BoxDecoration(
// color: Colors.black,
// ),
child: ListView.builder(
reverse: true,
itemCount: _infoStrings.length,
@ -390,7 +513,8 @@ class _PalRoomPageState extends State<PalRoomPage> {
borderRadius: BorderRadius.circular(5),
),
child: _filtering(_infoStrings[index]['type'],
_infoStrings[index]['content']),
_infoStrings[index]['content'],
name: _infoStrings[index]['name']),
),
),
],
@ -406,7 +530,6 @@ class _PalRoomPageState extends State<PalRoomPage> {
//
Widget _footer() {
final _input = TextEditingController();
return Container(
width: double.infinity,
alignment: Alignment.bottomCenter,
@ -441,10 +564,12 @@ class _PalRoomPageState extends State<PalRoomPage> {
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
//
child: Icon(Icons.tag_faces,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
Container(
//
width: 220,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
@ -452,7 +577,30 @@ class _PalRoomPageState extends State<PalRoomPage> {
borderRadius: BorderRadius.circular(30),
),
child: TextField(
controller: _input,
textInputAction: TextInputAction.send, //
// onEditingComplete: () async {
// late String speak = _controller.text.trim();
// if (speak.isEmpty) {
// showToast("请输入聊天内容~");
// return;
// }
// String userName = await Storage.get('userName'); //
// _wsChannel.sink.add(speak);
// // _controller.text = '';
// },
onSubmitted: (value) async {
late String speak = value.trim();
if (speak.isEmpty) {
showToast("请输入聊天内容~");
return;
}
String userName = await Storage.get('userName'); //
_wsChannel.sink.add(speak);
},
controller: _controller,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
@ -466,10 +614,10 @@ class _PalRoomPageState extends State<PalRoomPage> {
fontSize: 12,
color: Colors.white,
),
textInputAction: TextInputAction.send, //
),
),
GestureDetector(
//
child: Icon(Icons.add,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
@ -483,6 +631,14 @@ class _PalRoomPageState extends State<PalRoomPage> {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
print('离开');
_engine.setClientRole(ClientRole.Audience);
_engine.leaveChannel();
Navigator.of(context).pop();
}),
elevation: 0, // z轴阴影
titleSpacing: 0, //
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
@ -499,7 +655,7 @@ class _PalRoomPageState extends State<PalRoomPage> {
padding: EdgeInsets.only(right: 5),
child: ClipOval(
child: Image.asset(
'images/palRoom/photo.png',
'images/palRoom/user.png',
width: 46,
height: 46,
),

View File

@ -0,0 +1,652 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/utils/auth.dart';
import '../../utils/settings.dart';
import '../../utils/http_util.dart';
// 实现语音主要五个步骤逻辑:
// 初始化引擎
// 开启音频权限,启用音频模块
// 创建房间
// 设置事件监听(成功加入房间,是否有用户加入,用户是否离开,用户是否掉线)
// 布局实现
// 退出语音(根据需要销毁引擎,释放资源)
class PalRoomPage extends StatefulWidget {
final String agoraToken;
final String userName;
final String channelName;
final ClientRole? role;
PalRoomPage({
Key? key,
this.agoraToken = '',
this.userName = '',
this.channelName = '',
this.role,
}) : super(key: key);
@override
State<PalRoomPage> createState() => _PalRoomPageState();
}
class _PalRoomPageState extends State<PalRoomPage> {
final _users = <int>[]; // 用户id数组
final _infoStrings = []; // 状态文字信息列表
bool viewPanel = true; // 是否显示状态文字信息列表
bool muted = false; // 是否开麦
bool volume = true; // 音量
late RtcEngine _engine; // 声网实例变量
// 房间座位
List _list = [
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
{'name': '', 'content': false, 'image': 'images/palRoom/voice2.png'},
];
// 在线用户数据
List _userList = [];
@override
void initState() {
super.initState();
_infoStrings.insert(0, {
'type': 'system',
'content':
'|令牌:${widget.agoraToken}|用户名:${widget.userName}|房间:${widget.channelName}|身份:${widget.role}'
});
initialize();
if (widget.role == ClientRole.Broadcaster) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
} else if (widget.role == ClientRole.Audience) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为观众,点击麦克风图标上麦'});
}
}
@override
void dispose() {
super.dispose();
_users.clear();
_engine.leaveChannel();
_engine.destroy();
}
Future<void> initialize() async {
// 判断appid是否存在
if (APP_ID.isEmpty) {
setState(() {
_infoStrings.insert(0, {
'type': 'system',
'content': 'APP ID缺失请在settings.dart中提供您的APP ID'
});
_infoStrings.insert(0, {'type': 'system', 'content': 'Agora引擎没有启动'});
});
return;
}
_engine = await RtcEngine.create(APP_ID); // 初始化引擎
await _engine.enableAudio(); // 启用音频模块
await _engine.setDefaultAudioRouteToSpeakerphone(
true); // 设置默认的音频路由:该方法设置接收到的音频从听筒或扬声器出声。如果用户不调用本方法,音频默认从听筒出声。
await _engine.setAudioProfile(AudioProfile.SpeechStandard,
AudioScenario.ChatRoomEntertainment); // 设置音频编码属性和音频场景
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
_addAgoraEventHandlers(); // 调用状态文字信息列表方法
if (widget.role == ClientRole.Broadcaster) {
await _engine.setClientRole(ClientRole.Broadcaster);
}
String uid = await Storage.get('id');
print(widget.agoraToken);
print(widget.channelName);
print(uid);
await _engine.joinChannel(
widget.agoraToken, widget.channelName, null, int.parse(uid)); // 加入频道
}
// 获取房间详情接口
void _detail() async {
var res =
await MyHttpUtil().get("/chat/api/room/detail/${widget.channelName}");
print('_detail接口${res}');
setState(() {
this._userList = res.data.userList;
});
}
void _addAgoraEventHandlers() {
_engine.setEventHandler(RtcEngineEventHandler(error: (code) {
// 错误
setState(() {
final info = '错误 $code';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
}, userJoined: (uid, elapsed) {
// 用户加入
setState(() {
final info = '用户加入: uid $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.add(uid);
print('用户加入:');
_detail();
});
}, joinChannelSuccess: (channel, uid, elapsed) {
// 加入频道成功
setState(() {
final info = '加入频道: $channel, uid: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
print('加入频道成功:');
_detail();
});
}, leaveChannel: (stats) {
// 离开频道
setState(() {
_infoStrings.insert(0, {'type': 'system', 'content': '离开频道'});
_users.clear();
print('离开频道:');
_detail();
});
}, userOffline: (uid, reason) {
// 用户离线
setState(() {
final info = '用户离线: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.remove(uid);
print('用户离线:');
_detail();
});
}, clientRoleChanged: (oldRole, newRole) {
// 直播场景下用户角色切换成功回调
setState(() {
final info = '用户旧身份$oldRole,用户新身份$newRole';
_infoStrings.insert(0, {'type': 'system', 'content': info});
print('直播场景下用户角色切换成功回调:');
_detail();
});
}, rejoinChannelSuccess: (channel, uid, elapsed) {
// 成功重新加入频道回调
setState(() {
final info = '用户成功重新加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
_users.add(uid);
print('成功重新加入频道回调:');
_detail();
});
}, requestToken: () {
// Token 已过期回调
setState(() {
final info = 'Token 已过期';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
}));
}
// 头部
Widget _head() {
// 底部弹框组件
_modelBottomSheet(index) async {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 150.0,
child: Column(
children: [
_list[index]['content'] == false
? ListTile(
title: Text(
"连线上麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
for (var i = 0; i < _list.length; i++) {
if (i == index) {
_list[index]['content'] = true;
_list[index]['name'] = '_users';
_list[index]['image'] =
'images/palRoom/photo.png';
} else {
_list[i]['content'] = false;
_list[i]['name'] = '';
_list[i]['image'] = 'images/palRoom/voice2.png';
}
}
});
// 申请连麦需要改变身份为主播!
_engine.setClientRole(ClientRole.Broadcaster);
Navigator.pop(context);
print("连线上麦:${_list[index]['content']}");
},
)
: ListTile(
title: Text(
"下麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
_list[index]['content'] = false;
_list[index]['name'] = '';
_list[index]['image'] = 'images/palRoom/voice2.png';
});
// 下麦再将身份改回为观众(下麦不代表离开频道)
_engine.setClientRole(ClientRole.Audience);
Navigator.pop(context);
print("下麦:${_list[index]['content']}");
},
),
ListTile(
title: Text(
"取消",
textAlign: TextAlign.center,
),
onTap: () {
Navigator.pop(context);
}),
],
),
);
},
);
}
return Container(
child: ListView(
children: [
Container(
padding: EdgeInsets.fromLTRB(0, 27, 0, 5),
alignment: Alignment.topCenter,
child: Image.asset(
'images/palRoom/photo.png',
width: 70,
height: 70,
),
),
Container(
margin: EdgeInsets.only(bottom: 30),
alignment: Alignment.topCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 18,
height: 18,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color.fromRGBO(245, 173, 29, 1),
borderRadius: BorderRadius.circular(2),
),
child: Text(
'房',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
SizedBox(width: 5),
Text('以冬', style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
),
Container(
// padding: EdgeInsets.only(left: 15, right: 15),
child: GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 40,
mainAxisSpacing: 30,
crossAxisCount: 4,
childAspectRatio: 1 / 1.25,
),
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.role == ClientRole.Audience) {
// 观众身份的时候点击上麦克图标
_modelBottomSheet(index); // 调用底部弹框方法
} else {
_infoStrings.insert(
0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
}
});
},
child: Column(
children: [
Container(
width: 55,
height: 55,
margin: EdgeInsets.only(bottom: 5),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(50),
),
child: Center(
child: Image.asset(
_list[index]['image'],
width: _list[index]['content'] ? 55 : 14,
height: _list[index]['content'] ? 55 : 21,
),
),
),
Text(
_list[index]['name'],
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
},
),
),
],
),
);
}
// 消息列表
Widget _planel() {
_filtering(type, content) {
switch (type) {
case 'system':
return Container(
child: Text(
'系统公告:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'speak':
return Container(
child: Text(
'聊天消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'gift':
return Container(
child: Text(
'礼物消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
}
}
return Visibility(
visible: viewPanel,
child: Container(
padding: EdgeInsets.symmetric(vertical: 60),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
// decoration: BoxDecoration(
// color: Colors.black,
// ),
child: ListView.builder(
reverse: true,
itemCount: _infoStrings.length,
itemBuilder: (BuildContext context, int index) {
if (_infoStrings.isEmpty) {
return Text('null');
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
decoration: BoxDecoration(
color: Color.fromRGBO(50, 51, 71, 1),
borderRadius: BorderRadius.circular(5),
),
child: _filtering(_infoStrings[index]['type'],
_infoStrings[index]['content']),
),
),
],
),
);
},
),
),
),
),
);
}
// 底部输入框控制台
Widget _footer() {
final _input = TextEditingController();
return Container(
width: double.infinity,
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(
vertical: 18,
horizontal: 15,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
// 音量图标
onTap: () {
setState(() {
volume = !volume;
});
_engine.adjustPlaybackSignalVolume(
volume ? 100 : 0); // 调节本地播放的所有远端用户信号音量
},
child: Icon(volume ? Icons.volume_up : Icons.volume_off,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
// 麦克风图标
onTap: () {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted); // 取消或恢复发布本地音频流(是否静音)
},
child: Icon(muted ? Icons.mic_off : Icons.mic,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
child: Icon(Icons.tag_faces,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
Container(
width: 220,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(30),
),
child: TextField(
controller: _input,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: '我来说几句',
hintStyle: TextStyle(
fontSize: 12,
color: Color.fromRGBO(138, 138, 146, 1),
),
),
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
textInputAction: TextInputAction.send, // 键盘右下角图标
),
),
GestureDetector(
child: Icon(Icons.add,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
elevation: 0, // z轴阴影
titleSpacing: 0, // 标题与其他控件的间隔
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
title: Container(
height: 60,
decoration: BoxDecoration(
color: Color.fromRGBO(27, 28, 48, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: ClipOval(
child: Image.asset(
'images/palRoom/photo.png',
width: 46,
height: 46,
),
),
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
Text(
'以冬',
style: TextStyle(color: Colors.white, fontSize: 12),
),
SizedBox(width: 5),
Text(
'ID309060',
style: TextStyle(
color: Color.fromRGBO(242, 174, 30, 1),
fontSize: 10,
),
),
],
),
Row(
children: [
Text.rich(
TextSpan(
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
children: [
TextSpan(text: '交友'),
TextSpan(text: ' | '),
TextSpan(text: '房间名:成人避风港'),
]),
),
SizedBox(width: 5),
Container(
padding: EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Color.fromRGBO(47, 47, 59, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'在线121',
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
),
SizedBox(width: 5),
Image.asset(
'images/palRoom/whiteRight.png',
width: 5,
height: 8,
),
],
),
)
],
),
],
),
)
],
),
),
actions: [
IconButton(
onPressed: () {
print('more图标被点击');
setState(() {
viewPanel = !viewPanel;
});
},
icon: Image.asset(
'images/palRoom/more.png',
fit: BoxFit.cover,
width: 20.0,
height: 5.0,
),
),
],
),
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
body: Stack(
children: [
_head(),
_planel(),
_footer(),
],
),
);
}
}

View File

@ -0,0 +1,719 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/utils/auth.dart';
import '../../utils/settings.dart';
import '../../utils/http_util.dart';
import '../../utils/showToast.dart';
// 实现语音主要五个步骤逻辑:
// 初始化引擎
// 开启音频权限,启用音频模块
// 创建房间
// 设置事件监听(成功加入房间,是否有用户加入,用户是否离开,用户是否掉线)
// 布局实现
// 退出语音(根据需要销毁引擎,释放资源)
class PalRoomPage extends StatefulWidget {
final String agoraToken;
final String userName;
final String channelName;
final ClientRole? role;
PalRoomPage({
Key? key,
this.agoraToken = '',
this.userName = '',
this.channelName = '',
this.role,
}) : super(key: key);
@override
State<PalRoomPage> createState() => _PalRoomPageState();
}
class _PalRoomPageState extends State<PalRoomPage> {
// final _users = <int>[]; // 用户id数组
final _infoStrings = []; // 状态文字信息列表
bool viewPanel = true; // 是否显示状态文字信息列表
bool muted = false; // 是否开麦
bool volume = true; // 音量
late RtcEngine _engine; // 声网实例变量
// 房间座位
List _list = [
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
{'nickname': '', 'flag': false, 'image': 'images/palRoom/voice2.png'},
];
// 连麦用户列表数据
List newUserList = [];
@override
void initState() {
super.initState();
_infoStrings.insert(0, {
'type': 'system',
'content':
'|令牌:${widget.agoraToken}|用户名:${widget.userName}|房间:${widget.channelName}|身份:${widget.role}'
});
initialize();
if (widget.role == ClientRole.Broadcaster) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
} else if (widget.role == ClientRole.Audience) {
_infoStrings.insert(0, {'type': 'system', 'content': '您身份为观众,点击麦克风图标上麦'});
}
}
@override
void dispose() {
super.dispose();
// _users.clear();
_engine.leaveChannel();
_engine.destroy();
}
Future<void> initialize() async {
// 判断appid是否存在
if (APP_ID.isEmpty) {
setState(() {
_infoStrings.insert(0, {
'type': 'system',
'content': 'APP ID缺失请在settings.dart中提供您的APP ID'
});
_infoStrings.insert(0, {'type': 'system', 'content': 'Agora引擎没有启动'});
});
return;
}
_engine = await RtcEngine.create(APP_ID); // 初始化引擎
await _engine.enableAudio(); // 启用音频模块
await _engine.setDefaultAudioRouteToSpeakerphone(
true); // 设置默认的音频路由:该方法设置接收到的音频从听筒或扬声器出声。如果用户不调用本方法,音频默认从听筒出声。
await _engine.setAudioProfile(AudioProfile.SpeechStandard,
AudioScenario.ChatRoomEntertainment); // 设置音频编码属性和音频场景
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
_addAgoraEventHandlers(); // 调用状态文字信息列表方法
if (widget.role == ClientRole.Broadcaster) {
await _engine.setClientRole(ClientRole.Broadcaster);
}
String uid = await Storage.get('id');
await _engine.joinChannel(
widget.agoraToken, widget.channelName, null, int.parse(uid)); // 加入频道
_detail();
}
// 获取房间详情接口
void _detail() async {
var res =
await MyHttpUtil().get("/chat/api/room/detail/${widget.channelName}");
// print('${res}');
var _userList = res.data['userList'];
// 赋值image字段头像
_userList.forEach((item) {
item['image'] = 'images/palRoom/photo.png';
item['flag'] = true;
});
// 筛选出身份类型为主播的用户
newUserList =
_userList.where((item) => item['type'] != 'audience').toList();
print('-----------------筛选出身份类型为主播的用户:${newUserList}-----------------');
for (var i = 0; i < _list.length; i++) {
if (newUserList.length < (i + 1)) {
setState(() {
_list[i] = {
'nickname': '',
'flag': false,
'image': 'images/palRoom/voice2.png'
};
});
} else {
setState(() {
_list[i] = newUserList[i];
});
}
}
print('-----------------${_list}-----------------');
}
void _addAgoraEventHandlers() {
_engine.setEventHandler(RtcEngineEventHandler(
error: (code) {
// 错误
setState(() {
final info = '错误 $code';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
userJoined: (uid, elapsed) {
_detail();
// 用户加入
setState(() {
final info = '用户加入: uid $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
joinChannelSuccess: (channel, uid, elapsed) {
_detail();
// 加入频道成功
setState(() {
final info = '加入频道成功: $channel, uid: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
leaveChannel: (stats) {
_detail();
// 离开频道
setState(() {
_infoStrings.insert(0, {'type': 'system', 'content': '离开频道'});
// _users.clear();
});
},
userOffline: (uid, reason) {
_detail();
// 用户离开当前频道
setState(() {
final info = '用户离开当前频道: $uid , reason: $reason';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.remove(uid);
});
},
clientRoleChanged: (oldRole, newRole) {
_detail();
// 直播场景下用户角色切换成功回调
setState(() {
final info = '用户旧身份$oldRole,用户新身份$newRole';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
rejoinChannelSuccess: (channel, uid, elapsed) {
_detail();
// 成功重新加入频道回调
setState(() {
final info = '用户成功重新加入: $uid';
_infoStrings.insert(0, {'type': 'system', 'content': info});
// _users.add(uid);
});
},
warning: (warn) {
// 报告 RtcChannel 的警告码
print(warn);
},
tokenPrivilegeWillExpire: (token) {
// Token 服务将在30s内过期回调
// app 应重新获取 Token然后调用 _engine.renewToken(token) 将新的 Token 传给 SDK
print('即将服务失效的 Token:${token}');
},
requestToken: () {
// Token 已过期回调
setState(() {
final info = 'Token 已过期';
_infoStrings.insert(0, {'type': 'system', 'content': info});
});
},
));
}
// 头部
Widget _head() {
// 底部弹框组件
_modelBottomSheet(index) async {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 150.0,
child: Column(
children: [
_list[index]['flag'] == false
? ListTile(
title: Text(
"连线上麦",
textAlign: TextAlign.center,
),
onTap: () {
// setState(() {
// for (var i = 0; i < _list.length; i++) {
// if (i == index) {
// _list[index]['flag'] = true;
// _list[index]['nickname'] = '_users';
// _list[index]['image'] =
// 'images/palRoom/photo.png';
// } else {
// _list[i]['flag'] = false;
// _list[i]['name'] = '';
// _list[i]['image'] = 'images/palRoom/voice2.png';
// }
// }
// });
// 申请连麦需要改变身份为主播!
_engine.setClientRole(ClientRole.Broadcaster);
setState(() {
_list[index]['flag'] = true;
});
Navigator.pop(context);
},
)
: ListTile(
title: Text(
"下麦",
textAlign: TextAlign.center,
),
onTap: () {
setState(() {
_list[index]['flag'] = false;
_list[index]['nickname'] = '';
_list[index]['image'] = 'images/palRoom/voice2.png';
});
// 下麦再将身份改回为观众(下麦不代表离开频道)
_engine.setClientRole(ClientRole.Audience);
Navigator.pop(context);
},
),
ListTile(
title: Text(
"取消",
textAlign: TextAlign.center,
),
onTap: () {
Navigator.pop(context);
}),
],
),
);
},
);
}
return Container(
child: ListView(
children: [
// --房主 start --
Container(
padding: EdgeInsets.fromLTRB(0, 27, 0, 5),
alignment: Alignment.topCenter,
child: Image.asset(
'images/palRoom/photo.png',
width: 70,
height: 70,
),
),
Container(
margin: EdgeInsets.only(bottom: 30),
alignment: Alignment.topCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 18,
height: 18,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color.fromRGBO(245, 173, 29, 1),
borderRadius: BorderRadius.circular(2),
),
child: Text(
'房',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
SizedBox(width: 5),
Text('以冬', style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
),
// --房主 end --
// -- 网格 start --
Container(
// padding: EdgeInsets.only(left: 15, right: 15),
child: GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 40,
mainAxisSpacing: 30,
crossAxisCount: 4,
childAspectRatio: 1 / 1.25,
),
itemCount: _list.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.role == ClientRole.Audience) {
// 观众身份的时候点击上麦克图标
_modelBottomSheet(index); // 调用底部弹框方法
} else {
_infoStrings.insert(
0, {'type': 'system', 'content': '您身份为主播,已经上麦状态'});
}
});
},
child: Column(
children: [
Container(
width: 55,
height: 55,
margin: EdgeInsets.only(bottom: 5),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(50),
),
child: Center(
child: Image.asset(
_list[index]['image'],
width: _list[index]['flag'] ? 55 : 14,
height: _list[index]['flag'] ? 55 : 21,
),
),
),
Text(
_list[index]['nickname'],
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 12),
),
],
),
);
},
),
),
// -- 网格 end --
],
),
);
}
// 消息列表
Widget _planel() {
_filtering(type, content, {name}) {
switch (type) {
case 'system':
return Container(
child: Text(
'系统公告:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'speak':
return Container(
child: Text(
'${name}的聊天消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
case 'gift':
return Container(
child: Text(
'礼物消息:$content',
style: TextStyle(color: Color.fromRGBO(155, 154, 170, 1)),
),
);
}
}
return Visibility(
visible: viewPanel,
child: Container(
padding: EdgeInsets.symmetric(vertical: 60),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
// decoration: BoxDecoration(
// color: Colors.black,
// ),
child: ListView.builder(
reverse: true,
itemCount: _infoStrings.length,
itemBuilder: (BuildContext context, int index) {
if (_infoStrings.isEmpty) {
return Text('null');
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
decoration: BoxDecoration(
color: Color.fromRGBO(50, 51, 71, 1),
borderRadius: BorderRadius.circular(5),
),
child: _filtering(_infoStrings[index]['type'],
_infoStrings[index]['content'],
name: _infoStrings[index]['name']),
),
),
],
),
);
},
),
),
),
),
);
}
// 底部输入框控制台
Widget _footer() {
final _input = TextEditingController();
return Container(
width: double.infinity,
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(
vertical: 18,
horizontal: 15,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
GestureDetector(
// 音量图标
onTap: () {
setState(() {
volume = !volume;
});
_engine.adjustPlaybackSignalVolume(
volume ? 100 : 0); // 调节本地播放的所有远端用户信号音量
},
child: Icon(volume ? Icons.volume_up : Icons.volume_off,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
// 麦克风图标
onTap: () {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted); // 取消或恢复发布本地音频流(是否静音)
},
child: Icon(muted ? Icons.mic_off : Icons.mic,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
GestureDetector(
// 表情图标
child: Icon(Icons.tag_faces,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
Container(
// 输入框
width: 220,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Color.fromRGBO(52, 51, 69, 1),
borderRadius: BorderRadius.circular(30),
),
child: TextField(
onSubmitted: (value) async {
late String speak = value.trim();
if (speak.isEmpty) {
showToast("请输入聊天内容~");
return;
}
String userName = await Storage.get('userName'); // 用户名
setState(() {
_infoStrings.insert(0, {
'type': 'speak',
'content': '${speak}',
'name': '${userName}'
});
});
},
controller: _input,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: '我来说几句',
hintStyle: TextStyle(
fontSize: 12,
color: Color.fromRGBO(138, 138, 146, 1),
),
),
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
textInputAction: TextInputAction.send, // 键盘右下角图标
),
),
GestureDetector(
// 加号图标
child: Icon(Icons.add,
color: Color.fromRGBO(201, 201, 201, 1), size: 25.0),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
print('离开');
_engine.setClientRole(ClientRole.Audience);
_engine.leaveChannel();
Navigator.of(context).pop();
}),
elevation: 0, // z轴阴影
titleSpacing: 0, // 标题与其他控件的间隔
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
title: Container(
height: 60,
decoration: BoxDecoration(
color: Color.fromRGBO(27, 28, 48, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: ClipOval(
child: Image.asset(
'images/palRoom/photo.png',
width: 46,
height: 46,
),
),
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
Text(
'以冬',
style: TextStyle(color: Colors.white, fontSize: 12),
),
SizedBox(width: 5),
Text(
'ID309060',
style: TextStyle(
color: Color.fromRGBO(242, 174, 30, 1),
fontSize: 10,
),
),
],
),
Row(
children: [
Text.rich(
TextSpan(
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
children: [
TextSpan(text: '交友'),
TextSpan(text: ' | '),
TextSpan(text: '房间名:成人避风港'),
]),
),
SizedBox(width: 5),
Container(
padding: EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Color.fromRGBO(47, 47, 59, 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'在线121',
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
),
SizedBox(width: 5),
Image.asset(
'images/palRoom/whiteRight.png',
width: 5,
height: 8,
),
],
),
)
],
),
],
),
)
],
),
),
actions: [
IconButton(
onPressed: () {
print('more图标被点击');
setState(() {
viewPanel = !viewPanel;
});
},
icon: Image.asset(
'images/palRoom/more.png',
fit: BoxFit.cover,
width: 20.0,
height: 5.0,
),
),
],
),
backgroundColor: Color.fromRGBO(27, 28, 48, 1),
body: Stack(
children: [
_head(),
_planel(),
_footer(),
],
),
);
}
}

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/views/palRoom/PalRoom.dart';
import 'package:will_play/utils/auth.dart';
import '../../utils/http_util.dart';
import '../../api/part.dart';
class PartyPage extends StatefulWidget {
PartyPage({Key? key}) : super(key: key);
@ -126,7 +127,7 @@ class _PartyPageState extends State<PartyPage> {
}
}
// tab视图页
// tab视图页组件
class ViewsWidget extends StatefulWidget {
ViewsWidget({Key? key}) : super(key: key);
@ -135,53 +136,6 @@ class ViewsWidget extends StatefulWidget {
}
class _ViewsWidgetState extends State<ViewsWidget> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
Banner(),
Lists(),
],
),
);
}
}
//
class Banner extends StatefulWidget {
Banner({Key? key}) : super(key: key);
@override
State<Banner> createState() => _BannerState();
}
class _BannerState extends State<Banner> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(15, 15, 15, 0),
child: GestureDetector(
child: Image.asset('images/party/banner.png'),
onTap: () {
print('Banner on tap');
},
),
),
);
}
}
//
class Lists extends StatefulWidget {
Lists({Key? key}) : super(key: key);
@override
State<Lists> createState() => _ListsState();
}
class _ListsState extends State<Lists> {
List _list = [
{
"photo": "images/party/photo1.png",
@ -201,6 +155,15 @@ class _ListsState extends State<Lists> {
"sex": "1",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小红",
"num": "12",
"title": "成年人的避风港",
"type": "pal",
"sex": "1",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小黑",
@ -219,21 +182,23 @@ class _ListsState extends State<Lists> {
"sex": "0",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小白",
"num": "122",
"title": "今日听君歌一曲",
"type": "family",
"sex": "0",
"role": ClientRole.Audience,
},
];
@override
void initState() {
super.initState();
_getList();
}
_getList() async {
var res = await MyHttpUtil().get(
"http://101.35.117.69:9093/chat/api/room/page",
);
print(res);
// _list = res.data['content'];
}
int page = 1; //
int size = 10; // N条
late int total; //
bool isLoadmore = false; //
List _data = []; //
// ListView加一个 ScrollController
ScrollController _scrollController = new ScrollController();
//
Future<void> _handleCameraAndMic(Permission permission) async {
@ -245,7 +210,7 @@ class _ListsState extends State<Lists> {
}
//
Future<void> onJoin(userName, role) async {
Future<void> _onJoin(agoraToken, userName, channelName, role) async {
await _handleCameraAndMic(Permission.camera);
await _handleCameraAndMic(Permission.microphone);
@ -253,96 +218,202 @@ class _ListsState extends State<Lists> {
context,
MaterialPageRoute(
builder: (context) => PalRoomPage(
agoraToken: agoraToken,
userName: userName,
channelName: 'call',
channelName: channelName,
role: role,
),
),
);
}
List<Widget> _tempList() {
var tempList = _list.map((value) {
return GestureDetector(
onTap: () => onJoin(value['name'], value['role']),
child: Container(
height: 75,
padding: EdgeInsets.all(5),
margin: EdgeInsets.fromLTRB(15, 30, 15, 0),
width: double.infinity,
decoration: BoxDecoration(
color: Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.all(Radius.circular(50)),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.1),
offset: Offset(0.0, 1.0), // xy轴偏移量
blurRadius: 1.0, //
spreadRadius: 1.0 //
)
],
),
child: Row(
children: [
ClipOval(
child: Image.asset(
value['photo'],
width: 65,
height: 65,
),
),
SizedBox(
width: 10,
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
typeElement(type: value['type']),
Container(
child: Text(
value['title'],
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 1),
fontSize: 16),
),
)
],
),
SizedBox(height: 9),
Row(
children: [
Image.asset(
'images/party/live.png',
width: 17,
height: 18,
),
SizedBox(width: 5),
Text(
value["num"] + '',
style: TextStyle(
color: Color.fromRGBO(153, 153, 153, 1),
fontSize: 12),
)
],
),
],
)),
],
),
),
);
//
_onRefresh() {
_data.clear();
this.page = 1;
getList({'page': this.page, 'size': this.size}).then((res) {
print('_onRefresh${res.data}');
setState(() {
_data.addAll(res.data['content']);
});
});
return tempList.toList();
}
//
_onLoadmore() {
this.page++;
getList({'page': this.page, 'size': this.size}).then((res) {
setState(() {
print('_onLoadmore${res.data}');
_data.addAll(res.data['content']);
isLoadmore = false;
});
});
}
//
Widget _loadMoreWidget() {
return Padding(
padding: EdgeInsets.all(15.0),
child: Center(child: CircularProgressIndicator()),
);
}
@override
void initState() {
super.initState();
getList({'page': this.page, 'size': this.size}).then((res) {
// print('列表数据:${res.data}');
print('${res.data['content']}');
setState(() {
_data.addAll(res.data['content']!);
});
});
// _onRefresh();
// _scrollController.addListener(() {
// if (_scrollController.position.pixels ==
// _scrollController.position.maxScrollExtent) {
// _onLoadmore();
// }
// });
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
void _agoraToken(roomId, channelName, role) async {}
@override
Widget build(BuildContext context) {
return Column(
children: _tempList(),
// return RefreshIndicator( onRefresh: _onRefresh(),
return SingleChildScrollView(
child: Column(
children: [
Container(
padding: EdgeInsets.fromLTRB(15, 15, 15, 0),
child: GestureDetector(
child: Image.asset('images/party/banner.png'),
onTap: () {
print('Banner on tap');
},
),
),
_data.length > 0
? ListView.builder(
controller: _scrollController,
itemCount: _data.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
// if (index == this.data.length - 1) {
// return _loadMoreWidget();
// }
return GestureDetector(
onTap: () async {
var res = await MyHttpUtil()
.get("/chat/api/auth/agora/token", data: {
'channelName': _data[index]['id'],
'role': 2
});
var agoraToken = res.data["token"];
String uid = await Storage.get('id');
String userName = await Storage.get('userName'); //
print(agoraToken);
print(_data[index]['id']);
_onJoin(agoraToken, userName, _data[index]['id'],
ClientRole.Audience);
},
child: Container(
height: 75,
padding: EdgeInsets.all(5),
margin: EdgeInsets.fromLTRB(15, 30, 15, 0),
width: double.infinity,
decoration: BoxDecoration(
color: Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.all(Radius.circular(50)),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.1),
offset: Offset(0.0, 1.0), // xy轴偏移量
blurRadius: 1.0, //
spreadRadius: 1.0 //
)
],
),
child: Row(
children: [
ClipOval(
child: Image.asset(
'images/party/photo1.png',
width: 65,
height: 65,
),
),
SizedBox(
width: 10,
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
typeElement(type: 'pal'),
Container(
child: Text(
_data[index]['name'],
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color.fromRGBO(
51, 51, 51, 1),
fontSize: 16),
),
)
],
),
SizedBox(height: 9),
Row(
children: [
Image.asset(
'images/party/live.png',
width: 17,
height: 18,
),
SizedBox(width: 5),
Text(
_data[index]['id'],
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color.fromRGBO(
153, 153, 153, 1),
fontSize: 12),
)
],
),
],
)),
],
),
),
);
},
)
: Container(
padding: EdgeInsets.all(15),
child: Center(
child: Text('暂无数据~'),
),
),
],
),
);
}
}

467
lib/views/party/party.txt Normal file
View File

@ -0,0 +1,467 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:will_play/views/palRoom/PalRoom.dart';
import '../../api/part.dart';
class PartyPage extends StatefulWidget {
PartyPage({Key? key}) : super(key: key);
@override
State<PartyPage> createState() => _PartyPageState();
}
class _PartyPageState extends State<PartyPage> {
final _search = TextEditingController();
final _tabDataList = [
Tab(text: '关注'),
Tab(text: '推荐'),
Tab(text: '相亲'),
Tab(text: 'KTV'),
Tab(text: '交友'),
Tab(text: '音乐'),
];
@override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 1, // 默认选中
length: 6,
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0, // z轴阴影
leading: null,
titleSpacing: 10, // 标题与其他控件的间隔
backgroundColor: Colors.white,
title: Container(
height: 35,
margin: EdgeInsetsDirectional.only(end: 25),
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
decoration: BoxDecoration(
color: Color.fromRGBO(247, 247, 247, 1),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: Image.asset(
'images/party/search.png',
width: 17.0,
height: 17.0,
fit: BoxFit.cover,
),
),
),
Expanded(
flex: 1,
child: TextField(
controller: _search,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: '搜索房间号',
hintStyle: TextStyle(
fontSize: 12,
color: Color.fromRGBO(203, 203, 203, 1),
),
),
style: TextStyle(
fontSize: 12,
color: Colors.black,
),
textInputAction: TextInputAction.search, // 键盘右下角图标
),
),
],
),
),
actions: [
IconButton(
onPressed: () {
Navigator.pushNamed(context, 'CreateRoom');
},
icon: Image.asset(
'images/party/create.png',
fit: BoxFit.cover,
width: 22.0,
height: 22.0,
),
),
],
bottom: TabBar(
// isScrollable: true,
indicatorSize: TabBarIndicatorSize.label, // 指示器大小计算方式
indicatorColor: Color.fromRGBO(255, 255, 255, 0), // 指示器颜色
labelColor: Colors.black, // 选中label颜色
labelStyle: TextStyle(fontSize: 18), // 选中label的Style
unselectedLabelStyle: TextStyle(fontSize: 14), // 未选中label的Style
unselectedLabelColor:
Color.fromRGBO(153, 153, 153, 1), // 未选中label颜色
tabs: _tabDataList,
enableFeedback: true,
onTap: (index) {
print(_tabDataList[index]);
},
),
),
body: TabBarView(
children: [
ViewsWidget(),
ViewsWidget(),
ViewsWidget(),
ViewsWidget(),
ViewsWidget(),
ViewsWidget(),
],
),
),
);
}
}
// tab视图页组件
class ViewsWidget extends StatefulWidget {
ViewsWidget({Key? key}) : super(key: key);
@override
State<ViewsWidget> createState() => _ViewsWidgetState();
}
class _ViewsWidgetState extends State<ViewsWidget> {
List _list = [
{
"photo": "images/party/photo1.png",
"name": "小明",
"num": "12",
"title": "今日听君歌一曲",
"type": "music",
"sex": "0",
"role": ClientRole.Broadcaster,
},
{
"photo": "images/party/photo1.png",
"name": "小红",
"num": "12",
"title": "成年人的避风港",
"type": "pal",
"sex": "1",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小红",
"num": "12",
"title": "成年人的避风港",
"type": "pal",
"sex": "1",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小黑",
"num": "12",
"title": "今日听君歌一曲",
"type": "auction",
"sex": "0",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小白",
"num": "122",
"title": "今日听君歌一曲",
"type": "family",
"sex": "0",
"role": ClientRole.Audience,
},
{
"photo": "images/party/photo1.png",
"name": "小白",
"num": "122",
"title": "今日听君歌一曲",
"type": "family",
"sex": "0",
"role": ClientRole.Audience,
},
];
int page = 1; // 当前页面
int size = 10; // 每页N条
late int total; // 总条数
bool isLoadmore = false; // 加载更多
late List _data; // 列表数据
// 给ListView加一个 ScrollController 组件,通过事件监听滚动条的高度来显示和隐藏加载更多的组件
ScrollController _scrollController = new ScrollController();
// 判断是否开启权限方法
Future<void> _handleCameraAndMic(Permission permission) async {
PermissionStatus status = await permission.request();
print('权限状态$status');
if (!status.isGranted) {
openAppSettings();
}
}
// 页面跳转方法
Future<void> _onJoin(userName, role) async {
await _handleCameraAndMic(Permission.camera);
await _handleCameraAndMic(Permission.microphone);
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PalRoomPage(
userName: userName,
channelName: 'call',
role: role,
),
),
);
}
// 刷新列表方法
_onRefresh() {
this.data.clear();
this.page = 1;
getList({'page': this.page, 'size': this.size}).then((res) {
print('_onRefresh${res.data}');
setState(() {
this.data.addAll(res.data.content);
});
});
}
// 加载数据方法
_onLoadmore() {
this.page++;
getList({'page': this.page, 'size': this.size}).then((res) {
setState(() {
print('_onLoadmore${res.data}');
this.data.addAll(res.data.content);
isLoadmore = false;
});
});
}
// 加载中动画组件
Widget _loadMoreWidget() {
return Padding(
padding: EdgeInsets.all(15.0),
child: Center(child: CircularProgressIndicator()),
);
}
@override
void initState() {
super.initState();
getList({'page': this.page, 'size': this.size}).then((res) {
print('列表数据:${res.data}');
});
// _onRefresh();
// _scrollController.addListener(() {
// if (_scrollController.position.pixels ==
// _scrollController.position.maxScrollExtent) {
// _onLoadmore();
// }
// });
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
@override
Widget build(BuildContext context) {
// return RefreshIndicator( onRefresh: _onRefresh(),
return SingleChildScrollView(
child: Column(
children: [
Container(
padding: EdgeInsets.fromLTRB(15, 15, 15, 0),
child: GestureDetector(
child: Image.asset('images/party/banner.png'),
onTap: () {
print('Banner on tap');
},
),
),
ListView.builder(
controller: _scrollController,
itemCount: _list.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
// if (index == this.data.length - 1) {
// return _loadMoreWidget();
// }
return GestureDetector(
onTap: () =>
_onJoin(_list[index]['name'], _list[index]['role']),
child: Container(
height: 75,
padding: EdgeInsets.all(5),
margin: EdgeInsets.fromLTRB(15, 30, 15, 0),
width: double.infinity,
decoration: BoxDecoration(
color: Color.fromRGBO(255, 255, 255, 1),
borderRadius: BorderRadius.all(Radius.circular(50)),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.1),
offset: Offset(0.0, 1.0), // 阴影xy轴偏移量
blurRadius: 1.0, // 阴影模糊程度
spreadRadius: 1.0 // 阴影扩散程度
)
],
),
child: Row(
children: [
ClipOval(
child: Image.asset(
_list[index]['photo'],
width: 65,
height: 65,
),
),
SizedBox(
width: 10,
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
typeElement(type: _list[index]['type']),
Container(
child: Text(
_list[index]['title'],
style: TextStyle(
color: Color.fromRGBO(51, 51, 51, 1),
fontSize: 16),
),
)
],
),
SizedBox(height: 9),
Row(
children: [
Image.asset(
'images/party/live.png',
width: 17,
height: 18,
),
SizedBox(width: 5),
Text(
_list[index]["num"] + '人',
style: TextStyle(
color: Color.fromRGBO(153, 153, 153, 1),
fontSize: 12),
)
],
),
],
)),
],
),
),
);
},
)
],
),
);
}
}
// 房间类型标签小组件
class typeElement extends StatelessWidget {
String type;
var typeData = {
"music": {
'title': "音乐",
'color': [
Color.fromRGBO(207, 167, 248, 1),
Color.fromRGBO(133, 157, 254, 1),
],
},
"pal": {
"title": "交友",
'color': [
Color.fromRGBO(207, 167, 248, 1),
Color.fromRGBO(133, 157, 254, 1),
],
},
"auction": {
"title": "拍卖",
"color": [
Color.fromRGBO(249, 163, 125, 1),
Color.fromRGBO(251, 218, 137, 1),
]
},
"family": {
'title': "家族",
"color": [
Color.fromRGBO(85, 221, 236, 1),
Color.fromRGBO(66, 224, 154, 1),
]
},
};
var textData = {"music": '音乐', "pal": '交友', "auction": '拍卖', "family": '家族'};
var colorData = {
"music": [
Color.fromRGBO(207, 167, 248, 1),
Color.fromRGBO(133, 157, 254, 1),
],
"pal": [
Color.fromRGBO(254, 174, 205, 1),
Color.fromRGBO(247, 111, 162, 1),
],
"auction": [
Color.fromRGBO(249, 163, 125, 1),
Color.fromRGBO(251, 218, 137, 1),
],
"family": [
Color.fromRGBO(85, 221, 236, 1),
Color.fromRGBO(66, 224, 154, 1),
],
};
typeElement({Key? key, this.type = ''}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 32,
height: 16,
margin: EdgeInsets.only(right: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: colorData[type] ?? [],
),
),
child: Text(
textData[type] ?? "",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 10),
),
);
}
}

View File

@ -50,6 +50,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.16.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
cupertino_icons:
dependency: "direct main"
description:
@ -378,6 +385,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
win32:
dependency: transitive
description:

View File

@ -40,6 +40,7 @@ dependencies:
fluttertoast: ^8.0.6
shared_preferences: ^2.0.7
pretty_dio_logger: ^1.1.1
web_socket_channel: ^2.1.0
dev_dependencies:
flutter_test:
@ -105,6 +106,7 @@ flutter:
- images/palRoom/add.png
- images/palRoom/face.png
- images/palRoom/photo.png
- images/palRoom/user.png
- images/palRoom/tip.png
- images/palRoom/voice1.png
- images/palRoom/voice2.png