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/.idea/misc.xml b/android/.idea/misc.xml
index 9fc6b7d..fd48f71 100644
--- a/android/.idea/misc.xml
+++ b/android/.idea/misc.xml
@@ -14,8 +14,13 @@
+
+
+
+
+
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'