diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml index 00ecf65..81148c9 100644 --- a/android/.idea/gradle.xml +++ b/android/.idea/gradle.xml @@ -13,6 +13,8 @@ diff --git a/android/Readme.md b/android/Readme.md index 6cd1893..8e9e348 100644 --- a/android/Readme.md +++ b/android/Readme.md @@ -1,32 +1,34 @@ # PushDeer for Android - - ### 适配进度 * MiPush(状态:已调通、已接入) - * miui 12.5.6 正常 - * 原生、类原生 可成功注册,无法收到推送,可能和地区识别有关,待解决 - * miui下处于"几乎可用"的状态,已接入 appleId + * miui √ + * Mokee √ + * HuaWei √ + * 部分原生、类原生 可成功注册,无法收到推送,可能和地区识别有关,待解决 ### TODO * ~~调通 MiPush~~ * ~~接入 MiPush~~ * 完善 log ~~采集~~ 回传机制,便于调试 -* 测试不同厂商设备上的通知推送效果 +* 测试不同厂商设备/系统上的通知推送效果 miui√ Mokee√ HuaWei√ * ~~接入 PushDeer~~ * ~~界面设计:BottomBar+Navigation(Device Key Message Setting)~~ * 调整 KeyList MessageList 等处的自定义绘制 * ~~增加 DeviceList 外的侧滑手势~~ * ~~增加侧滑手势相关动作~~ -* 增加各种操作前的二次确认弹窗,包括自动登陆 -* 增加非Miui设备的权限获取 -* 增加手动修改服务器地址/退出登陆的逻辑(并放置到登陆界面) +* 增加手动修改服务器地址逻辑(并放置到登陆界面) +* ~~增加退出登陆的逻辑~~ * 增加登陆过程中的图形提示 -* 增加对PushKey重命名的逻辑 -* 增加对设备重命名的逻辑 -* 增加长按复制消息内容的逻辑 +* ~~增加对PushKey重命名的逻辑~~ +* ~~增加对设备重命名的逻辑~~ +* ~~增加长按复制消息内容的逻辑~~ +* 增加点击设置界面用户名修改用户名逻辑 +* ~~适配image类型的消息显示~~ +* ~~修改app图标~~ + ### 日志 @@ -105,6 +107,12 @@ * 修改登陆界面ui * 增加key/device的重命名逻辑 * 适配英语和中文 + * 增加点击PushKey显示二维码功能 + +* 2022-01-22 + * 增加点击 Message 列表项目复制文本功能 + * 增加适配Image类型Message的显示 + * 完善登陆注销逻辑 ### 感谢 diff --git a/android/app/build.gradle b/android/app/build.gradle index 365c115..b00d603 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,11 +91,12 @@ dependencies { implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" + // qr implementation 'com.google.zxing:core:3.2.1' implementation 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar' implementation 'cn.bingoogolapple:bga-zxing:1.1.7@aar' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' final def markwon_version = '4.6.2' implementation "io.noties.markwon:core:$markwon_version" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1bd89e6..9bf459d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,12 +4,13 @@ package="com.pushdeer.os"> + - - + + + @@ -18,8 +19,9 @@ android:protectionLevel="signature" /> - + + @@ -49,11 +51,11 @@ - + - + - diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..f745bf0 Binary files /dev/null and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/java/com/pushdeer/os/MainActivity.kt b/android/app/src/main/java/com/pushdeer/os/MainActivity.kt index 740d9cc..c840559 100644 --- a/android/app/src/main/java/com/pushdeer/os/MainActivity.kt +++ b/android/app/src/main/java/com/pushdeer/os/MainActivity.kt @@ -5,11 +5,8 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.util.Linkify -import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.ExperimentalAnimationApi @@ -29,7 +26,6 @@ import coil.ImageLoader import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.statusBarsPadding import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.pushdeer.os.activity.QrScanActivity import com.pushdeer.os.holder.RequestHolder import com.pushdeer.os.store.SettingStore import com.pushdeer.os.ui.compose.componment.MyAlertDialog @@ -37,12 +33,12 @@ import com.pushdeer.os.ui.compose.page.LogDogPage import com.pushdeer.os.ui.compose.page.LoginPage import com.pushdeer.os.ui.compose.page.main.MainPage import com.pushdeer.os.ui.theme.PushDeerTheme +import com.pushdeer.os.util.ActivityOpener import com.pushdeer.os.util.SystemUtil import com.pushdeer.os.viewmodel.LogDogViewModel import com.pushdeer.os.viewmodel.MessageViewModel import com.pushdeer.os.viewmodel.PushDeerViewModel import com.pushdeer.os.viewmodel.UiViewModel -import com.wh.common.util.UiUtils import io.noties.markwon.Markwon import io.noties.markwon.image.coil.CoilImagesPlugin import io.noties.markwon.linkify.LinkifyPlugin @@ -62,18 +58,36 @@ class MainActivity : AppCompatActivity(), RequestHolder { override val messageViewModel: MessageViewModel by viewModels { viewModelFactory } override val settingStore: SettingStore by lazy { (application as App).storeKeeper.settingStore } override val fragmentManager: FragmentManager by lazy { this.supportFragmentManager } -// override val resource: Resources by lazy { this.resource } override val coilImageLoader: ImageLoader by lazy { ImageLoader.Builder(this) .apply { availableMemoryPercentage(0.5) bitmapPoolPercentage(0.5) - crossfade(true) + crossfade(750) + allowHardware(true) } .build() } - override val alert: RequestHolder.AlertRequest by lazy { object : RequestHolder.AlertRequest(resources) {} } + override val alert: RequestHolder.AlertRequest by lazy { + object : RequestHolder.AlertRequest(resources) {} + } + override val key: RequestHolder.KeyRequest by lazy { + object : RequestHolder.KeyRequest(this) {} + } + override val device: RequestHolder.DeviceRequest by lazy { + object : RequestHolder.DeviceRequest(this) {} + } + override val message: RequestHolder.MessageRequest by lazy { + object : RequestHolder.MessageRequest(this) {} + } + override val clip: RequestHolder.ClipRequest by lazy { + object : RequestHolder.ClipRequest( + getSystemService( + Context.CLIPBOARD_SERVICE + ) as ClipboardManager + ) {} + } override val markdown: Markwon by lazy { Markwon.builder(this) @@ -84,10 +98,9 @@ class MainActivity : AppCompatActivity(), RequestHolder { override lateinit var globalNavController: NavHostController override lateinit var coroutineScope: CoroutineScope - override lateinit var myActivity: ComponentActivity - override val clipboardManager by lazy { getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } - - override lateinit var activityOpener: ActivityResultLauncher + override lateinit var myActivity: AppCompatActivity + override lateinit var qrScanActivityOpener: ActivityResultLauncher + override lateinit var requestPermissionOpener: ActivityResultLauncher> @ExperimentalAnimationApi @ExperimentalMaterialApi @@ -95,28 +108,23 @@ class MainActivity : AppCompatActivity(), RequestHolder { super.onCreate(savedInstanceState) myActivity = this - activityOpener = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - Toast.makeText( - this, - "${result.data?.getStringExtra(QrScanActivity.DataKey)}", - Toast.LENGTH_SHORT - ).show() - } + qrScanActivityOpener = ActivityOpener.forResult(this) + requestPermissionOpener = ActivityOpener.forPermission(this) - UiUtils.keepScreenOn(window) setContent { globalNavController = rememberNavController() coroutineScope = rememberCoroutineScope() val useDarkIcons = MaterialTheme.colors.isLight val systemUiController = rememberSystemUiController() when { - SystemUtil.isMiui() -> systemUiController.setStatusBarColor(Color.Transparent, useDarkIcons) + SystemUtil.isMiui() -> systemUiController.setStatusBarColor( + Color.Transparent, + useDarkIcons + ) else -> systemUiController.setSystemBarsColor(Color.Transparent, useDarkIcons) } WindowCompat.setDecorFitsSystemWindows(window, true) miPushRepository.regId.observe(this) { - // 这个操作放到注册成功后进行 settingStore.thisDeviceId = it } diff --git a/android/app/src/main/java/com/pushdeer/os/activity/QrScanActivity.kt b/android/app/src/main/java/com/pushdeer/os/activity/QrScanActivity.kt index 5aaaae1..fa265a3 100644 --- a/android/app/src/main/java/com/pushdeer/os/activity/QrScanActivity.kt +++ b/android/app/src/main/java/com/pushdeer/os/activity/QrScanActivity.kt @@ -1,66 +1,66 @@ -package com.pushdeer.os.activity - - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.util.Log -import androidx.appcompat.app.AppCompatActivity -import cn.bingoogolapple.qrcode.core.QRCodeView -import com.pushdeer.os.R - - -class QrScanActivity : AppCompatActivity(), QRCodeView.Delegate { - - private val TAG = "WH_" + javaClass.simpleName - private lateinit var qrCode: QRCodeView - - companion object { - val RequestCode_get_scan_result = 436 - val DataKey = "qr_scan_result" - - fun forScanResultIntent(context: Context): Intent { - return Intent(context, QrScanActivity::class.java).apply { - putExtra(DataKey, 1) - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_qr_scan) - qrCode = findViewById(R.id.qrcode1) - qrCode.setDelegate(this) - } - - override fun onStart() { - super.onStart() - Log.d(TAG, "onStart") - qrCode.startSpotAndShowRect() - } - - override fun onStop() { - Log.d(TAG, "onStop") - qrCode.stopCamera() - super.onStop() - } - - override fun onDestroy() { - qrCode.onDestroy() - super.onDestroy() - } - - override fun onScanQRCodeSuccess(result: String?) { - Log.d(TAG, "onScanQRCodeSuccess: $result") - qrCode.stopCamera() - val intent = Intent() - intent.putExtra(DataKey, result) - setResult(RequestCode_get_scan_result, intent) - finish() - } - - override fun onScanQRCodeOpenCameraError() { - Log.e(TAG, "onScanQRCodeOpenCameraError") - qrCode.startSpotAndShowRect() - } -} \ No newline at end of file +//package com.pushdeer.os.activity +// +// +//import android.content.Context +//import android.content.Intent +//import android.os.Bundle +//import android.util.Log +//import androidx.appcompat.app.AppCompatActivity +//import cn.bingoogolapple.qrcode.core.QRCodeView +//import com.pushdeer.os.R +// +// +//class QrScanActivity : AppCompatActivity(), QRCodeView.Delegate { +// +// private val TAG = "WH_" + javaClass.simpleName +// private lateinit var qrCode: QRCodeView +// +// companion object { +// val RequestCode_get_scan_result = 436 +// val DataKey = "qr_scan_result" +// +// fun forScanResultIntent(context: Context): Intent { +// return Intent(context, QrScanActivity::class.java).apply { +// putExtra(DataKey, 1) +// } +// } +// } +// +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// setContentView(R.layout.activity_qr_scan) +// qrCode = findViewById(R.id.qrcode1) +// qrCode.setDelegate(this) +// } +// +// override fun onStart() { +// super.onStart() +// Log.d(TAG, "onStart") +// qrCode.startSpotAndShowRect() +// } +// +// override fun onStop() { +// Log.d(TAG, "onStop") +// qrCode.stopCamera() +// super.onStop() +// } +// +// override fun onDestroy() { +// qrCode.onDestroy() +// super.onDestroy() +// } +// +// override fun onScanQRCodeSuccess(result: String?) { +// Log.d(TAG, "onScanQRCodeSuccess: $result") +// qrCode.stopCamera() +// val intent = Intent() +// intent.putExtra(DataKey, result) +// setResult(RequestCode_get_scan_result, intent) +// finish() +// } +// +// override fun onScanQRCodeOpenCameraError() { +// Log.e(TAG, "onScanQRCodeOpenCameraError") +// qrCode.startSpotAndShowRect() +// } +//} \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt b/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt index d6640a0..5fc747e 100644 --- a/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt +++ b/android/app/src/main/java/com/pushdeer/os/holder/RequestHolder.kt @@ -4,9 +4,9 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Intent import android.content.res.Resources -import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -15,7 +15,6 @@ import androidx.fragment.app.FragmentManager import androidx.navigation.NavHostController import coil.ImageLoader import com.pushdeer.os.R -import com.pushdeer.os.activity.QrScanActivity import com.pushdeer.os.data.api.data.request.DeviceInfo import com.pushdeer.os.data.api.data.response.Message import com.pushdeer.os.data.api.data.response.PushKey @@ -24,6 +23,7 @@ import com.pushdeer.os.viewmodel.LogDogViewModel import com.pushdeer.os.viewmodel.MessageViewModel import com.pushdeer.os.viewmodel.PushDeerViewModel import com.pushdeer.os.viewmodel.UiViewModel +import com.wh.common.activity.QrScanActivity import io.noties.markwon.Markwon import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -37,106 +37,22 @@ interface RequestHolder { val settingStore: SettingStore val globalNavController: NavHostController val coroutineScope: CoroutineScope - val myActivity: ComponentActivity + val myActivity: AppCompatActivity val markdown: Markwon - val activityOpener: ActivityResultLauncher + val qrScanActivityOpener: ActivityResultLauncher + val requestPermissionOpener:ActivityResultLauncher> val coilImageLoader: ImageLoader -// val resource:Resources - val fragmentManager: FragmentManager val alert: AlertRequest - - val clipboardManager: ClipboardManager - - fun copyPlainString(str: String) { - clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-pushkey", str)) - } + val key:KeyRequest + val device:DeviceRequest + val message:MessageRequest + val clip:ClipRequest fun startQrScanActivity() { - activityOpener.launch(QrScanActivity.forScanResultIntent(myActivity)) - } - - fun keyGen() { - coroutineScope.launch { - pushDeerViewModel.keyGen() - } - } - - fun keyRegen(keyId: String) { - coroutineScope.launch { - pushDeerViewModel.keyRegen(keyId) - } - } - - fun keyRemove(pushKey: PushKey) { - coroutineScope.launch { - pushDeerViewModel.keyList.remove(pushKey) - pushDeerViewModel.keyRemove(pushKey.id) - } - } - - fun keyRename(pushKey: PushKey){ - coroutineScope.launch { - pushDeerViewModel.keyRename(pushKey){ - coroutineScope.launch { - pushDeerViewModel.keyList() - } - } - } - } - - fun deviceReg(deviceInfo: DeviceInfo) { - coroutineScope.launch { - pushDeerViewModel.deviceReg(deviceInfo) - } - } - - fun deviceRemove(deviceInfo: DeviceInfo) { - coroutineScope.launch { - pushDeerViewModel.deviceList.remove(deviceInfo) - pushDeerViewModel.deviceRemove(deviceInfo.id) - } - } - - fun deviceRename(deviceInfo: DeviceInfo) { - coroutineScope.launch { - pushDeerViewModel.deviceRename(deviceInfo) { - coroutineScope.launch { - pushDeerViewModel.deviceList() - } - } - } - } - - fun messagePush(text: String, desp: String, type: String, pushkey: String) { - coroutineScope.launch { - pushDeerViewModel.messagePush(text, desp, type, pushkey) - } - } - - fun messagePushTest(text: String) { - if (pushDeerViewModel.keyList.isNotEmpty()) { - messagePush(text, "pushtest", "markdown", pushDeerViewModel.keyList[0].key) - coroutineScope.launch { - delay(1000) - pushDeerViewModel.messageList() - } - } else { - alert.alert( - R.string.global_alert_title_alert, - R.string.main_message_send_alert, - onOk = {}) - } - } - - fun messageRemove(message: Message, onDone: () -> Unit = {}) { - coroutineScope.launch { -// pushDeerViewModel.messageList.remove(message) - pushDeerViewModel.messageRemove(message.id) - onDone() - } + qrScanActivityOpener.launch(QrScanActivity.forScanResultIntent(myActivity)) } fun toggleMessageSender() { @@ -145,8 +61,20 @@ interface RequestHolder { } fun clearLogDog() { - coroutineScope.launch { - logDogViewModel.clear() + alert.alert(R.string.global_alert_title_confirm,"Clear?",onOk = { + coroutineScope.launch { + logDogViewModel.clear() + } + }) + } + + abstract class ClipRequest(private val clipboardManager: ClipboardManager) { + fun copyMessagePlainText(str: String) { + clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-copy-plain-text", str)) + } + + fun copyPushKey(str: String){ + clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-copy-pushkey", str)) } } @@ -188,9 +116,105 @@ interface RequestHolder { @StringRes title: Int, content: @Composable () -> Unit, onOk: () -> Unit, - onCancel: () -> Unit={} + onCancel: () -> Unit = {} + ) { + alert(resources.getString(title), content, onOk, onCancel) + } + + fun alert( + @StringRes title: Int, + content: String, + onOk: () -> Unit, + onCancel: () -> Unit = {} ) { alert(resources.getString(title), content, onOk, onCancel) } } + + abstract class KeyRequest(private val requestHolder: RequestHolder){ + fun gen() { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.keyGen() + } + } + + fun regen(keyId: String) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.keyRegen(keyId) + } + } + + fun remove(pushKey: PushKey) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.keyList.remove(pushKey) + requestHolder.pushDeerViewModel.keyRemove(pushKey.id) + } + } + + fun rename(pushKey: PushKey) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.keyRename(pushKey) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.keyList() + } + } + } + } + } + + abstract class DeviceRequest(private val requestHolder: RequestHolder){ + fun deviceReg(deviceInfo: DeviceInfo) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.deviceReg(deviceInfo) + } + } + + fun deviceRemove(deviceInfo: DeviceInfo) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.deviceList.remove(deviceInfo) + requestHolder.pushDeerViewModel.deviceRemove(deviceInfo.id) + } + } + + fun deviceRename(deviceInfo: DeviceInfo) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.deviceRename(deviceInfo) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.deviceList() + } + } + } + } + } + + abstract class MessageRequest(private val requestHolder: RequestHolder){ + fun messagePush(text: String, desp: String, type: String, pushkey: String) { + requestHolder.coroutineScope.launch { + requestHolder.pushDeerViewModel.messagePush(text, desp, type, pushkey) + } + } + + fun messagePushTest(text: String) { + if (requestHolder.pushDeerViewModel.keyList.isNotEmpty()) { + messagePush(text, "pushtest", "markdown", requestHolder.pushDeerViewModel.keyList[0].key) + requestHolder.coroutineScope.launch { + delay(1000) + requestHolder.pushDeerViewModel.messageList() + } + } else { + requestHolder.alert.alert( + R.string.global_alert_title_alert, + R.string.main_message_send_alert, + onOk = {}) + } + } + + fun messageRemove(message: Message, onDone: () -> Unit = {}) { + requestHolder.coroutineScope.launch { +// pushDeerViewModel.messageList.remove(message) + requestHolder.pushDeerViewModel.messageRemove(message.id) + onDone() + } + } + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt index d0fa3dd..418ecf6 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/Item.kt @@ -108,7 +108,7 @@ fun CardItemMultiLine( @ExperimentalMaterialApi @Composable -fun CardItemWithContent(onClick: () -> Unit = {}, content: @Composable () -> Unit = {}) { +fun CardItemWithContent(onClick: () -> Unit, content: @Composable () -> Unit = {}) { Card( onClick = onClick, shape = RoundedCornerShape(4.dp), @@ -123,6 +123,22 @@ fun CardItemWithContent(onClick: () -> Unit = {}, content: @Composable () -> Uni ) } +@ExperimentalMaterialApi +@Composable +fun CardItemWithContent(content: @Composable () -> Unit = {}) { + Card( + shape = RoundedCornerShape(4.dp), + modifier = Modifier + .border( + width = 1.dp, + color = MainBlue, + shape = RoundedCornerShape(4.dp) + ), + content = content, + elevation = 5.dp + ) +} + @Composable fun ListBottomBlankItem() { Row( diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt index d1b2b51..517c78c 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/KeyItem.kt @@ -1,8 +1,10 @@ package com.pushdeer.os.ui.compose.componment +import android.widget.ImageView import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -17,15 +19,17 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView import com.pushdeer.os.R import com.pushdeer.os.data.api.data.response.PushKey import com.pushdeer.os.holder.RequestHolder import com.pushdeer.os.ui.theme.MBlue +import com.wh.common.util.QRCodeGenerator import com.wh.common.util.TimeUtils @ExperimentalMaterialApi @Composable -fun KeyItem(key: PushKey,requestHolder: RequestHolder) { +fun KeyItem(key: PushKey, requestHolder: RequestHolder) { var name by remember { mutableStateOf(key.name) } @@ -52,7 +56,7 @@ fun KeyItem(key: PushKey,requestHolder: RequestHolder) { }, onOk = { key.name = name - requestHolder.keyRename(key) + requestHolder.key.rename(key) } ) }) { @@ -118,24 +122,36 @@ fun KeyItem(key: PushKey,requestHolder: RequestHolder) { color = Color.Gray, shape = RoundedCornerShape(4.dp) ) + .clickable { + requestHolder.alert.alert("QrCode For ${key.name}", { + Box( + modifier = Modifier.width(400.dp) + ) { + AndroidView( + factory = { + ImageView(it).apply { + this.setImageBitmap( + QRCodeGenerator( + key.key, + 400.dp.value.toInt(), + 400.dp.value.toInt() + ).qrCode + ) + } + }, + modifier = Modifier.align(alignment = Alignment.Center) + ) + } + }, onOk = {}) + } .padding(horizontal = 14.dp, vertical = 8.dp) ) -// Canvas(modifier = Modifier -// .fillMaxWidth() -// .height(16.dp), onDraw = { -// val linePath = Path() -// val linePaint = Paint() -// linePaint.pathEffect = PathEffect.dashPathEffect(FloatArray(10),10F) -// drawIntoCanvas { -// it.drawPath(linePath, linePaint) -// } -// }) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { OutlinedButton( - onClick = { requestHolder.keyRegen(key.id) }, + onClick = { requestHolder.key.regen(key.id) }, colors = ButtonDefaults.outlinedButtonColors( backgroundColor = Color.Transparent, contentColor = MaterialTheme.colors.MBlue @@ -147,7 +163,7 @@ fun KeyItem(key: PushKey,requestHolder: RequestHolder) { } Button( onClick = { - requestHolder.copyPlainString(key.key) + requestHolder.clip.copyPushKey(key.key) }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.MBlue, diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt index d10a873..c8ca6a2 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/componment/MessageItem.kt @@ -1,7 +1,9 @@ package com.pushdeer.os.ui.compose.componment +import android.widget.ImageView import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card @@ -12,12 +14,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView +import coil.load import com.pushdeer.os.R import com.pushdeer.os.data.database.entity.MessageEntity import com.pushdeer.os.holder.RequestHolder @@ -27,11 +29,14 @@ import com.pushdeer.os.values.ConstValues @ExperimentalMaterialApi @Composable -fun PlainTextMessageItem(message: MessageEntity) { +fun PlainTextMessageItem(message: MessageEntity,requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(4.dp)) + .clickable { + requestHolder.clip.copyMessagePlainText(message.text) + } .background(color = MaterialTheme.colors.surface) ) { @@ -47,7 +52,7 @@ fun PlainTextMessageItem(message: MessageEntity) { modifier = Modifier.size(40.dp) ) Text( - text = "${message.text}·${ + text = "${message.pushkey_name}·${ CurrentTimeUtil.resolveUTCTimeAndNow( message.created_at, System.currentTimeMillis() @@ -56,9 +61,9 @@ fun PlainTextMessageItem(message: MessageEntity) { ) } - CardItemWithContent() { + CardItemWithContent { Text( - text = message.desp, + text = message.text, overflow = TextOverflow.Visible, lineHeight = 24.sp, modifier = Modifier @@ -71,12 +76,15 @@ fun PlainTextMessageItem(message: MessageEntity) { @ExperimentalMaterialApi @Composable -fun ImageMessageItem(message: MessageEntity) { +fun ImageMessageItem(message: MessageEntity, requestHolder: RequestHolder) { Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(4.dp)) - .background (color = MaterialTheme.colors.surface) + .clickable { + requestHolder.clip.copyMessagePlainText(message.text) + } + .background(color = MaterialTheme.colors.surface) ) { Row( @@ -92,7 +100,7 @@ fun ImageMessageItem(message: MessageEntity) { modifier = Modifier.size(40.dp) ) Text( - text = "${message.text}·${ + text = "${message.pushkey_name}·${ CurrentTimeUtil.resolveUTCTimeAndNow( message.created_at, System.currentTimeMillis() @@ -100,12 +108,13 @@ fun ImageMessageItem(message: MessageEntity) { }" ) } - Card(modifier = Modifier.fillMaxWidth(), onClick = {}) { - Image( - painter = painterResource(id = R.drawable.logo_com_x2), - contentDescription = "", - contentScale = ContentScale.FillWidth - ) + Card(modifier = Modifier.fillMaxWidth()) { + AndroidView(factory = { + ImageView(it).apply { + scaleType = ImageView.ScaleType.FIT_CENTER + load(message.text, requestHolder.coilImageLoader) + } + }, modifier = Modifier.fillMaxWidth()) } } } @@ -117,6 +126,9 @@ fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) { modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(4.dp)) + .clickable { + requestHolder.clip.copyMessagePlainText(message.text) + } .background(color = MaterialTheme.colors.surface) ) { @@ -163,6 +175,13 @@ fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) { }, modifier = Modifier .fillMaxWidth() +// .pointerInput(Unit) { +// this.detectTapGestures( +// onLongPress = { +// Log.d("WH_", "MarkdownMessageItem: ") +// } +// ) +// } .padding(16.dp) ) } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt index c636595..cfb79d6 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/LoginPage.kt @@ -59,7 +59,7 @@ fun LoginPage(requestHolder: RequestHolder) { } } is SignInWithAppleResult.Failure -> { - requestHolder.alert.alert("Warning", { + requestHolder.alert.alert("Warning Apple Id Login Failed", { result.error.message }, onOk = {}) Log.d( diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt index 933c3d7..1b320ad 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/DeviceListPage.kt @@ -1,6 +1,5 @@ package com.pushdeer.os.ui.compose.page.main -import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -41,7 +40,7 @@ fun DeviceListPage(requestHolder: RequestHolder) { onOk = {}) // device regid got failed } else { - requestHolder.deviceReg( + requestHolder.device.deviceReg( deviceInfo = DeviceInfo().apply { name = SystemUtil.getDeviceModel() device_id = requestHolder.settingStore.thisDeviceId @@ -72,7 +71,7 @@ fun DeviceListPage(requestHolder: RequestHolder) { mutableStateOf(deviceInfo.name) } SwipeToDismissItem( - onAction = { requestHolder.deviceRemove(deviceInfo) } + onAction = { requestHolder.device.deviceRemove(deviceInfo) } ) { CardItemSingleLineWithIcon( onClick = { @@ -99,7 +98,7 @@ fun DeviceListPage(requestHolder: RequestHolder) { }, onOk = { deviceInfo.name = name - requestHolder.deviceRename(deviceInfo) + requestHolder.device.deviceRename(deviceInfo) } ) }, @@ -110,7 +109,7 @@ fun DeviceListPage(requestHolder: RequestHolder) { ) }) " else deviceInfo.name ) - Log.d("WH_", "DeviceListPage: $deviceInfo") +// Log.d("WH_", "DeviceListPage: $deviceInfo") } } item { diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt index f1b97c3..d531aca 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/KeyListPage.kt @@ -25,7 +25,7 @@ import com.pushdeer.os.ui.navigation.Page fun KeyListPage(requestHolder: RequestHolder) { MainPageFrame( titleStringId = Page.Keys.labelStringId, - onSideIconClick = { requestHolder.keyGen() } + onSideIconClick = { requestHolder.key.gen() } ) { if(requestHolder.pushDeerViewModel.keyList.isEmpty()){ Column( @@ -42,7 +42,7 @@ fun KeyListPage(requestHolder: RequestHolder) { items( requestHolder.pushDeerViewModel.keyList, key = { item: PushKey -> item.id }) { pushKey: PushKey -> - SwipeToDismissItem(onAction = { requestHolder.keyRemove(pushKey) } + SwipeToDismissItem(onAction = { requestHolder.key.remove(pushKey) } ) { KeyItem(key = pushKey, requestHolder = requestHolder) } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPage.kt index 543f5b0..287fdfa 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MainPage.kt @@ -43,7 +43,7 @@ fun MainPage(requestHolder: RequestHolder) { } var titleStringId by remember { - mutableStateOf(Page.Devices.labelStringId) + mutableStateOf(Page.Messages.labelStringId) } val navController = rememberNavController() Scaffold( @@ -98,7 +98,7 @@ fun MainPage(requestHolder: RequestHolder) { ) NavHost( navController = navController, - startDestination = Page.Devices.route, + startDestination = Page.Messages.route, ) { composable(Page.Devices.route) { DeviceListPage(requestHolder = requestHolder) diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt index 4265f5f..6c22481 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/MessageListPage.kt @@ -73,7 +73,7 @@ fun MessageListPage(requestHolder: RequestHolder) { ) Button( onClick = { - requestHolder.messagePushTest(s) + requestHolder.message.messagePushTest(s) }, colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.MBlue, @@ -90,7 +90,7 @@ fun MessageListPage(requestHolder: RequestHolder) { key = { item: MessageEntity -> item.id }) { message: MessageEntity -> SwipeToDismissItem( onAction = { - requestHolder.messageRemove(message.toMessage(), onDone = { + requestHolder.message.messageRemove(message.toMessage(), onDone = { requestHolder.messageViewModel.delete(message) }) }, @@ -98,8 +98,8 @@ fun MessageListPage(requestHolder: RequestHolder) { ) { when (message.type) { "markdown" -> MarkdownMessageItem(message, requestHolder) - "text" -> PlainTextMessageItem(message) - "image" -> ImageMessageItem(message) + "text" -> PlainTextMessageItem(message, requestHolder) + "image" -> ImageMessageItem(message, requestHolder) } } } diff --git a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt index 1294670..881cefb 100644 --- a/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt +++ b/android/app/src/main/java/com/pushdeer/os/ui/compose/page/main/SettingPage.kt @@ -26,10 +26,18 @@ fun SettingPage(requestHolder: RequestHolder) { text = "${stringResource(id = R.string.main_setting_user_hi)} ${requestHolder.pushDeerViewModel.userInfo.name} !", buttonString = stringResource(id = R.string.main_setting_user_logout) ) { + requestHolder.pushDeerViewModel.deviceList.filter { it.device_id == requestHolder.settingStore.thisDeviceId }.forEach { + requestHolder.device.deviceRemove(it) + } requestHolder.settingStore.userToken = "" - // logout 操作: - // 从服务器删除本设备 - // 删除保存的 token + requestHolder.globalNavController.navigate("login") { + requestHolder.globalNavController.popBackStack() + } + requestHolder.alert.alert( + "提示", + "由于厂商推送设备服务限制,暂时不支持更换为自建 PushDeer 服务器,但仅更换登陆账号并不会影响您的使用", + {} + ) } } // item { diff --git a/android/app/src/main/java/com/pushdeer/os/util/ActivityOpener.kt b/android/app/src/main/java/com/pushdeer/os/util/ActivityOpener.kt new file mode 100644 index 0000000..be455b1 --- /dev/null +++ b/android/app/src/main/java/com/pushdeer/os/util/ActivityOpener.kt @@ -0,0 +1,30 @@ +package com.pushdeer.os.util + +import android.content.Intent +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import com.wh.common.activity.QrScanActivity + +object ActivityOpener { + + fun forResult(activity: AppCompatActivity): ActivityResultLauncher { + return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + Toast.makeText( + activity, + "${result.data?.getStringExtra(QrScanActivity.DataKey)}", + Toast.LENGTH_SHORT + ).show() + } + } + + fun forPermission(activity: AppCompatActivity): ActivityResultLauncher> { + return activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + it.entries.forEach { + Log.d("WH_", "forPermission:${it.key} ${it.value} ") + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..ab10448 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cf..7353dbd 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index eca70cf..7353dbd 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..ddfeedb Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..bfa5049 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..e36786d Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..e835c46 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..c189529 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..df92f86 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..00d0129 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..724bf62 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f467534 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8134b3e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/android/common/build.gradle b/android/common/build.gradle index ea139a6..c24cc18 100644 --- a/android/common/build.gradle +++ b/android/common/build.gradle @@ -48,4 +48,9 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.9.2") implementation 'com.google.code.gson:gson:2.8.9' + + // qr + implementation 'com.google.zxing:core:3.2.1' + implementation 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar' + implementation 'cn.bingoogolapple:bga-zxing:1.1.7@aar' } \ No newline at end of file diff --git a/android/common/src/main/AndroidManifest.xml b/android/common/src/main/AndroidManifest.xml index ff551d0..ad933ed 100644 --- a/android/common/src/main/AndroidManifest.xml +++ b/android/common/src/main/AndroidManifest.xml @@ -5,4 +5,6 @@ + + \ No newline at end of file diff --git a/android/common/src/main/java/com/wh/common/activity/QrScanActivity.kt b/android/common/src/main/java/com/wh/common/activity/QrScanActivity.kt new file mode 100644 index 0000000..b17425a --- /dev/null +++ b/android/common/src/main/java/com/wh/common/activity/QrScanActivity.kt @@ -0,0 +1,65 @@ +package com.wh.common.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import cn.bingoogolapple.qrcode.core.QRCodeView +import com.wh.common.R + + +class QrScanActivity : AppCompatActivity(), QRCodeView.Delegate { + + private val TAG = "WH_" + javaClass.simpleName + private lateinit var qrCode: QRCodeView + + companion object { + val RequestCode_get_scan_result = 436 + val DataKey = "qr_scan_result" + + fun forScanResultIntent(context: Context): Intent { + return Intent(context, QrScanActivity::class.java).apply { + putExtra(DataKey, 1) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_qr_scan) + qrCode = findViewById(R.id.qrcode1) + qrCode.setDelegate(this) + } + + override fun onStart() { + super.onStart() + Log.d(TAG, "onStart") + qrCode.startSpotAndShowRect() + } + + override fun onStop() { + Log.d(TAG, "onStop") + qrCode.stopCamera() + super.onStop() + } + + override fun onDestroy() { + qrCode.onDestroy() + super.onDestroy() + } + + override fun onScanQRCodeSuccess(result: String?) { + Log.d(TAG, "onScanQRCodeSuccess: $result") + qrCode.stopCamera() + val intent = Intent() + intent.putExtra(DataKey, result) + setResult(RequestCode_get_scan_result, intent) + finish() + } + + override fun onScanQRCodeOpenCameraError() { + Log.e(TAG, "onScanQRCodeOpenCameraError") + qrCode.startSpotAndShowRect() + } +} \ No newline at end of file diff --git a/android/common/src/main/java/com/wh/common/util/ActivityOpener.kt b/android/common/src/main/java/com/wh/common/util/ActivityOpener.kt new file mode 100644 index 0000000..96fcbc3 --- /dev/null +++ b/android/common/src/main/java/com/wh/common/util/ActivityOpener.kt @@ -0,0 +1,38 @@ +package com.wh.common.util + +import android.content.Intent +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import com.wh.common.activity.QrScanActivity + +object ActivityOpener { + + fun forQrScanResult( + activity: AppCompatActivity, + onReturn: (String) -> Unit = {} + ): ActivityResultLauncher { + return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + result.data?.getStringExtra(QrScanActivity.DataKey)?.let { + onReturn(it) + Toast.makeText(activity, it, Toast.LENGTH_SHORT).show() + } + } + } + + fun forPermission( + activity: AppCompatActivity, + onReturn: () -> Unit = {} + ): ActivityResultLauncher> { + return activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + it.entries.forEach { + Log.d("WH_", "forPermission:${it.key} ${it.value} ") + } + if (it.entries.all { it.value == true }){ + onReturn() + } + } + } +} \ No newline at end of file diff --git a/android/common/src/main/java/com/wh/common/util/QRCodeGenerator.java b/android/common/src/main/java/com/wh/common/util/QRCodeGenerator.java new file mode 100644 index 0000000..b8c0da1 --- /dev/null +++ b/android/common/src/main/java/com/wh/common/util/QRCodeGenerator.java @@ -0,0 +1,54 @@ +package com.wh.common.util; + + +import static android.graphics.Color.BLACK; +import static android.graphics.Color.WHITE; + +import android.graphics.Bitmap; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; + +import java.util.HashMap; +import java.util.Map; + +public class QRCodeGenerator { + private final Map hints; + private Bitmap bitmap = null; + private final String str; + private final int WIDTH; + private final int HEIGHT; + + public QRCodeGenerator(String str, int WIDTH, int HEIGHT){ + this.str = (str.length()>300)?"str is too long":str; + this.WIDTH=WIDTH; + this.HEIGHT=HEIGHT; + this.hints = new HashMap<>(); + this.hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + } + + public Bitmap getQRCode() { + try { + BitMatrix Result = new MultiFormatWriter().encode(str, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);//通过字符串创建二维矩阵 + int width = Result.getWidth(); + int height = Result.getHeight(); + + int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = Result.get(x, y) ? BLACK : WHITE;//根据二维矩阵数据创建数组 + } + } + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//创建位图 + bitmap.setPixels(pixels, 0, width, 0, 0, width, height);//将数组加载到位图中 + return bitmap; + } catch (WriterException e) { + e.printStackTrace(); + } + return bitmap; + } +} \ No newline at end of file diff --git a/android/common/src/main/res/layout/activity_qr_scan.xml b/android/common/src/main/res/layout/activity_qr_scan.xml new file mode 100644 index 0000000..9594e0c --- /dev/null +++ b/android/common/src/main/res/layout/activity_qr_scan.xml @@ -0,0 +1,46 @@ + + + + + + \ No newline at end of file diff --git a/android/compose/src/main/AndroidManifest.xml b/android/compose/src/main/AndroidManifest.xml index e9afbff..7b6c631 100644 --- a/android/compose/src/main/AndroidManifest.xml +++ b/android/compose/src/main/AndroidManifest.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/android/pushdeercommon/.gitignore b/android/pushdeercommon/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/pushdeercommon/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/pushdeercommon/build.gradle b/android/pushdeercommon/build.gradle new file mode 100644 index 0000000..cf2998f --- /dev/null +++ b/android/pushdeercommon/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' +} + +android { + compileSdk 32 + + defaultConfig { + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-scalars:2.0.0' + implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.squareup.retrofit2:converter-gson:2.3.0' +} \ No newline at end of file diff --git a/android/pushdeercommon/consumer-rules.pro b/android/pushdeercommon/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/android/pushdeercommon/proguard-rules.pro b/android/pushdeercommon/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/pushdeercommon/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/pushdeercommon/src/androidTest/java/com/pushdeer/common/ExampleInstrumentedTest.kt b/android/pushdeercommon/src/androidTest/java/com/pushdeer/common/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..595afea --- /dev/null +++ b/android/pushdeercommon/src/androidTest/java/com/pushdeer/common/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.pushdeer.common + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.pushdeer.common.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/AndroidManifest.xml b/android/pushdeercommon/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8a0aaf8 --- /dev/null +++ b/android/pushdeercommon/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/PushDeerApi.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/PushDeerApi.kt new file mode 100644 index 0000000..b867f84 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/PushDeerApi.kt @@ -0,0 +1,93 @@ +package com.pushdeer.common.api + +import com.pushdeer.common.api.data.response.* +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import retrofit2.http.Field +import retrofit2.http.FieldMap +import retrofit2.http.FormUrlEncoded +import retrofit2.http.POST + +interface PushDeerApi { + companion object { + private const val baseUrl = "https://api2.pushdeer.com" + + fun create(): PushDeerApi { + return Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(PushDeerApi::class.java) + } + } + + @FormUrlEncoded + @POST("/login/idtoken") + suspend fun loginIdToken(@Field("idToken") idToken: String): ReturnData + +// @GET("/login/fake") +// suspend fun fakeLogin(): ReturnData + + @FormUrlEncoded + @POST("/user/info") + suspend fun userInfo(@Field("token") token: String): ReturnData + + @FormUrlEncoded + @POST("/device/reg") + suspend fun deviceReg(@FieldMap data: Map): ReturnData + + @FormUrlEncoded + @POST("/device/list") + suspend fun deviceList(@Field("token") token: String): ReturnData + + @FormUrlEncoded + @POST("/device/remove") + suspend fun deviceRemove(@Field("token") token: String, @Field("id") id: Int): String + + @FormUrlEncoded + @POST("/device/rename") + suspend fun deviceRename( + @Field("token") token: String, + @Field("id") id: Int, + @Field("name") newName: String + ): String + + @FormUrlEncoded + @POST("/key/gen") + suspend fun keyGen(@Field("token") token: String): ReturnData + + @FormUrlEncoded + @POST("/key/regen") + suspend fun keyRegen(@FieldMap data: Map): String + + @FormUrlEncoded + @POST("/key/list") + suspend fun keyList(@Field("token") token: String): ReturnData + + @FormUrlEncoded + @POST("/key/remove") + suspend fun keyRemove(@FieldMap data: Map): String + + @FormUrlEncoded + @POST("/key/rename") + suspend fun keyRename( + @Field("token") token: String, + @Field("id") id: String, + @Field("name") newName: String + ): String + + // pushkey text desp type:text/image/markdown + @FormUrlEncoded + @POST("/message/push") + suspend fun messagePush(@FieldMap data: Map): String + + @FormUrlEncoded + @POST("/message/list") + suspend fun messageList(@Field("token") token: String): ReturnData + + @FormUrlEncoded + @POST("/message/remove") + suspend fun messageRemove(@Field("token") token: String, @Field("id") id: Int): String +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/request/DeviceInfo.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/request/DeviceInfo.kt new file mode 100644 index 0000000..23aab2b --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/request/DeviceInfo.kt @@ -0,0 +1,24 @@ +package com.pushdeer.common.api.data.request + +class DeviceInfo { + var id:Int = 0 + var uid:String = "" + var name:String = "" + var type:String = "" + var device_id: String = "" + var is_clip: Int = 0 + + fun toRequestMap(token:String): Map { + return mapOf( + "token" to token, + "name" to name, + "device_id" to device_id, + "is_clip" to is_clip.toString(), + "type" to "android" + ) + } + + override fun toString(): String { + return "id:$id uid:$uid name:$name type:$type device_id:$device_id is_clip:$is_clip" + } +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/DeviceInfo.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/DeviceInfo.kt new file mode 100644 index 0000000..611e488 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/DeviceInfo.kt @@ -0,0 +1,12 @@ +package com.pushdeer.common.api.data.response + +import com.pushdeer.common.api.data.request.DeviceInfo + + +class DeviceInfoList{ + var devices:List = emptyList() + + override fun toString(): String { + return "devices:$devices" + } +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/Message.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/Message.kt new file mode 100644 index 0000000..e3390b7 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/Message.kt @@ -0,0 +1,16 @@ +package com.pushdeer.common.api.data.response + + +class Message { + var id = 0 + var uid: String? = null + var text: String? = null + var desp: String? = null + var type: String? = null + var pushkey_name: String? = null + var created_at: String? = null +} + +class MessageList { + var messages = emptyList() +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/PushKey.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/PushKey.kt new file mode 100644 index 0000000..09266f5 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/PushKey.kt @@ -0,0 +1,20 @@ +package com.pushdeer.common.api.data.response + +class PushKey { + var id:String = "" + var key: String = "" + var name: String = "" + var created_at = "" + + override fun toString(): String { + return "id:$id key:$key name:$name created_at:$created_at" + } +} + +class PushKeyList { + var keys: List = emptyList() + + override fun toString(): String { + return "keys:$keys" + } +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/ReturnData.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/ReturnData.kt new file mode 100644 index 0000000..b07fe18 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/ReturnData.kt @@ -0,0 +1,15 @@ +package com.pushdeer.common.api.data.response + +class ReturnData { + var code: Int = 0 + var content: T?=null + var error:String = "" + + override fun toString(): String { + return "code:${code} error:${error} content:${content.toString()}" + } +} + +class TokenOnly{ + var token:String = "" +} \ No newline at end of file diff --git a/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/UserInfo.kt b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/UserInfo.kt new file mode 100644 index 0000000..80c0a16 --- /dev/null +++ b/android/pushdeercommon/src/main/java/com/pushdeer/common/api/data/response/UserInfo.kt @@ -0,0 +1,23 @@ +package com.pushdeer.common.api.data.response + +class UserInfo { + var id: String = "" + var name: String = "" + var email: String = "" + var app_id: String = "" + var wechat_id: String = "" + var level: Int = 1 + var created_at: String = "" + var updated_at: String = "" + + override fun toString(): String { + return "id:$id\n" + + "name:$name\n" + + "email:$email\n" + + "app_id:$app_id\n" + + "wechat_id:$wechat_id\n" + + "level:$level\n" + + "created:$created_at\n" + + "updated:$updated_at" + } +} \ No newline at end of file diff --git a/android/pushdeercommon/src/test/java/com/pushdeer/common/ExampleUnitTest.kt b/android/pushdeercommon/src/test/java/com/pushdeer/common/ExampleUnitTest.kt new file mode 100644 index 0000000..906e0d2 --- /dev/null +++ b/android/pushdeercommon/src/test/java/com/pushdeer/common/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.pushdeer.common + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index b6b75fc..1bfaa3c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -11,3 +11,4 @@ rootProject.name = "PushDeer" include ':app' include ':common' include ':compose' +include ':pushdeercommon'