├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── keystore │ └── quicker.jks ├── libs │ ├── mina-core-2.0.18.jar │ └── slf4j-android-1.6.1-RC1.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── kotlin │ │ └── test │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cuiliang │ │ │ └── quicker │ │ │ ├── ConfigActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── QrcodeScanActivity.java │ │ │ ├── QuickerApplication.java │ │ │ ├── UiButtonItem.java │ │ │ ├── adapter │ │ │ ├── EventOrActionAdapter.kt │ │ │ ├── GridLayoutAdapter.java │ │ │ ├── TaskDetailsItemAdapter.kt │ │ │ └── TaskListAdapter.kt │ │ │ ├── client │ │ │ ├── ClientConfig.java │ │ │ ├── ClientManager.java │ │ │ ├── ClientService.kt │ │ │ ├── ConnectionStatus.java │ │ │ ├── MessageCache.java │ │ │ ├── NetworkStatusChangeReceiver.kt │ │ │ └── QuickerServiceHandler.kt │ │ │ ├── events │ │ │ ├── ConnectionStatusChangedEvent.java │ │ │ └── SessionClosedEvent.java │ │ │ ├── messages │ │ │ ├── MessageBase.java │ │ │ ├── recv │ │ │ │ ├── LoginStateMessage.java │ │ │ │ ├── UpdateButtonsMessage.java │ │ │ │ └── VolumeStateMessage.java │ │ │ └── send │ │ │ │ ├── ButtonClickedMessage.java │ │ │ │ ├── CommandMessage.java │ │ │ │ ├── DeviceLoginMessage.java │ │ │ │ ├── PhotoMessage.java │ │ │ │ ├── TextDataMessage.java │ │ │ │ ├── ToggleMuteMessage.java │ │ │ │ └── UpdateVolumeMessage.java │ │ │ ├── network │ │ │ ├── ConnectServiceCallback.java │ │ │ ├── MyCodecFactory.java │ │ │ ├── MyDataDecoder.java │ │ │ ├── MyDataEncoder.java │ │ │ ├── NetRequestObj.kt │ │ │ ├── NetworkManager.kt │ │ │ ├── ScanDeviceUtils.java │ │ │ ├── shareToPc │ │ │ │ └── ShareApi.kt │ │ │ └── websocket │ │ │ │ ├── ConnectListener.kt │ │ │ │ ├── MessageType.kt │ │ │ │ ├── MsgRequestData.kt │ │ │ │ ├── MsgResponseData.kt │ │ │ │ ├── ServiceRequestFactory.kt │ │ │ │ ├── WebSocketClient.kt │ │ │ │ └── WebSocketNetListener.kt │ │ │ ├── service │ │ │ └── TaskManagerService.kt │ │ │ ├── svg │ │ │ ├── SvgDecoder.kt │ │ │ ├── SvgDrawableTranscoder.kt │ │ │ ├── SvgModule.kt │ │ │ └── SvgSoftwareLayerSetter.java │ │ │ ├── taskManager │ │ │ ├── BaseEventOrAction.kt │ │ │ ├── JsonTask.kt │ │ │ ├── Task.kt │ │ │ ├── TaskDataFactory.kt │ │ │ ├── TaskType.kt │ │ │ ├── action │ │ │ │ ├── Action.kt │ │ │ │ ├── ActionAdd.kt │ │ │ │ └── ActionWebSocketMsg.kt │ │ │ └── event │ │ │ │ ├── Event.kt │ │ │ │ ├── EventAdd.kt │ │ │ │ ├── EventBatteryStatus.kt │ │ │ │ ├── EventRunningListener.kt │ │ │ │ └── EventWebSocket.kt │ │ │ ├── ui │ │ │ ├── EventOrActionActivity.kt │ │ │ ├── share │ │ │ │ ├── ShareActivity.kt │ │ │ │ ├── ShareViewModel.kt │ │ │ │ └── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ └── Type.kt │ │ │ ├── taskEdit │ │ │ │ ├── TaskEditActivity.kt │ │ │ │ ├── TaskEditModel.kt │ │ │ │ └── TaskEditViewModel.kt │ │ │ └── taskManager │ │ │ │ ├── MyTaskFragment.kt │ │ │ │ ├── TaskConfig.kt │ │ │ │ ├── TaskConstant.kt │ │ │ │ ├── TaskList.kt │ │ │ │ └── TaskListActivity.kt │ │ │ ├── util │ │ │ ├── DataPageValues.java │ │ │ ├── FileTools.kt │ │ │ ├── GsonUtils.kt │ │ │ ├── ImagePicker.java │ │ │ ├── KLog.kt │ │ │ ├── SPUtils.java │ │ │ ├── ShareDataToPCManager.kt │ │ │ ├── SystemUtils.kt │ │ │ ├── ToastUtils.java │ │ │ └── View.kt │ │ │ └── view │ │ │ ├── DataPageContextView.java │ │ │ ├── DataPageGlobalView.java │ │ │ ├── DataPageView.java │ │ │ ├── DataPageViewPager.java │ │ │ └── ViewPagerCuePoint.java │ └── res │ │ ├── animator │ │ └── rotation.xml │ │ ├── drawable │ │ ├── anim_load.xml │ │ ├── bg_btn_block.xml │ │ ├── ic_accept.xml │ │ ├── ic_add.xml │ │ ├── ic_back.xml │ │ ├── ic_battery.xml │ │ ├── ic_camera_alt_black_24dp.xml │ │ ├── ic_close_fill.xml │ │ ├── ic_computer_black_24dp.xml │ │ ├── ic_desktop_windows_black_24dp.xml │ │ ├── ic_info_black_24dp.xml │ │ ├── ic_keyboard_voice_black_24dp.xml │ │ ├── ic_load.xml │ │ ├── ic_lock_black_24dp.xml │ │ ├── ic_lock_open_black_24dp.xml │ │ ├── ic_notifications_black_24dp.xml │ │ ├── ic_point_dark.xml │ │ ├── ic_point_light.xml │ │ ├── ic_qrcode_scan.xml │ │ ├── ic_settings_black_24dp.xml │ │ ├── ic_share.xml │ │ ├── ic_sync_black_24dp.xml │ │ ├── ic_task.xml │ │ ├── ic_textsms_black_24dp.xml │ │ ├── ic_volume_down_black_24dp.xml │ │ ├── ic_volume_mute_black_24dp.xml │ │ ├── ic_volume_off_black_24dp.xml │ │ ├── ic_volume_up_black_24dp.xml │ │ ├── shape_accept.xml │ │ ├── shape_cancel.xml │ │ └── shape_task_item_bg.xml │ │ ├── layout │ │ ├── activity_config.xml │ │ ├── activity_main.xml │ │ ├── activity_main_portrait.xml │ │ ├── activity_qrcode_scan.xml │ │ ├── activity_share.xml │ │ ├── activity_task_edit.xml │ │ ├── activity_task_list.xml │ │ ├── fragment_my_task.xml │ │ ├── layout_action_button.xml │ │ ├── layout_event_item.xml │ │ ├── layout_input_auth_code.xml │ │ ├── layout_page_center_info.xml │ │ ├── layout_page_center_info_landscape.xml │ │ ├── layout_task_details_item.xml │ │ └── layout_task_info.xml │ │ ├── menu │ │ ├── menu_task_edit.xml │ │ └── menu_task_manager.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ └── file_paths.xml │ └── test │ └── kotlin │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── q_base ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cuiliang │ │ └── quicker │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── cuiliang │ │ └── quicker │ │ ├── ui │ │ ├── BaseActivity.kt │ │ ├── BaseComposableActivity.kt │ │ ├── BaseDBActivity.kt │ │ ├── BaseModel.kt │ │ ├── BaseVBActivity.kt │ │ ├── BaseViewModel.kt │ │ └── EmptyViewModel.kt │ │ └── utils │ │ └── BindingReflex.kt │ └── test │ └── java │ └── com │ └── cuiliang │ └── quicker │ └── ExampleUnitTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | # Uncomment the following line if you do not want to check your keystore files in. 41 | #*.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | # Google Services (e.g. APIs or Firebase) 47 | google-services.json 48 | 49 | # Freeline 50 | freeline.py 51 | freeline/ 52 | freeline_project_description.json 53 | 54 | # fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | fastlane/readme.md 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickerAndroid 2 | Quicker 安卓APP 3 | 4 | 因本人不懂Android开发,在时间有限的情况下,只能做到这个程度了。 5 | 6 | 如果你有兴趣有能力,可以对安卓客户端进行功能扩展。 7 | 8 | 我将协助进行协议调整和PC端对应功能的实现。 9 | 10 | 目前有一些构思: 11 | 1. 增加一个service运行在后台,从Quicker可以发送文本到客户端(命令+内容的形式),客户端可以执行相应的操作:比如放入剪贴板、打开URI等。 12 | 2. 将电脑屏幕内容显示在app上,在app上实现一些控制。比如模拟鼠标什么的。 13 | 14 | 15 | 16 | # 贡献者 17 | 感谢各位给Quicker提交代码的大神! 18 | - edhlily 19 | - ExistNotSee 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/keystore/quicker.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/keystore/quicker.jks -------------------------------------------------------------------------------- /app/libs/mina-core-2.0.18.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/libs/mina-core-2.0.18.jar -------------------------------------------------------------------------------- /app/libs/slf4j-android-1.6.1-RC1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/libs/slf4j-android-1.6.1-RC1.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/test/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import cuiliang.quicker.client.ClientConfig 5 | import cuiliang.quicker.network.websocket.WebSocketClient 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Created by Voidcom on 2023/9/11 18:21 11 | * 12 | * Description: TODO 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun connect() { 18 | ClientConfig.getInstance().enableHttps = true 19 | ClientConfig.getInstance().mServerHost = "192.168.1.100" 20 | ClientConfig.getInstance().mServerPort = "668" 21 | ClientConfig.getInstance().ConnectionCode = "aaa" 22 | WebSocketClient.instance().connectRequest { result, msg -> 23 | if (!result) 24 | println("服务连接失败:$msg") 25 | } 26 | //因为单元测试方法运行完会直接结束,而其他线程工作还没结束,这里加一个延时 27 | Thread.sleep(3000) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/QrcodeScanActivity.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.os.Vibrator; 6 | import android.util.Log; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import com.cuiliang.quicker.ui.BaseVBActivity; 12 | import com.cuiliang.quicker.ui.EmptyViewModel; 13 | 14 | import java.util.List; 15 | 16 | import cn.bingoogolapple.qrcode.core.QRCodeView; 17 | import cuiliang.quicker.databinding.ActivityQrcodeScanBinding; 18 | import pub.devrel.easypermissions.AfterPermissionGranted; 19 | import pub.devrel.easypermissions.EasyPermissions; 20 | 21 | // https://github.com/bingoogolapple/BGAQRCode-Android 22 | public class QrcodeScanActivity extends BaseVBActivity implements QRCodeView.Delegate, EasyPermissions.PermissionCallbacks { 23 | private static final String TAG = QrcodeScanActivity.class.getSimpleName(); 24 | private static final int REQUEST_CODE_QRCODE_PERMISSIONS = 1; 25 | 26 | @NonNull 27 | @Override 28 | protected EmptyViewModel getMViewModel() { 29 | return new EmptyViewModel(); 30 | } 31 | 32 | @Override 33 | public void onInit() { 34 | getMBinding().zbarview.setDelegate(this); 35 | } 36 | 37 | @Override 38 | protected void onStart() { 39 | super.onStart(); 40 | requestCodeQRCodePermissions(); 41 | getMBinding().zbarview.startSpot(); 42 | } 43 | 44 | @Override 45 | protected void onStop() { 46 | getMBinding().zbarview.stopCamera(); 47 | super.onStop(); 48 | } 49 | 50 | @Override 51 | protected void onDestroy() { 52 | getMBinding().zbarview.onDestroy(); 53 | super.onDestroy(); 54 | } 55 | 56 | private void vibrate() { 57 | Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); 58 | if (vibrator == null) return; 59 | vibrator.vibrate(200); 60 | } 61 | 62 | 63 | @Override 64 | public void onScanQRCodeSuccess(String result) { 65 | Log.i(TAG, "result:" + result); 66 | Toast.makeText(this, result, Toast.LENGTH_SHORT).show(); 67 | vibrate(); 68 | // getMBinding().zbarview.startSpot(); 69 | 70 | Intent intent = new Intent();// 重新声明一个意图。 71 | intent.putExtra("barcode", result); // 将three回传到意图中。 72 | // 通过Intent对象返回结果,调用setResult方法。 73 | setResult(RESULT_OK, intent); 74 | finish();// 结束当前Activity的生命周期。 75 | } 76 | 77 | @Override 78 | public void onCameraAmbientBrightnessChanged(boolean isDark) { 79 | } 80 | 81 | @Override 82 | public void onScanQRCodeOpenCameraError() { 83 | Log.e(TAG, "打开相机出错"); 84 | } 85 | 86 | @Override 87 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 88 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 89 | 90 | // Forward results to EasyPermissions 91 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 92 | } 93 | 94 | @AfterPermissionGranted(REQUEST_CODE_QRCODE_PERMISSIONS) 95 | private void requestCodeQRCodePermissions() { 96 | String[] perms = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE}; 97 | if (!EasyPermissions.hasPermissions(this, perms)) { 98 | EasyPermissions.requestPermissions(this, "扫描二维码需要打开相机和散光灯的权限", REQUEST_CODE_QRCODE_PERMISSIONS, perms); 99 | }else { 100 | startScan(); 101 | } 102 | } 103 | 104 | 105 | @Override 106 | public void onPermissionsGranted(int requestCode, @NonNull List perms) { 107 | Log.d(TAG, "权限授权!"); 108 | startScan(); 109 | } 110 | 111 | @Override 112 | public void onPermissionsDenied(int requestCode, @NonNull List perms) { 113 | Log.d(TAG, "权限被拒绝!"); 114 | finish(); 115 | } 116 | 117 | private void startScan() { 118 | getMBinding().zbarview.startCamera(); 119 | getMBinding().zbarview.showScanRect(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/QuickerApplication.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker; 2 | 3 | import android.app.Application; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.util.DisplayMetrics; 7 | 8 | import cuiliang.quicker.client.ClientService; 9 | import cuiliang.quicker.service.TaskManagerService; 10 | import cuiliang.quicker.util.SPUtils; 11 | 12 | public class QuickerApplication extends Application { 13 | public static DisplayMetrics displayMetrics = null; 14 | 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | displayMetrics = getResources().getDisplayMetrics(); 19 | SPUtils.init(this); 20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 21 | startForegroundService(new Intent(this, ClientService.class)); 22 | } else { 23 | startService(new Intent(this, ClientService.class)); 24 | } 25 | startService(new Intent(this, TaskManagerService.class)); 26 | 27 | // closeHideApiDialog(); 28 | } 29 | 30 | /** 31 | * 解决androidP 第一次打开程序出现莫名弹窗 32 | * 弹窗内容“detected problems with api ” 33 | */ 34 | //这段代码会导致闪退,先注释,后续尝试复现这段注释出现的问题,然后用其他方法解决 35 | // private void closeHideApiDialog() { 36 | // try { 37 | // Class aClass = Class.forName("android.content.pm.PackageParser$Package"); 38 | // Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class); 39 | // declaredConstructor.setAccessible(true); 40 | // } catch (Exception e) { 41 | // e.printStackTrace(); 42 | // } 43 | // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { 44 | // try { 45 | // Class cls = Class.forName("android.app.ActivityThread"); 46 | // Method declaredMethod = cls.getDeclaredMethod("currentActivityThread"); 47 | // declaredMethod.setAccessible(true); 48 | // Object activityThread = declaredMethod.invoke(null); 49 | // Field mHiddenApiWarningShown = cls.getDeclaredField("mHiddenApiWarningShown"); 50 | // mHiddenApiWarningShown.setAccessible(true); 51 | // mHiddenApiWarningShown.setBoolean(activityThread, true); 52 | // } catch (Exception e) { 53 | // e.printStackTrace(); 54 | // } 55 | // } 56 | // } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/UiButtonItem.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker; 2 | 3 | import android.view.ViewGroup; 4 | import android.widget.ImageView; 5 | import android.widget.LinearLayout; 6 | import android.widget.TextView; 7 | 8 | // 按钮项各组件的引用 9 | public class UiButtonItem { 10 | public ViewGroup button; 11 | public TextView textView; 12 | public ImageView imageView; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/adapter/EventOrActionAdapter.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.appcompat.widget.AppCompatImageView 9 | import androidx.appcompat.widget.AppCompatTextView 10 | import androidx.core.content.ContextCompat 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.bumptech.glide.Glide 13 | import cuiliang.quicker.R 14 | import cuiliang.quicker.taskManager.BaseEventOrAction 15 | 16 | class EventOrActionAdapter( 17 | private val context: Context, 18 | private val callback: ((BaseEventOrAction) -> Unit) 19 | ) : 20 | RecyclerView.Adapter() { 21 | private val eventList = arrayListOf() 22 | private var unableAddArray = arrayOf() 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskHolder { 25 | return TaskHolder( 26 | LayoutInflater.from(context).inflate(R.layout.layout_event_item, parent, false) 27 | ) 28 | } 29 | 30 | override fun getItemCount(): Int = eventList.size 31 | 32 | override fun onBindViewHolder(holder: TaskHolder, position: Int) { 33 | holder.setData(eventList[position]) 34 | } 35 | 36 | /** 37 | * 设置不可添加列表 38 | */ 39 | fun setUnableAddList(array: Array) { 40 | this.unableAddArray = array 41 | } 42 | 43 | @SuppressLint("NotifyDataSetChanged") 44 | fun setEvents(list: List) { 45 | eventList.clear() 46 | eventList.addAll(list) 47 | notifyDataSetChanged() 48 | } 49 | 50 | inner class TaskHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 51 | private val eventIcon: AppCompatImageView = itemView.findViewById(R.id.ivIcon) 52 | private val eventTitle: AppCompatTextView = itemView.findViewById(R.id.tvTitle) 53 | 54 | fun setData(data: BaseEventOrAction) { 55 | eventTitle.text = data.getName() 56 | Glide.with(context).load(data.getIcon()).into(eventIcon) 57 | 58 | //检查这个item是否已经添加,如果添加了就设置为不可点击 59 | for (u in unableAddArray.iterator()) { 60 | if (data.getName() == u) { 61 | eventTitle.setTextColor( 62 | ContextCompat.getColor( 63 | context, 64 | R.color.event_unableClick 65 | ) 66 | ) 67 | itemView.isEnabled = false 68 | itemView.isClickable = false 69 | return 70 | } 71 | } 72 | itemView.setOnClickListener { 73 | data.showDialogAndCallback(context, callback) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/adapter/GridLayoutAdapter.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.viewpager.widget.PagerAdapter; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import cuiliang.quicker.UiButtonItem; 14 | import cuiliang.quicker.client.ClientManager; 15 | import cuiliang.quicker.messages.send.CommandMessage; 16 | import cuiliang.quicker.view.DataPageContextView; 17 | import cuiliang.quicker.view.DataPageGlobalView; 18 | import cuiliang.quicker.view.DataPageView; 19 | 20 | /** 21 | * Created by Void on 2020/3/3 15:36 22 | */ 23 | public class GridLayoutAdapter extends PagerAdapter { 24 | private List items = new ArrayList<>(); 25 | //用于记录当前viewpager索引 26 | private int currentPageIndex = 0; 27 | 28 | @Override 29 | public int getCount() { 30 | return items.size(); 31 | } 32 | 33 | @Override 34 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 35 | return view == object; 36 | } 37 | 38 | @NonNull 39 | @Override 40 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 41 | container.addView(items.get(position)); 42 | return items.get(position); 43 | } 44 | 45 | @Override 46 | public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { 47 | container.removeView(items.get(position)); 48 | } 49 | 50 | public UiButtonItem getActionBtnObject(int index, int currentPageIndex) { 51 | try { 52 | return items.get(currentPageIndex).actionBtnArray.get(index); 53 | } catch (Exception e) { 54 | return null; 55 | } 56 | } 57 | 58 | /** 59 | * 因为{@link cuiliang.quicker.messages.recv.UpdateButtonsMessage}消息下发的比较频繁, 60 | * 且数据格式都是一样的,因此抛弃每次收到下发消息后清除所有view重新根据数据生成view的思路。 61 | * 而使用根据下发的页数动态生成 {@link DataPageView}。 62 | * 最终展示数据的DataPageView仅仅是数据的载体,和数据没有联系。 63 | * 64 | * @param num 总页数 65 | */ 66 | public void createPages(Context context, int num, boolean isGlobal) { 67 | while (true) { 68 | int tmp = items.size() - num; 69 | if (tmp > 0) { 70 | items.remove(items.size() - 1); 71 | } else if (tmp < 0 && isGlobal) { 72 | items.add(new DataPageGlobalView(context)); 73 | } else if (tmp < 0) { 74 | items.add(new DataPageContextView(context)); 75 | } else { 76 | break; 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * 根据索引刷新数据页 83 | */ 84 | public void updateDatePage(int index, boolean isGlobal) { 85 | if (items.isEmpty()) return; 86 | int tmp = currentPageIndex - index; 87 | if (isGlobal) { 88 | //全局数据页 89 | if (tmp > 0) { 90 | ClientManager.getInstance().requestUpdateDataPage(CommandMessage.DATA_PAGE_GLOBAL_LEFT); 91 | } else { 92 | ClientManager.getInstance().requestUpdateDataPage(CommandMessage.DATA_PAGE_GLOBAL_RIGHT); 93 | } 94 | } else { 95 | //上下文页 96 | if (tmp > 0) { 97 | ClientManager.getInstance().requestUpdateDataPage(CommandMessage.DATA_PAGE_CONTEXT_LEFT); 98 | } else { 99 | ClientManager.getInstance().requestUpdateDataPage(CommandMessage.DATA_PAGE_CONTEXT_RIGHT); 100 | } 101 | } 102 | currentPageIndex = index; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/adapter/TaskDetailsItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.View.OnClickListener 7 | import android.view.View.OnLongClickListener 8 | import android.view.ViewGroup 9 | import android.widget.RelativeLayout 10 | import androidx.appcompat.widget.AppCompatImageView 11 | import androidx.appcompat.widget.AppCompatTextView 12 | import androidx.recyclerview.widget.RecyclerView 13 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 14 | import com.bumptech.glide.Glide 15 | import com.google.android.material.snackbar.Snackbar 16 | import cuiliang.quicker.R 17 | import cuiliang.quicker.taskManager.BaseEventOrAction 18 | import cuiliang.quicker.util.KLog 19 | import cuiliang.quicker.util.gone 20 | import cuiliang.quicker.util.visible 21 | 22 | /** 23 | * Created by Voidcom on 2023/9/15 18:30 24 | * TODO 25 | */ 26 | class TaskDetailsItemAdapter( 27 | private val context: Context, 28 | ) : RecyclerView.Adapter.ItemViewHolder>() { 29 | private val items: ArrayList = arrayListOf() 30 | private var callback: ((List) -> Unit)? = null 31 | private var footerViewData: T? = null 32 | 33 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { 34 | return ItemViewHolder( 35 | LayoutInflater.from(context).inflate(R.layout.layout_task_details_item, parent, false) 36 | ) 37 | } 38 | 39 | override fun getItemCount(): Int = items.size + 1 40 | override fun getItemViewType(position: Int): Int { 41 | return if (position < items.size) TYPE_INVALID else TYPE_FOOTER 42 | } 43 | 44 | override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { 45 | if (TYPE_FOOTER == getItemViewType(position)) { 46 | footerViewData?.let { holder.setData(it, TYPE_FOOTER) } 47 | } else { 48 | KLog.d("FactorItemAdapter", "position:$position; data:${items[position]}") 49 | holder.setData(items[position], TYPE_INVALID) 50 | } 51 | } 52 | 53 | fun addItem(data: T) { 54 | items.add(data) 55 | notifyItemChanged(items.size - 1) 56 | } 57 | 58 | fun addItems(array: List) { 59 | items.clear() 60 | items.addAll(array) 61 | notifyItemRangeChanged(0, itemCount) 62 | } 63 | 64 | fun removeItem(i: Int) { 65 | items.removeAt(i) 66 | notifyItemRemoved(i) 67 | } 68 | 69 | fun setFooterData(data: T) { 70 | this.footerViewData = data 71 | notifyItemChanged(itemCount - 1) 72 | } 73 | 74 | fun setCallback(c: (List) -> Unit) { 75 | this.callback=c 76 | } 77 | 78 | inner class ItemViewHolder(v: View) : ViewHolder(v), OnClickListener, 79 | OnLongClickListener { 80 | private var rootView: RelativeLayout = v.findViewById(R.id.rootView) 81 | private var ivIcon: AppCompatImageView = v.findViewById(R.id.ivIcon) 82 | private var ivClose: AppCompatImageView = v.findViewById(R.id.ivClose) 83 | private var tvTitle: AppCompatTextView = v.findViewById(R.id.tvTitle) 84 | private var tvSubTitle: AppCompatTextView = v.findViewById(R.id.tvSubTitle) 85 | 86 | fun setData(d: T, type: Int) { 87 | tvTitle.text = d.getName() 88 | if (d.resultStr().isEmpty()) { 89 | tvSubTitle.gone() 90 | } else { 91 | tvSubTitle.text = d.resultStr() 92 | } 93 | Glide.with(context).load(d.getIcon()).into(ivIcon) 94 | if (TYPE_INVALID == type) { 95 | ivClose.setOnClickListener(this) 96 | ivClose.visible() 97 | rootView.setOnLongClickListener(this) 98 | } else { 99 | ivClose.gone() 100 | rootView.setOnClickListener(this) 101 | } 102 | } 103 | 104 | override fun onClick(v: View?) { 105 | when (v?.id) { 106 | R.id.rootView -> callback?.invoke(items) 107 | R.id.ivClose -> removeItem(layoutPosition) 108 | } 109 | } 110 | 111 | override fun onLongClick(v: View?): Boolean { 112 | when (v?.id) { 113 | R.id.rootView -> { 114 | Snackbar.make(v, "还没做好", Snackbar.LENGTH_LONG).show() 115 | } 116 | } 117 | return true 118 | } 119 | } 120 | 121 | companion object { 122 | private const val TYPE_INVALID = 0 123 | private const val TYPE_FOOTER = 1 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/adapter/TaskListAdapter.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.appcompat.widget.AppCompatTextView 8 | import androidx.appcompat.widget.SwitchCompat 9 | import androidx.recyclerview.widget.RecyclerView 10 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 11 | import cuiliang.quicker.R 12 | import cuiliang.quicker.taskManager.Task 13 | import cuiliang.quicker.ui.taskManager.TaskList 14 | 15 | /** 16 | * Created by Voidcom on 2023/9/13 16:49 17 | * TODO 18 | */ 19 | class TaskListAdapter( 20 | private val context: Context, 21 | private val taskList: TaskList, 22 | private val callback: ((Task) -> Unit) 23 | ) : 24 | RecyclerView.Adapter() { 25 | 26 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskHolder { 27 | return TaskHolder( 28 | LayoutInflater.from(context).inflate(R.layout.layout_task_info, parent, false) 29 | ) 30 | } 31 | 32 | override fun getItemCount(): Int = taskList.size() 33 | 34 | override fun onBindViewHolder(holder: TaskHolder, position: Int) { 35 | holder.setData(taskList.get(position)) 36 | } 37 | 38 | inner class TaskHolder(itemView: View) : ViewHolder(itemView) { 39 | private val taskTitle: AppCompatTextView = itemView.findViewById(R.id.taskTitle) 40 | private val taskSubTitle: AppCompatTextView = itemView.findViewById(R.id.taskSubTitle) 41 | private val taskSwitch: SwitchCompat = itemView.findViewById(R.id.taskSwitch) 42 | 43 | init { 44 | taskSwitch.setOnCheckedChangeListener { _, isChecked -> 45 | taskList.get(taskTitle.text.toString()).updateState(context, isChecked) 46 | } 47 | } 48 | 49 | fun setData(data: Task) { 50 | taskTitle.text = data.name 51 | taskSubTitle.text = data.toDescription() 52 | taskSwitch.isChecked = data.getIsEnable() 53 | // KLog.d( 54 | // "TaskListAdapter", 55 | // "isChecked:${taskSwitch.isChecked}; taskState:${data.getIsEnable()}" 56 | // ) 57 | itemView.setOnClickListener { 58 | //任务Item点击事件 59 | callback.invoke(data) 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/client/ClientConfig.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.client; 2 | 3 | import android.text.TextUtils; 4 | 5 | import cuiliang.quicker.util.KLog; 6 | import cuiliang.quicker.util.SPUtils; 7 | 8 | // 客户端设置 9 | public class ClientConfig { 10 | 11 | // 服务器主机名 12 | public String mServerHost = "192.168.1.1"; 13 | 14 | // 服务器端口 15 | public String mServerPort = "666"; 16 | 17 | // 设备名称 18 | public String DeviceName; 19 | 20 | // 连接验证码 21 | public String ConnectionCode = "quicker"; 22 | 23 | //推送用户名,用于推送服务 24 | public String userName; 25 | //推送验证码 26 | public String pushAuthCode; 27 | 28 | //WebSocket 端口 29 | public String webSocketPort = "668"; 30 | public String webSocketCode = "quicker"; 31 | 32 | //true启用wss安全连接 33 | public boolean enableHttps = true; 34 | 35 | private ClientConfig() { 36 | } 37 | 38 | private static class ClientConfigHolder { 39 | public final static ClientConfig INSTANCE = new ClientConfig(); 40 | } 41 | 42 | public static ClientConfig getInstance() { 43 | return ClientConfigHolder.INSTANCE; 44 | } 45 | 46 | public boolean hasCache() { 47 | return SPUtils.contains("mServerHost") && SPUtils.contains("mServerPort") && SPUtils.contains("ConnectionCode"); 48 | } 49 | 50 | public void saveConfig() { 51 | SPUtils.putString("mServerHost", mServerHost); 52 | SPUtils.putString("mServerPort", mServerPort); 53 | SPUtils.putString("ConnectionCode", ConnectionCode); 54 | SPUtils.putBoolean("enableHttps", enableHttps); 55 | SPUtils.putString("webSocketPort", webSocketPort); 56 | SPUtils.putString("webSocketCode", webSocketCode); 57 | } 58 | 59 | public void readConfig() { 60 | mServerHost = SPUtils.getString("mServerHost", "192.168.1.1"); 61 | mServerPort = SPUtils.getString("mServerPort", "666"); 62 | ConnectionCode = SPUtils.getString("ConnectionCode", "quicker"); 63 | enableHttps = SPUtils.getBoolean("enableHttps", true); 64 | webSocketPort = SPUtils.getString("webSocketPort", "668"); 65 | webSocketCode = SPUtils.getString("webSocketCode", "quicker"); 66 | } 67 | 68 | /** 69 | * 生成合适的WebSocket连接地址 70 | * wss://192-168-1-1.lan.quicker.cc:668/ws 71 | */ 72 | public String applyAddress() { 73 | if (TextUtils.isEmpty(mServerHost)) return ""; 74 | StringBuilder sb; 75 | if (enableHttps) { 76 | sb = new StringBuilder("wss://"); 77 | sb.append(mServerHost.replace(".", "-")); 78 | sb.append(".lan.quicker.cc"); 79 | } else { 80 | sb = new StringBuilder("ws://"); 81 | sb.append(mServerHost); 82 | } 83 | sb.append(":"); 84 | sb.append(webSocketPort); 85 | sb.append("/ws"); 86 | KLog.d("ClientConfig", "请求连接地址:" + sb); 87 | return sb.toString(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/client/ConnectionStatus.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.client; 2 | 3 | 4 | /** 5 | * PC 连接状态 6 | */ 7 | public enum ConnectionStatus { 8 | Disconnected, //未连接 9 | Connecting, //连接中 10 | Connected, //已连接 11 | CONNECT_FAIL,//连接失败 12 | CLOSE, //连接关闭 13 | LoggedIn, // 已登录 14 | LoginFailed, // 登录失败 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/client/MessageCache.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.client; 2 | 3 | import cuiliang.quicker.messages.recv.UpdateButtonsMessage; 4 | import cuiliang.quicker.messages.recv.VolumeStateMessage; 5 | 6 | /** 7 | * 应用状态 8 | */ 9 | public class MessageCache { 10 | // public SparseArray buttons = new SparseArray(); 11 | // 12 | // public boolean mute; 13 | // 14 | // public int masterVolume; 15 | // 16 | // public String profileName; 17 | 18 | public UpdateButtonsMessage lastUpdateButtonsMessage = null; 19 | 20 | public VolumeStateMessage lastVolumeStateMessage = null; 21 | 22 | // public void processUpdateButtonsMessage(UpdateButtonsMessage msg){ 23 | // this.lastUpdateButtonsMessage = msg; 24 | // } 25 | // 26 | // public void processVolumeStateMessage(VolumeStateMessage msg){ 27 | // this.lastVolumeStateMessage = msg; 28 | // } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/client/NetworkStatusChangeReceiver.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.client 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.NetworkInfo 7 | import android.net.wifi.WifiManager 8 | import android.os.Build 9 | import android.os.Parcelable 10 | import android.util.Log 11 | 12 | @Suppress("DEPRECATION") 13 | class NetworkStatusChangeReceiver(private val callback: NetChangeCallback) : BroadcastReceiver() { 14 | override fun onReceive(context: Context, intent: Intent) { 15 | // ref:https://blog.csdn.net/qq_20785431/article/details/51520459 16 | // 监听wifi的连接状态即是否连上了一个有效无线路由 17 | if (WifiManager.NETWORK_STATE_CHANGED_ACTION != intent.action) return 18 | val parcelableExtra = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 19 | intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO, NetworkInfo::class.java) 20 | } else { 21 | intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) 22 | } 23 | val isConnected = parcelableExtra?.let { 24 | //判断网络是否已经连接 25 | (it as NetworkInfo).state == NetworkInfo.State.CONNECTED 26 | } ?: false 27 | Log.e("NetworkStatusChangeReceiver", "wifi连接:$isConnected") 28 | callback.onChange(isConnected) 29 | } 30 | } 31 | 32 | interface NetChangeCallback { 33 | fun onChange(connected: Boolean) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/client/QuickerServiceHandler.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.client 2 | 3 | import androidx.collection.arraySetOf 4 | import cuiliang.quicker.messages.MessageBase 5 | import cuiliang.quicker.util.KLog 6 | import org.apache.mina.core.service.IoHandlerAdapter 7 | import org.apache.mina.core.session.IdleStatus 8 | import org.apache.mina.core.session.IoSession 9 | 10 | /** 11 | * Created by voidcom on 2023/10/23 12 | * 13 | */ 14 | class QuickerServiceHandler private constructor() : IoHandlerAdapter() { 15 | private val listeners = arraySetOf() 16 | 17 | override fun exceptionCaught(session: IoSession?, cause: Throwable?) { 18 | super.exceptionCaught(session, cause) 19 | KLog.e(TAG, "异常,重连网络") 20 | } 21 | 22 | override fun messageReceived(session: IoSession, message: Any) { 23 | super.messageReceived(session, message) 24 | KLog.e(TAG, "接收到服务器端消息:$message") 25 | listeners.forEach { it.onMessage(message as MessageBase) } 26 | } 27 | 28 | override fun messageSent(session: IoSession?, message: Any?) { 29 | super.messageSent(session, message) 30 | KLog.d(TAG, "客户端调用messageSent") 31 | // session?.close(true);//加上这句话实现短连接的效果,向客户端成功发送数据后断开连接 32 | } 33 | 34 | override fun sessionCreated(session: IoSession?) { 35 | super.sessionCreated(session) 36 | KLog.d(TAG,"客户端调用sessionCreated") 37 | } 38 | 39 | override fun sessionIdle(session: IoSession?, status: IdleStatus?) { 40 | super.sessionIdle(session, status) 41 | KLog.d(TAG,"客户端调用sessionIdle") 42 | } 43 | 44 | override fun sessionOpened(session: IoSession?) { 45 | super.sessionOpened(session) 46 | KLog.d(TAG,"客户端调用sessionOpened") 47 | } 48 | 49 | override fun sessionClosed(session: IoSession?) { 50 | super.sessionClosed(session) 51 | KLog.d(TAG, "客户端调用sessionClosed-isClosing=${session?.isClosing}") 52 | listeners.forEach { it.onClose() } 53 | } 54 | 55 | override fun inputClosed(session: IoSession?) { 56 | super.inputClosed(session) 57 | KLog.d(TAG, ":inputClosed") 58 | listeners.forEach { it.onClose() } 59 | } 60 | 61 | fun addListener(listener: QuickerServiceListener) { 62 | listeners.add(listener) 63 | } 64 | 65 | fun removeListener(listener: QuickerServiceListener) { 66 | listeners.remove(listener) 67 | } 68 | 69 | companion object { 70 | const val TAG = "QuickerServiceHandler" 71 | val instant: QuickerServiceHandler by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { QuickerServiceHandler() } 72 | } 73 | } 74 | 75 | open class QuickerServiceListener { 76 | open fun onMessage(msg: MessageBase){} 77 | open fun onClose(){} 78 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/events/ConnectionStatusChangedEvent.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.events; 2 | 3 | import cuiliang.quicker.client.ConnectionStatus; 4 | 5 | /** 6 | * 到pc的连接状态改变了 7 | */ 8 | public class ConnectionStatusChangedEvent { 9 | 10 | public ConnectionStatus status; 11 | 12 | public String message; 13 | 14 | public ConnectionStatusChangedEvent(ConnectionStatus status, String message){ 15 | this.status = status; 16 | this.message = message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/events/SessionClosedEvent.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.events; 2 | 3 | /** 4 | * 连接关闭事件 5 | */ 6 | public class SessionClosedEvent { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/MessageBase.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages; 2 | 3 | public interface MessageBase { 4 | int getMessageType(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/recv/LoginStateMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.recv; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | // 登录状态消息 6 | public class LoginStateMessage implements MessageBase { 7 | public static final int MessageType = 201; 8 | 9 | public Boolean IsLoggedIn; 10 | 11 | public int ErrorCode; 12 | 13 | public String ErrorMessage; 14 | 15 | 16 | @Override 17 | public int getMessageType() { 18 | return MessageType; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/recv/UpdateButtonsMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.recv; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class UpdateButtonsMessage implements MessageBase { 6 | public static final int MessageType = 1; 7 | 8 | public String ProfileName; 9 | 10 | /// 全局面板数量 11 | public int GlobalPageCount; 12 | 13 | /// 14 | /// 当前全局面板编号 15 | /// 16 | public int GlobalPageIndex; 17 | 18 | /// 19 | /// 上下文面板数量 20 | /// 21 | public int ContextPageCount; 22 | 23 | /// 24 | /// 当前上下文面板编号 25 | /// 26 | public int ContextPageIndex; 27 | 28 | /// 29 | /// 上下文面板切换是否锁定 30 | /// 31 | public boolean IsContextPanelLocked; 32 | 33 | // 34 | public ButtonItem[] Buttons; 35 | 36 | @Override 37 | public int getMessageType() { 38 | return MessageType; 39 | } 40 | 41 | /// 42 | /// 要更新的每个按钮的信息 43 | /// 44 | public class ButtonItem 45 | { 46 | 47 | 48 | /// 49 | /// 编号 50 | /// 51 | public int Index; 52 | 53 | /// 54 | /// 是否启用 55 | /// 56 | public boolean IsEnabled; 57 | 58 | /// 59 | /// 按钮文字标签 60 | /// 61 | public String Label; 62 | 63 | /// 64 | /// 图标文件名 65 | /// 66 | public String IconFileName; 67 | 68 | /// 69 | /// base64编码的图标文件内容 70 | /// 71 | public String IconFileContent; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/recv/VolumeStateMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.recv; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class VolumeStateMessage implements MessageBase { 6 | public static final int MessageType = 2; 7 | 8 | public boolean Mute; 9 | 10 | public int MasterVolume; 11 | 12 | @Override 13 | public int getMessageType() { 14 | return MessageType; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/ButtonClickedMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class ButtonClickedMessage implements MessageBase { 6 | public static final int MessageType = 101; 7 | public int ButtonIndex; 8 | 9 | @Override 10 | public int getMessageType() { 11 | return MessageType; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/CommandMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | /** 6 | * 通用命令消息 7 | */ 8 | public class CommandMessage implements MessageBase { 9 | public static final int MessageType = 110; 10 | 11 | public static final String OPEN_MAINWIN = "OPEN_MAINWIN"; 12 | public static final String START_VOICE_INPUT = "START_VOICE_INPUT"; 13 | public static final String RESEND_STATE = "RESEND_STATE"; // 客户端请求pc重新发送状态(按钮、音量等) 14 | 15 | public static final String LOCK_PANEL = "LOCK_PANEL"; //锁定/解锁面板 16 | 17 | public static final String CHANGE_PAGE = "CHANGE_PAGE"; //面板翻页; 18 | 19 | 20 | public static final String DATA_PAGE_GLOBAL_LEFT = "DATA_GLOBAL_LEFT"; //全局面板向左翻页; 21 | public static final String DATA_PAGE_GLOBAL_RIGHT = "DATA_GLOBAL_RIGHT"; //全局面板向右翻页; 22 | public static final String DATA_PAGE_CONTEXT_LEFT = "DATA_CONTEXT_LEFT"; //上下文面板向左翻页; 23 | public static final String DATA_PAGE_CONTEXT_RIGHT = "DATA_CONTEXT_RIGHT"; //上下文面板向右翻页; 24 | 25 | public String Command; 26 | 27 | public String Data; 28 | 29 | @Override 30 | public int getMessageType() { 31 | return MessageType; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/DeviceLoginMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | /** 6 | * 通用命令消息 7 | */ 8 | public class DeviceLoginMessage implements MessageBase { 9 | public static final int MessageType = 200; 10 | 11 | public static final String OPEN_MAINWIN = "OPEN_MAINWIN"; 12 | 13 | 14 | /// 15 | /// 连接验证码,防止误连接 16 | /// 17 | public String ConnectionCode; 18 | 19 | /// 20 | /// 客户端版本 21 | /// 22 | public String Version; 23 | 24 | /// 25 | /// 设备名称 26 | /// 27 | public String DeviceName; 28 | 29 | @Override 30 | public int getMessageType() { 31 | return MessageType; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/PhotoMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class PhotoMessage implements MessageBase { 6 | public static final int MessageType = 105; 7 | 8 | public String FileName ; 9 | 10 | public String Data; 11 | 12 | @Override 13 | public int getMessageType() { 14 | return MessageType; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/TextDataMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | 6 | 7 | public class TextDataMessage implements MessageBase { 8 | 9 | // 文本内容:二维码 10 | public static final int TYPE_QRCODE = 1; 11 | 12 | // 文本内容:语音识别 13 | public static final int TYPE_VOICE_RECOGNITION = 2; 14 | 15 | public static final int MessageType = 104; 16 | 17 | public int DataType ; 18 | 19 | public String Data; 20 | 21 | @Override 22 | public int getMessageType() { 23 | return MessageType; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/ToggleMuteMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class ToggleMuteMessage implements MessageBase { 6 | public static final int MessageType = 102; 7 | 8 | @Override 9 | public int getMessageType() { 10 | return MessageType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/messages/send/UpdateVolumeMessage.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.messages.send; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | 5 | public class UpdateVolumeMessage implements MessageBase { 6 | public static final int MessageType = 103; 7 | 8 | public int MasterVolume; 9 | 10 | @Override 11 | public int getMessageType() { 12 | return MessageType; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/ConnectServiceCallback.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | /** 6 | * Created by Void on 2020/3/29 14:43 7 | * 连接服务回调 8 | */ 9 | public interface ConnectServiceCallback { 10 | 11 | /** 12 | * 连接pc服务回调 13 | * @param isSuccess 连接状态 14 | * @param obj 状态相关信息,可为空! 15 | */ 16 | void connectCallback(boolean isSuccess,@Nullable Object obj); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/MyCodecFactory.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network; 2 | 3 | import org.apache.mina.core.session.IoSession; 4 | import org.apache.mina.filter.codec.ProtocolCodecFactory; 5 | import org.apache.mina.filter.codec.ProtocolDecoder; 6 | import org.apache.mina.filter.codec.ProtocolEncoder; 7 | 8 | public class MyCodecFactory implements ProtocolCodecFactory { 9 | private MyDataDecoder decoder; 10 | private MyDataEncoder encoder; 11 | public MyCodecFactory() { 12 | encoder = new MyDataEncoder(); 13 | decoder = new MyDataDecoder(); 14 | } 15 | @Override 16 | public ProtocolDecoder getDecoder(IoSession session) throws Exception { 17 | return decoder; 18 | } 19 | @Override 20 | public ProtocolEncoder getEncoder(IoSession session) throws Exception { 21 | return encoder; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/MyDataDecoder.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network; 2 | 3 | import android.util.Log; 4 | 5 | import cuiliang.quicker.messages.recv.LoginStateMessage; 6 | import cuiliang.quicker.messages.send.ButtonClickedMessage; 7 | import cuiliang.quicker.messages.MessageBase; 8 | import cuiliang.quicker.messages.recv.UpdateButtonsMessage; 9 | import cuiliang.quicker.messages.recv.VolumeStateMessage; 10 | import cuiliang.quicker.messages.send.CommandMessage; 11 | 12 | import com.google.gson.Gson; 13 | 14 | import org.apache.mina.core.buffer.IoBuffer; 15 | import org.apache.mina.core.session.IoSession; 16 | import org.apache.mina.filter.codec.CumulativeProtocolDecoder; 17 | import org.apache.mina.filter.codec.ProtocolDecoderOutput; 18 | 19 | import java.nio.charset.Charset; 20 | 21 | public class MyDataDecoder extends CumulativeProtocolDecoder { 22 | private static final String TAG = MyDataDecoder.class.getSimpleName(); 23 | /** 24 | * 返回值含义: 25 | * 1、当内容刚好时,返回false,告知父类接收下一批内容 26 | * 2、内容不够时需要下一批发过来的内容,此时返回false,这样父类 CumulativeProtocolDecoder 27 | * 会将内容放进IoSession中,等下次来数据后就自动拼装再交给本类的doDecode 28 | * 3、当内容多时,返回true,因为需要再将本批数据进行读取,父类会将剩余的数据再次推送本类的doDecode方法 29 | */ 30 | @Override 31 | public boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) 32 | throws Exception { 33 | // Log.d(TAG, "解码消息... len = " + in.remaining()); 34 | 35 | /** 36 | * 假定消息格式为:消息头(int类型:表示消息体的长度、short类型:表示事件号)+消息体 37 | */ 38 | if (in.remaining() < 12) 39 | { 40 | return false; 41 | } 42 | 43 | //以便后继的reset操作能恢复position位置 44 | in.mark(); 45 | 46 | int headFlag = in.getInt(); 47 | int msgType = in.getInt(); 48 | int msgLength = in.getInt(); 49 | 50 | if ( in.remaining() >= (msgLength + 4) ) { 51 | String msgJson = in.getString(msgLength, Charset.forName("UTF-8").newDecoder()); 52 | int end = in.getInt(); 53 | 54 | MessageBase msg = deserializeMsg(msgType, msgJson); 55 | out.write(msg); 56 | 57 | if (in.hasRemaining()) { 58 | return true; 59 | }else { 60 | return false; 61 | } 62 | }else { 63 | in.reset(); 64 | 65 | // 消息不完整 66 | return false; 67 | } 68 | 69 | 70 | } 71 | 72 | private MessageBase deserializeMsg(int msgType, String content){ 73 | Gson gson = new Gson(); 74 | switch(msgType) { 75 | case UpdateButtonsMessage.MessageType: 76 | // update buttons 77 | return gson.fromJson(content, UpdateButtonsMessage.class); 78 | case ButtonClickedMessage.MessageType: 79 | return gson.fromJson(content, ButtonClickedMessage.class); 80 | case VolumeStateMessage.MessageType: 81 | return gson.fromJson(content, VolumeStateMessage.class); 82 | case LoginStateMessage.MessageType: 83 | return gson.fromJson(content, LoginStateMessage.class); 84 | case CommandMessage.MessageType: 85 | return gson.fromJson(content, CommandMessage.class); 86 | default: 87 | Log.e(TAG, "不支持的消息类型:" + msgType); 88 | 89 | } 90 | return null; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/MyDataEncoder.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network; 2 | 3 | import cuiliang.quicker.messages.MessageBase; 4 | import com.google.gson.Gson; 5 | 6 | import org.apache.mina.core.buffer.IoBuffer; 7 | import org.apache.mina.core.session.IoSession; 8 | import org.apache.mina.filter.codec.ProtocolEncoderAdapter; 9 | import org.apache.mina.filter.codec.ProtocolEncoderOutput; 10 | 11 | /** 12 | * 编码器将数据直接发出去(不做处理) 13 | */ 14 | public class MyDataEncoder extends ProtocolEncoderAdapter { 15 | 16 | @Override 17 | public void encode(IoSession session, Object message, 18 | ProtocolEncoderOutput out) throws Exception { 19 | if (message instanceof MessageBase) { 20 | Gson gson = new Gson(); 21 | String msgJson = gson.toJson(message); 22 | byte[] msgBytes = msgJson.getBytes("utf-8"); 23 | 24 | IoBuffer buffer = IoBuffer.allocate(msgBytes.length + 16); 25 | buffer.putInt(0xFFFFFFFF); 26 | buffer.putInt(((MessageBase) message).getMessageType()); 27 | buffer.putInt(msgBytes.length); 28 | buffer.put(msgBytes); 29 | buffer.putInt(0); 30 | buffer.flip(); 31 | 32 | out.write(buffer); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/NetRequestObj.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network 2 | 3 | /** 4 | * Created by Void on 2019/7/15 09:38 5 | * 网络请求对象 6 | * @param url 请求地址 7 | * @param requestCallback 结果回调,可为空 8 | */ 9 | class NetRequestObj(var url: String, var requestCallback: NetWorkManager.RequestCallback?) { 10 | private var data = HashMap() 11 | private var requestHeader = HashMap() 12 | 13 | init { 14 | //默认参数格式 15 | addHeader("Content-Type", "application/json") 16 | } 17 | 18 | /** 19 | * 请求参数是否进行HTML编码 20 | */ 21 | var isEncode = false 22 | 23 | fun getData() = data 24 | 25 | fun getRequestHeader() = requestHeader 26 | 27 | fun addBody(keyStr: String, valueStr: String): NetRequestObj { 28 | data[keyStr] = valueStr 29 | return this 30 | } 31 | 32 | fun addHeader(keyStr: String, valueStr: String): NetRequestObj { 33 | requestHeader[keyStr] = valueStr 34 | return this 35 | } 36 | 37 | fun setContentType(string: String): NetRequestObj { 38 | removeHeader("Content-Type") 39 | addHeader("Content-Type", string) 40 | return this 41 | } 42 | 43 | fun addAllBody(items: Map) { 44 | data.putAll(items) 45 | } 46 | 47 | fun addAllHeader(items: Map) { 48 | requestHeader.putAll(items) 49 | } 50 | 51 | fun removeBody(keyStr: String) { 52 | data.remove(keyStr) 53 | } 54 | 55 | fun removeHeader(keyStr: String) { 56 | data.remove(keyStr) 57 | } 58 | 59 | fun removeAllData() { 60 | data.clear() 61 | } 62 | 63 | fun removeAllHeader() { 64 | requestHeader.clear() 65 | } 66 | 67 | fun sizeBody() = data.size 68 | 69 | fun sizeHeader() = requestHeader.size 70 | 71 | fun containsKeyBody(keyStr: String) = data.containsKey(keyStr) 72 | 73 | fun containsKeyHeader(keyStr: String) = requestHeader.containsKey(keyStr) 74 | 75 | override fun toString(): String { 76 | var output = "\n url:\"$url\"; \n" 77 | for (s in data) 78 | output += s.key + ":" + s.value + "; \n" 79 | return output 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/shareToPc/ShareApi.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.shareToPc 2 | 3 | /** 4 | * Created by Void on 2020/4/12 12:19 5 | */ 6 | object ShareApi { 7 | //域名 8 | private const val baseUrl = "https://push.getquicker.cn" 9 | //分享接口(get、post都是用这个接口,详情请查看文档)。https://www.getquicker.net/KC/Help/Doc/connection 10 | const val shareUrl = "$baseUrl/to/quicker" 11 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/ConnectListener.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | import cuiliang.quicker.client.ConnectionStatus 4 | 5 | /** 6 | * Created by Voidcom on 2023/9/20 22:55 7 | * TODO 8 | */ 9 | interface ConnectListener { 10 | fun onStatus(status: ConnectionStatus) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/MessageType.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | /** 4 | * Created by Voidcom on 2023/9/11 21:35 5 | * 6 | * Description: TODO 7 | */ 8 | enum class MessageType(private val i: Int) { 9 | //2:命令请求消息,用于发送操作指令和内容。 10 | REQUEST_COMMAND(2), 11 | //5:身份验证请求,客户端发送验证码。 12 | REQUEST_AUTH(5), 13 | 14 | //4:命令响应消息,返回指令操作结果。 15 | RESPONSE_COMMAND(4), 16 | //6:身份验证响应,返回密码是否正确。 17 | RESPONSE_AUTH(6); 18 | 19 | fun getValue(): Int = i 20 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/MsgRequestData.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | import cuiliang.quicker.util.GsonUtils 4 | 5 | /** 6 | * Created by Voidcom on 2023/9/11 17:57 7 | * { 8 | * messageType: 消息类型常量, 9 | * serial: 消息编号, 10 | * operation: '操作类型,如copy将data参数内容写入剪贴板', 11 | * data: '数据,为文本,也可能为对象', 12 | * action: '操作类型为action时指定执行的动作名称或id', 13 | * extData: '可选的额外数据,文本或对象', 14 | * wait: 是否等待操作返回结果 15 | * } 16 | 17 | * operation(可选):操作类型,默认为 copy 。可选值: 18 | * copy 将内容复制到剪贴板 19 | * paste 将内容粘贴到当前窗口 20 | * action 运行动作。此时通过“action”参数传入动作名称或ID,通过“data”参数传入动作参数(可选)。 21 | * open 打开网址。此时通过data参数传入要打开的网址。 22 | * input sendkeys 模拟输入内容。此时通过data传入“模拟按键B”语法格式的内容。(1.27.3+版本请使用sendkeys) 23 | * inputtext 模拟输入文本(原样输入)。 24 | * inputscript 多步骤输入。组合多个键盘和鼠标输入步骤,参考文档。 (1.28.16+) 25 | * downloadfile 下载文件。下载data参数中给定的文件网址(单个)。 (1.28.16+) 26 | * 支持sendfile(传送文件)、pasteimage(粘贴图片到当前窗口) 27 | * 28 | * 对应于消息中messageType参数的取值。 29 | * 2:命令请求消息,用于发送操作指令和内容。 30 | * 4:命令响应消息,返回指令操作结果。 31 | * 5:身份验证请求,客户端发送验证码。 32 | * 6:身份验证响应,返回密码是否正确。 33 | * 34 | * Description: 常规请求消息 35 | */ 36 | class MsgRequestData(v: Int) { 37 | val serial:Int = v 38 | var messageType: Int = 0 39 | var operation: String = "" 40 | var data: String = "" 41 | var action: String = "" 42 | var extData: String = "" 43 | var wait: Boolean = false 44 | 45 | fun setData( 46 | type: Int, 47 | operation: String = "", 48 | data: String = "", 49 | action: String = "", 50 | extData: String = "", 51 | wait: Boolean = false 52 | ): MsgRequestData { 53 | this.messageType = type 54 | this.operation = operation 55 | this.data = data 56 | this.action = action 57 | this.extData = extData 58 | this.wait = wait 59 | return this 60 | } 61 | 62 | 63 | override fun toString(): String = GsonUtils.toString(this) 64 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/MsgResponseData.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | import cuiliang.quicker.util.GsonUtils 4 | 5 | 6 | /** 7 | * Created by Voidcom on 2023/9/11 17:57 8 | * https://getquicker.net/KC/Manual/Doc/websocketservice 9 | * 常规响应消息: 10 | * { 11 | * messageType: 消息类型常量, 12 | * serial: 消息编号, 13 | * replyTo: 响应的请求消息编号, 14 | * isSuccess: 操作是否成功, 15 | * message: 操作失败时的提示消息, 16 | * data: 可选的返回数据(文本或对象), 17 | * extData: 可选的额外返回数据(文本或对象) 18 | * } 19 | * 20 | * 对应于消息中messageType参数的取值。 21 | * 2:命令请求消息,用于发送操作指令和内容。 22 | * 4:命令响应消息,返回指令操作结果。 23 | * 5:身份验证请求,客户端发送验证码。 24 | * 6:身份验证响应,返回密码是否正确。 25 | * 26 | * Description: 常规响应消息 27 | */ 28 | data class MsgResponseData( 29 | val messageType: Int, 30 | val replyTo: Int = -1, 31 | val isSuccess: Boolean, 32 | val serial: Int = 0, 33 | val message: String = "", 34 | val data: String = "", 35 | val extData: String = "" 36 | ) { 37 | override fun toString(): String = GsonUtils.toString(this) 38 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/ServiceRequestFactory.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.net.Uri 8 | import android.os.Handler 9 | import android.os.Looper 10 | import cuiliang.quicker.util.KLog 11 | import cuiliang.quicker.util.ToastUtils 12 | 13 | /** 14 | * Created by voidcom on 2023/10/13 15 | * 用于解析PC请求 16 | * @see MsgRequestData 17 | */ 18 | object ServiceRequestFactory { 19 | private val mHandler=Handler(Looper.getMainLooper()) 20 | fun decodeRequest(ctx: Context, data: MsgRequestData) { 21 | when (data.extData) { 22 | "ShareAndroid" -> { 23 | when (data.operation) { 24 | "copy" -> { 25 | (ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(ClipData.newPlainText(data.data, data.data)) 26 | mHandler.post { 27 | ToastUtils.showShort(ctx,"剪贴板增加: ${data.data}") 28 | } 29 | } 30 | 31 | "open" -> if (data.data.startsWith("http:") || data.data.startsWith("https:")) { 32 | ctx.startActivity( 33 | Intent( 34 | Intent.ACTION_VIEW, 35 | Uri.parse(data.data) 36 | ).apply { 37 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 38 | } 39 | ) 40 | mHandler.post { 41 | ToastUtils.showShort(ctx,"打开网页:${data.data}") 42 | } 43 | KLog.d("decodeRequest", "打开网页:${data.data}") 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/network/websocket/WebSocketNetListener.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.network.websocket 2 | 3 | open class WebSocketNetListener { 4 | open fun onRequest(data: MsgRequestData): MsgRequestData = data 5 | 6 | open fun onResponse(data: MsgResponseData) {} 7 | 8 | open fun onFail(error: String) {} 9 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/service/TaskManagerService.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.service 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.Binder 6 | import android.os.IBinder 7 | import cuiliang.quicker.network.websocket.MsgRequestData 8 | import cuiliang.quicker.network.websocket.ServiceRequestFactory 9 | import cuiliang.quicker.network.websocket.WebSocketClient 10 | import cuiliang.quicker.ui.taskManager.TaskList 11 | import cuiliang.quicker.util.KLog 12 | import java.util.concurrent.ExecutorService 13 | import java.util.concurrent.Executors 14 | 15 | /** 16 | * Created by Voidcom on 2023/9/20 22:51 17 | * 用于执行任务的服务 18 | */ 19 | class TaskManagerService : Service() { 20 | private lateinit var taskList: TaskList 21 | private val mBinder: TaskManagerBinder by lazy { TaskManagerBinder() } 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | taskList = TaskList(applicationContext) 26 | taskList.readFileConfig() 27 | } 28 | 29 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 30 | WebSocketClient.instance().readyExecuteAction = executeAction 31 | return super.onStartCommand(intent, flags, startId) 32 | } 33 | 34 | override fun onDestroy() { 35 | super.onDestroy() 36 | taskList.clear() 37 | 38 | WebSocketClient.instance().readyExecuteAction = null 39 | } 40 | 41 | override fun onBind(intent: Intent?): IBinder { 42 | intent?.let { 43 | KLog.d(TAG, "onBind: " + it.component?.className) 44 | } 45 | return mBinder 46 | } 47 | 48 | private val executeAction = object : (MsgRequestData) -> Unit { 49 | override fun invoke(p1: MsgRequestData) { 50 | ServiceRequestFactory.decodeRequest(applicationContext, p1) 51 | } 52 | } 53 | 54 | inner class TaskManagerBinder : Binder() { 55 | fun getTaskList(): TaskList = taskList 56 | } 57 | 58 | companion object { 59 | const val TAG = "TaskManagerService" 60 | val threadPool: ExecutorService = Executors.newScheduledThreadPool(2) 61 | // val aa 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/svg/SvgDecoder.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.svg 2 | 3 | import com.bumptech.glide.load.Options 4 | import com.bumptech.glide.load.ResourceDecoder 5 | import com.bumptech.glide.load.engine.Resource 6 | import com.bumptech.glide.load.resource.SimpleResource 7 | import com.bumptech.glide.request.target.Target 8 | import com.caverock.androidsvg.SVG 9 | import com.caverock.androidsvg.SVGParseException 10 | import java.io.IOException 11 | import java.io.InputStream 12 | 13 | /** Decodes an SVG internal representation from an [InputStream]. */ 14 | class SvgDecoder : ResourceDecoder { 15 | 16 | override fun handles(source: InputStream, options: Options): Boolean = true 17 | 18 | @Throws(IOException::class) 19 | override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? { 20 | return try { 21 | val svg = SVG.getFromInputStream(source) 22 | if (width != Target.SIZE_ORIGINAL) { 23 | svg.documentWidth = width.toFloat() 24 | } 25 | if (height != Target.SIZE_ORIGINAL) { 26 | svg.documentHeight = height.toFloat() 27 | } 28 | SimpleResource(svg) 29 | } catch (ex: SVGParseException) { 30 | throw IOException("Cannot load SVG from stream", ex) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/svg/SvgDrawableTranscoder.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.svg 2 | 3 | import android.graphics.Picture 4 | import android.graphics.drawable.PictureDrawable 5 | import com.bumptech.glide.load.Options 6 | import com.bumptech.glide.load.engine.Resource 7 | import com.bumptech.glide.load.resource.SimpleResource 8 | import com.bumptech.glide.load.resource.transcode.ResourceTranscoder 9 | import com.caverock.androidsvg.SVG 10 | 11 | /** 12 | * Convert the [SVG]'s internal representation to an Android-compatible one ([Picture]). 13 | */ 14 | class SvgDrawableTranscoder : ResourceTranscoder { 15 | 16 | override fun transcode(toTranscode: Resource, options: Options): Resource { 17 | val svg = toTranscode.get() 18 | val picture = svg.renderToPicture() 19 | val drawable = PictureDrawable(picture) 20 | return SimpleResource(drawable) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/svg/SvgModule.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.svg 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.PictureDrawable 5 | import com.bumptech.glide.Glide 6 | import com.bumptech.glide.GlideBuilder 7 | import com.bumptech.glide.Registry 8 | import com.bumptech.glide.annotation.GlideModule 9 | import com.bumptech.glide.load.DecodeFormat 10 | import com.bumptech.glide.load.engine.DiskCacheStrategy 11 | import com.bumptech.glide.module.AppGlideModule 12 | import com.bumptech.glide.request.RequestOptions 13 | import com.caverock.androidsvg.SVG 14 | import java.io.InputStream 15 | 16 | /** 17 | * 注册SVG资源支持 18 | * */ 19 | @GlideModule 20 | class SvgModule : AppGlideModule() { 21 | override fun applyOptions(context: Context, builder: GlideBuilder) { 22 | super.applyOptions(context, builder) 23 | //设置默认请求选项 24 | builder.setDefaultRequestOptions( 25 | RequestOptions() 26 | // .diskCacheStrategy(DiskCacheStrategy.ALL) 27 | .format(DecodeFormat.PREFER_RGB_565) 28 | ) 29 | } 30 | 31 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) { 32 | registry.register(SVG::class.java, PictureDrawable::class.java, SvgDrawableTranscoder()) 33 | .append(InputStream::class.java, SVG::class.java, SvgDecoder()) 34 | } 35 | 36 | // Disable manifest parsing to avoid adding similar modules twice. 37 | override fun isManifestParsingEnabled(): Boolean = false 38 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/svg/SvgSoftwareLayerSetter.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.svg; 2 | 3 | import android.graphics.drawable.PictureDrawable; 4 | import android.widget.ImageView; 5 | import com.bumptech.glide.load.DataSource; 6 | import com.bumptech.glide.load.engine.GlideException; 7 | import com.bumptech.glide.request.RequestListener; 8 | import com.bumptech.glide.request.target.ImageViewTarget; 9 | import com.bumptech.glide.request.target.Target; 10 | 11 | /** 12 | * Listener which updates the {@link ImageView} to be software rendered, because {@link 13 | * com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on a 14 | * hardware backed {@link android.graphics.Canvas Canvas}. 15 | */ 16 | public class SvgSoftwareLayerSetter implements RequestListener { 17 | 18 | @Override 19 | public boolean onLoadFailed( 20 | GlideException e, Object model, Target target, boolean isFirstResource) { 21 | ImageView view = ((ImageViewTarget) target).getView(); 22 | view.setLayerType(ImageView.LAYER_TYPE_NONE, null); 23 | return false; 24 | } 25 | 26 | @Override 27 | public boolean onResourceReady( 28 | PictureDrawable resource, 29 | Object model, 30 | Target target, 31 | DataSource dataSource, 32 | boolean isFirstResource) { 33 | ImageView view = ((ImageViewTarget) target).getView(); 34 | view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/BaseEventOrAction.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AlertDialog 5 | 6 | /** 7 | * Created by Voidcom on 2023/9/19 23:04 8 | * 任务接口 9 | */ 10 | interface BaseEventOrAction { 11 | 12 | fun getIcon(): Int 13 | 14 | fun getName(): String 15 | fun getType(): TaskType 16 | 17 | /** 18 | * 点这个接口的实体类被选择后应该显示的文本内容。 19 | */ 20 | fun resultStr():String 21 | 22 | /** 23 | * 事件弹窗 24 | * 在添加事件时,事件被点击弹出的窗口 25 | * 用于显示该事件支持内容 26 | * 举例: 27 | * 电量状态——显示列表,电量低于xx会触发这个事件 28 | * @param callback 弹窗内容操作完成的回调,数据用bundle携带。 29 | */ 30 | fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) 31 | 32 | fun init(context: Context) 33 | 34 | fun release(context: Context) 35 | 36 | fun getDialogBuilder(context: Context): AlertDialog.Builder = 37 | AlertDialog.Builder(context) 38 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/JsonTask.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager 2 | 3 | import cuiliang.quicker.taskManager.action.Action 4 | import cuiliang.quicker.taskManager.event.Event 5 | import cuiliang.quicker.util.GsonUtils 6 | 7 | /** 8 | * Created by Voidcom on 2023/9/29 20:20 9 | * 描述如何将 @see [Task] 序列化和反序列化 10 | * 11 | * json:{"events":[{"eventState":true,"resultStr":"电量低于20%"}],"icon":0,"isEnable":true,"name":"充电通知","taskActions":[{"resultStr":"快没电了!"}]} 12 | */ 13 | class JsonTask(task: Task) { 14 | var icon: Int = 0 15 | var isEnable: Boolean = false 16 | var name: String = "" 17 | val events: ArrayList = arrayListOf() 18 | val actions: ArrayList = arrayListOf() 19 | 20 | init { 21 | icon = task.icon 22 | isEnable = task.getIsEnable() 23 | name = task.name 24 | task.events.forEach { 25 | events.add(JsonEvent(it)) 26 | } 27 | task.taskActions.forEach { 28 | actions.add(JsonAction(it)) 29 | } 30 | } 31 | 32 | override fun toString(): String = GsonUtils.toString(this) 33 | 34 | fun toTask(): Task { 35 | val task = Task(isEnable) 36 | task.icon = icon 37 | task.name = name 38 | events.forEach { 39 | task.events.add(it.toEvent()) 40 | } 41 | actions.forEach { 42 | task.taskActions.add(it.toAction()) 43 | } 44 | return task 45 | } 46 | 47 | companion object { 48 | fun jsonToTask(json: String): JsonTask = GsonUtils.toBean(json, JsonTask::class.java) 49 | } 50 | } 51 | 52 | /** 53 | * @see cuiliang.quicker.taskManager.event.Event 54 | */ 55 | data class JsonEvent(val type: TaskEventType, val eventState: Boolean, val resultStr: String) { 56 | constructor(event: Event) : this( 57 | event.getType() as TaskEventType, 58 | event.eventState, 59 | event.description 60 | ) 61 | 62 | override fun toString(): String = GsonUtils.toString(this) 63 | 64 | fun toEvent(): Event { 65 | val factory = TaskDataFactory() 66 | return factory.getEvent(type).let { 67 | it.eventState = eventState 68 | it.description = resultStr 69 | it 70 | } 71 | } 72 | companion object{ 73 | fun jsonToEvent(json: String): JsonEvent = GsonUtils.toBean(json, JsonEvent::class.java) 74 | } 75 | } 76 | 77 | /** 78 | * @see cuiliang.quicker.taskManager.action.Action 79 | */ 80 | data class JsonAction(val type: TaskActionType, val resultStr: String) { 81 | constructor(action: Action) : this(action.getType() as TaskActionType, action.description) 82 | 83 | override fun toString(): String = GsonUtils.toString(this) 84 | 85 | fun toAction(): Action { 86 | val factory = TaskDataFactory() 87 | return factory.getAction(type).let { 88 | it.description = resultStr 89 | it 90 | } 91 | } 92 | 93 | companion object{ 94 | fun jsonToAction(json: String): JsonAction = GsonUtils.toBean(json, JsonAction::class.java) 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/Task.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager 2 | 3 | import android.content.Context 4 | import cuiliang.quicker.service.TaskManagerService 5 | import cuiliang.quicker.taskManager.action.Action 6 | import cuiliang.quicker.taskManager.event.Event 7 | import cuiliang.quicker.taskManager.event.EventRunningListener 8 | 9 | /** 10 | * Created by Voidcom on 2023/9/13 16:54 11 | * 定义任务数据结构。 12 | * 一个TaskData代表一个任务 13 | */ 14 | class Task(state: Boolean) : EventRunningListener { 15 | //任务名 16 | var name: String = "" 17 | 18 | var icon: Int = 0 19 | 20 | //是否启用 21 | private var isEnable: Boolean = state 22 | 23 | //事件列表 24 | val events: ArrayList = arrayListOf() 25 | 26 | //动作列表,当条件达成是执行的操作 27 | val taskActions: ArrayList = arrayListOf() 28 | 29 | 30 | /** 31 | * 事件运行监听; 32 | * 当一个事件条件达成时会执行这个接口,然后会检查事件列表其余事件,如果都达成了就执行动作列表 33 | */ 34 | override fun runningListener() { 35 | //判断是否全部触发条件都达成;如果是,tmp应该是一个空list 36 | val tmp = events.filter { 37 | !it.eventState 38 | } 39 | // KLog.d("Task:", "taskName:$name; tmpSize:${tmp.size}") 40 | if (tmp.isEmpty()) { 41 | taskActions.forEach { 42 | it.actionRunnable()?.run { 43 | TaskManagerService.threadPool.execute(this) 44 | } 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * 任务描述 51 | */ 52 | fun toDescription(): String { 53 | val builder = StringBuilder() 54 | events.forEach { 55 | builder.append(it.getName()) 56 | if (it.description.isNotEmpty()) 57 | builder.append(":").append(it.description) 58 | builder.append(", ") 59 | } 60 | builder.delete(builder.length - 2, builder.length).append(" | ") 61 | taskActions.forEach { 62 | builder.append(it.getName()) 63 | if (it.description.isNotEmpty()) 64 | builder.append(":").append(it.description) 65 | builder.append(", ") 66 | } 67 | builder.deleteCharAt(builder.length - 1) 68 | return builder.toString() 69 | } 70 | 71 | fun updateState(context: Context, b: Boolean) { 72 | if (isEnable == b) return 73 | if (b) { 74 | taskInit(context) 75 | } else { 76 | release(context) 77 | } 78 | isEnable = b 79 | } 80 | 81 | fun getIsEnable() = isEnable 82 | 83 | fun taskInit(context: Context) { 84 | events.forEach { 85 | it.listener = this 86 | it.init(context) 87 | } 88 | taskActions.forEach { it.init(context) } 89 | } 90 | 91 | fun release(context: Context) { 92 | events.forEach { it.release(context) } 93 | taskActions.forEach { it.release(context) } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/TaskDataFactory.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager 2 | 3 | import cuiliang.quicker.taskManager.action.Action 4 | import cuiliang.quicker.taskManager.action.ActionAdd 5 | import cuiliang.quicker.taskManager.action.ActionWebSocketMsg 6 | import cuiliang.quicker.taskManager.event.Event 7 | import cuiliang.quicker.taskManager.event.EventAdd 8 | import cuiliang.quicker.taskManager.event.EventBatteryStatus 9 | import cuiliang.quicker.taskManager.event.EventWebSocket 10 | 11 | /** 12 | * Created by voidcom on ${DATE} ${TIME} 13 | * @see TaskType 14 | * 15 | */ 16 | class TaskDataFactory { 17 | 18 | fun getEvent(key: TaskEventType): Event { 19 | return when (key) { 20 | TaskEventType.EVENT_ADD -> EventAdd() 21 | TaskEventType.EVENT_WEBSOCKET_CONNECTING -> EventWebSocket() 22 | TaskEventType.EVENT_BATTERY_STATUS -> EventBatteryStatus() 23 | } 24 | } 25 | 26 | fun getEvent(key: String): Event? { 27 | return when (key) { 28 | TaskEventType.EVENT_ADD.toString() -> EventAdd() 29 | TaskEventType.EVENT_WEBSOCKET_CONNECTING.toString() -> EventWebSocket() 30 | TaskEventType.EVENT_BATTERY_STATUS.toString() -> EventBatteryStatus() 31 | else -> null 32 | } 33 | } 34 | 35 | fun getAction(key: TaskActionType): Action { 36 | return when (key) { 37 | TaskActionType.ACTION_ADD -> ActionAdd() 38 | TaskActionType.ACTION_WEBSOCKET_MESSAGE -> ActionWebSocketMsg() 39 | } 40 | } 41 | 42 | fun getAction(key: String): Action? { 43 | return when (key) { 44 | TaskActionType.ACTION_ADD.toString() -> ActionAdd() 45 | TaskActionType.ACTION_WEBSOCKET_MESSAGE.toString() -> ActionWebSocketMsg() 46 | else -> null 47 | } 48 | } 49 | 50 | /** 51 | * 获取所有的事件 52 | */ 53 | fun getEvents(): List { 54 | return arrayListOf().apply { 55 | TaskEventType.values().forEach { 56 | if (it != TaskEventType.EVENT_ADD) this.add(getEvent(it)) 57 | } 58 | } 59 | } 60 | 61 | fun getActions(): List { 62 | return arrayListOf().apply { 63 | TaskActionType.values().forEach { 64 | if (it != TaskActionType.ACTION_ADD)this.add(getAction(it)) 65 | } 66 | } 67 | } 68 | 69 | fun defaultTaskConfig(): List { 70 | val list = arrayListOf() 71 | list.add(Task(true).apply { 72 | name = "WebSocket连接通知" 73 | events.add(EventWebSocket()) 74 | taskActions.add(ActionWebSocketMsg("WebSocket连接成功!")) 75 | }) 76 | 77 | list.add(Task(true).apply { 78 | name = "充电通知" 79 | events.add(EventBatteryStatus("电量低于20%")) 80 | taskActions.add(ActionWebSocketMsg("快没电了!")) 81 | }) 82 | return list 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/TaskType.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager 2 | 3 | /** 4 | * Created by Silent on 2023/9/18 16:49 5 | * 6 | * 7 | */ 8 | interface TaskType 9 | 10 | enum class TaskEventType(private var v: String) : TaskType { 11 | EVENT_ADD("EVENT_ADD"), 12 | EVENT_WEBSOCKET_CONNECTING("EVENT_WEBSOCKET_CONNECTING"), 13 | EVENT_BATTERY_STATUS("EVENT_BATTERY_STATUS"); 14 | 15 | override fun toString(): String = v 16 | } 17 | 18 | enum class TaskActionType(private val v: String) : TaskType { 19 | ACTION_ADD("ACTION_ADD"), 20 | ACTION_WEBSOCKET_MESSAGE("ACTION_WEBSOCKET_MESSAGE"); 21 | 22 | override fun toString(): String = v 23 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/action/Action.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.action 2 | 3 | import cuiliang.quicker.taskManager.BaseEventOrAction 4 | 5 | /** 6 | * Created by Voidcom on 2023/9/19 22:16 7 | * 抽象类,抽象了任务满足条件后执行的结果。 8 | * @see cuiliang.quicker.taskManager.event.Event 9 | */ 10 | abstract class Action : BaseEventOrAction { 11 | var description: String = "" 12 | 13 | override fun resultStr(): String = description 14 | 15 | /** 16 | * 条件达成后执行的操作 17 | */ 18 | abstract fun actionRunnable(): Runnable? 19 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/action/ActionAdd.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.action 2 | 3 | import android.content.Context 4 | import cuiliang.quicker.R 5 | import cuiliang.quicker.taskManager.BaseEventOrAction 6 | import cuiliang.quicker.taskManager.TaskActionType 7 | import cuiliang.quicker.taskManager.TaskType 8 | 9 | /** 10 | * Created by Voidcom on 2023/9/29 22:51 11 | * TODO 12 | */ 13 | class ActionAdd : Action() { 14 | override fun actionRunnable(): Runnable? = null 15 | 16 | override fun getIcon(): Int = R.drawable.ic_add 17 | 18 | override fun getName(): String = "添加结果" 19 | override fun resultStr(): String = "例如:发送充电提示通知" 20 | 21 | override fun getType(): TaskType = TaskActionType.ACTION_ADD 22 | 23 | override fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) { 24 | } 25 | 26 | override fun init(context: Context) { 27 | } 28 | 29 | override fun release(context: Context) { 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/action/ActionWebSocketMsg.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.action 2 | 3 | import android.content.Context 4 | import androidx.appcompat.widget.AppCompatEditText 5 | import cuiliang.quicker.R 6 | import cuiliang.quicker.network.websocket.MessageType 7 | import cuiliang.quicker.network.websocket.MsgRequestData 8 | import cuiliang.quicker.network.websocket.WebSocketClient 9 | import cuiliang.quicker.network.websocket.WebSocketNetListener 10 | import cuiliang.quicker.taskManager.BaseEventOrAction 11 | import cuiliang.quicker.taskManager.TaskActionType 12 | import cuiliang.quicker.ui.taskManager.TaskConstant 13 | 14 | /** 15 | * Created by Voidcom on 2023/9/19 22:21 16 | * TODO 17 | */ 18 | class ActionWebSocketMsg : Action { 19 | 20 | constructor() : super() 21 | 22 | constructor(s: String) { 23 | this.description = s 24 | } 25 | 26 | override fun getIcon(): Int = R.drawable.ic_notifications_black_24dp 27 | 28 | override fun getName(): String = "WebSocket消息通知" 29 | 30 | override fun getType(): TaskActionType = TaskActionType.ACTION_WEBSOCKET_MESSAGE 31 | 32 | override fun actionRunnable(): Runnable? { 33 | //给PC发送一条连接成功通知 34 | WebSocketClient.instance().newCall(ConnectedHint()) 35 | return null 36 | } 37 | 38 | override fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) { 39 | val et = AppCompatEditText(context) 40 | et.hint = "例:WebSocket连接成功!" 41 | getDialogBuilder(context).setTitle(getName()) 42 | .setView(et) 43 | .setNegativeButton(R.string.btnCancel, null) 44 | .setPositiveButton(R.string.btnAccept) { a, _ -> 45 | a.dismiss() 46 | description = et.text.toString() 47 | callback(this) 48 | }.create().show() 49 | } 50 | 51 | override fun init(context: Context) { 52 | } 53 | 54 | override fun release(context: Context) { 55 | } 56 | 57 | private inner class ConnectedHint : WebSocketNetListener() { 58 | override fun onRequest(data: MsgRequestData): MsgRequestData { 59 | return data.setData( 60 | MessageType.REQUEST_COMMAND.getValue(), 61 | "action", 62 | description, 63 | TaskConstant.ANDROID_NOTIFICATION, 64 | "", 65 | false 66 | ) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/event/Event.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.event 2 | 3 | import cuiliang.quicker.taskManager.BaseEventOrAction 4 | 5 | /** 6 | * Created by Silent on 2023/9/18 16:12 7 | * 事件接口 8 | */ 9 | abstract class Event : BaseEventOrAction { 10 | /* 11 | * 事件状态,当该事件达到完成条件时为true 12 | * 默认false; 13 | * 举例: 14 | * 电量状态设置为低于20%触发某个操作,则该值在电量低于20%应该为 true 15 | * */ 16 | var eventState = false 17 | 18 | var description: String = "" 19 | 20 | var listener: EventRunningListener? = null 21 | 22 | override fun resultStr(): String = description 23 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/event/EventAdd.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.event 2 | 3 | import android.content.Context 4 | import cuiliang.quicker.R 5 | import cuiliang.quicker.taskManager.BaseEventOrAction 6 | import cuiliang.quicker.taskManager.TaskEventType 7 | import cuiliang.quicker.taskManager.TaskType 8 | 9 | /** 10 | * Created by Voidcom on 2023/9/29 22:48 11 | */ 12 | class EventAdd : Event() { 13 | override fun getIcon(): Int = R.drawable.ic_add 14 | 15 | override fun getName(): String = "添加条件" 16 | override fun resultStr(): String = "如:当电量低于20%" 17 | 18 | override fun getType(): TaskType = TaskEventType.EVENT_ADD 19 | 20 | override fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) { 21 | } 22 | 23 | override fun init(context: Context) { 24 | } 25 | 26 | override fun release(context: Context) { 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/event/EventBatteryStatus.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.event 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.os.BatteryManager 8 | import cuiliang.quicker.R 9 | import cuiliang.quicker.taskManager.BaseEventOrAction 10 | import cuiliang.quicker.taskManager.TaskEventType 11 | import cuiliang.quicker.util.KLog 12 | 13 | /** 14 | * Created by Silent on 2023/9/18 17:23 15 | * 16 | */ 17 | class EventBatteryStatus : Event { 18 | private var receiver: BatteryBroadcast? = null 19 | 20 | private var targetBattery: Int = 20 21 | 22 | constructor() : super() 23 | 24 | constructor(s: String) { 25 | this.description = s 26 | } 27 | 28 | override fun getIcon(): Int = R.drawable.ic_battery 29 | 30 | override fun getName(): String = "电池状态" 31 | 32 | override fun getType(): TaskEventType = TaskEventType.EVENT_BATTERY_STATUS 33 | 34 | override fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) { 35 | getDialogBuilder(context) 36 | .setTitle(getName()) 37 | .setItems(R.array.dialog_battery_status_list) { a, b -> 38 | description = context.resources.getStringArray(R.array.dialog_battery_status_list)[b] 39 | callback(this) 40 | a.dismiss() 41 | }.create().show() 42 | } 43 | 44 | override fun init(context: Context) { 45 | targetBattery = description.substring(4, 6).toInt() 46 | if (receiver != null) return 47 | receiver = BatteryBroadcast() 48 | context.applicationContext.registerReceiver( 49 | receiver, 50 | IntentFilter(Intent.ACTION_BATTERY_CHANGED) 51 | ) 52 | } 53 | 54 | override fun release(context: Context) { 55 | if (receiver == null) return 56 | context.applicationContext.unregisterReceiver(receiver) 57 | receiver = null 58 | } 59 | 60 | inner class BatteryBroadcast : BroadcastReceiver() { 61 | override fun onReceive(context: Context?, intent: Intent?) { 62 | //在这里接受电量信息 63 | val batteryPct: Int = intent?.let { 64 | val level: Int = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) 65 | val scale: Int = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1) 66 | level * 100 / scale 67 | } ?: -1 68 | KLog.d( 69 | "Event", 70 | "EventBatteryStatus-batteryPct:$batteryPct; targetBattery:$targetBattery" 71 | ) 72 | eventState = batteryPct in 1..targetBattery 73 | if (batteryPct <= targetBattery) listener?.runningListener() 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/event/EventRunningListener.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.event 2 | 3 | /** 4 | * Created by Voidcom on 2023/9/29 13:55 5 | * 一个事件运行监听 6 | * 当一个事件条件达成时会执行这个接口 7 | */ 8 | interface EventRunningListener { 9 | fun runningListener() 10 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/taskManager/event/EventWebSocket.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.taskManager.event 2 | 3 | import android.content.Context 4 | import cuiliang.quicker.R 5 | import cuiliang.quicker.client.ConnectionStatus 6 | import cuiliang.quicker.network.websocket.ConnectListener 7 | import cuiliang.quicker.network.websocket.WebSocketClient 8 | import cuiliang.quicker.taskManager.BaseEventOrAction 9 | import cuiliang.quicker.taskManager.TaskEventType 10 | 11 | /** 12 | * Created by Silent on 2023/9/18 16:49 13 | * 14 | */ 15 | class EventWebSocket : Event(), ConnectListener { 16 | 17 | override fun getIcon(): Int = R.drawable.ic_computer_black_24dp 18 | 19 | override fun getName(): String = "WebSocket连接成功" 20 | 21 | override fun showDialogAndCallback(context: Context, callback: (BaseEventOrAction) -> Unit) { 22 | callback(this) 23 | } 24 | 25 | override fun release(context: Context) { 26 | WebSocketClient.instance().connectListeners.remove(this) 27 | } 28 | 29 | override fun getType(): TaskEventType = TaskEventType.EVENT_WEBSOCKET_CONNECTING 30 | 31 | override fun init(context: Context) { 32 | WebSocketClient.instance().connectListeners.add(this) 33 | } 34 | 35 | override fun onStatus(status: ConnectionStatus) { 36 | eventState = status == ConnectionStatus.LoggedIn || status == ConnectionStatus.Connected 37 | if (status == ConnectionStatus.LoggedIn || status == ConnectionStatus.Connected) { 38 | listener?.runningListener() 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/EventOrActionActivity.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.result.ActivityResult 8 | import androidx.activity.result.ActivityResultCallback 9 | import androidx.activity.result.ActivityResultLauncher 10 | import androidx.activity.result.contract.ActivityResultContracts 11 | import com.cuiliang.quicker.ui.BaseVBActivity 12 | import com.cuiliang.quicker.ui.EmptyViewModel 13 | import cuiliang.quicker.adapter.EventOrActionAdapter 14 | import cuiliang.quicker.databinding.FragmentMyTaskBinding 15 | import cuiliang.quicker.taskManager.BaseEventOrAction 16 | import cuiliang.quicker.taskManager.JsonAction 17 | import cuiliang.quicker.taskManager.JsonEvent 18 | import cuiliang.quicker.taskManager.TaskDataFactory 19 | import cuiliang.quicker.taskManager.TaskEventType 20 | import cuiliang.quicker.taskManager.action.Action 21 | import cuiliang.quicker.taskManager.event.Event 22 | 23 | /** 24 | * 事件和动作列表 25 | * 事件,或者说条件。比如电量更新事件、系统通知事件、WIFI打开事件、蓝牙关闭事件等等 26 | * 动作:当满足某个事件时执行的操作,比如电量低于xxx发送提醒消息 27 | * @see TaskDataFactory 28 | * @see TaskEventType 29 | */ 30 | class EventOrActionActivity : BaseVBActivity() { 31 | private lateinit var eventOrActionAdapter: EventOrActionAdapter 32 | private var dataType = 0 33 | 34 | override val mViewModel: EmptyViewModel by lazy { EmptyViewModel() } 35 | 36 | override fun onInit() { 37 | eventOrActionAdapter = EventOrActionAdapter(this) { 38 | resultAndFinish(it) 39 | } 40 | intent.getStringArrayExtra(UNABLE_ADD_LIST)?.let { 41 | eventOrActionAdapter.setUnableAddList(it) 42 | } 43 | dataType = intent.getIntExtra(DATA_TYPE, 0) 44 | if (dataType == 0) { 45 | eventOrActionAdapter.setEvents(TaskDataFactory().getEvents()) 46 | } else { 47 | eventOrActionAdapter.setEvents(TaskDataFactory().getActions()) 48 | } 49 | mBinding.root.adapter = eventOrActionAdapter 50 | } 51 | 52 | private fun resultAndFinish(data: BaseEventOrAction) { 53 | setResult(Activity.RESULT_OK, Intent().apply { 54 | putExtra(DATA_TYPE, dataType) 55 | putExtra( 56 | RESULT_DATA, 57 | if (dataType == 0) { 58 | JsonEvent(data as Event).toString() 59 | } else { 60 | JsonAction(data as Action).toString() 61 | } 62 | ) 63 | }) 64 | finish() 65 | } 66 | 67 | companion object { 68 | const val UNABLE_ADD_LIST = "UNABLE_ADD_LIST" 69 | const val DATA_TYPE = "DATA_TYPE" 70 | const val RESULT_DATA = "RESULT_DATA" 71 | 72 | fun getLauncher( 73 | activity: ComponentActivity, 74 | callback: ActivityResultCallback 75 | ): ActivityResultLauncher { 76 | return activity.registerForActivityResult( 77 | ActivityResultContracts.StartActivityForResult(), 78 | callback 79 | ) 80 | } 81 | 82 | /** 83 | * @param type 0=if条件;1=满足条件执行的内容 84 | * @param list 不可添加列表,当该item已经添加过了,就属于不可添加 85 | */ 86 | fun getInstant(context: Context, type: Int, list: Array): Intent { 87 | return Intent(context, EventOrActionActivity::class.java).apply { 88 | putExtra(DATA_TYPE, type) 89 | putExtra(UNABLE_ADD_LIST, list) 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/share/ShareViewModel.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.share 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Parcelable 7 | import androidx.compose.runtime.MutableState 8 | import androidx.compose.runtime.mutableStateOf 9 | import androidx.datastore.core.DataStore 10 | import androidx.datastore.preferences.core.Preferences 11 | import androidx.datastore.preferences.core.edit 12 | import androidx.datastore.preferences.core.stringPreferencesKey 13 | import androidx.datastore.preferences.preferencesDataStore 14 | import com.cuiliang.quicker.ui.BaseModel 15 | import com.cuiliang.quicker.ui.BaseViewModel 16 | import cuiliang.quicker.util.KLog 17 | import cuiliang.quicker.util.ShareDataToPCManager 18 | import kotlinx.coroutines.flow.catch 19 | import kotlinx.coroutines.flow.collect 20 | import kotlinx.coroutines.flow.first 21 | import kotlinx.coroutines.flow.map 22 | 23 | /** 24 | * Created by voidcom on 2023/10/10 25 | * 26 | */ 27 | class ShareViewModel : BaseViewModel() { 28 | //用户名,用于分享操作 29 | val userName: MutableState by lazy { mutableStateOf("") } 30 | 31 | //用户推送验证码,用于分享操作 32 | val authCode: MutableState by lazy { mutableStateOf("") } 33 | 34 | val showLoading: MutableState by lazy { mutableStateOf(true) } 35 | 36 | override val model: BaseModel? = null 37 | 38 | override fun onInit(context: Context) { 39 | } 40 | 41 | override fun onInitData() { 42 | } 43 | 44 | fun haveUserConfig(): Boolean = userName.value.isNotEmpty() && authCode.value.isNotEmpty() 45 | 46 | suspend fun readShareConfig(context: Context) { 47 | context.dataStore.data.map { 48 | return@map arrayListOf().apply { 49 | add(it[SHARE_USER_NAME] ?: "") 50 | add(it[SHARE_AUTH_CODE] ?: "") 51 | } 52 | }.collect { 53 | userName.value = it[0] 54 | authCode.value = it[1] 55 | } 56 | } 57 | 58 | suspend fun saveShareConfig(context: Context) { 59 | context.dataStore.edit { 60 | it[SHARE_USER_NAME] = userName.value 61 | it[SHARE_AUTH_CODE] = authCode.value 62 | } 63 | } 64 | 65 | /** 66 | * @param callback 执行在IO线程 67 | */ 68 | fun handleSendText(txt: String, callback: ((Boolean) -> Unit)) { 69 | txt?.let { 70 | KLog.d("ShareActivity", "it:$it") 71 | ShareDataToPCManager.instant.sendShareText( 72 | userName.value, 73 | authCode.value, 74 | it, 75 | callback 76 | ) 77 | } 78 | } 79 | 80 | fun handleSendImage(intent: Intent, callback: ((Boolean) -> Unit)) { 81 | (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { 82 | // Update UI to reflect image being shared 83 | } 84 | } 85 | 86 | fun handleSendMultipleImages(intent: Intent, callback: ((Boolean) -> Unit)) { 87 | intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { 88 | // Update UI to reflect multiple images being shared 89 | } 90 | } 91 | 92 | companion object { 93 | private val Context.dataStore: DataStore by preferencesDataStore(name = "ShareConfig") 94 | private val SHARE_USER_NAME = stringPreferencesKey("SHARE_USER_NAME") 95 | private val SHARE_AUTH_CODE = stringPreferencesKey("SHARE_AUTH_CODE") 96 | } 97 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/share/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.share.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | //val Purple80 = Color(0xFFFFFFFF) 6 | //val PurpleGrey80 = Color(0xFF0B4F94) 7 | //val Pink80 = Color(0xFF2473C3) 8 | 9 | val Purple40 = Color(0xFF0B4F94) 10 | val PurpleGrey40 = Color(0xFF3F51B5) 11 | val Pink40 = Color(0xFF2473C3) -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/share/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.share.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | /** 19 | * 深色主题 20 | * */ 21 | //private val DarkColorScheme = darkColorScheme( 22 | // primary = Purple80, 23 | // secondary = PurpleGrey80, 24 | // tertiary = Pink80 25 | //) 26 | 27 | /** 28 | * 浅色主题 29 | * */ 30 | private val LightColorScheme = lightColorScheme( 31 | primary = Purple40, 32 | secondary = PurpleGrey40, 33 | tertiary = Pink40 34 | 35 | /* Other default colors to override 36 | background = Color(0xFFFFFBFE), 37 | surface = Color(0xFFFFFBFE), 38 | onPrimary = Color.White, 39 | onSecondary = Color.White, 40 | onTertiary = Color.White, 41 | onBackground = Color(0xFF1C1B1F), 42 | onSurface = Color(0xFF1C1B1F), 43 | */ 44 | ) 45 | 46 | @Composable 47 | fun QuickerAndroidTheme( 48 | darkTheme: Boolean = isSystemInDarkTheme(), 49 | // Dynamic color is available on Android 12+ 50 | // dynamicColor: Boolean = true, 51 | content: @Composable () -> Unit 52 | ) { 53 | // val colorScheme = when { 54 | // dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 55 | // val context = LocalContext.current 56 | // if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 57 | // } 58 | 59 | // darkTheme -> DarkColorScheme 60 | // else -> LightColorScheme 61 | // } 62 | val colorScheme= LightColorScheme 63 | val view = LocalView.current 64 | if (!view.isInEditMode) { 65 | SideEffect { 66 | val window = (view.context as Activity).window 67 | window.statusBarColor = colorScheme.primary.toArgb() 68 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 69 | } 70 | } 71 | 72 | MaterialTheme( 73 | colorScheme = colorScheme, 74 | typography = Typography, 75 | content = content 76 | ) 77 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/share/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.share.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskEdit/TaskEditActivity.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskEdit 2 | 3 | import android.app.Activity 4 | import android.app.Service 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.ServiceConnection 9 | import android.os.IBinder 10 | import android.view.Menu 11 | import android.view.MenuItem 12 | import androidx.activity.ComponentActivity 13 | import androidx.activity.result.ActivityResult 14 | import androidx.activity.result.ActivityResultCallback 15 | import androidx.activity.result.ActivityResultLauncher 16 | import androidx.activity.result.contract.ActivityResultContracts 17 | import com.cuiliang.quicker.ui.BaseDBActivity 18 | import cuiliang.quicker.R 19 | import cuiliang.quicker.databinding.ActivityTaskEditBinding 20 | import cuiliang.quicker.service.TaskManagerService 21 | import cuiliang.quicker.taskManager.Task 22 | 23 | class TaskEditActivity : BaseDBActivity(), 24 | ServiceConnection { 25 | 26 | private var mBinder: TaskManagerService.TaskManagerBinder? = null 27 | 28 | override val mViewModel: TaskEditViewModel by lazy { TaskEditViewModel() } 29 | 30 | override fun getLayoutID(): Int = R.layout.activity_task_edit 31 | 32 | override fun onInit() { 33 | mBinding.vm = mViewModel 34 | setSupportActionBar(mBinding.toolbar) 35 | supportActionBar?.setHomeButtonEnabled(true) 36 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 37 | } 38 | 39 | override fun onStart() { 40 | super.onStart() 41 | bindService(Intent(this, TaskManagerService::class.java), this, Service.BIND_AUTO_CREATE) 42 | } 43 | 44 | override fun onStop() { 45 | super.onStop() 46 | unbindService(this) 47 | } 48 | 49 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 50 | menuInflater.inflate(R.menu.menu_task_edit, menu) 51 | return true 52 | } 53 | 54 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 55 | return when (item.itemId) { 56 | android.R.id.home -> { 57 | finish() 58 | true 59 | } 60 | 61 | R.id.btn_save -> { 62 | resultAndFinish(mViewModel.model.task) 63 | true 64 | } 65 | 66 | else -> super.onOptionsItemSelected(item) 67 | } 68 | } 69 | 70 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 71 | if (service == null) return 72 | mBinder = service as TaskManagerService.TaskManagerBinder 73 | initData() 74 | } 75 | 76 | override fun onServiceDisconnected(name: ComponentName?) { 77 | mBinder = null 78 | } 79 | 80 | private fun initData() { 81 | if (editType == 0) { 82 | mViewModel.title.value = getString(R.string.createTask_str) 83 | mViewModel.model.task = Task(true) 84 | return 85 | } 86 | val i = intent.getStringExtra(TASK_NAME) ?: "" 87 | mViewModel.model.task = mBinder?.getTaskList()?.get(i) ?: Task(true) 88 | mViewModel.title.value = getString(R.string.editTask_str) 89 | mViewModel.taskName.value = mViewModel.model.task.name 90 | mViewModel.refreshAllData() 91 | } 92 | 93 | private fun resultAndFinish(task: Task) { 94 | if (!mViewModel.saveData()) return 95 | mBinder?.getTaskList()?.put(task.name, task) 96 | setResult(Activity.RESULT_OK, Intent().apply { 97 | putExtra(TASK_NAME, task.name) 98 | putExtra(EDIT_TYPE, editType) 99 | }) 100 | finish() 101 | } 102 | 103 | companion object { 104 | private var editType: Int = 0 //0:新建任务 1:编辑任务 105 | const val EDIT_TYPE = "EDIT_TYPE" 106 | const val TASK_NAME = "TASK_NAME" 107 | 108 | fun getLauncher( 109 | activity: ComponentActivity, 110 | callback: ActivityResultCallback 111 | ): ActivityResultLauncher { 112 | return activity.registerForActivityResult( 113 | ActivityResultContracts.StartActivityForResult(), 114 | callback 115 | ) 116 | } 117 | 118 | /** 119 | * @param taskName 当taskName=null时,表示新增任务;否则是编辑任务 120 | */ 121 | fun getIntent(context: Context, taskName: String? = null): Intent { 122 | editType = if (taskName.isNullOrEmpty()) 0 else 1 123 | return Intent(context, TaskEditActivity::class.java).apply { 124 | putExtra(TASK_NAME, taskName) 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskEdit/TaskEditModel.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskEdit 2 | 3 | import com.cuiliang.quicker.ui.BaseModel 4 | import cuiliang.quicker.taskManager.Task 5 | 6 | /** 7 | * Created by voidcom on 2023/10/7 8 | * 9 | */ 10 | class TaskEditModel: BaseModel() { 11 | 12 | lateinit var task: Task 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskEdit/TaskEditViewModel.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskEdit 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.activity.result.ActivityResult 7 | import androidx.activity.result.ActivityResultCallback 8 | import androidx.activity.result.ActivityResultLauncher 9 | import androidx.lifecycle.MutableLiveData 10 | import com.cuiliang.quicker.ui.BaseViewModel 11 | import cuiliang.quicker.adapter.TaskDetailsItemAdapter 12 | import cuiliang.quicker.databinding.ActivityTaskEditBinding 13 | import cuiliang.quicker.taskManager.BaseEventOrAction 14 | import cuiliang.quicker.taskManager.JsonAction 15 | import cuiliang.quicker.taskManager.JsonEvent 16 | import cuiliang.quicker.taskManager.action.Action 17 | import cuiliang.quicker.taskManager.action.ActionAdd 18 | import cuiliang.quicker.taskManager.event.Event 19 | import cuiliang.quicker.taskManager.event.EventAdd 20 | import cuiliang.quicker.ui.EventOrActionActivity 21 | 22 | /** 23 | * Created by voidcom on 2023/10/7 24 | * 25 | */ 26 | class TaskEditViewModel : BaseViewModel() { 27 | private lateinit var launcher: ActivityResultLauncher 28 | private lateinit var ifFactoryAdapter: TaskDetailsItemAdapter 29 | private lateinit var ifActionAdapter: TaskDetailsItemAdapter 30 | var taskName = MutableLiveData("") 31 | var title = MutableLiveData("") 32 | 33 | override val model: TaskEditModel by lazy { TaskEditModel() } 34 | 35 | override fun onInit(context: Context) { 36 | launcher = EventOrActionActivity.getLauncher(context as TaskEditActivity, addEventCallback) 37 | 38 | ifFactoryAdapter = TaskDetailsItemAdapter(context) 39 | ifFactoryAdapter.setCallback(adapterClickCallback(context, 0)) 40 | ifFactoryAdapter.setFooterData(EventAdd()) 41 | 42 | ifActionAdapter = TaskDetailsItemAdapter(context) 43 | ifActionAdapter.setCallback(adapterClickCallback(context, 1)) 44 | ifActionAdapter.setFooterData(ActionAdd()) 45 | (vmBinding as ActivityTaskEditBinding).run { 46 | rvIfFactorList.adapter = ifFactoryAdapter 47 | rvIfActionList.adapter = ifActionAdapter 48 | } 49 | } 50 | 51 | override fun onInitData() { 52 | ifFactoryAdapter.setFooterData(EventAdd()) 53 | ifActionAdapter.setFooterData(ActionAdd()) 54 | } 55 | 56 | fun saveData(): Boolean { 57 | model.task.name = taskName.value ?: "" 58 | return model.task.name.isNotEmpty() && model.task.events.isNotEmpty() && model.task.taskActions.isNotEmpty() 59 | } 60 | 61 | fun refreshAllData() { 62 | ifFactoryAdapter.addItems(model.task.events) 63 | ifActionAdapter.addItems(model.task.taskActions) 64 | } 65 | 66 | private val addEventCallback = ActivityResultCallback { result -> 67 | if (result.resultCode != Activity.RESULT_OK || result.data == null) return@ActivityResultCallback 68 | val dataType = result.data!!.getIntExtra(EventOrActionActivity.DATA_TYPE, 0) 69 | result.data?.getStringExtra(EventOrActionActivity.RESULT_DATA)?.let { 70 | if (dataType == 0) { 71 | ifFactoryAdapter.addItem(JsonEvent.jsonToEvent(it).toEvent()) 72 | } else { 73 | ifActionAdapter.addItem(JsonAction.jsonToAction(it).toAction()) 74 | } 75 | } 76 | } 77 | 78 | private fun adapterClickCallback( 79 | context: Context, 80 | type: Int 81 | ): (List) -> Unit { 82 | return { 83 | val array = Array(it.size) { "" } 84 | for (i in it.indices) { 85 | array[i] = it[i].getName() 86 | } 87 | launcher.launch(EventOrActionActivity.getInstant(context, type, array)) 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskManager/MyTaskFragment.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskManager 2 | 3 | import android.app.Activity 4 | import android.app.Service 5 | import android.content.ComponentName 6 | import android.content.Intent 7 | import android.content.ServiceConnection 8 | import android.os.Bundle 9 | import android.os.IBinder 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import androidx.activity.result.ActivityResult 14 | import androidx.activity.result.ActivityResultCallback 15 | import androidx.activity.result.ActivityResultLauncher 16 | import androidx.fragment.app.Fragment 17 | import cuiliang.quicker.adapter.TaskListAdapter 18 | import cuiliang.quicker.databinding.FragmentMyTaskBinding 19 | import cuiliang.quicker.service.TaskManagerService 20 | import cuiliang.quicker.ui.taskEdit.TaskEditActivity 21 | import cuiliang.quicker.util.KLog 22 | 23 | class MyTaskFragment : Fragment(), ServiceConnection { 24 | private lateinit var mBinding: FragmentMyTaskBinding 25 | private lateinit var goTaskEditLauncher: ActivityResultLauncher 26 | private var mBinder: TaskManagerService.TaskManagerBinder? = null 27 | private var taskListAdapter: TaskListAdapter? = null 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, 31 | container: ViewGroup?, 32 | savedInstanceState: Bundle? 33 | ): View { 34 | return FragmentMyTaskBinding.inflate(inflater).run { 35 | mBinding = this 36 | this.root 37 | } 38 | } 39 | 40 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 41 | super.onViewCreated(view, savedInstanceState) 42 | goTaskEditLauncher = TaskEditActivity.getLauncher(requireActivity(), editTaskCallback) 43 | } 44 | 45 | override fun onStart() { 46 | super.onStart() 47 | requireActivity().bindService( 48 | Intent(requireContext(), TaskManagerService::class.java), 49 | this, 50 | Service.BIND_AUTO_CREATE 51 | ) 52 | } 53 | 54 | override fun onStop() { 55 | super.onStop() 56 | mBinder?.getTaskList()?.saveConfig() 57 | requireActivity().unbindService(this) 58 | } 59 | 60 | 61 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 62 | KLog.d(TAG, "---onServiceConnected") 63 | if (service == null) return 64 | mBinder = service as TaskManagerService.TaskManagerBinder 65 | mBinder?.let { binder -> 66 | taskListAdapter = TaskListAdapter(requireActivity(), binder.getTaskList()) { 67 | openEditPageAndAddNewTask(it.name) 68 | } 69 | mBinding.taskListRv.adapter = taskListAdapter 70 | } 71 | } 72 | 73 | override fun onServiceDisconnected(name: ComponentName?) { 74 | mBinder = null 75 | taskListAdapter = null 76 | } 77 | 78 | /** 79 | * 打开任务编辑界面 80 | * @param name 创建一个新的任务,因为没有传data,所以操作类型为‘新建任务’ 81 | */ 82 | fun openEditPageAndAddNewTask(name: String? = null) { 83 | goTaskEditLauncher.launch(TaskEditActivity.getIntent(requireContext(), name)) 84 | } 85 | 86 | private val editTaskCallback = ActivityResultCallback { result -> 87 | if (result.resultCode != Activity.RESULT_OK || result.data == null) return@ActivityResultCallback 88 | val name = result.data?.getStringExtra(TaskEditActivity.TASK_NAME) ?: "" 89 | // KLog.d("MyTaskFragment", "json:$name") 90 | mBinder?.getTaskList()?.let { 91 | it.saveConfig() 92 | taskListAdapter?.notifyItemChanged(it.indexOf(name)) 93 | } 94 | } 95 | 96 | companion object { 97 | const val TAG = "MyTaskFragment" 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskManager/TaskConfig.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskManager 2 | 3 | /** 4 | * Created by Voidcom on 2023/9/13 17:13 5 | * TODO 6 | */ 7 | object TaskConfig { 8 | var ACTION_LIST: HashMap = hashMapOf() 9 | 10 | fun decodeActionMsg(msg: String) { 11 | val tmp = msg.split(":", "\r\n") 12 | for (i in tmp.indices step 2) { 13 | ACTION_LIST[tmp[i]] = tmp[i + 1] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskManager/TaskConstant.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskManager 2 | 3 | /** 4 | * Created by Voidcom on 2023/9/13 17:10 5 | * 6 | */ 7 | object TaskConstant { 8 | const val SP_ACTION_LIST = "SP_ACTION_LIST" 9 | //Android WebSocket 通知动作ID 10 | const val ANDROID_NOTIFICATION = "fae008a2-42bd-438b-ae87-b836e98a104d" 11 | 12 | //一个PC的动作ID,通过这个动作获取可运行动作列表。它是固定的 13 | const val ACTION_LIST_ID = "85b45597-1cb3-4f76-94ca-07c19356884c" 14 | //Android分享动作ID,通过这个动作可以将内容发送的PC端 15 | const val ACTION_SHARE_ID = "b3ce0a01-6d35-42a4-a631-7f9ea524ca44" 16 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskManager/TaskList.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskManager 2 | 3 | import android.content.Context 4 | import android.util.ArraySet 5 | import cuiliang.quicker.taskManager.JsonTask 6 | import cuiliang.quicker.taskManager.Task 7 | import cuiliang.quicker.taskManager.TaskDataFactory 8 | import cuiliang.quicker.util.FileTools 9 | import kotlinx.coroutines.DelicateCoroutinesApi 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.Job 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.launch 14 | 15 | /** 16 | * Created by Voidcom on 2023/9/29 10:03 17 | * 任务列表,用于service中的任务管理 18 | * 任务列表的数据在 [MyTaskFragment]展示 19 | * 20 | * @see cuiliang.quicker.service.TaskManagerService 21 | */ 22 | class TaskList(private val context: Context) { 23 | private val taskKey: ArraySet = ArraySet() 24 | private val taskValues: ArrayList = arrayListOf() 25 | private var job: Job? = null 26 | 27 | fun put(key: String, value: Task) { 28 | if (taskKey.add(key)) { 29 | taskValues.add(value) 30 | } else { 31 | taskValues[indexOf(key)] = value 32 | } 33 | value.taskInit(context) 34 | } 35 | 36 | fun remove(key: String) { 37 | val index = taskKey.indexOf(key) 38 | if (index < 0) return 39 | taskValues[index].release(context) 40 | taskKey.removeAt(index) 41 | taskValues.removeAt(index) 42 | saveConfig() 43 | } 44 | 45 | fun indexOf(key: String): Int = taskKey.indexOf(key) 46 | 47 | fun size(): Int = taskKey.size 48 | 49 | fun get(i: Int): Task = taskValues[i] 50 | 51 | fun get(key: String): Task = taskValues[indexOf(key)] 52 | 53 | fun clear() { 54 | taskValues.forEach { 55 | it.release(context) 56 | } 57 | taskKey.clear() 58 | taskValues.clear() 59 | } 60 | 61 | /** 62 | * 读取任务配置,仅在服务启动时调用 63 | * 先 判断是否有配置文件 64 | * 没有配置文件,直接加载默认配置。 65 | */ 66 | @OptIn(DelicateCoroutinesApi::class) 67 | fun readFileConfig() { 68 | val files = FileTools.getFileList(context) 69 | //判断是否有配置文件 70 | if (files.isNullOrEmpty()) { 71 | //没有配置文件,直接加载默认配置。finish Activity时会保存配置,然后这份默认不论有没有被修改都会变成配置文件 72 | TaskDataFactory().defaultTaskConfig().forEach { 73 | put(it.name, it) 74 | } 75 | } else { 76 | GlobalScope.launch { 77 | files.forEach { 78 | val tmp = JsonTask.jsonToTask(FileTools.readJsonFromFile(it)).toTask() 79 | put(tmp.name, tmp) 80 | } 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * 保存任务到文件 87 | * 该方法短时间可能被多次调用,所以做了处理,只有最后一次会执行写入文件操作 88 | * 类似Handler.postDelayed() 和 Handler().removeCallbacks() 89 | */ 90 | @OptIn(DelicateCoroutinesApi::class) 91 | fun saveConfig() { 92 | job?.let { 93 | if (it.isActive) { 94 | it.cancel(null) 95 | } 96 | job = null 97 | } 98 | job = GlobalScope.launch { 99 | delay(500) 100 | taskValues.forEach { 101 | FileTools.saveJsonToFile(context, it.name, JsonTask(it).toString()) 102 | } 103 | } 104 | job?.start() 105 | } 106 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/ui/taskManager/TaskListActivity.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.ui.taskManager 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuItem 6 | import androidx.appcompat.app.AppCompatActivity 7 | import cuiliang.quicker.R 8 | import cuiliang.quicker.databinding.ActivityTaskListBinding 9 | 10 | /** 11 | * Created by Voidcom on 2023/9/13 16:33 12 | * 用于显示任务列表 13 | */ 14 | class TaskListActivity : AppCompatActivity() { 15 | private lateinit var mBinding: ActivityTaskListBinding 16 | private val myTaskFragment=MyTaskFragment() 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | mBinding = ActivityTaskListBinding.inflate(layoutInflater) 21 | setContentView(mBinding.root) 22 | setSupportActionBar(mBinding.toolbar) 23 | supportActionBar?.setHomeButtonEnabled(true) 24 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 25 | 26 | supportFragmentManager.beginTransaction().let { 27 | it.add(R.id.my_task_fragment, myTaskFragment) 28 | it.commit() 29 | } 30 | } 31 | 32 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 33 | menuInflater.inflate(R.menu.menu_task_manager, menu) 34 | return true 35 | } 36 | 37 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 38 | return when (item.itemId) { 39 | android.R.id.home -> { 40 | finish() 41 | true 42 | } 43 | 44 | R.id.add_new_task -> { 45 | myTaskFragment.openEditPageAndAddNewTask() 46 | true 47 | } 48 | 49 | else -> super.onOptionsItemSelected(item) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/DataPageValues.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util; 2 | 3 | /** 4 | * Created by Void on 2020/3/12 16:33 5 | * 用于存放通用page数据。 6 | * 如:全局页面总数、上下文页面总数、当前显示的上下文页面在总页面的索引等 7 | */ 8 | public class DataPageValues { 9 | //上下文page名称 10 | public static String contextPageName = ""; 11 | //全局页面总数 12 | public static int globalDataPageCount = 0; 13 | //当前页面在全局总页数中的索引 14 | public static int currentGlobalPageIndex = 0; 15 | //上下文页面总数 16 | public static int contextDataPageCount = 0; 17 | //当前页面在上下文总页数中的索引 18 | public static int currentContextPageIndex = 0; 19 | // 上下文面板切换是否锁定 20 | public static boolean IsContextPanelLocked = false; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/FileTools.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | import java.io.FileOutputStream 6 | import java.io.FileReader 7 | import java.io.FileWriter 8 | import java.io.IOException 9 | import java.io.PrintWriter 10 | 11 | /** 12 | * Created by Voidcom on 2023/9/20 14:44 13 | * TODO 14 | */ 15 | object FileTools { 16 | private val TAG = FileTools::class.java.name 17 | 18 | /** 19 | * 文件保存在: /data/data/[application package]/files 20 | * 该位置不需要读写权限 21 | */ 22 | fun saveJsonToFile(context: Context, name: String, content: String) { 23 | if (content.isEmpty()) { 24 | KLog.e(TAG, "保存的内容不能为空!") 25 | return 26 | } 27 | KLog.d(TAG,"保存任务到文件-name:$name; content:$content") 28 | val jsonFile = getTaskJsonFile(context, name) ?: return 29 | var fileWriter: FileWriter? = null 30 | var writer: PrintWriter? = null 31 | try { 32 | if (jsonFile.exists()) { 33 | //如果文件存在,清空文件内容 34 | fileWriter = FileWriter(jsonFile) 35 | fileWriter.write("") 36 | fileWriter.flush() 37 | } else { 38 | jsonFile.createNewFile() 39 | } 40 | writer = PrintWriter(FileOutputStream(jsonFile)) 41 | writer.print(content) 42 | } catch (e: IOException) { 43 | e.printStackTrace() 44 | } finally { 45 | fileWriter?.close() 46 | writer?.close() 47 | } 48 | } 49 | 50 | fun readJsonFromFile(jsonFile: File): String { 51 | var fileReader: FileReader? = null 52 | var str = "" 53 | try { 54 | fileReader = FileReader(jsonFile) 55 | str = fileReader.readText() 56 | } catch (e: IOException) { 57 | e.printStackTrace() 58 | } finally { 59 | fileReader?.close() 60 | } 61 | KLog.d(TAG, "path:${jsonFile.path}; content:$str") 62 | return str 63 | } 64 | 65 | fun readJsonFromFile(context: Context, name: String): String { 66 | val jsonFile = getTaskJsonFile(context, name) ?: return "" 67 | return readJsonFromFile(jsonFile) 68 | } 69 | 70 | fun getTaskCachePath(context: Context): String = context.filesDir.path + "/taskJson" 71 | 72 | private fun getTaskJsonFile(context: Context, name: String): File? { 73 | if (name.isEmpty()) { 74 | KLog.e(TAG, "文件名不能为空!") 75 | return null 76 | } 77 | val folder = File(getTaskCachePath(context)) 78 | folder.mkdirs() 79 | return File(folder, "$name.json").apply { 80 | KLog.d(TAG, "JsonFilePath:${this.path}") 81 | } 82 | } 83 | 84 | /** 85 | * 获取getTaskCachePath() 文件夹下的文件列表 86 | * @return 返回的文件列表进包含后缀为.json 87 | */ 88 | fun getFileList(context: Context): Array? { 89 | val folder = File(getTaskCachePath(context)) 90 | return folder.listFiles { _, name -> name?.endsWith(".json") ?: false } 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/SPUtils.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util; 2 | 3 | import android.app.Application; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | /** 10 | * Created by Void on 2020/4/12 13:39 11 | * SharedPreferences工具类 12 | */ 13 | public class SPUtils { 14 | private static SharedPreferences sp; 15 | 16 | public static void init(Application application) { 17 | sp = PreferenceManager.getDefaultSharedPreferences(application); 18 | 19 | } 20 | 21 | private SPUtils() { 22 | throw new UnsupportedOperationException(); 23 | } 24 | 25 | public static boolean contains(@NonNull String key) { 26 | return sp.contains(key); 27 | } 28 | 29 | public static void putString(String key, String value) { 30 | sp.edit().putString(key, value).apply(); 31 | } 32 | 33 | public static void putInt(String key, int value) { 34 | sp.edit().putInt(key, value).apply(); 35 | } 36 | 37 | public static void putBoolean(String key, boolean value) { 38 | sp.edit().putBoolean(key, value).apply(); 39 | } 40 | 41 | public static void putLong(String key, long value) { 42 | sp.edit().putLong(key, value).apply(); 43 | } 44 | 45 | public static String getString(String key, String defaultValue) { 46 | return sp.getString(key, defaultValue); 47 | } 48 | 49 | public static int getInt(String key, int defaultValue) { 50 | return sp.getInt(key, defaultValue); 51 | } 52 | 53 | public static Boolean getBoolean(String key, boolean defaultValue) { 54 | return sp.getBoolean(key, defaultValue); 55 | } 56 | 57 | public static long getLong(String key, long defaultValue) { 58 | return sp.getLong(key, defaultValue); 59 | } 60 | 61 | public static void remove(String key) { 62 | sp.edit().remove(key).apply(); 63 | } 64 | 65 | public static void clear() { 66 | sp.edit().clear().apply(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/ShareDataToPCManager.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util 2 | 3 | import android.util.Log 4 | import androidx.compose.runtime.MutableState 5 | import androidx.compose.runtime.mutableStateOf 6 | import cuiliang.quicker.network.NetRequestObj 7 | import cuiliang.quicker.network.NetWorkManager.Companion.getInstant 8 | import cuiliang.quicker.network.NetWorkManager.RequestCallback 9 | import cuiliang.quicker.network.shareToPc.ShareApi 10 | import cuiliang.quicker.network.websocket.MessageType 11 | import cuiliang.quicker.network.websocket.MsgRequestData 12 | import cuiliang.quicker.network.websocket.MsgResponseData 13 | import cuiliang.quicker.network.websocket.WebSocketClient 14 | import cuiliang.quicker.network.websocket.WebSocketNetListener 15 | import cuiliang.quicker.ui.taskManager.TaskConstant 16 | import okhttp3.Response 17 | import java.io.IOException 18 | 19 | /** 20 | * Created by Void on 2020/4/12 12:12 21 | * 分享数据到PC端。 22 | * 分享可以分两种,一种是通过WebSocket,另一种是推送服务 23 | * 默认情况使用WebSocket,当WebSocket没连上才用推送 24 | * [文档](https://getquicker.net/kc/manual/doc/connection) 25 | */ 26 | class ShareDataToPCManager private constructor() { 27 | 28 | fun sendShareText(name: String, code: String, text: String, callback: ((Boolean) -> Unit)) { 29 | if (WebSocketClient.instance().isConnected()) { 30 | WebSocketClient.instance().newCall(object : WebSocketNetListener() { 31 | override fun onRequest(data: MsgRequestData): MsgRequestData { 32 | return data.setData( 33 | MessageType.REQUEST_COMMAND.getValue(), 34 | operation = "action", 35 | data = text, 36 | action = TaskConstant.ACTION_SHARE_ID, 37 | wait = false 38 | ) 39 | } 40 | 41 | override fun onResponse(data: MsgResponseData) { 42 | super.onResponse(data) 43 | callback(true) 44 | } 45 | 46 | override fun onFail(error: String) { 47 | super.onFail(error) 48 | //WebSocket失败后尝试使用推送服务发送分享数据 49 | pushShareToPC(name, code, text, callback) 50 | } 51 | }) 52 | } else { 53 | pushShareToPC(name, code, text, callback) 54 | } 55 | } 56 | 57 | fun pushShareToPC(name: String, code: String, data: String, callback: ((Boolean) -> Unit)) { 58 | pushShareToPC(name, code, data, object : RequestCallback { 59 | override fun onSuccess(response: Response) { 60 | callback(true) 61 | } 62 | 63 | override fun onError(e: IOException, errorMessage: String?) { 64 | callback(false) 65 | } 66 | }) 67 | } 68 | 69 | /** 70 | * 使用推送服务分享内容到PC 71 | * 注:该请求为异步操作 72 | * [文档](https://getquicker.net/kc/manual/doc/connection) 73 | */ 74 | fun pushShareToPC(name: String, code: String, data: String, callback: RequestCallback?) { 75 | if (name.isEmpty() || code.isEmpty() || data.isEmpty()) { 76 | Log.e(TAG, "用户名或推送验证码为空!") 77 | callback?.onError(IOException("")) 78 | return 79 | } 80 | val requestObj = NetRequestObj(ShareApi.shareUrl, callback) 81 | requestObj.addBody("toUser", name) 82 | requestObj.addBody("code", code) 83 | requestObj.addBody("operation", "action") 84 | requestObj.addBody("data", data) 85 | requestObj.isEncode = true 86 | getInstant().executeRequest1(requestObj) 87 | } 88 | 89 | companion object { 90 | private val TAG = ShareDataToPCManager::class.java.simpleName 91 | const val SHARE_USER_NAME = "SHARE_USER_NAME" 92 | const val SHARE_AUTH_CODE = "SHARE_AUTH_CODE" 93 | 94 | val instant: ShareDataToPCManager by lazy { ShareDataToPCManager() } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/SystemUtils.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | 6 | import android.content.IntentFilter 7 | 8 | 9 | /** 10 | * Created by Voidcom on 2023/9/13 19:18 11 | * TODO 12 | */ 13 | object SystemUtils { 14 | private const val TAG = "SystemUtils" 15 | fun getSystemBattery(context: Context): Int { 16 | val batteryInfoIntent = context.applicationContext.registerReceiver( 17 | null, 18 | IntentFilter(Intent.ACTION_BATTERY_CHANGED) 19 | ) ?: return 0 20 | val level = batteryInfoIntent.getIntExtra("level", 0) 21 | val batterySum = batteryInfoIntent.getIntExtra("scale", 100) 22 | val percentBattery = 100 * level / batterySum 23 | KLog.i(TAG, "手机当前电量------$percentBattery") 24 | return percentBattery 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.widget.Toast; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.annotation.StringRes; 10 | import androidx.annotation.UiThread; 11 | 12 | import cuiliang.quicker.BuildConfig; 13 | 14 | /** 15 | * Created by Void on 2020/4/12 15:23 16 | */ 17 | public final class ToastUtils { 18 | 19 | private static Toast toast; 20 | 21 | private ToastUtils() { 22 | throw new UnsupportedOperationException(); 23 | } 24 | 25 | @Nullable 26 | private static Toast getToast(Context context) { 27 | if (context == null) { 28 | return null; 29 | } 30 | if (toast == null) { 31 | Context applicationContext = context.getApplicationContext(); 32 | toast = Toast.makeText(applicationContext, "", Toast.LENGTH_SHORT); 33 | } 34 | return toast; 35 | } 36 | 37 | @UiThread 38 | public static void showShort(Context context, String message) { 39 | show(context, message, Toast.LENGTH_SHORT); 40 | } 41 | 42 | public static void showShortInMainThread(Context context, String message) { 43 | Handler handler = new Handler(Looper.getMainLooper()); 44 | handler.post(() -> showShort(context, message)); 45 | } 46 | 47 | public static void showLongInMainThread(Context context, String message) { 48 | Handler handler = new Handler(Looper.getMainLooper()); 49 | handler.post(() -> showLong(context, message)); 50 | } 51 | 52 | public static void debugToast(Context context, String msg) { 53 | if (BuildConfig.DEBUG) { 54 | Handler handler = new Handler(Looper.getMainLooper()); 55 | handler.post(() -> showShort(context, msg)); 56 | } 57 | } 58 | 59 | @UiThread 60 | public static void showShort(Context context, @StringRes int message) { 61 | show(context, message, Toast.LENGTH_SHORT); 62 | } 63 | 64 | @UiThread 65 | public static void showLong(Context context, String message) { 66 | show(context, message, Toast.LENGTH_LONG); 67 | } 68 | 69 | @UiThread 70 | public static void showLong(Context context, @StringRes int message) { 71 | show(context, message, Toast.LENGTH_LONG); 72 | } 73 | 74 | private static void show(Context context, String message, int duration) { 75 | Toast toast = getToast(context); 76 | if (toast != null) { 77 | toast.setText(message); 78 | toast.setDuration(duration); 79 | toast.show(); 80 | } 81 | } 82 | 83 | private static void show(Context context, @StringRes int messageRes, int duration) { 84 | Toast toast = getToast(context); 85 | if (toast != null) { 86 | toast.setText(messageRes); 87 | toast.setDuration(duration); 88 | toast.show(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/util/View.kt: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.util 2 | 3 | import android.os.Looper 4 | import android.util.TypedValue 5 | import android.view.View 6 | import cuiliang.quicker.QuickerApplication 7 | 8 | fun View.visible() { 9 | visibility = View.VISIBLE 10 | } 11 | 12 | fun View.gone() { 13 | visibility = View.GONE 14 | } 15 | 16 | internal fun View.setVisible(visible: Boolean) { 17 | visibility = if (visible) 18 | View.VISIBLE 19 | else 20 | View.GONE 21 | } 22 | 23 | fun dp2px(dpValue: Float): Float = QuickerApplication.displayMetrics?.let { 24 | TypedValue.applyDimension( 25 | TypedValue.COMPLEX_UNIT_DIP, 26 | dpValue, 27 | it 28 | ) 29 | } ?: 0f 30 | 31 | /** 32 | * 判断当前线程是否是主线程 33 | */ 34 | fun isMainThread() = Looper.myLooper() == Looper.getMainLooper() 35 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/view/DataPageContextView.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | /** 7 | * Created by Void on 2020/3/9 16:37 8 | * 上下文数据页面 9 | * 用于管理上下文page View的显示和数据更新等 10 | * 如当前页面索引,上下文页面总数量等 11 | */ 12 | public class DataPageContextView extends DataPageView { 13 | 14 | public DataPageContextView(Context context) { 15 | this(context, null); 16 | } 17 | 18 | public DataPageContextView(Context context, AttributeSet attrs) { 19 | this(context, attrs, 0); 20 | } 21 | 22 | public DataPageContextView(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | setColAndRowCount(4, 4); 25 | createActionButton(); 26 | } 27 | 28 | protected Integer getButtonIndex(int row, int col) { 29 | return 1000000 + row * 1000 + col; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/view/DataPageGlobalView.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.Configuration; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | 8 | /** 9 | * Created by Void on 2020/3/9 16:37 10 | * 上下文数据页面 11 | * 继承GridLayout是为了能够储存,该页面一些数据。 12 | * 如当前页面索引,全局页面总数量等 13 | */ 14 | public class DataPageGlobalView extends DataPageView { 15 | 16 | public DataPageGlobalView(Context context) { 17 | this(context, null); 18 | } 19 | 20 | public DataPageGlobalView(Context context, AttributeSet attrs) { 21 | this(context, attrs, 0); 22 | } 23 | 24 | public DataPageGlobalView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 27 | setColAndRowCount(3, 4); 28 | } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 29 | setColAndRowCount(3, 4); 30 | }else { 31 | Log.e("DataPageGlobalView","获取屏幕方向结果异常!"); 32 | } 33 | createActionButton(); 34 | } 35 | 36 | protected Integer getButtonIndex(int row, int col) { 37 | return row * 1000 + col; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/view/DataPageView.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.GridLayout; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | import cuiliang.quicker.R; 15 | import cuiliang.quicker.UiButtonItem; 16 | import cuiliang.quicker.client.ClientManager; 17 | 18 | /** 19 | * Created by Void on 2020/3/11 16:57 20 | * 将展示动作按钮数据的页面抽象, 21 | * 全局和上下文page的单独操作在其实现类里面进行 22 | */ 23 | public abstract class DataPageView extends GridLayout implements View.OnClickListener { 24 | 25 | protected static final String TAG = DataPageView.class.getSimpleName(); 26 | //存放page的按钮对象,用于更新按钮的图片和文字等 27 | public Map actionBtnArray = new HashMap<>(); 28 | //当前页面的行,用于布局 29 | public int currentPageRow = 0; 30 | //当前页面的列,用于布局 31 | public int currentPageCol = 0; 32 | 33 | public DataPageView(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public DataPageView(Context context, AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public DataPageView(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | } 44 | 45 | @Override 46 | public void onClick(View v) { 47 | int btnIndex = (int) v.getTag(); 48 | Log.d(TAG, "按钮触摸!" + btnIndex); 49 | ClientManager.getInstance().sendButtonClickMsg(btnIndex); 50 | } 51 | 52 | /** 53 | * 根据位置生成按钮索引 54 | */ 55 | abstract Integer getButtonIndex(int row, int col); 56 | 57 | protected void setColAndRowCount(int row, int col) { 58 | setRowCount(row); 59 | setColumnCount(col); 60 | this.currentPageRow = row; 61 | this.currentPageCol = col; 62 | Log.i("QuickPageUi", getClass().getSimpleName() + ";row:" + row + ";col:" + col); 63 | } 64 | 65 | /** 66 | * 根据屏幕方向创建按钮 67 | */ 68 | public void createActionButton() { 69 | if (!actionBtnArray.isEmpty()) actionBtnArray.clear(); 70 | for (int rowIndex = 0; rowIndex < currentPageRow; rowIndex++) 71 | for (int colIndex = 0; colIndex < currentPageCol; colIndex++) { 72 | View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_action_button, null); 73 | ViewGroup actionBtn = view.findViewById(R.id.actionBtn); 74 | actionBtn.setTag(getButtonIndex(rowIndex, colIndex)); 75 | actionBtn.setOnClickListener(this); 76 | GridLayout.LayoutParams gridLayoutParam = new GridLayout.LayoutParams( 77 | GridLayout.spec(rowIndex, 1f), 78 | GridLayout.spec(colIndex, 1f) 79 | ); 80 | gridLayoutParam.setMargins(1, 1, 1, 1); 81 | gridLayoutParam.height = 0; 82 | gridLayoutParam.width = 0; 83 | addView(view, gridLayoutParam); 84 | UiButtonItem item = new UiButtonItem(); 85 | item.button = actionBtn; 86 | item.imageView = view.findViewById(R.id.actionBtnBg); 87 | item.textView = view.findViewById(R.id.actionBtnText); 88 | actionBtnArray.put(getButtonIndex(rowIndex, colIndex), item); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/view/DataPageViewPager.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.viewpager.widget.ViewPager; 9 | 10 | import cuiliang.quicker.UiButtonItem; 11 | import cuiliang.quicker.adapter.GridLayoutAdapter; 12 | 13 | /** 14 | * Created by Void on 2020/3/19 13:16 15 | */ 16 | public class DataPageViewPager extends ViewPager { 17 | 18 | private ViewPagerCuePoint cuePointView; 19 | private GridLayoutAdapter dataPageAdapter; 20 | /*判断页面是代码切换还是手势手动切换是手势滑动切换page, 21 | 当调用setCurrentItem()方法时,为false*/ 22 | private boolean isGesture = false; 23 | //这个view是否是全局page 24 | private boolean isGlobal; 25 | 26 | public DataPageViewPager(@NonNull Context context) { 27 | this(context, null); 28 | } 29 | 30 | public DataPageViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { 31 | super(context, attrs); 32 | } 33 | 34 | public void initView(ViewPagerCuePoint view, final boolean isGlobal) { 35 | this.cuePointView = view; 36 | this.isGlobal = isGlobal; 37 | dataPageAdapter = new GridLayoutAdapter(); 38 | setAdapter(dataPageAdapter); 39 | /* 40 | * 注:在 ViewPager 内有个onPageScrolled方法,这与接口OnPageChangeListener中方法重名, 41 | * 这样会造成异常,并导致堆溢出,所以推荐这样写。 42 | * */ 43 | addOnPageChangeListener(new OnPageChangeListener() { 44 | @Override 45 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 46 | } 47 | 48 | @Override 49 | public void onPageSelected(int position) { 50 | if (isGesture) dataPageAdapter.updateDatePage(position, isGlobal); 51 | if (isGlobal) 52 | cuePointView.updateGlobalCuePoint(); 53 | else 54 | cuePointView.updateContextCuePoint(); 55 | } 56 | 57 | @Override 58 | public void onPageScrollStateChanged(int state) { 59 | if (state == ViewPager.SCROLL_STATE_IDLE) { 60 | isGesture = false; 61 | } else if (state == ViewPager.SCROLL_STATE_DRAGGING) { 62 | isGesture = true; 63 | } 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * 更新page页数。 70 | * 全局和上下文page的页数是不固定的,会根据场景变化。 71 | * viewpager更新page页数不会影响显示的数据,以为数据和page是分开的。 72 | * pc端会根据情况下发显示的action按钮数据 73 | * 74 | * @param num 总页数 75 | * @param index 当前page索引 76 | */ 77 | public void updatePage(int num, int index) { 78 | dataPageAdapter.createPages(getContext(), num, isGlobal); 79 | dataPageAdapter.notifyDataSetChanged(); 80 | setCurrentItem(index); 81 | if (isGlobal) 82 | cuePointView.updateGlobalCuePoint(); 83 | else 84 | cuePointView.updateContextCuePoint(); 85 | } 86 | 87 | public UiButtonItem getActionBtnObject(int index, int currentPageIndex) { 88 | return dataPageAdapter.getActionBtnObject(index, currentPageIndex); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/cuiliang/quicker/view/ViewPagerCuePoint.java: -------------------------------------------------------------------------------- 1 | package cuiliang.quicker.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.Gravity; 6 | import android.widget.ImageView; 7 | import android.widget.LinearLayout; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import cuiliang.quicker.R; 12 | import cuiliang.quicker.util.DataPageValues; 13 | 14 | /** 15 | * Created by Void on 2020/3/22 09:42 16 | * viewpager的导航点 17 | */ 18 | public class ViewPagerCuePoint extends LinearLayout { 19 | private LinearLayout gLayout; 20 | private LinearLayout cLayout; 21 | 22 | public ViewPagerCuePoint(Context context) { 23 | this(context, null); 24 | } 25 | 26 | public ViewPagerCuePoint(Context context, @Nullable AttributeSet attrs) { 27 | this(context, attrs, 0); 28 | } 29 | 30 | public ViewPagerCuePoint(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | gLayout = new LinearLayout(context); 33 | cLayout = new LinearLayout(context); 34 | if(getOrientation()==LinearLayout.HORIZONTAL) 35 | { 36 | float dpRatio = context.getResources().getDisplayMetrics().density; 37 | int pixelForDp = (int)(7 * dpRatio); 38 | LinearLayout.LayoutParams lpGlobal=new LinearLayout.LayoutParams(0,LayoutParams.WRAP_CONTENT); 39 | lpGlobal.weight=3; 40 | lpGlobal.setMargins(0,0,pixelForDp,0); 41 | gLayout.setLayoutParams(lpGlobal); 42 | //gLayout.setBackgroundColor(Color.RED); 43 | LinearLayout.LayoutParams lpContext=new LinearLayout.LayoutParams(0,LayoutParams.WRAP_CONTENT); 44 | lpContext.weight=4; 45 | lpContext.setMargins(pixelForDp,0,0,0); 46 | cLayout.setLayoutParams(lpContext); 47 | //cLayout.setBackgroundColor(Color.GREEN); 48 | } 49 | gLayout.setGravity(Gravity.CENTER); 50 | addView(gLayout); 51 | cLayout.setGravity(Gravity.CENTER); 52 | addView(cLayout); 53 | } 54 | 55 | /** 56 | * 更新全局页指示点 57 | */ 58 | public void updateGlobalCuePoint() { 59 | while (true) { 60 | int tmp = gLayout.getChildCount() - DataPageValues.globalDataPageCount; 61 | if (tmp > 0) { 62 | gLayout.removeViewAt(gLayout.getChildCount() - 1); 63 | } else if (tmp < 0) { 64 | addImageView(true); 65 | } else { 66 | break; 67 | } 68 | } 69 | refreshView(true); 70 | } 71 | 72 | /** 73 | * 更新上下文页面指示点 74 | */ 75 | public void updateContextCuePoint() { 76 | while (true) { 77 | int tmp = cLayout.getChildCount() - DataPageValues.contextDataPageCount; 78 | if (tmp > 0) { 79 | cLayout.removeViewAt(cLayout.getChildCount() - 1); 80 | } else if (tmp < 0) { 81 | addImageView(false); 82 | } else { 83 | break; 84 | } 85 | } 86 | refreshView(false); 87 | } 88 | 89 | /** 90 | * 更新指示点的ui 91 | */ 92 | private void refreshView(boolean isGlobal) { 93 | LinearLayout view = isGlobal ? gLayout : cLayout; 94 | int count = isGlobal ? DataPageValues.globalDataPageCount : DataPageValues.contextDataPageCount; 95 | int index = isGlobal ? DataPageValues.currentGlobalPageIndex : DataPageValues.currentContextPageIndex; 96 | try { 97 | for (int i = 0; i < count; i++) { 98 | if (i == index) { 99 | view.getChildAt(i).setBackgroundResource(R.drawable.ic_point_light); 100 | } else { 101 | view.getChildAt(i).setBackgroundResource(R.drawable.ic_point_dark); 102 | } 103 | } 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | /** 110 | * 生成一个imageView并添加进布局 111 | */ 112 | public void addImageView(boolean isGlobal) { 113 | ImageView image = new ImageView(getContext()); 114 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 115 | getResources().getDimensionPixelSize(R.dimen.d_5), 116 | getResources().getDimensionPixelSize(R.dimen.d_5) 117 | ); 118 | layoutParams.setMargins(10, 10, 10, 10); 119 | image.setLayoutParams(layoutParams); 120 | if (isGlobal) 121 | gLayout.addView(image); 122 | else 123 | cLayout.addView(image); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/res/animator/rotation.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/anim_load.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_btn_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_accept.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_battery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera_alt_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_fill.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_computer_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_desktop_windows_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_keyboard_voice_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_load.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock_open_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_point_dark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_point_light.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_qrcode_scan.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sync_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_task.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_textsms_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_down_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_mute_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_off_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_up_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_accept.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_cancel.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_task_item_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_qrcode_scan.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_share.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_task_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 17 | 18 | 21 | 22 | 31 | 32 | 44 | 45 | 53 | 54 | 63 | 64 | 72 | 73 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_task_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 18 | 19 | 27 | 28 | 29 | 30 | 34 | 35 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_my_task.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_action_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_event_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_input_auth_code.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_page_center_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 29 | 30 | 40 | 41 | 51 | 52 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_page_center_info_landscape.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 32 | 33 | 43 | 44 | 54 | 55 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_task_details_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 28 | 29 | 38 | 39 | 45 | 46 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_task_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 26 | 27 | 38 | 39 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_task_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_task_manager.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0B4F94 4 | #3F51B5 5 | #2473C3 6 | #9D9EA2 7 | 8 | #66000000 9 | #ff671d 10 | #dcdcdc 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5dp 4 | 10dp 5 | 15dp 6 | 20dp 7 | 30dp 8 | 40dp 9 | 50dp 10 | 60dp 11 | 70dp 12 | 100dp 13 | 45dp 14 | 16dp 15 | 16 | 16dp 17 | 16dp 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Quicker 3 | 选择照片 4 | 5 | 用户名: 6 | 7 | 请输入用户名 8 | 推送验证码: 9 | 10 | 请输入推送验证码 11 | 12 | 取消 13 | 确定 14 | 下次再说 15 | 用户分享信息配置 16 | 设置用户分享信息后,不在同一个局域网也可以分享。\n不设置用户分享信息时仅支持局域网分享。 17 | 暂时仅支持文字内容,如URL、文字等;图片、视频、文件等后续添加 18 | 19 | Quicker登录账号 20 | 推送验证码 21 | 电脑 IP: 22 | 端口: 23 | 24 | 添加任务 25 | 26 | 接受 27 | 请输入任务名称 28 | 29 | 如果 30 | 就执行 31 | 编辑任务 32 | 新建任务 33 | 34 | 电量低于50% 35 | 电量低于30% 36 | 电量低于20% 37 | 38 | MainActivity 39 | test1 40 | Home 41 | Dashboard 42 | Notifications 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 20 | 21 | 24 | 25 | 37 | 38 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/kotlin/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | import cuiliang.quicker.client.ClientConfig 2 | import cuiliang.quicker.network.websocket.WebSocketClient 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by Voidcom on 2023/9/11 20:50 7 | * 8 | * Description: TODO 9 | */ 10 | class ExampleUnitTest { 11 | @Test 12 | fun connect(){ 13 | ClientConfig.getInstance().enableHttps = true 14 | ClientConfig.getInstance().mServerHost = "192.168.1.100" 15 | ClientConfig.getInstance().mServerPort = "668" 16 | ClientConfig.getInstance().ConnectionCode = "aaa" 17 | WebSocketClient.instance().connectRequest { result, msg -> 18 | if (!result) 19 | println("服务连接失败:$msg") 20 | } 21 | //因为单元测试方法运行完会直接结束,而其他线程工作还没结束,这里加一个延时 22 | Thread.sleep(3000) 23 | } 24 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | plugins { 4 | id 'com.android.application' version "8.1.0-rc01" apply false 5 | id 'com.android.library' version "8.1.0-rc01" apply false 6 | id 'org.jetbrains.kotlin.android' version "1.7.20" apply false 7 | //https://developer.android.com/studio/build/migrate-to-ksp?hl=zh-cn#groovy 8 | id 'com.google.devtools.ksp' version '1.7.20-1.0.8' apply false 9 | id 'org.jetbrains.kotlin.jvm' version "1.7.20" apply false 10 | } 11 | 12 | allprojects { 13 | //全局设置编码 14 | tasks.withType(JavaCompile).tap { 15 | configureEach { 16 | options.encoding = "UTF-8" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | # 提高项目构建性能,启用JVM并行垃圾回收器 10 | # https://developer.android.google.cn/studio/build/optimize-your-build?hl=zh-cn#optimize 11 | org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC -Dkotlin.daemon.jvm.options\="-Xmx1024M" -Dfile.encoding\=UTF-8 12 | 13 | # When configured, Gradle will run in incubating parallel mode. 14 | # This option should only be used with decoupled projects. More details, visit 15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 16 | # org.gradle.parallel=true 17 | 18 | # androidx 19 | android.useAndroidX=true 20 | 21 | android.enableJetifier=false 22 | android.nonTransitiveRClass=true 23 | kotlin.code.style=official 24 | org.gradle.unsafe.configuration-cache=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 19 22:10:27 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /q_base/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /q_base/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.devtools.ksp' 5 | } 6 | 7 | android { 8 | namespace 'com.cuiliang.quicker' 9 | compileSdk 34 10 | 11 | defaultConfig { 12 | minSdk 23 13 | targetSdk 34 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_17 27 | targetCompatibility JavaVersion.VERSION_17 28 | } 29 | kotlinOptions { 30 | jvmTarget = '17' 31 | } 32 | buildFeatures { 33 | viewBinding = true 34 | } 35 | dataBinding { 36 | enabled = true 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | api 'androidx.core:core-ktx:1.9.0' 43 | api 'androidx.appcompat:appcompat:1.6.1' 44 | api 'com.google.android.material:material:1.9.0' 45 | testApi 'junit:junit:4.13.2' 46 | androidTestApi 'androidx.test.ext:junit:1.1.5' 47 | androidTestApi 'androidx.test.espresso:espresso-core:3.5.1' 48 | api "androidx.datastore:datastore-preferences:1.0.0" 49 | } -------------------------------------------------------------------------------- /q_base/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cuiliang/QuickerAndroid/342392b9d3a18753f31a0d2e8ab31a2e7bf4c765/q_base/consumer-rules.pro -------------------------------------------------------------------------------- /q_base/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /q_base/src/androidTest/java/com/cuiliang/quicker/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.cuiliang.quicker.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /q_base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | /** 8 | * Created by voidcom on 2023/10/7 9 | * 10 | */ 11 | abstract class BaseActivity : AppCompatActivity() { 12 | protected val mHandler: Handler by lazy { Handler(Looper.getMainLooper()) } 13 | 14 | protected abstract val mViewModel: VM 15 | 16 | abstract fun onInit() 17 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseComposableActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.os.Bundle 4 | import androidx.databinding.DataBindingUtil 5 | import androidx.databinding.ViewDataBinding 6 | 7 | /** 8 | * Created by voidcom on 2023/10/7 9 | * 10 | */ 11 | abstract class BaseComposableActivity : BaseActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | mViewModel.onInit(this) 16 | mViewModel.onInitData() 17 | } 18 | 19 | override fun onInit() { 20 | } 21 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseDBActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.os.Bundle 4 | import androidx.databinding.DataBindingUtil 5 | import androidx.databinding.ViewDataBinding 6 | 7 | /** 8 | * Created by voidcom on 2023/10/7 9 | * DataBinding 10 | */ 11 | abstract class BaseDBActivity : BaseActivity() { 12 | protected val mBinding: DB by lazy { DataBindingUtil.setContentView(this, getLayoutID()) } 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | mBinding.lifecycleOwner = this 17 | mViewModel.vmBinding = mBinding 18 | onInit() 19 | mViewModel.onInit(this) 20 | mViewModel.onInitData() 21 | } 22 | 23 | abstract fun getLayoutID(): Int 24 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseModel.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | /** 4 | * Created by voidcom on 2023/10/7 5 | * 6 | */ 7 | abstract class BaseModel -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseVBActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.os.Bundle 4 | import androidx.viewbinding.ViewBinding 5 | import com.cuiliang.quicker.utils.BindingReflex 6 | 7 | /** 8 | * Created by voidcom on 2023/10/7 9 | * ViewBinding 10 | */ 11 | abstract class BaseVBActivity : BaseActivity() { 12 | 13 | protected val mBinding: VB by lazy(LazyThreadSafetyMode.NONE) { 14 | BindingReflex.reflexViewBinding(javaClass, layoutInflater) 15 | } 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(mBinding.root) 20 | mViewModel.vmBinding = mBinding 21 | onInit() 22 | mViewModel.onInit(this) 23 | mViewModel.onInitData() 24 | } 25 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.ViewModel 5 | import androidx.viewbinding.ViewBinding 6 | 7 | /** 8 | * Created by voidcom on 2023/10/7 9 | * 10 | */ 11 | abstract class BaseViewModel : ViewModel() { 12 | lateinit var vmBinding: ViewBinding 13 | 14 | abstract val model: BaseModel? 15 | 16 | abstract fun onInit(context: Context) 17 | 18 | abstract fun onInitData() 19 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/ui/EmptyViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.ui 2 | 3 | import android.content.Context 4 | 5 | /** 6 | * Created by voidcom on 2023/10/7 7 | * 8 | */ 9 | class EmptyViewModel : BaseViewModel() { 10 | override val model: BaseModel? = null 11 | 12 | override fun onInit(context: Context) { 13 | } 14 | 15 | override fun onInitData() { 16 | } 17 | } -------------------------------------------------------------------------------- /q_base/src/main/java/com/cuiliang/quicker/utils/BindingReflex.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker.utils 2 | 3 | import android.view.LayoutInflater 4 | import androidx.viewbinding.ViewBinding 5 | import java.lang.reflect.InvocationTargetException 6 | import java.lang.reflect.ParameterizedType 7 | import java.util.Objects 8 | 9 | /** 10 | * Created by voidcom on 2023/10/7 11 | * 12 | */ 13 | object BindingReflex { 14 | 15 | /** 16 | * 反射获取ViewBinding 17 | * 18 | * @param ViewBinding 实现类 19 | * @param aClass 当前类 20 | * @param from layouinflater 21 | * @return viewBinding实例 22 | */ 23 | fun reflexViewBinding(aClass: Class<*>, from: LayoutInflater): V { 24 | try { 25 | val actualTypeArguments = 26 | (Objects.requireNonNull(aClass.genericSuperclass) as ParameterizedType).actualTypeArguments 27 | for (i in actualTypeArguments.indices) { 28 | val tClass: Class<*> 29 | try { 30 | tClass = actualTypeArguments[i] as Class<*> 31 | } catch (e: Exception) { 32 | continue 33 | } 34 | if (ViewBinding::class.java.isAssignableFrom(tClass)) { 35 | val inflate = tClass.getMethod("inflate", LayoutInflater::class.java) 36 | return inflate.invoke(null, from) as V 37 | } 38 | } 39 | return reflexViewBinding(aClass.superclass, from) 40 | } catch (e: NoSuchMethodException) { 41 | e.printStackTrace() 42 | } catch (e: IllegalAccessException) { 43 | e.printStackTrace() 44 | } catch (e: InvocationTargetException) { 45 | e.printStackTrace() 46 | } 47 | throw RuntimeException("ViewBinding初始化失败") 48 | } 49 | } -------------------------------------------------------------------------------- /q_base/src/test/java/com/cuiliang/quicker/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.cuiliang.quicker 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url 'https://maven.aliyun.com/repository/google' } 4 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 5 | maven { url "https://jitpack.io" } 6 | google() 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } 11 | dependencyResolutionManagement { 12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 13 | repositories { 14 | //阿里云开源代码库,用于解决网络无法访问墙外代码库的情况 15 | //因为查找库时是按配置的maven顺序的,所以不要吧google()和mavenCentral()放到前面 16 | maven { url 'https://maven.aliyun.com/repository/google' } 17 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 18 | maven { url 'https://jitpack.io' } 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | include ':app' 24 | //include ':testapplication' 25 | include ':q_base' 26 | --------------------------------------------------------------------------------