android late request permission;update chat UI,launch chat from UI cm

This commit is contained in:
csf 2022-04-04 14:54:00 +08:00
parent 299bd11481
commit f083816fc7
9 changed files with 236 additions and 46 deletions

View File

@ -15,7 +15,6 @@ import androidx.annotation.RequiresApi
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.lang.Exception
const val MEDIA_REQUEST_CODE = 42
@ -36,7 +35,6 @@ class MainActivity : FlutterActivity() {
Intent(this, MainService::class.java).also {
bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
}
checkPermissions(this)
updateMachineInfo()
flutterMethodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
@ -67,6 +65,16 @@ class MainActivity : FlutterActivity() {
result.success(false)
}
}
"check_permission" -> {
if(call.arguments is String){
result.success(checkPermission(context, call.arguments as String))
}
}
"request_permission" -> {
if(call.arguments is String){
requestPermission(context, call.arguments as String)
}
}
"check_video_permission" -> {
mainService?.let {
result.success(it.checkMediaPermission())
@ -76,11 +84,11 @@ class MainActivity : FlutterActivity() {
}
"check_service" -> {
flutterMethodChannel.invokeMethod(
"on_permission_changed",
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
flutterMethodChannel.invokeMethod(
"on_permission_changed",
"on_state_changed",
mapOf("name" to "media", "value" to mainService?.isReady.toString())
)
}
@ -94,7 +102,7 @@ class MainActivity : FlutterActivity() {
}
InputService.ctx = null
flutterMethodChannel.invokeMethod(
"on_permission_changed",
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
}
@ -154,7 +162,7 @@ class MainActivity : FlutterActivity() {
Log.d(logTag, "onResume inputPer:$inputPer")
activity.runOnUiThread {
flutterMethodChannel.invokeMethod(
"on_permission_changed",
"on_state_changed",
mapOf("name" to "input", "value" to inputPer.toString())
)
}

View File

@ -340,7 +340,7 @@ class MainService : Service() {
fun checkMediaPermission(): Boolean {
Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod(
"on_permission_changed",
"on_state_changed",
mapOf("name" to "media", "value" to isReady.toString())
)
}

View File

@ -1,17 +1,14 @@
package com.carriez.flutter_hbb
import android.annotation.SuppressLint
import android.app.*
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import java.util.*
@ -40,13 +37,44 @@ fun testVP9Support(): Boolean {
return res != null
}
fun checkPermissions(context: Context) {
fun requestPermission(context: Context,type: String){
val permission = when (type) {
"audio" -> {
Permission.RECORD_AUDIO
}
"file" -> {
Permission.MANAGE_EXTERNAL_STORAGE
}
else -> {
return
}
}
XXPermissions.with(context)
.permission(Permission.RECORD_AUDIO)
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.permission(permission)
.request { permissions, all ->
if (all) {
Log.d("loglog", "获取存储权限成功:$permissions")
Log.d("checkPermissions", "获取存储权限成功:$permissions")
Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel.invokeMethod(
"on_android_permission_result",
mapOf("type" to type, "result" to all)
)
}
}
}
}
fun checkPermission(context: Context,type: String): Boolean {
val permission = when (type) {
"audio" -> {
Permission.RECORD_AUDIO
}
"file" -> {
Permission.MANAGE_EXTERNAL_STORAGE
}
else -> {
return false
}
}
return XXPermissions.isGranted(context,permission)
}

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter_easyloading/flutter_easyloading.dart';
final globalKey = GlobalKey<NavigatorState>();
final navigationBarKey = GlobalKey();
var isAndroid = false;
var isIOS = false;

View File

@ -29,10 +29,21 @@ class ChatModel with ChangeNotifier {
int get currentID => _currentID;
ChatUser get currentUser =>
FFI.serverModel.clients[_currentID]?.chatUser ?? me;
changeCurrentID(int id){
if(_messages.containsKey(id)){
_currentID = id;
notifyListeners();
} else {
final chatUser = FFI.serverModel.clients[id]?.chatUser;
if(chatUser == null){
return debugPrint("Failed to changeCurrentID,remote user doesn't exist");
}
_messages[id] = [];
_currentID = id;
}
}

View File

@ -38,12 +38,40 @@ class ServerModel with ChangeNotifier {
ServerModel() {
()async{
await Future.delayed(Duration(seconds: 2));
final audioOption = FFI.getByName('option', 'enable-audio');
_audioOk = audioOption.isEmpty; // audio true by default
/**
* 1. check android permission
* 2. check config
* audio true by default (if permission on)
* file true by default (if permission on)
* input false by default (it need turning on manually everytime)
*/
await Future.delayed(Duration(seconds: 1));
final fileOption = FFI.getByName('option', 'enable-file-transfer');
_fileOk = fileOption.isEmpty;
// audio
if(!await PermissionManager.check("audio")){
_audioOk = false;
FFI.setByName('option', jsonEncode(
Map()
..["name"] = "enable-audio"
..["value"] = "N"));
}else{
final audioOption = FFI.getByName('option', 'enable-audio');
_audioOk = audioOption.isEmpty;
}
// file
if(!await PermissionManager.check("file")) {
_fileOk = false;
FFI.setByName('option', jsonEncode(
Map()
..["name"] = "enable-file-transfer"
..["value"] = "N"));
} else{
final fileOption = FFI.getByName('option', 'enable-file-transfer');
_fileOk = fileOption.isEmpty;
}
// input (mouse control)
Map<String, String> res = Map()
..["name"] = "enable-keyboard"
..["value"] = 'N';
@ -52,7 +80,18 @@ class ServerModel with ChangeNotifier {
}();
}
toggleAudio(){
toggleAudio() async {
if(!_audioOk && !await PermissionManager.check("audio")){
debugPrint("toggleAudio 无权限 开始获取权限");
final res = await PermissionManager.request("audio");
debugPrint("权限请求结果:$res");
if(!res){
// TODO handle fail
return;
}
}
_audioOk = !_audioOk;
Map<String, String> res = Map()
..["name"] = "enable-audio"
@ -61,7 +100,15 @@ class ServerModel with ChangeNotifier {
notifyListeners();
}
toggleFile() {
toggleFile() async {
if(!_fileOk && !await PermissionManager.check("file")){
final res = await PermissionManager.request("file");
if(!res){
// TODO handle fail
return;
}
}
_fileOk = !_fileOk;
Map<String, String> res = Map()
..["name"] = "enable-file-transfer"
@ -128,7 +175,7 @@ class ServerModel with ChangeNotifier {
getIDPasswd();
}
Future<Null> stopService() async {
Future<Null> stopService() async {
_isStart = false;
_interval?.cancel();
_interval = null;
@ -408,3 +455,54 @@ showInputWarnAlert() async {
});
DialogManager.drop();
}
class PermissionManager {
static Completer<bool>? _completer;
static Timer? _timer;
static var _current = "";
static final permissions = ["audio","file"];
static bool isWaitingFile(){
if(_completer != null){
return !_completer!.isCompleted && _current == "file";
}
return false;
}
static Future<bool> check(String type){
if(!permissions.contains(type))
return Future.error("Wrong permission!$type");
return FFI.invokeMethod("check_permission",type);
}
static Future<bool> request(String type){
if(!permissions.contains(type))
return Future.error("Wrong permission!$type");
_current = type;
_completer = Completer<bool>();
FFI.invokeMethod("request_permission",type);
// timeout
_timer?.cancel();
_timer = Timer(Duration(seconds: 60),(){
if(_completer == null) return;
if(!_completer!.isCompleted){
_completer!.complete(false);
}
_completer = null;
_current = "";
});
return _completer!.future;
}
static complete(String type,bool res){
if(type != _current){
res = false;
}
_timer?.cancel();
_completer?.complete(res);
_current = "";
}
}

View File

@ -30,7 +30,7 @@ class ChatPage extends StatelessWidget implements PageShape {
final id = entry.key;
final user = serverModel.clients[id]?.chatUser ?? chatModel.me;
return PopupMenuItem<int>(
child: Text("${user.name} - ${user.uid}"),
child: Text("${user.name} ${user.uid}"),
value: id,
);
}).toList();
@ -47,18 +47,36 @@ class ChatPage extends StatelessWidget implements PageShape {
child: Container(
color: MyTheme.grayBg,
child: Consumer<ChatModel>(builder: (context, chatModel, child) {
return DashChat(
inputContainerStyle: BoxDecoration(color: Colors.white70),
sendOnEnter: false,
// if true,reload keyboard everytime,need fix
onSend: (chatMsg) {
chatModel.send(chatMsg);
},
user: chatModel.me,
messages: chatModel.messages[chatModel.currentID] ?? [],
// default scrollToBottom has bug https://github.com/fayeed/dash_chat/issues/53
scrollToBottom: false,
scrollController: chatModel.scroller,
final currentUser = chatModel.currentUser;
return Stack(
children: [
DashChat(
inputContainerStyle: BoxDecoration(color: Colors.white70),
sendOnEnter: false,
// if true,reload keyboard everytime,need fix
onSend: (chatMsg) {
chatModel.send(chatMsg);
},
user: chatModel.me,
messages: chatModel.messages[chatModel.currentID] ?? [],
// default scrollToBottom has bug https://github.com/fayeed/dash_chat/issues/53
scrollToBottom: false,
scrollController: chatModel.scroller,
),
chatModel.currentID == ChatModel.clientModeID
? SizedBox.shrink()
: Padding(
padding: EdgeInsets.all(10),
child: Row(
children: [
Icon(Icons.account_circle,
color: MyTheme.accent80),
SizedBox(width: 5),
Text(
"${currentUser.name ?? ""} ${currentUser.uid ?? ""}",style: TextStyle(color: MyTheme.accent50),),
],
)),
],
);
})));
}

View File

@ -56,6 +56,7 @@ class _HomePageState extends State<HomePage> {
actions: _pages.elementAt(_selectedIndex).appBarActions,
),
bottomNavigationBar: BottomNavigationBar(
key: navigationBarKey,
items: _pages
.map((page) =>
BottomNavigationBarItem(icon: page.icon, label: page.title))

View File

@ -76,9 +76,13 @@ class ServerPage extends StatelessWidget implements PageShape {
}
}
void checkService() {
//
void checkService() async {
FFI.invokeMethod("check_service"); // jvm
// for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page
if(PermissionManager.isWaitingFile() && !FFI.serverModel.fileOk){
PermissionManager.complete("file",await PermissionManager.check("file"));
debugPrint("file permission finished");
}
}
class ServerInfo extends StatefulWidget {
@ -268,9 +272,21 @@ class ConnectionManager extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: clientInfo(entry.value),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
clientInfo(entry.value),
entry.value.isFileTransfer
?SizedBox.shrink()
:IconButton(onPressed: (){
FFI.chatModel.changeCurrentID(entry.value.id);
final bar = navigationBarKey.currentWidget;
if(bar!=null){
bar as BottomNavigationBar;
bar.onTap!(1);
}
}, icon: Icon(Icons.chat,color: MyTheme.accent80,))
],
),
ElevatedButton.icon(
style: ButtonStyle(
@ -338,7 +354,9 @@ class PaddingCard extends StatelessWidget {
}
Widget clientInfo(Client client) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(
children: [
CircleAvatar(
@ -356,7 +374,7 @@ Widget clientInfo(Client client) {
])
],
),
]);
]));
}
void toAndroidChannelInit() {
@ -370,14 +388,21 @@ void toAndroidChannelInit() {
FFI.serverModel.updateClientState();
break;
}
case "on_permission_changed":
case "on_state_changed":
{
var name = arguments["name"] as String;
var value = arguments["value"] as String == "true";
debugPrint("from jvm:on_permission_changed,$name:$value");
debugPrint("from jvm:on_state_changed,$name:$value");
FFI.serverModel.changeStatue(name, value);
break;
}
case "on_android_permission_result":
{
var type = arguments["type"] as String;
var result = arguments["result"] as bool;
PermissionManager.complete(type, result);
break;
}
}
} catch (e) {
debugPrint("MethodCallHandler err:$e");