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

View File

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

View File

@ -1,17 +1,14 @@
package com.carriez.flutter_hbb package com.carriez.flutter_hbb
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.*
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.media.MediaCodecList import android.media.MediaCodecList
import android.media.MediaFormat import android.media.MediaFormat
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions import com.hjq.permissions.XXPermissions
import java.util.* import java.util.*
@ -40,13 +37,44 @@ fun testVP9Support(): Boolean {
return res != null 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) XXPermissions.with(context)
.permission(Permission.RECORD_AUDIO) .permission(permission)
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.request { permissions, all -> .request { permissions, all ->
if (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'; import 'package:flutter_easyloading/flutter_easyloading.dart';
final globalKey = GlobalKey<NavigatorState>(); final globalKey = GlobalKey<NavigatorState>();
final navigationBarKey = GlobalKey();
var isAndroid = false; var isAndroid = false;
var isIOS = false; var isIOS = false;

View File

@ -29,10 +29,21 @@ class ChatModel with ChangeNotifier {
int get currentID => _currentID; int get currentID => _currentID;
ChatUser get currentUser =>
FFI.serverModel.clients[_currentID]?.chatUser ?? me;
changeCurrentID(int id){ changeCurrentID(int id){
if(_messages.containsKey(id)){ if(_messages.containsKey(id)){
_currentID = id; _currentID = id;
notifyListeners(); 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() { ServerModel() {
()async{ ()async{
await Future.delayed(Duration(seconds: 2)); /**
final audioOption = FFI.getByName('option', 'enable-audio'); * 1. check android permission
_audioOk = audioOption.isEmpty; // audio true by default * 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'); // audio
_fileOk = fileOption.isEmpty; 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() Map<String, String> res = Map()
..["name"] = "enable-keyboard" ..["name"] = "enable-keyboard"
..["value"] = 'N'; ..["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; _audioOk = !_audioOk;
Map<String, String> res = Map() Map<String, String> res = Map()
..["name"] = "enable-audio" ..["name"] = "enable-audio"
@ -61,7 +100,15 @@ class ServerModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
toggleFile() { toggleFile() async {
if(!_fileOk && !await PermissionManager.check("file")){
final res = await PermissionManager.request("file");
if(!res){
// TODO handle fail
return;
}
}
_fileOk = !_fileOk; _fileOk = !_fileOk;
Map<String, String> res = Map() Map<String, String> res = Map()
..["name"] = "enable-file-transfer" ..["name"] = "enable-file-transfer"
@ -128,7 +175,7 @@ class ServerModel with ChangeNotifier {
getIDPasswd(); getIDPasswd();
} }
Future<Null> stopService() async { Future<Null> stopService() async {
_isStart = false; _isStart = false;
_interval?.cancel(); _interval?.cancel();
_interval = null; _interval = null;
@ -408,3 +455,54 @@ showInputWarnAlert() async {
}); });
DialogManager.drop(); 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 id = entry.key;
final user = serverModel.clients[id]?.chatUser ?? chatModel.me; final user = serverModel.clients[id]?.chatUser ?? chatModel.me;
return PopupMenuItem<int>( return PopupMenuItem<int>(
child: Text("${user.name} - ${user.uid}"), child: Text("${user.name} ${user.uid}"),
value: id, value: id,
); );
}).toList(); }).toList();
@ -47,18 +47,36 @@ class ChatPage extends StatelessWidget implements PageShape {
child: Container( child: Container(
color: MyTheme.grayBg, color: MyTheme.grayBg,
child: Consumer<ChatModel>(builder: (context, chatModel, child) { child: Consumer<ChatModel>(builder: (context, chatModel, child) {
return DashChat( final currentUser = chatModel.currentUser;
inputContainerStyle: BoxDecoration(color: Colors.white70), return Stack(
sendOnEnter: false, children: [
// if true,reload keyboard everytime,need fix DashChat(
onSend: (chatMsg) { inputContainerStyle: BoxDecoration(color: Colors.white70),
chatModel.send(chatMsg); sendOnEnter: false,
}, // if true,reload keyboard everytime,need fix
user: chatModel.me, onSend: (chatMsg) {
messages: chatModel.messages[chatModel.currentID] ?? [], chatModel.send(chatMsg);
// default scrollToBottom has bug https://github.com/fayeed/dash_chat/issues/53 },
scrollToBottom: false, user: chatModel.me,
scrollController: chatModel.scroller, 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, actions: _pages.elementAt(_selectedIndex).appBarActions,
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
key: navigationBarKey,
items: _pages items: _pages
.map((page) => .map((page) =>
BottomNavigationBarItem(icon: page.icon, label: page.title)) 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 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 { class ServerInfo extends StatefulWidget {
@ -268,9 +272,21 @@ class ConnectionManager extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Row(
padding: EdgeInsets.symmetric(vertical: 5.0), mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: clientInfo(entry.value), 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( ElevatedButton.icon(
style: ButtonStyle( style: ButtonStyle(
@ -338,7 +354,9 @@ class PaddingCard extends StatelessWidget {
} }
Widget clientInfo(Client client) { Widget clientInfo(Client client) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row( Row(
children: [ children: [
CircleAvatar( CircleAvatar(
@ -356,7 +374,7 @@ Widget clientInfo(Client client) {
]) ])
], ],
), ),
]); ]));
} }
void toAndroidChannelInit() { void toAndroidChannelInit() {
@ -370,14 +388,21 @@ void toAndroidChannelInit() {
FFI.serverModel.updateClientState(); FFI.serverModel.updateClientState();
break; break;
} }
case "on_permission_changed": case "on_state_changed":
{ {
var name = arguments["name"] as String; var name = arguments["name"] as String;
var value = arguments["value"] as String == "true"; 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); FFI.serverModel.changeStatue(name, value);
break; 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) { } catch (e) {
debugPrint("MethodCallHandler err:$e"); debugPrint("MethodCallHandler err:$e");