临时群
This commit is contained in:
parent
e33e64df14
commit
d4801dd02c
|
|
@ -53,6 +53,14 @@
|
||||||
android:label="开发者模式"
|
android:label="开发者模式"
|
||||||
android:theme="@style/Theme.StandAPP" />
|
android:theme="@style/Theme.StandAPP" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".TempCallActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="临时呼叫"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:launchMode="singleTop" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".printer.PrintTestActivity"
|
android:name=".printer.PrintTestActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 临时呼叫状态区域 -->
|
||||||
|
<div id="temp-call-status" style="display: none; margin-top: 10px; padding: 10px; background: #fef3c7; border-radius: 8px; border: 1px solid #f59e0b;">
|
||||||
|
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="font-size: 20px;">📞</span>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight: bold; color: #92400e; font-size: 14px;">临时呼叫进行中</div>
|
||||||
|
<div id="temp-call-group-name" style="font-size: 12px; color: #b45309;">临时群组</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="btn-maximize-temp-call" class="btn"
|
||||||
|
style="margin: 0; padding: 8px 16px; font-size: 12px; background: #f59e0b; min-width: auto;"
|
||||||
|
onclick="maximizeTempCall()">
|
||||||
|
最大化
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 8px; font-size: 13px;">
|
<div style="margin-top: 8px; font-size: 13px;">
|
||||||
当前群组: <span id="ptt-current-group" style="color: #6366f1; font-weight: bold;">-</span>
|
当前群组: <span id="ptt-current-group" style="color: #6366f1; font-weight: bold;">-</span>
|
||||||
<span id="ptt-current-group-id" style="color: #94a3b8; font-size: 11px; margin-left: 5px;"></span>
|
<span id="ptt-current-group-id" style="color: #94a3b8; font-size: 11px; margin-left: 5px;"></span>
|
||||||
|
|
@ -625,8 +643,75 @@
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
// 同步临时呼叫状态
|
||||||
|
syncTempCallStatus();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
// === 临时呼叫状态管理 ===
|
||||||
|
function syncTempCallStatus() {
|
||||||
|
if (window.android && window.android.getTempCallStatus) {
|
||||||
|
try {
|
||||||
|
var status = JSON.parse(window.android.getTempCallStatus());
|
||||||
|
updateTempCallUI(status.isOpen, status.isMinimized);
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTempCallUI(isOpen, isMinimized) {
|
||||||
|
var statusDiv = document.getElementById('temp-call-status');
|
||||||
|
var maxBtn = document.getElementById('btn-maximize-temp-call');
|
||||||
|
var groupSpan = document.getElementById('temp-call-group-name');
|
||||||
|
|
||||||
|
// 只有在最小化状态时才显示最大化按钮
|
||||||
|
if (isMinimized) {
|
||||||
|
statusDiv.style.display = 'block';
|
||||||
|
maxBtn.style.display = 'inline-block';
|
||||||
|
maxBtn.disabled = false;
|
||||||
|
maxBtn.style.background = '#f59e0b';
|
||||||
|
maxBtn.style.cursor = 'pointer';
|
||||||
|
} else if (isOpen) {
|
||||||
|
// Activity 打开但未最小化,不显示状态区域
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
// Activity 已销毁且未最小化
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maximizeTempCall() {
|
||||||
|
if (window.android && window.android.maximizeTempCall) {
|
||||||
|
window.android.maximizeTempCall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 临时呼叫状态变化回调 (由 Android 原生调用)
|
||||||
|
window.onTempCallStateChanged = function(state) {
|
||||||
|
console.log("临时呼叫状态变化: " + state);
|
||||||
|
var statusDiv = document.getElementById('temp-call-status');
|
||||||
|
var maxBtn = document.getElementById('btn-maximize-temp-call');
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case 'minimized':
|
||||||
|
// 最小化:显示状态区域和最大化按钮
|
||||||
|
statusDiv.style.display = 'block';
|
||||||
|
maxBtn.style.display = 'inline-block';
|
||||||
|
maxBtn.disabled = false;
|
||||||
|
maxBtn.style.background = '#f59e0b';
|
||||||
|
showPttResult("[回调] 临时呼叫已最小化,可通过最大化按钮恢复");
|
||||||
|
break;
|
||||||
|
case 'resumed':
|
||||||
|
// 恢复:隐藏状态区域
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
showPttResult("[回调] 临时呼叫页面已恢复");
|
||||||
|
break;
|
||||||
|
case 'destroyed':
|
||||||
|
// 销毁:隐藏所有
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
showPttResult("[回调] 临时呼叫已结束");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 告知 Android 页面加载完成(已移至 checkApp 中,等 bridge 注入后再调用)
|
// 告知 Android 页面加载完成(已移至 checkApp 中,等 bridge 注入后再调用)
|
||||||
window.onload = function() {};
|
window.onload = function() {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -238,8 +238,61 @@ public class CallBackResolution {
|
||||||
for (int i = 0; i < stringsTalk.size(); i++) {
|
for (int i = 0; i < stringsTalk.size(); i++) {
|
||||||
tempTxt += unicode(stringsTalk.get(i));
|
tempTxt += unicode(stringsTalk.get(i));
|
||||||
}
|
}
|
||||||
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt);
|
int code=-1;
|
||||||
|
if(tempTxt.equals("退出临时呼叫")){
|
||||||
|
code=0;
|
||||||
|
} else if (tempTxt.startsWith("临时呼叫临时群组")) {
|
||||||
|
code=1;
|
||||||
|
} else if (tempTxt.equals("呼出失败")||tempTxt.equals("呼叫失败")) {
|
||||||
|
code=2;
|
||||||
|
} else if (tempTxt.equals("呼叫成功")||tempTxt.equals("呼出成功")) {
|
||||||
|
code=3;
|
||||||
|
}else{
|
||||||
|
code=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, " 临时讲话 信息通知的指令 " + tempTxt + " code=" + code);
|
||||||
CallBackUtil.callBackTempCallNotify(tempTxt);
|
CallBackUtil.callBackTempCallNotify(tempTxt);
|
||||||
|
|
||||||
|
// 处理临时呼叫页面逻辑
|
||||||
|
if (code == 1) {
|
||||||
|
// 临时呼叫临时群组 → 打开临时呼叫页面
|
||||||
|
try {
|
||||||
|
android.content.Intent intent = new android.content.Intent(
|
||||||
|
com.stand.standapp.MyApplication.getAppInstance(),
|
||||||
|
com.stand.standapp.TempCallActivity.class
|
||||||
|
);
|
||||||
|
// 从84消息中提取临时群名称,格式为"临时呼叫XXX"
|
||||||
|
String tempGroupName = "临时会话";
|
||||||
|
if (tempTxt.startsWith("临时呼叫") && tempTxt.length() > "临时呼叫".length()) {
|
||||||
|
tempGroupName = tempTxt.substring("临时呼叫".length());
|
||||||
|
}
|
||||||
|
intent.putExtra("group_name", tempGroupName);
|
||||||
|
intent.addFlags(
|
||||||
|
android.content.Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||||
|
android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
);
|
||||||
|
com.stand.standapp.MyApplication.getAppInstance().startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Failed to open TempCallActivity: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if (code == 0) {
|
||||||
|
// 退出临时呼叫 → 关闭临时呼叫页面
|
||||||
|
if (com.stand.standapp.TempCallActivity.isOpen()) {
|
||||||
|
com.stand.standapp.TempCallActivity.closeWithMessage("临时呼叫已结束");
|
||||||
|
} else if (com.stand.standapp.TempCallActivity.isMinimizedState()) {
|
||||||
|
// 最小化状态下收到结束消息,清除状态并通知前端
|
||||||
|
com.stand.standapp.TempCallActivity.clearMinimizedState();
|
||||||
|
}
|
||||||
|
} else if (code == 2) {
|
||||||
|
// 呼叫失败 → 关闭临时呼叫页面
|
||||||
|
if (com.stand.standapp.TempCallActivity.isOpen()) {
|
||||||
|
com.stand.standapp.TempCallActivity.closeWithMessage("呼叫失败");
|
||||||
|
} else if (com.stand.standapp.TempCallActivity.isMinimizedState()) {
|
||||||
|
// 最小化状态下收到失败消息,清除状态并通知前端
|
||||||
|
com.stand.standapp.TempCallActivity.clearMinimizedState();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ public class SendAtUtil {
|
||||||
send("0E000000000000\r\n");
|
send("0E000000000000\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 结束多成员临时呼叫 */
|
||||||
|
public static void stopTempCall(){
|
||||||
|
send("5500000100000000\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串转换成十六进制字符串
|
* 字符串转换成十六进制字符串
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
"onPageFinished" -> { onPageFinished(); "" }
|
"onPageFinished" -> { onPageFinished(); "" }
|
||||||
"showRestartDialog" -> { showRestartDialog(args.optString(0, ""), args.optString(1, "提示")); "" }
|
"showRestartDialog" -> { showRestartDialog(args.optString(0, ""), args.optString(1, "提示")); "" }
|
||||||
"showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" }
|
"showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" }
|
||||||
|
"getTempCallStatus" -> getTempCallStatus()
|
||||||
|
"maximizeTempCall" -> { maximizeTempCall(); "" }
|
||||||
else -> {
|
else -> {
|
||||||
Timber.tag("Bridge").w("Unknown method: $method")
|
Timber.tag("Bridge").w("Unknown method: $method")
|
||||||
""
|
""
|
||||||
|
|
@ -464,4 +466,25 @@ class AndroidInterface(private val activity: MainActivity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTempCallStatus(): String {
|
||||||
|
return try {
|
||||||
|
val json = JSONObject()
|
||||||
|
json.put("isOpen", TempCallActivity.isOpen())
|
||||||
|
json.put("isMinimized", TempCallActivity.isMinimizedState())
|
||||||
|
json.toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"{\"isOpen\":false,\"isMinimized\":false}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maximizeTempCall() {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
try {
|
||||||
|
TempCallActivity.maximize()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag("TempCall").e(e, "maximizeTempCall error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
package com.stand.standapp
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.view.animation.AlphaAnimation
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.LinearInterpolator
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.example.kingway.ptt.SendAtUtil
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class TempCallActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var isSpeaking = false
|
||||||
|
private var lastSpeakTime = 0L
|
||||||
|
private val MIN_SPEAK_DURATION = 200L
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var pulseAnimator: ObjectAnimator? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
var instance: TempCallActivity? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var isMinimized = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 存储当前临时群组名称,用于恢复时传递
|
||||||
|
@Volatile
|
||||||
|
var currentGroupName: String = "临时会话"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isOpen(): Boolean = instance != null
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isMinimizedState(): Boolean = isMinimized
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun maximize() {
|
||||||
|
if (isMinimized) {
|
||||||
|
isMinimized = false
|
||||||
|
val intent = android.content.Intent(
|
||||||
|
com.stand.standapp.MyApplication.getAppInstance(),
|
||||||
|
TempCallActivity::class.java
|
||||||
|
)
|
||||||
|
intent.putExtra("group_name", currentGroupName)
|
||||||
|
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
com.stand.standapp.MyApplication.getAppInstance().startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun closeWithMessage(message: String) {
|
||||||
|
instance?.runOnUiThread {
|
||||||
|
Toast.makeText(instance, message, Toast.LENGTH_SHORT).show()
|
||||||
|
instance?.finish()
|
||||||
|
} ?: run {
|
||||||
|
// Activity 已销毁但状态还在,清除状态
|
||||||
|
clearMinimizedState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun clearMinimizedState() {
|
||||||
|
isMinimized = false
|
||||||
|
currentGroupName = "临时会话"
|
||||||
|
MainActivity.executeJs("onTempCallStateChanged('destroyed')")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// 全屏覆盖 + 常亮 + 锁屏显示
|
||||||
|
window.addFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
||||||
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
||||||
|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||||
|
)
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_temp_call)
|
||||||
|
instance = this
|
||||||
|
isMinimized = false
|
||||||
|
|
||||||
|
val groupName = intent.getStringExtra("group_name") ?: "临时会话"
|
||||||
|
currentGroupName = groupName
|
||||||
|
findViewById<TextView>(R.id.tv_group_name).text = groupName
|
||||||
|
|
||||||
|
setupMinimizeButton()
|
||||||
|
setupPttButton()
|
||||||
|
setupHangupButton()
|
||||||
|
startPulseAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
isMinimized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
pulseAnimator?.cancel()
|
||||||
|
if (instance == this) {
|
||||||
|
instance = null
|
||||||
|
// 如果不是最小化状态,说明是真正结束,清除所有状态
|
||||||
|
if (!isMinimized) {
|
||||||
|
currentGroupName = "临时会话"
|
||||||
|
}
|
||||||
|
// 通知 web 页面状态变化
|
||||||
|
MainActivity.executeJs("onTempCallStateChanged('" + if (isMinimized) "minimized" else "destroyed" + "')")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拦截返回键
|
||||||
|
override fun onBackPressed() {
|
||||||
|
showMinimizeDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不要在 recent tasks 中显示
|
||||||
|
override fun finish() {
|
||||||
|
super.finish()
|
||||||
|
overridePendingTransition(0, android.R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateGroupName(name: String) {
|
||||||
|
findViewById<TextView>(R.id.tv_group_name)?.text = name
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPulseAnimation() {
|
||||||
|
val pulse = findViewById<ImageView>(R.id.iv_pulse) ?: return
|
||||||
|
pulseAnimator = ObjectAnimator.ofFloat(pulse, "alpha", 1f, 0.3f).apply {
|
||||||
|
duration = 1000
|
||||||
|
repeatCount = ObjectAnimator.INFINITE
|
||||||
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPttButton() {
|
||||||
|
val btnPtt = findViewById<Button>(R.id.btn_ptt)
|
||||||
|
val viewGlow = findViewById<View>(R.id.view_glow)
|
||||||
|
|
||||||
|
val startSpeak = {
|
||||||
|
if (!isSpeaking) {
|
||||||
|
isSpeaking = true
|
||||||
|
lastSpeakTime = System.currentTimeMillis()
|
||||||
|
SendAtUtil.speakPlay()
|
||||||
|
btnPtt.text = "正在\n说话..."
|
||||||
|
btnPtt.setBackgroundResource(R.drawable.circle_ptt_speaking)
|
||||||
|
viewGlow.setBackgroundResource(R.drawable.circle_ptt_glow_active)
|
||||||
|
findViewById<TextView>(R.id.tv_talk_info).text = "正在通话"
|
||||||
|
findViewById<TextView>(R.id.tv_talk_status).apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "● 对讲中"
|
||||||
|
setTextColor(0xFFEF4444.toInt())
|
||||||
|
}
|
||||||
|
Timber.tag("TempCall").d("Speak Play")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stopSpeak = {
|
||||||
|
if (isSpeaking) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val duration = now - lastSpeakTime
|
||||||
|
if (duration < MIN_SPEAK_DURATION) {
|
||||||
|
handler.postDelayed({ realRelease(btnPtt, viewGlow) }, MIN_SPEAK_DURATION - duration)
|
||||||
|
} else {
|
||||||
|
realRelease(btnPtt, viewGlow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnPtt.setOnTouchListener { _, event ->
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
startSpeak()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
stopSpeak()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun realRelease(btnPtt: Button, viewGlow: View) {
|
||||||
|
if (isSpeaking) {
|
||||||
|
isSpeaking = false
|
||||||
|
SendAtUtil.speakRelease()
|
||||||
|
btnPtt.text = "按住\n说话"
|
||||||
|
btnPtt.setBackgroundResource(R.drawable.circle_ptt_button)
|
||||||
|
viewGlow.setBackgroundResource(R.drawable.circle_ptt_glow)
|
||||||
|
findViewById<TextView>(R.id.tv_talk_info).text = "按下按钮讲话"
|
||||||
|
findViewById<TextView>(R.id.tv_talk_status).apply {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
Timber.tag("TempCall").d("Speak Release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupHangupButton() {
|
||||||
|
findViewById<Button>(R.id.btn_hangup).setOnClickListener {
|
||||||
|
try {
|
||||||
|
SendAtUtil.stopTempCall()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.tag("TempCall").e(e, "stopTempCall error")
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMinimizeButton() {
|
||||||
|
findViewById<View>(R.id.ll_minimize).setOnClickListener {
|
||||||
|
minimizeAndFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMinimizeDialog() {
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
||||||
|
.setTitle("临时呼叫")
|
||||||
|
.setMessage("是否最小化临时呼叫页面?")
|
||||||
|
.setPositiveButton("最小化") { _, _ ->
|
||||||
|
minimizeAndFinish()
|
||||||
|
}
|
||||||
|
.setNegativeButton("取消", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun minimizeAndFinish() {
|
||||||
|
isMinimized = true
|
||||||
|
// 通知 web 页面临时呼叫页面已最小化
|
||||||
|
MainActivity.executeJs("onTempCallStateChanged('minimized')")
|
||||||
|
// finish 但保持 isMinimized = true
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由 GpioManager 调用,同步 GPIO 按下状态
|
||||||
|
fun onGpioPressed() {
|
||||||
|
val btnPtt = findViewById<Button>(R.id.btn_ptt) ?: return
|
||||||
|
val viewGlow = findViewById<View>(R.id.view_glow) ?: return
|
||||||
|
if (!isSpeaking) {
|
||||||
|
isSpeaking = true
|
||||||
|
lastSpeakTime = System.currentTimeMillis()
|
||||||
|
SendAtUtil.speakPlay()
|
||||||
|
btnPtt.text = "正在\n说话..."
|
||||||
|
btnPtt.setBackgroundResource(R.drawable.circle_ptt_speaking)
|
||||||
|
viewGlow.setBackgroundResource(R.drawable.circle_ptt_glow_active)
|
||||||
|
findViewById<TextView>(R.id.tv_talk_info).text = "正在通话"
|
||||||
|
findViewById<TextView>(R.id.tv_talk_status).apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = "● 对讲中"
|
||||||
|
setTextColor(0xFFEF4444.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由 GpioManager 调用,同步 GPIO 松开状态
|
||||||
|
fun onGpioReleased() {
|
||||||
|
val btnPtt = findViewById<Button>(R.id.btn_ptt) ?: return
|
||||||
|
val viewGlow = findViewById<View>(R.id.view_glow) ?: return
|
||||||
|
if (isSpeaking) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val duration = now - lastSpeakTime
|
||||||
|
if (duration < MIN_SPEAK_DURATION) {
|
||||||
|
handler.postDelayed({ realRelease(btnPtt, viewGlow) }, MIN_SPEAK_DURATION - duration)
|
||||||
|
} else {
|
||||||
|
realRelease(btnPtt, viewGlow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import com.stand.standapp.MainActivity
|
import com.stand.standapp.MainActivity
|
||||||
|
import com.stand.standapp.TempCallActivity
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,6 +76,8 @@ object GpioManager {
|
||||||
Timber.tag("GPIO").d("PTT Speak Play (Hardware)")
|
Timber.tag("GPIO").d("PTT Speak Play (Hardware)")
|
||||||
// 统一使用 updatePttTalkStatus 更新 UI
|
// 统一使用 updatePttTalkStatus 更新 UI
|
||||||
MainActivity.executeJs("updatePttTalkStatus(true)")
|
MainActivity.executeJs("updatePttTalkStatus(true)")
|
||||||
|
// 同步 TempCallActivity
|
||||||
|
TempCallActivity.instance?.onGpioPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -104,6 +107,8 @@ object GpioManager {
|
||||||
Timber.tag("GPIO").d("PTT Speak Release (Hardware)")
|
Timber.tag("GPIO").d("PTT Speak Release (Hardware)")
|
||||||
// 统一使用 updatePttTalkStatus 更新 UI
|
// 统一使用 updatePttTalkStatus 更新 UI
|
||||||
MainActivity.executeJs("updatePttTalkStatus(false)")
|
MainActivity.executeJs("updatePttTalkStatus(false)")
|
||||||
|
// 同步 TempCallActivity
|
||||||
|
TempCallActivity.instance?.onGpioReleased()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#66000000" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
<padding
|
||||||
|
android:left="8dp"
|
||||||
|
android:right="8dp"
|
||||||
|
android:top="4dp"
|
||||||
|
android:bottom="4dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#EF4444" />
|
||||||
|
<size android:width="64dp" android:height="64dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#66FFFFFF" />
|
||||||
|
<size
|
||||||
|
android:width="40dp"
|
||||||
|
android:height="40dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#3B82F6" />
|
||||||
|
<size android:width="150dp" android:height="150dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#223B82F6" />
|
||||||
|
<size android:width="180dp" android:height="180dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#333B82F6" />
|
||||||
|
<size android:width="180dp" android:height="180dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#2563EB" />
|
||||||
|
<size android:width="150dp" android:height="150dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#10B981" />
|
||||||
|
<size android:width="10dp" android:height="10dp" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bg_login">
|
||||||
|
|
||||||
|
<!-- 最小化按钮 (右上角,明显的按钮样式) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_minimize"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginTop="40dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:background="@drawable/btn_minimize_bg"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||||
|
android:tint="#FFFFFF" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn_minimize"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:text="最小化"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 顶部状态栏区域 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="50dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<!-- 临时呼叫图标 -->
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:src="@android:drawable/ic_menu_call"
|
||||||
|
android:tint="#FFFFFF" />
|
||||||
|
|
||||||
|
<!-- 状态文字 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="临时呼叫中"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:letterSpacing="0.1" />
|
||||||
|
|
||||||
|
<!-- 脉冲动画指示器 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_pulse"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:src="@drawable/circle_pulse" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 中间信息卡片 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:background="@drawable/shape_login_box"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="临时群组"
|
||||||
|
android:textColor="#99FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:letterSpacing="0.15" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_group_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="临时群组"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<View
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:background="#33FFFFFF" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_talk_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="按下按钮讲话"
|
||||||
|
android:textColor="#CCFFFFFF"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_talk_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text=""
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 底部按钮区域 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="48dp">
|
||||||
|
|
||||||
|
<!-- PTT 大按钮 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp">
|
||||||
|
|
||||||
|
<!-- 外圈光晕 -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/view_glow"
|
||||||
|
android:layout_width="180dp"
|
||||||
|
android:layout_height="180dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/circle_ptt_glow" />
|
||||||
|
|
||||||
|
<!-- PTT 按钮 -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_ptt"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/circle_ptt_button"
|
||||||
|
android:text="按住\n说话"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- 挂断按钮 -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_hangup"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:background="@drawable/circle_hangup_button"
|
||||||
|
android:text="挂断"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
Loading…
Reference in New Issue