diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fd602e..64deca8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,14 @@ android:label="开发者模式" android:theme="@style/Theme.StandAPP" /> + + + + +
当前群组: - @@ -625,8 +643,75 @@ } } catch(e) {} } + // 同步临时呼叫状态 + syncTempCallStatus(); }, 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 注入后再调用) window.onload = function() {}; diff --git a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java index bd79674..bbe3cbe 100644 --- a/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java +++ b/app/src/main/java/com/example/kingway/ptt/CallBackResolution.java @@ -238,8 +238,61 @@ public class CallBackResolution { for (int i = 0; i < stringsTalk.size(); 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); + + // 处理临时呼叫页面逻辑 + 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; diff --git a/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java b/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java index e18f32d..6b45ebd 100644 --- a/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java +++ b/app/src/main/java/com/example/kingway/ptt/SendAtUtil.java @@ -105,6 +105,11 @@ public class SendAtUtil { send("0E000000000000\r\n"); } + /** 结束多成员临时呼叫 */ + public static void stopTempCall(){ + send("5500000100000000\r\n"); + } + /** * 字符串转换成十六进制字符串 diff --git a/app/src/main/java/com/stand/standapp/AndroidInterface.kt b/app/src/main/java/com/stand/standapp/AndroidInterface.kt index c15eb3e..3184845 100644 --- a/app/src/main/java/com/stand/standapp/AndroidInterface.kt +++ b/app/src/main/java/com/stand/standapp/AndroidInterface.kt @@ -83,6 +83,8 @@ class AndroidInterface(private val activity: MainActivity) { "onPageFinished" -> { onPageFinished(); "" } "showRestartDialog" -> { showRestartDialog(args.optString(0, ""), args.optString(1, "提示")); "" } "showJsonDialog" -> { showJsonDialog(args.optString(0, "")); "" } + "getTempCallStatus" -> getTempCallStatus() + "maximizeTempCall" -> { maximizeTempCall(); "" } else -> { 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") + } + } + } } diff --git a/app/src/main/java/com/stand/standapp/TempCallActivity.kt b/app/src/main/java/com/stand/standapp/TempCallActivity.kt new file mode 100644 index 0000000..46b3eac --- /dev/null +++ b/app/src/main/java/com/stand/standapp/TempCallActivity.kt @@ -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(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(R.id.tv_group_name)?.text = name + } + + private fun startPulseAnimation() { + val pulse = findViewById(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