add pushdeer-for-android

This commit is contained in:
alone-wolf 2022-01-16 21:52:21 +08:00
parent 539a41da62
commit cd8fbb513a
171 changed files with 6850 additions and 0 deletions

16
android/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/app/src/main/java/com/pushdeer/os/AppKeys.kt

3
android/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

22
android/.idea/gradle.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/common" />
<option value="$PROJECT_DIR$/compose" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,20 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

27
android/.idea/misc.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/compose-model-1640490388500.xml" value="0.33" />
<entry key="../../../../layout/compose-model-1640490589000.xml" value="0.36944444444444446" />
<entry key="../../../../layout/compose-model-1640569321856.xml" value="1.6638655462184875" />
<entry key="../../../../layout/compose-model-1641297602161.xml" value="0.29771959459459457" />
<entry key="../../../../layout/compose-model-1641300051131.xml" value="1.0" />
<entry key="../../../../layout/compose-model-1641348859824.xml" value="0.1" />
<entry key="../../../../layout/compose-model-1641366243757.xml" value="2.0" />
<entry key="../../../../layout/compose-model-1641659551303.xml" value="2.0" />
<entry key="../../../../layout/compose-model-1641659962289.xml" value="2.0" />
<entry key="../../../../layout/compose-model-1641694023752.xml" value="0.1" />
<entry key="app/src/main/res/drawable/fragment_qr_scan.xml" value="0.12314814814814815" />
<entry key="app/src/main/res/drawable/ic_markdown.xml" value="0.12962962962962962" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
android/.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

93
android/Readme.md Normal file
View File

@ -0,0 +1,93 @@
# PushDeer for Android
### 适配进度
* MiPush状态已调通、已接入
* miui 12.5.6 正常
* 原生、类原生 可成功注册,无法收到推送,可能和地区识别有关,待解决
### TODO
* ~~调通 MiPush~~
* 接入 MiPush
* 完善 log ~~采集~~ 回传机制,便于调试
* 测试不同厂商设备上的通知推送效果
* ~~接入 PushDeer~~
* ~~界面设计BottomBar+Navigation(Device Key Message Setting)~~
* 调整 KeyList MessageList 等处的自定义绘制
* ~~增加 DeviceList 外的侧滑手势~~
* ~~增加侧滑手势相关动作~~
* 增加各种操作前的二次确认弹窗,包括自动登陆
* 增加非Miui设备的权限获取
### 日志
* 2021-12-25
* 文件夹已经创建完毕
* MiPush
* 小米官方文档写的像shit陈年旧注释都不删
* 测试环境推送消息不推送
* 增加了 channel 参数,默认属于 运营,推送数量太少,需要申请新 channel
* 因为不是所有SDK都支持透传暂时选择跳过这个功能
* 2022-01-01
* 增加图片、颜色等资源,抽取字符串
* 增加登陆界面
* 重新组织主题
* 使用"纯自研" TopBar 替换 TopAppBar
* PushDeer api已调通未接入
* 2022-01-04
* PushDeer /user/info 响应类型去掉列表
* 为 MessageList 界面适配右上角箭头icon、增加输入框及其切换动画
* 适配设置界面不显示右上角icon
* MessageList、KeyList item适配
* DeviceList 增加侧滑手势
* 抽象 item 相关 ui提高代码复用率
* 2022-01-05
* 从 AppDatabase.kt 删除 message
* 调通user/info device/list key/list message/list四个路由
* 去掉message相关的各种repository、viewmodel等
* 一些小修改
* 2022-01-06
* 完善device/key/message的删除逻辑
* 创建 KeyListPage.kt
* 一些小修改
* 2022-01-08
* 增加二维码扫描组件
* 重绘设置界面
* 增加日志列表界面
* 调整滑动删除界面绘制逻辑和操作逻辑
* 调整列表界面绘制逻辑
* 增加消息列表对Markdown类型消息的渲染支持
* 增加消息列表对Image类型消息的显示支持demo级别硬编码
* 调整Key列表中项目绘制逻辑
* 一些细节变化
* 2022-01-09
* 增加列表尾部占位符
* 微调Key和Message列表项的构图
* 微调滑动删除图标背景
* 调整列表项阴影
* 调整设置界面的按钮颜色
* 界面整体配色几近处理完善
* Markdown:
* 增加链接解析支持
* 增加图片显示支持
* 增加html解析支持
* 增加task-list解析支持
* 增加table解析支持
* 2022-01-15
* 增加 Message 的数据库表
* 调整项目文件结构
* 将数据库用作message列表的直接数据来源
### 感谢
https://github.com/taoweiji/MixPush

2
android/app/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/src/main/java/com/pushdeer/os/values/AppKeys.kt

111
android/app/build.gradle Normal file
View File

@ -0,0 +1,111 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.pushdeer.os"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
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'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.31'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.activity:activity-compose:1.4.0'
implementation files('libs/MiPush_SDK_Client_4_9_0.jar')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation project(path: ':common')
implementation project(path: ':compose')
// navigation
implementation "androidx.navigation:navigation-compose:2.4.0-rc01"
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'
def accompanist_version = "0.22.0-rc"
// implementation "com.google.accompanist:accompanist-pager:$accompanist_version"
// implementation "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
// implementation "com.google.accompanist:accompanist-navigation-material:$accompanist_version"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation "com.google.accompanist:accompanist-insets:$accompanist_version"
// implementation "com.google.accompanist:accompanist-insets-ui:$accompanist_version"
// room
def room_version = "2.4.0"
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
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'
final def markwon_version = '4.6.2'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:ext-tasklist:$markwon_version"
implementation "io.noties.markwon:image-coil:$markwon_version"
implementation "io.noties.markwon:linkify:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
implementation "io.coil-kt:coil:1.4.0"
// implementation 'com.github.bumptech.glide:glide:4.12.0'
// annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}

Binary file not shown.

21
android/app/proguard-rules.pro vendored Normal file
View File

@ -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

View File

@ -0,0 +1,24 @@
package com.pushdeer.os
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.os", appContext.packageName)
}
}

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.pushdeer.os">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.INTERNET"/>
<permission
android:name="com.pushdeer.os.permission.MIPUSH_RECEIVE"
android:protectionLevel="signature" />
<uses-permission android:name="com.pushdeer.os.permission.MIPUSH_RECEIVE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera" />
<application
android:name=".App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.PushDeer"
tools:targetApi="m">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/Theme.PushDeer.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:screenOrientation="portrait"
android:name=".activity.QrScanActivity"
android:theme="@style/Theme.PushDeer.NoActionBar"
/>
<!-- start -->
<service
android:name="com.xiaomi.push.service.XMPushService"
android:enabled="true"
android:process=":pushservice" />
<service
android:name="com.xiaomi.push.service.XMJobService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":pushservice" />
<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true"
android:permission="com.xiaomi.xmsf.permission.MIPUSH_RECEIVE" />
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true" />
<receiver
android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"
tools:ignore="BatteryLife" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- end -->
<receiver
android:name="com.xiaomi.push.service.receivers.PingReceiver"
android:exported="false"
android:process=":pushservice">
<intent-filter>
<action android:name="com.xiaomi.push.PING_TIMER" />
</intent-filter>
</receiver>
<receiver
android:name="com.pushdeer.os.receiver.MessageReceiver"
android:exported="true">
<!--这里com.xiaomi.mipushdemo.DemoMessageRreceiver改成app中定义的完整类名-->
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,80 @@
package com.pushdeer.os
import android.app.ActivityManager
import android.app.Application
import android.os.Process
import android.util.Log
import com.pushdeer.os.data.api.PushDeerApi
import com.pushdeer.os.data.database.AppDatabase
import com.pushdeer.os.factory.ViewModelFactory
import com.pushdeer.os.keeper.RepositoryKeeper
import com.pushdeer.os.keeper.StoreKeeper
import com.pushdeer.os.values.AppKeys
import com.xiaomi.channel.commonutils.logger.LoggerInterface
import com.xiaomi.mipush.sdk.Logger
import com.xiaomi.mipush.sdk.MiPushClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
class App : Application() {
val storeKeeper by lazy { StoreKeeper(this) }
val database by lazy { AppDatabase.getDatabase(this) }
val repositoryKeeper by lazy { RepositoryKeeper(database) }
val pushDeerService by lazy {
Retrofit.Builder()
.baseUrl("http://0.0.0.0:8800")
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PushDeerApi::class.java)
}
val viewModelFactory by lazy {
ViewModelFactory(
repositoryKeeper,
storeKeeper,
pushDeerService
)
}
override fun onCreate() {
super.onCreate()
//初始化push推送服务
if (shouldInit()) {
MiPushClient.registerPush(this, AppKeys.MiPush_Id, AppKeys.MiPush_Key)
}
//打开Log
val newLogger: LoggerInterface = object : LoggerInterface {
override fun setTag(tag: String) {
// ignore
}
override fun log(content: String, t: Throwable) {
Log.d(TAG, content, t)
}
override fun log(content: String) {
Log.d(TAG, content)
}
}
Logger.setLogger(this, newLogger)
}
private fun shouldInit(): Boolean {
val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val processInfoList = am.runningAppProcesses
val mainProcessName = applicationInfo.processName
val myPid = Process.myPid()
for (info in processInfoList) {
if (info.pid == myPid && mainProcessName == info.processName) {
return true
}
}
return false
}
companion object {
const val TAG = "TAG"
}
}

View File

@ -0,0 +1,169 @@
package com.pushdeer.os
import android.content.ClipboardManager
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.compose.animation.ExperimentalAnimationApi
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
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.data.api.data.request.DeviceInfo
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.store.SettingStore
import com.pushdeer.os.ui.compose.page.LogDaoPage
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.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
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.*
class MainActivity : ComponentActivity(), RequestHolder {
private val viewModelFactory by lazy { (application as App).viewModelFactory }
private val repositoryKeeper by lazy { (application as App).repositoryKeeper }
private val miPushRepository by lazy { repositoryKeeper.miPushRepository }
override val uiViewModel: UiViewModel by viewModels { viewModelFactory }
override val pushDeerViewModel: PushDeerViewModel by viewModels { viewModelFactory }
override val logDogViewModel: LogDogViewModel by viewModels { viewModelFactory }
override val messageViewModel: MessageViewModel by viewModels { viewModelFactory }
override val settingStore: SettingStore by lazy { (application as App).storeKeeper.settingStore }
override val coilImageLoader: ImageLoader by lazy {
ImageLoader.Builder(this)
.apply {
availableMemoryPercentage(0.5)
bitmapPoolPercentage(0.5)
crossfade(true)
}
.build()
}
override val markdown: Markwon by lazy {
Markwon.builder(this)
.usePlugin(CoilImagesPlugin.create(this, coilImageLoader))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.build();
}
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<Intent>
@ExperimentalAnimationApi
@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myActivity = this
activityOpener =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Toast.makeText(
this,
"${result.data?.getStringExtra(QrScanActivity.DataKey)}",
Toast.LENGTH_SHORT
).show()
}
UiUtils.keepScreenOn(window)
setContent {
globalNavController = rememberNavController()
coroutineScope = rememberCoroutineScope()
val useDarkIcons = MaterialTheme.colors.isLight
val systemUiController = rememberSystemUiController()
when {
SystemUtil.isMiui() -> {
systemUiController.setStatusBarColor(Color.Transparent, useDarkIcons)
}
else -> {
systemUiController.setSystemBarsColor(Color.Transparent, useDarkIcons)
}
}
WindowCompat.setDecorFitsSystemWindows(window, true)
miPushRepository.regId.observe(this) {
// 这个操作放到注册成功后进行
settingStore.thisDeviceId = it
coroutineScope.launch {
if (pushDeerViewModel.shouldRegDevice()) {
pushDeerViewModel.deviceReg(DeviceInfo().apply {
this.name = SystemUtil.getDeviceModel()
this.device_id = it
this.is_clip = 0
})
}
}
}
SideEffect {
coroutineScope.launch {
pushDeerViewModel.login().also {
pushDeerViewModel.userInfo()
pushDeerViewModel.keyList()
pushDeerViewModel.deviceList()
pushDeerViewModel.messageList()
globalNavController.navigate("main") {
globalNavController.popBackStack()
}
}
}
}
PushDeerTheme {
ProvideWindowInsets {
Surface(color = MaterialTheme.colors.background) {
NavHost(
navController = globalNavController,
startDestination = "login",
modifier = Modifier.statusBarsPadding()
) {
composable("login") {
LoginPage(requestHolder = this@MainActivity)
}
composable("logdog") {
LogDaoPage(requestHolder = this@MainActivity)
}
composable("main") {
MainPage(requestHolder = this@MainActivity)
}
}
}
}
}
}
}
}

View File

@ -0,0 +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()
}
}

View File

@ -0,0 +1,53 @@
package com.pushdeer.os.data.api
import com.pushdeer.os.data.api.data.response.*
import retrofit2.http.*
interface PushDeerApi {
@GET("/login/fake")
suspend fun fakeLogin(): ReturnData<TokenOnly>
@FormUrlEncoded
@POST("/user/info")
suspend fun userInfo(@Field("token") token: String): ReturnData<UserInfo>
@FormUrlEncoded
@POST("/device/reg")
suspend fun deviceReg(@FieldMap data: Map<String, String>): ReturnData<DeviceInfoList>
@FormUrlEncoded
@POST("/device/list")
suspend fun deviceList(@Field("token") token: String): ReturnData<DeviceInfoList>
@FormUrlEncoded
@POST("/device/remove")
suspend fun deviceRemove(@Field("token") token: String,@Field("id") id:Int): String
@FormUrlEncoded
@POST("/key/gen")
suspend fun keyGen(@Field("token") token: String): ReturnData<PushKeyList>
@FormUrlEncoded
@POST("/key/regen")
suspend fun keyRegen(@FieldMap data: Map<String, String>): String
@FormUrlEncoded
@POST("/key/list")
suspend fun keyList(@Field("token") token: String): ReturnData<PushKeyList>
@FormUrlEncoded
@POST("/key/remove")
suspend fun keyRemove(@FieldMap data: Map<String, String>): String
@FormUrlEncoded
@POST("/message/push")
suspend fun messagePush(@FieldMap data: Map<String, String>): String
@FormUrlEncoded
@POST("/message/list")
suspend fun messageList(@Field("token") token: String): ReturnData<MessageList>
@FormUrlEncoded
@POST("/message/remove")
suspend fun messageRemove(@Field("token")token:String,@Field("id")id:Int): String
}

View File

@ -0,0 +1,23 @@
package com.pushdeer.os.data.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<String, String> {
return mapOf(
"token" to token,
"name" to name,
"device_id" to device_id,
"is_clip" to is_clip.toString()
)
}
override fun toString(): String {
return "id:$id uid:$uid name:$name type:$type device_id:$device_id is_clip:$is_clip"
}
}

View File

@ -0,0 +1,12 @@
package com.pushdeer.os.data.api.data.response
import com.pushdeer.os.data.api.data.request.DeviceInfo
class DeviceInfoList{
var devices:List<DeviceInfo> = emptyList()
override fun toString(): String {
return "devices:$devices"
}
}

View File

@ -0,0 +1,13 @@
package com.pushdeer.os.data.api.data.response
class DeviceRemove {
var token: String = ""
var id = ""
fun toMap():Map<String,String> {
return mapOf(
"token" to token,
"id" to id
)
}
}

View File

@ -0,0 +1,20 @@
package com.pushdeer.os.data.api.data.response
import com.pushdeer.os.data.database.entity.MessageEntity
class Message : MessageEntity() {
fun toMessageEntity(): MessageEntity {
return MessageEntity().apply {
this.id = this@Message.id
this.uid = this@Message.uid
this.text = this@Message.text
this.desp = this@Message.desp
this.type = this@Message.type
this.created_at = this@Message.created_at
}
}
}
class MessageList {
var messages = emptyList<Message>()
}

View File

@ -0,0 +1,20 @@
package com.pushdeer.os.data.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<PushKey> = emptyList()
override fun toString(): String {
return "keys:$keys"
}
}

View File

@ -0,0 +1,15 @@
package com.pushdeer.os.data.api.data.response
class ReturnData<T> {
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 = ""
}

View File

@ -0,0 +1,23 @@
package com.pushdeer.os.data.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"
}
}

View File

@ -0,0 +1,33 @@
package com.pushdeer.os.data.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.pushdeer.os.data.database.dao.LogDogDao
import com.pushdeer.os.data.database.dao.MessageDao
import com.pushdeer.os.data.database.entity.LogDog
import com.pushdeer.os.data.database.entity.MessageEntity
@Database(entities = [LogDog::class,MessageEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun logDogDao(): LogDogDao
abstract fun messageDao():MessageDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "app-database"
).build()
INSTANCE = instance
instance
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.pushdeer.os.data.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.pushdeer.os.data.database.entity.LogDog
import kotlinx.coroutines.flow.Flow
@Dao
interface LogDogDao {
@get:Query("select * from LogDog Order by id desc")
val all:Flow<List<LogDog>>
@Insert
suspend fun insert(vararg logDog: LogDog)
@Query("delete from LogDog")
suspend fun clear()
}

View File

@ -0,0 +1,22 @@
package com.pushdeer.os.data.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.REPLACE
import androidx.room.Query
import com.pushdeer.os.data.database.entity.MessageEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface MessageDao {
@get:Query("select * from message order by id desc")
val all: Flow<List<MessageEntity>>
@Delete
suspend fun delete(vararg messageEntity: MessageEntity)
@Insert(onConflict = REPLACE)
suspend fun insert(vararg messageEntity: MessageEntity)
}

View File

@ -0,0 +1,74 @@
package com.pushdeer.os.data.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.wh.common.typeExt.toTimestamp
@Entity
class LogDog {
@PrimaryKey(autoGenerate = true)
var id:Int = 0
var level:String = "d"
var entity:String = ""
var event:String = ""
var log:String = ""
var timestamp:Long = System.currentTimeMillis()
override fun toString(): String {
return "id:$id level:$level entity:$entity\nlog:$log time:${timestamp.toTimestamp()}"
}
// @Composable
// fun resolve(
// d: @Composable () -> Unit,
// e: @Composable () -> Unit,
// i: @Composable () -> Unit,
// w: @Composable () -> Unit
// ) {
// when(this.level){
// "d" -> d()
// "e" -> e()
// "i" -> i()
// "w" -> w()
// }
//
// }
companion object{
fun logd(entity: String,event:String,log:String): LogDog {
return LogDog().apply {
level = "d"
this.entity = entity
this.event = event
this.log = log
}
}
fun loge(entity: String,event:String,log:String): LogDog {
return LogDog().apply {
level = "e"
this.entity = entity
this.event = event
this.log = log
}
}
fun logi(entity: String,event:String,log:String): LogDog {
return LogDog().apply {
level = "i"
this.entity = entity
this.event = event
this.log = log
}
}
fun logw(entity: String,event:String,log:String): LogDog {
return LogDog().apply {
level = "w"
this.entity = entity
this.event = event
this.log = log
}
}
}
}

View File

@ -0,0 +1,29 @@
package com.pushdeer.os.data.database.entity;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.pushdeer.os.data.api.data.response.Message;
@Entity(tableName = "message")
public class MessageEntity {
@PrimaryKey
public int id;
public String uid;
public String text;
public String desp;
public String type;
public String created_at;
public Message toMessage(){
Message m = new Message();
m.id = id;
m.uid = uid;
m.created_at = created_at;
m.desp = desp;
m.text = text;
m.type = type;
return m;
}
}

View File

@ -0,0 +1,42 @@
package com.pushdeer.os.data.repository
import com.pushdeer.os.data.database.dao.LogDogDao
import com.pushdeer.os.data.database.entity.LogDog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class LogDogRepository(private val logDogDao: LogDogDao) {
val all = logDogDao.all
suspend fun clear(){
logDogDao.clear()
}
suspend fun insert(vararg logDog: LogDog) {
logDogDao.insert(*logDog)
}
suspend fun logd(entity: String, event: String, log: String) {
withContext(Dispatchers.IO) {
insert(LogDog.logd(entity, event, log))
}
}
suspend fun loge(entity: String, event: String, log: String) {
withContext(Dispatchers.IO) {
insert(LogDog.loge(entity, event, log))
}
}
suspend fun logi(entity: String, event: String, log: String) {
withContext(Dispatchers.IO) {
insert(LogDog.logi(entity, event, log))
}
}
suspend fun logw(entity: String, event: String, log: String) {
withContext(Dispatchers.IO) {
insert(LogDog.logw(entity, event, log))
}
}
}

View File

@ -0,0 +1,25 @@
package com.pushdeer.os.data.repository
import com.pushdeer.os.data.api.data.response.Message
import com.pushdeer.os.data.database.dao.MessageDao
import com.pushdeer.os.data.database.entity.MessageEntity
class MessageRepository(private val messageDao: MessageDao) {
val all = messageDao.all
suspend fun insert(vararg message: Message) {
message.forEach {
messageDao.insert(it.toMessageEntity())
}
}
suspend fun delete(vararg message: Message) {
message.forEach {
messageDao.delete(it.toMessageEntity())
}
}
suspend fun delete(vararg messageEntity: MessageEntity) {
messageDao.delete(*messageEntity)
}
}

View File

@ -0,0 +1,7 @@
package com.pushdeer.os.data.repository
import androidx.lifecycle.MutableLiveData
class MiPushRepository {
val regId = MutableLiveData("")
}

View File

@ -0,0 +1,48 @@
package com.pushdeer.os.factory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.pushdeer.os.data.api.PushDeerApi
import com.pushdeer.os.keeper.RepositoryKeeper
import com.pushdeer.os.keeper.StoreKeeper
import com.pushdeer.os.viewmodel.LogDogViewModel
import com.pushdeer.os.viewmodel.MessageViewModel
import com.pushdeer.os.viewmodel.PushDeerViewModel
import com.pushdeer.os.viewmodel.UiViewModel
class ViewModelFactory(
private val repositoryKeeper: RepositoryKeeper,
private val storeKeeper: StoreKeeper,
private val pushDeerService: PushDeerApi
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
when {
modelClass.isAssignableFrom(UiViewModel::class.java) -> {
@Suppress("UNCHECKED_CAST")
return UiViewModel(settingStore = storeKeeper.settingStore) as T
}
modelClass.isAssignableFrom(PushDeerViewModel::class.java) -> {
@Suppress("UNCHECKED_CAST")
return PushDeerViewModel(
settingStore = storeKeeper.settingStore,
logDogRepository = repositoryKeeper.logDogRepository,
pushDeerService = pushDeerService,
messageRepository = repositoryKeeper.messageRepository
) as T
}
modelClass.isAssignableFrom(LogDogViewModel::class.java) -> {
@Suppress("UNCHECKED_CAST")
return LogDogViewModel(repositoryKeeper.logDogRepository) as T
}
modelClass.isAssignableFrom(MessageViewModel::class.java) -> {
@Suppress("UNCHECKED_CAST")
return MessageViewModel(
messageRepository = repositoryKeeper.messageRepository,
pushDeerService = pushDeerService,
settingStore = storeKeeper.settingStore
) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,4 @@
package com.pushdeer.os.holder
interface DataHolder {
}

View File

@ -0,0 +1,115 @@
package com.pushdeer.os.holder
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.navigation.NavHostController
import coil.ImageLoader
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
import com.pushdeer.os.store.SettingStore
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 io.noties.markwon.Markwon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
interface RequestHolder {
val uiViewModel: UiViewModel
val pushDeerViewModel: PushDeerViewModel
val logDogViewModel: LogDogViewModel
val messageViewModel:MessageViewModel
val settingStore: SettingStore
val globalNavController: NavHostController
val coroutineScope: CoroutineScope
val myActivity: ComponentActivity
val markdown: Markwon
val activityOpener: ActivityResultLauncher<Intent>
val coilImageLoader: ImageLoader
val clipboardManager: ClipboardManager
fun copyPlainString(str: String) {
clipboardManager.setPrimaryClip(ClipData.newPlainText("pushdeer-pushkey", str))
}
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 deviceReg(deviceInfo: DeviceInfo){
coroutineScope.launch {
pushDeerViewModel.deviceReg(deviceInfo)
}
}
fun deviceRemove(deviceInfo: DeviceInfo) {
coroutineScope.launch {
pushDeerViewModel.deviceList.remove(deviceInfo)
pushDeerViewModel.deviceRemove(deviceInfo.id)
}
}
fun messagePush(text: String, desp: String, type: String, pushkey: String) {
coroutineScope.launch {
pushDeerViewModel.messagePush(text, desp, type, pushkey)
}
}
fun messagePushTest(desp: String) {
if (pushDeerViewModel.keyList.isNotEmpty()) {
messagePush("pushtest", desp, "markdown", pushDeerViewModel.keyList[0].key)
coroutineScope.launch {
delay(1000)
pushDeerViewModel.messageList()
}
} else
Log.d("WH_", "messagePushTest: keylist is empty")
}
fun messageRemove(message: Message,onDone:()->Unit={}) {
coroutineScope.launch {
// pushDeerViewModel.messageList.remove(message)
pushDeerViewModel.messageRemove(message.id)
onDone()
}
}
fun toggleMessageSender() {
settingStore.showMessageSender = settingStore.showMessageSender.not()
uiViewModel.showMessageSender = settingStore.showMessageSender
}
fun clearLogDog() {
coroutineScope.launch {
logDogViewModel.clear()
}
}
}

View File

@ -0,0 +1,12 @@
package com.pushdeer.os.keeper
import com.pushdeer.os.data.database.AppDatabase
import com.pushdeer.os.data.repository.LogDogRepository
import com.pushdeer.os.data.repository.MessageRepository
import com.pushdeer.os.data.repository.MiPushRepository
class RepositoryKeeper(database: AppDatabase) {
val miPushRepository = MiPushRepository()
val logDogRepository = LogDogRepository(database.logDogDao())
val messageRepository = MessageRepository(database.messageDao())
}

View File

@ -0,0 +1,8 @@
package com.pushdeer.os.keeper
import android.content.Context
import com.pushdeer.os.store.SettingStore
class StoreKeeper(context: Context) {
val settingStore = SettingStore(context)
}

View File

@ -0,0 +1,123 @@
package com.pushdeer.os.receiver
import android.content.Context
import android.text.TextUtils
import com.pushdeer.os.App
import com.pushdeer.os.data.repository.MiPushRepository
import com.pushdeer.os.keeper.RepositoryKeeper
import com.xiaomi.mipush.sdk.*
class MessageReceiver : PushMessageReceiver() {
private var mRegId: String? = null
private val mResultCode: Long = -1
private val mReason: String? = null
private val mCommand: String? = null
private var mMessage: String? = null
private var mTopic: String? = null
private var mAlias: String? = null
private var mUserAccount: String? = null
private var mStartTime: String? = null
private var mEndTime: String? = null
private var repositoryKeeper:RepositoryKeeper?=null
private var miPushRepository: MiPushRepository?=null
fun init(context: Context){
if (repositoryKeeper==null){
repositoryKeeper = (context.applicationContext as App).repositoryKeeper
}
if (miPushRepository==null){
miPushRepository = repositoryKeeper!!.miPushRepository
}
}
override fun onReceivePassThroughMessage(context: Context, message: MiPushMessage) {
init(context)
// Log.d("WH_", "onReceivePassThroughMessage: $message")
mMessage = message.content
if (!TextUtils.isEmpty(message.topic)) {
mTopic = message.topic
} else if (!TextUtils.isEmpty(message.alias)) {
mAlias = message.alias
} else if (!TextUtils.isEmpty(message.userAccount)) {
mUserAccount = message.userAccount
}
}
override fun onNotificationMessageClicked(context: Context, message: MiPushMessage) {
init(context)
// Log.d("WH_", "onNotificationMessageClicked: $message")
mMessage = message.content
if (!TextUtils.isEmpty(message.topic)) {
mTopic = message.topic
} else if (!TextUtils.isEmpty(message.alias)) {
mAlias = message.alias
} else if (!TextUtils.isEmpty(message.userAccount)) {
mUserAccount = message.userAccount
}
}
override fun onNotificationMessageArrived(context: Context, message: MiPushMessage) {
init(context)
// Log.d("WH_", "onNotificationMessageArrived: $message")
mMessage = message.content
if (!TextUtils.isEmpty(message.topic)) {
mTopic = message.topic
} else if (!TextUtils.isEmpty(message.alias)) {
mAlias = message.alias
} else if (!TextUtils.isEmpty(message.userAccount)) {
mUserAccount = message.userAccount
}
}
override fun onCommandResult(context: Context, message: MiPushCommandMessage) {
init(context)
// Log.d("WH_", "onCommandResult: $message")
val command = message.command
val arguments = message.commandArguments
val cmdArg1 = if (arguments != null && arguments.size > 0) arguments[0] else null
val cmdArg2 = if (arguments != null && arguments.size > 1) arguments[1] else null
if (MiPushClient.COMMAND_REGISTER == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mRegId = cmdArg1
}
} else if (MiPushClient.COMMAND_SET_ALIAS == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mAlias = cmdArg1
}
} else if (MiPushClient.COMMAND_UNSET_ALIAS == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mAlias = cmdArg1
}
} else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mTopic = cmdArg1
}
} else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mTopic = cmdArg1
}
} else if (MiPushClient.COMMAND_SET_ACCEPT_TIME == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mStartTime = cmdArg1
mEndTime = cmdArg2
}
}
}
override fun onReceiveRegisterResult(context: Context, message: MiPushCommandMessage) {
init(context)
// Log.d("WH_", "onReceiveRegisterResult: $message")
val command = message.command
val arguments = message.commandArguments
val cmdArg1 = if (arguments != null && arguments.size > 0) arguments[0] else null
val cmdArg2 = if (arguments != null && arguments.size > 1) arguments[1] else null
if (MiPushClient.COMMAND_REGISTER == command) {
if (message.resultCode == ErrorCode.SUCCESS.toLong()) {
mRegId = cmdArg1
miPushRepository!!.regId.postValue(mRegId)
}
}
}
}

View File

@ -0,0 +1,19 @@
//package com.pushdeer.os.sss
//
//import android.content.Context
//import android.content.Intent
//import androidx.activity.result.contract.ActivityResultContract
//import com.pushdeer.os.activity.QrScanActivity
//
//class MyActivityResultContract : ActivityResultContract<String, String>() {
// override fun createIntent(context: Context, input: String): Intent {
// return QrScanActivity.forScanResultIntent(context)
// }
//
// override fun parseResult(resultCode: Int, intent: Intent?): String {
// intent?.let {
// return it.getStringExtra(QrScanActivity.DataKey).toString()
// }
// return ""
// }
//}

View File

@ -0,0 +1,21 @@
package com.pushdeer.os.store
import android.content.Context
import com.wh.common.store.Store
class SettingStore(context:Context) {
val store = Store.create(context,"setting")
var userToken by store.string("user-token","")
var deviceName by store.string("device-name","My Dear Deer")
var useRecv by store.boolean("use-recv",false) // 启用接收
var useSend by store.boolean("use-send",false)
var useSendNotification by store.boolean("use-send-notification",false)
var notificationPackages by store.stringSet("notification-packages", emptySet())
var useSendMissedCall by store.boolean("use-send=missed-call",false)
var useSendSMS by store.boolean("use-send-sms",false)
var showMessageSender by store.boolean("show-message-sender",true)
var thisPushSdk by store.string("this-push-sdk","mi-push")
var thisDeviceId by store.string("this-device-id","")
}

View File

@ -0,0 +1,8 @@
package com.pushdeer.os.typeExt
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

View File

@ -0,0 +1,146 @@
package com.pushdeer.os.ui.compose.componment
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pushdeer.os.R
import com.pushdeer.os.ui.theme.MBlue
import com.pushdeer.os.ui.theme.MainBlue
@ExperimentalMaterialApi
@Composable
fun CardItemSingleLineWithIcon(
onClick: () -> Unit = {},
@DrawableRes resId: Int = R.drawable.iphone2x,
text: String = "Easy's iPhone"
) {
Card(
onClick = onClick,
shape = RoundedCornerShape(4.dp),
modifier = Modifier
.border(
width = 1.dp,
color = MainBlue,
shape = RoundedCornerShape(4.dp)
),
elevation = 5.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = resId),
contentDescription = "",
colorFilter = ColorFilter.tint(color = MaterialTheme.colors.MBlue),
modifier = Modifier.size(28.dp)
)
Text(
text = text,
color = MainBlue,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
@ExperimentalMaterialApi
@Composable
fun CardItemMultiLine(
onClick: () -> Unit = {},
@DrawableRes resId: Int = R.drawable.iphone2x,
text: String = "Easy's iPhone"
) {
Card(
onClick = onClick,
shape = RoundedCornerShape(4.dp),
modifier = Modifier
// .padding(bottom = 16.dp)
.border(
width = 1.dp,
color = MainBlue,
shape = RoundedCornerShape(4.dp)
),
elevation = 5.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = resId),
contentDescription = "",
colorFilter = ColorFilter.tint(color = MaterialTheme.colors.MBlue),
modifier = Modifier.size(28.dp)
)
Text(
text = text,
color = MainBlue,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
@ExperimentalMaterialApi
@Composable
fun CardItemWithContent(onClick: () -> Unit = {}, content: @Composable () -> Unit = {}) {
Card(
onClick = onClick,
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(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Top
) {
// Text(text = "End of List",color = Color.Gray)
}
}
@ExperimentalMaterialApi
@Preview(showBackground = true)
@Composable
fun IP() {
CardItemWithContent {
Text(text = "aaa")
}
}

View File

@ -0,0 +1,134 @@
package com.pushdeer.os.ui.compose.componment
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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 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.TimeUtils
@ExperimentalMaterialApi
@Composable
fun KeyItem(key: PushKey, requestHolder: RequestHolder) {
CardItemWithContent {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 20.dp),
) {
Row(verticalAlignment = Alignment.Bottom) {
Image(
painter = painterResource(id = R.drawable.ic_deer_head_with_mail),
contentDescription = "",
modifier = Modifier.size(36.dp)
)
Text(
text = key.name,
fontSize = 16.sp,
color = MaterialTheme.colors.MBlue,
modifier = Modifier.padding(start = 10.dp)
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = "",
tint = Color.Gray,
modifier = Modifier
.size(20.dp)
.padding(end = 4.dp)
)
Text(
text = TimeUtils.getFormattedTime(
TimeUtils.utcTS2ms(
key.created_at,
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
), "MM/dd HH:mm"
),
color = Color.Gray,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 12.sp
)
}
}
Text(
text = key.key,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = Color.Gray,
fontSize = 14.sp,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 14.dp)
.border(
width = 1.dp,
color = Color.Gray,
shape = RoundedCornerShape(4.dp)
)
.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) },
colors = ButtonDefaults.outlinedButtonColors(
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.MBlue
),
border = BorderStroke(1.dp, MaterialTheme.colors.MBlue),
shape = RoundedCornerShape(6.dp)
) {
Text(text = "Reset")
}
Button(
onClick = {
requestHolder.copyPlainString(key.key)
},
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.MBlue,
contentColor = Color.White
),
shape = RoundedCornerShape(6.dp)
) {
Text(text = "Copy")
}
}
}
}
}

View File

@ -0,0 +1,168 @@
package com.pushdeer.os.ui.compose.componment
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
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 com.pushdeer.os.R
import com.pushdeer.os.data.database.entity.MessageEntity
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.theme.MBlue
import com.pushdeer.os.util.CurrentTimeUtil
import com.pushdeer.os.values.ConstValues
@ExperimentalMaterialApi
@Composable
fun PlainTextMessageItem(message: MessageEntity) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(4.dp))
.background(color = MaterialTheme.colors.surface)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.ic_deer_head_with_mail),
contentDescription = "",
modifier = Modifier.size(40.dp)
)
Text(
text = "${message.text}·${
CurrentTimeUtil.resolveUTCTimeAndNow(
message.created_at,
System.currentTimeMillis()
)
}"
)
}
CardItemWithContent() {
Text(
text = message.desp,
overflow = TextOverflow.Visible,
lineHeight = 24.sp,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
}
}
@ExperimentalMaterialApi
@Composable
fun ImageMessageItem(message: MessageEntity) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(4.dp))
.background (color = MaterialTheme.colors.surface)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = ConstValues.MainPageSidePadding)
.padding(bottom = 12.dp),
) {
Image(
painter = painterResource(id = R.drawable.ic_deer_head_with_mail),
contentDescription = "",
modifier = Modifier.size(40.dp)
)
Text(
text = "${message.text}·${
CurrentTimeUtil.resolveUTCTimeAndNow(
message.created_at,
System.currentTimeMillis()
)
}"
)
}
Card(modifier = Modifier.fillMaxWidth(), onClick = {}) {
Image(
painter = painterResource(id = R.drawable.logo_com_x2),
contentDescription = "",
contentScale = ContentScale.FillWidth
)
}
}
}
@ExperimentalMaterialApi
@Composable
fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(4.dp))
.background(color = MaterialTheme.colors.surface)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp), verticalAlignment = Alignment.CenterVertically
) {
Box {
Image(
painter = painterResource(id = R.drawable.ic_deer_head_with_mail),
contentDescription = "",
modifier = Modifier.size(40.dp)
)
Icon(
painter = painterResource(id = R.drawable.ic_markdown),
contentDescription = "",
tint = MaterialTheme.colors.MBlue,
modifier = Modifier
.size(20.dp)
.align(alignment = Alignment.BottomCenter)
)
}
Text(
text = "${message.text}·${
CurrentTimeUtil.resolveUTCTimeAndNow(
message.created_at,
System.currentTimeMillis()
)
}"
)
}
CardItemWithContent {
AndroidView(
factory = { ctx ->
android.widget.TextView(ctx).apply {
this.post {
// requestHolder.markdown.configuration().theme().
requestHolder.markdown.setMarkdown(this, message.desp)
}
}
}, modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
}
}

View File

@ -0,0 +1,54 @@
package com.pushdeer.os.ui.compose.componment
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.pushdeer.os.ui.theme.MBlue
@ExperimentalMaterialApi
@Composable
fun SettingItem(text: String, buttonString: String, onClick: () -> Unit) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
CardItemWithContent() {
Row(
modifier = Modifier
.fillMaxWidth()
// .padding(vertical = 10.dp)
.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = text,
fontSize = 16.sp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Button(
onClick = onClick,
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.MBlue,
contentColor = Color.White
)
) {
Text(
text = buttonString,
fontSize = 15.sp
)
}
}
}
}
}

View File

@ -0,0 +1,80 @@
package com.pushdeer.os.ui.compose.componment
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Done
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.pushdeer.os.values.ConstValues
@ExperimentalMaterialApi
@Composable
fun SwipeToDismissItem(
onDismiss: () -> Unit,
sidePadding: Boolean = false,
content: @Composable RowScope.() -> Unit
) {
val dismissState = rememberDismissState()
if (dismissState.isDismissed(DismissDirection.EndToStart)) {
onDismiss()
}
Column(modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)) {
SwipeToDismiss(
state = dismissState,
background = {
val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
val color by animateColorAsState(
when (dismissState.targetValue) {
DismissValue.DismissedToEnd -> Color.Green
DismissValue.DismissedToStart -> Color.Red
else -> Color.Gray
}
)
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> Alignment.CenterEnd
}
val icon = when (direction) {
DismissDirection.StartToEnd -> Icons.Default.Done
DismissDirection.EndToStart -> Icons.Default.Delete
}
Box(
contentAlignment = alignment,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(4.dp))
.background(color)
.padding(end = 32.dp)
) {
Icon(
imageVector = icon,
contentDescription = "",
// tint = Color.Red
)
}
},
directions = setOf(DismissDirection.EndToStart, DismissDirection.EndToStart),
dismissThresholds = { direction ->
FractionalThreshold(if (direction == DismissDirection.EndToStart) 0.45f else 0.57f)
},
dismissContent = content,
modifier = Modifier.padding(horizontal = if (sidePadding) ConstValues.MainPageSidePadding else 0.dp)
)
}
}

View File

@ -0,0 +1,75 @@
package com.pushdeer.os.ui.compose.page
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.pushdeer.os.R
import com.pushdeer.os.data.database.entity.LogDog
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.compose.page.main.MainPageFrame
@ExperimentalMaterialApi
@Composable
fun LogDaoPage(requestHolder: RequestHolder) {
MainPageFrame(
titleStringId = R.string.global_logdog,
sideIcon = Icons.Default.Delete,
onSideIconClick = {
requestHolder.clearLogDog()
}) {
val logDogs by requestHolder.logDogViewModel.all.collectAsState(initial = emptyList())
Scaffold(modifier = Modifier.fillMaxSize()) {
if (logDogs.isEmpty()) {
Row(
modifier = Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = R.drawable.logo_half),
contentDescription = "",
)
}
} else {
LazyColumn(
content = {
items(logDogs, key = { item: LogDog -> item.id }) { logDog: LogDog ->
Card(
onClick = { /*TODO*/ },
elevation = 5.dp,
shape = RoundedCornerShape(4.dp),
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = logDog.toString())
}
}
}
},
modifier = Modifier.fillMaxWidth()
)
}
}
}
}

View File

@ -0,0 +1,76 @@
package com.pushdeer.os.ui.compose.page
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.pushdeer.os.R
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.theme.MainBlue
import com.pushdeer.os.ui.theme.MainGreen
@ExperimentalMaterialApi
@Composable
fun LoginPage(requestHolder: RequestHolder) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.drawable.logo_com_x2),
contentDescription = "big push deer logo"
)
Card(
onClick = { /*TODO*/ },
shape = RoundedCornerShape(4.dp),
modifier = Modifier
.padding(bottom = 16.dp)
.border(
width = 1.dp,
color = MainBlue,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = "Sign in with Apple",
color = MainBlue,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(vertical = 16.dp)
.fillMaxWidth(0.6F)
)
}
Card(
onClick = {},
shape = RoundedCornerShape(4.dp),
modifier = Modifier.border(
width = 1.dp,
color = MainGreen,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = "Sign in with WeChat",
color = MainGreen,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(vertical = 16.dp)
.fillMaxWidth(0.6F)
)
}
}
}

View File

@ -0,0 +1,109 @@
package com.pushdeer.os.ui.compose.page.main
import android.os.Build
import android.text.TextUtils
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pushdeer.os.R
import com.pushdeer.os.data.api.data.request.DeviceInfo
import com.pushdeer.os.data.api.data.response.UserInfo
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.compose.componment.CardItemSingleLineWithIcon
import com.pushdeer.os.ui.compose.componment.ListBottomBlankItem
import com.pushdeer.os.ui.compose.componment.SwipeToDismissItem
import com.pushdeer.os.ui.navigation.Page
import com.pushdeer.os.util.SystemUtil
@ExperimentalMaterialApi
@Composable
fun DeviceListPage(requestHolder: RequestHolder) {
MainPageFrame(
titleStringId = Page.Devices.labelStringId,
onSideIconClick = {
requestHolder.deviceReg(
deviceInfo = DeviceInfo().apply {
name = System.currentTimeMillis().toString()
device_id = "sdsdf"
is_clip = 0
}
)
}
) {
val state = rememberLazyListState()
LazyColumn(state = state) {
items(
items = requestHolder.pushDeerViewModel.deviceList,
key = { item: DeviceInfo -> item.id }) { deviceInfo: DeviceInfo ->
SwipeToDismissItem(onDismiss = { requestHolder.deviceRemove(deviceInfo) }) {
CardItemSingleLineWithIcon(
onClick = {},
resId = R.drawable.ipad_landscape2x,
text = if (deviceInfo.device_id == requestHolder.settingStore.thisDeviceId) "${deviceInfo.name} (this device)" else deviceInfo.name
)
}
}
item {
ListBottomBlankItem()
}
}
}
}
@ExperimentalMaterialApi
@Composable
fun DeviceListPage(userInfo: UserInfo, token: String, regid: String) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
.padding(top = 8.dp)
) {
item {
Card(elevation = 5.dp, onClick = {}, modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp)) {
Text(text = "当前版本 Android ${SystemUtil.getSystemVersion()}")
Text(text = "本机品牌 ${SystemUtil.getDeviceBrand()}")
Text(text = "本机型号 ${SystemUtil.getDeviceModel()}")
Text(text = "产品名称 ${Build.PRODUCT}")
MiuiVersion()
}
}
}
item {
Card(elevation = 5.dp, onClick = {}, modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp)) {
Text(text = "id ${userInfo.id}")
Text(text = "name ${userInfo.name}")
Text(text = "email ${userInfo.email}")
Text(text = "app_id ${userInfo.app_id}")
Text(text = "wechat_id ${userInfo.wechat_id}")
Text(text = "created_at ${userInfo.created_at}")
Text(text = "updated_at ${userInfo.updated_at}")
Text(text = "level ${userInfo.level}")
Text(text = "token $token")
Text(text = "regid $regid")
}
}
}
}
}
@Composable
fun MiuiVersion() {
val v = SystemUtil.getMiuiVersion()
if (!TextUtils.isEmpty(v)) {
Text(text = "Miui 版本 ${v}")
}
}

View File

@ -0,0 +1,37 @@
package com.pushdeer.os.ui.compose.page.main
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.pushdeer.os.data.api.data.response.PushKey
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.compose.componment.KeyItem
import com.pushdeer.os.ui.compose.componment.ListBottomBlankItem
import com.pushdeer.os.ui.compose.componment.SwipeToDismissItem
import com.pushdeer.os.ui.navigation.Page
@ExperimentalMaterialApi
@Composable
fun KeyListPage(requestHolder: RequestHolder) {
MainPageFrame(
titleStringId = Page.Keys.labelStringId,
onSideIconClick = { requestHolder.keyGen() }
) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(
requestHolder.pushDeerViewModel.keyList,
key = { item: PushKey -> item.id }) { pushKey: PushKey ->
SwipeToDismissItem(onDismiss = { requestHolder.keyRemove(pushKey) }
) {
KeyItem(key = pushKey, requestHolder = requestHolder)
}
}
item {
ListBottomBlankItem()
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.pushdeer.os.ui.compose.page.main
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.pushdeer.os.R
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.navigation.Page
import com.pushdeer.os.ui.navigation.pageList
import com.pushdeer.os.ui.theme.mainBottomBtn
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@Composable
fun MainPage(requestHolder: RequestHolder) {
var titleStringId by remember {
mutableStateOf(Page.Devices.labelStringId)
}
val navController = rememberNavController()
Scaffold(
scaffoldState = rememberScaffoldState(),
bottomBar = {
BottomNavigation(backgroundColor = MaterialTheme.colors.surface) {
val navBackStackEntry by requestHolder.globalNavController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
pageList.forEach { page ->
val selected = page.labelStringId == titleStringId
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = page.id),
contentDescription = stringResource(id = titleStringId),
modifier = Modifier.size(23.dp),
tint = MaterialTheme.colors.mainBottomBtn(selected = selected)
)
},
label = {
Text(
stringResource(id = page.labelStringId),
color = MaterialTheme.colors.mainBottomBtn(selected = selected)
)
},
selected = currentDestination?.hierarchy?.any { it.route == page.route } == true,
onClick = {
navController.navigate(page.route) {
titleStringId = page.labelStringId
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
},
snackbarHost = { },
content = {
Box(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = R.drawable.ic_logo_svg_1),
contentDescription = "",
alpha = 0.05F, colorFilter = ColorFilter.tint(color = Color.Gray),
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.size(500.dp)
.offset(x = (-180).dp, y = 140.dp),
)
NavHost(
navController = navController,
startDestination = Page.Devices.route,
) {
composable(Page.Devices.route) {
DeviceListPage(requestHolder = requestHolder)
}
composable(Page.Keys.route) {
KeyListPage(requestHolder = requestHolder)
}
composable(Page.Messages.route) {
MessageListPage(requestHolder = requestHolder)
}
composable(Page.Settings.route) {
SettingPage(requestHolder = requestHolder)
}
}
}
},
)
}

View File

@ -0,0 +1,70 @@
package com.pushdeer.os.ui.compose.page.main
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.pushdeer.os.values.ConstValues
@Composable
fun MainPageFrame(
titleStringId: Int,
showSideIcon: Boolean = true,
sideIcon: ImageVector = Icons.Default.Add,
onSideIconClick: () -> Unit = {},
sidePadding: Boolean = true,
content: @Composable BoxScope.() -> Unit
) {
// val sizePaddingValue = if (sidePadding) PaddingValues()
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
// .padding(horizontal = if (sidePadding) 37.dp else 0.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp, top = 27.dp)
.padding(horizontal = ConstValues.MainPageSidePadding)
.background(color = Color.Transparent),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(id = titleStringId), fontSize = 32.sp,
fontWeight = FontWeight.W400,
)
// if (showSideIcon) {
IconButton(onClick = onSideIconClick,modifier = Modifier.alpha(if (showSideIcon)1F else 0F)) {
Icon(
imageVector = sideIcon,
contentDescription = "",
tint = Color.LightGray
)
}
// }
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = if (sidePadding) ConstValues.MainPageSidePadding else 0.dp)
) {
content()
}
}
}
}

View File

@ -0,0 +1,113 @@
package com.pushdeer.os.ui.compose.page.main
import androidx.compose.animation.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.pushdeer.os.data.database.entity.MessageEntity
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.compose.componment.*
import com.pushdeer.os.ui.navigation.Page
import com.pushdeer.os.ui.theme.MBlue
import com.pushdeer.os.values.ConstValues
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@Composable
fun MessageListPage(requestHolder: RequestHolder) {
MainPageFrame(
titleStringId = Page.Messages.labelStringId,
sideIcon = if (requestHolder.uiViewModel.showMessageSender) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
onSideIconClick = { requestHolder.toggleMessageSender() },
sidePadding = false
) {
var s by remember {
mutableStateOf("")
}
val messageList by requestHolder.messageViewModel.all.collectAsState(initial = emptyList())
LazyColumn(content = {
item {
AnimatedVisibility(
visible = requestHolder.uiViewModel.showMessageSender,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
.padding(horizontal = ConstValues.MainPageSidePadding)
) {
OutlinedTextField(
value = s,
onValueChange = { s = it },
shape = RoundedCornerShape(4.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color.Transparent,
focusedBorderColor = MaterialTheme.colors.MBlue,
focusedLabelColor = Color.Transparent,
unfocusedBorderColor = MaterialTheme.colors.MBlue,
unfocusedLabelColor = Color.Transparent,
),
maxLines = 5,
singleLine = false,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
)
Button(
onClick = {
requestHolder.messagePushTest(s)
},
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.MBlue,
contentColor = Color.White
),
) {
Text(text = "Send")
}
}
}
}
items(
items = messageList,
key = { item: MessageEntity -> item.id }) { message: MessageEntity ->
SwipeToDismissItem(
onDismiss = {
requestHolder.messageRemove(message.toMessage(), onDone = {
requestHolder.messageViewModel.delete(message)
})
},
// sidePadding = false
sidePadding = message.type != "image"
) {
// ImageMessageItem(message)
when (message.type) {
"markdown" -> MarkdownMessageItem(message, requestHolder)
"text" -> PlainTextMessageItem(message)
"image" -> ImageMessageItem(message)
}
}
}
item {
ListBottomBlankItem()
}
})
}
}

View File

@ -0,0 +1,72 @@
package com.pushdeer.os.ui.compose.page.main
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.pushdeer.os.holder.RequestHolder
import com.pushdeer.os.ui.compose.componment.SettingItem
import com.pushdeer.os.ui.navigation.Page
@ExperimentalMaterialApi
@Composable
fun SettingPage(requestHolder: RequestHolder) {
MainPageFrame(
titleStringId = Page.Settings.labelStringId,
showSideIcon = false
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
SettingItem(
text = "Hi ${requestHolder.pushDeerViewModel.userInfo.name} !",
buttonString = "Logout"
) {
requestHolder.settingStore.userToken = ""
// logout 操作:
// 从服务器删除本设备
// 删除保存的 token
}
}
item {
SettingItem(
text = "Customize Server",
buttonString = "Scan QR"
) {
requestHolder.startQrScanActivity()
}
}
item {
SettingItem(
text = "Do you like PushDeer ?",
buttonString = "Like"
) {
}
}
item {
SettingItem(
text = "LogDog",
buttonString = "Open"
) {
requestHolder.globalNavController.navigate("logdog")
}
}
}
}
}
@Composable
fun TogglePreferenceItem(label: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxSize()
) {
Text(text = label)
}
}

View File

@ -0,0 +1,16 @@
package com.pushdeer.os.ui.navigation
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.pushdeer.os.R
sealed class Page(val route: String, @StringRes val labelStringId:Int, @DrawableRes val id : Int) {
object Devices : Page("device",R.string.main_device, R.drawable.ipad_and_iphon2x)
object Keys:Page("key",R.string.main_key,R.drawable.key2x)
object Messages:Page("message",R.string.main_message,R.drawable.message2x)
object Settings:Page("setting",R.string.main_setting,R.drawable.gearshape2x)
}
val pageList = listOf(
Page.Devices,Page.Keys,Page.Messages,Page.Settings
)

View File

@ -0,0 +1,12 @@
package com.pushdeer.os.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val MainBlue = Color(0xFF3B4789)
val MainGreen = Color(0xFF296C05)
val MainBottomBtn = Color(0xFF8E8E8E)

View File

@ -0,0 +1,11 @@
package com.pushdeer.os.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)

View File

@ -0,0 +1,71 @@
package com.pushdeer.os.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorPalette = darkColors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200
)
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
val Colors.MBlue: Color
@Composable get() = if (isLight) MainBlue else MainBlue
val Colors.MBottomBtn: Color
@Composable get() = MainBottomBtn
val Colors.MBottomBarBgc: Color
@Composable get() = if (isLight) Color.White else Color.White
//val Colors.thingNormal: Color
// @Composable get() = if (isLight) Green400 else Green700
//
//val Colors.thingLost: Color
// @Composable get() = if (isLight) Red400 else Red700
//
//val Colors.thingEnd: Color
// @Composable get() = if (isLight) Color.LightGray else Color.DarkGray
@Composable
fun Colors.mainBottomBtn(selected: Boolean): Color {
return if (selected) MBlue else MBottomBtn
}
@Composable
fun PushDeerTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
LightColorPalette
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}

View File

@ -0,0 +1,28 @@
package com.pushdeer.os.ui.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
/* Other default text styles to override
button = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.W500,
fontSize = 14.sp
),
caption = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)

View File

@ -0,0 +1,47 @@
package com.pushdeer.os.util
import android.annotation.SuppressLint
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.abs
object CurrentTimeUtil {
@SuppressLint("SimpleDateFormat")
val ymdFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
@SuppressLint("SimpleDateFormat")
val ymdthmssFmt = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'")
private val currentTimeZone: TimeZone = TimeZone.getDefault()
private val tz2utcMSOffset = currentTimeZone.getOffset(System.currentTimeMillis())
fun utcTS2ms(utcTS: String): Long {
val calendar = Calendar.getInstance(currentTimeZone)
val date = ymdthmssFmt.parse(utcTS)!!
calendar.time = date
return calendar.time.time + tz2utcMSOffset
}
fun msTSDis(now: Long, then: Long): String {
val dis = abs(now - then)
return when {
dis < 60_000 -> {
(dis / 1_000).toString() + "s ago"
}
dis < 3_600_000 -> {
(dis / 60_000).toString() + "min ago"
}
dis < 86_400_000 -> {
(dis / 3_600_000).toString() + "h ago"
}
else -> {
ymdFmt.format(Date(then))
}
}
}
fun resolveUTCTimeAndNow(utcTS: String, now: Long): String {
return msTSDis(now, utcTS2ms(utcTS))
}
}

View File

@ -0,0 +1,329 @@
package com.pushdeer.os.util;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created zmm
* <p>
* Functions: 设置状态栏透明并改变状态栏颜色为深色工具类
*/
public class StatusBarUtils {
public static int getStatusBarHeight(Resources resources,Context context) {
int statusBarHeight = 0;
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = resources.getDimensionPixelSize(resourceId);
}
Log.d("CompatToolbar", "状态栏高度:" + px2dp(statusBarHeight,context) + "dp");
return statusBarHeight;
}
public static float px2dp(float pxVal, Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return (pxVal / scale);
}
public static void setStatusBarFontIconDark(Window window,boolean dark) {
// 小米MIUI
try {
Class clazz = window.getClass();
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) { //状态栏亮色且黑色字体
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else { //清除黑色字体
extraFlagField.invoke(window, 0, darkModeFlag);
}
} catch (Exception e) {
e.printStackTrace();
}
// 魅族FlymeUI
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception e) {
e.printStackTrace();
}
// android6.0+系统
// 这个设置和在xml的style文件中用这个<item name="android:windowLightStatusBar">true</item>属性是一样的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (dark) {
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
}
/**
* 设置魅族手机状态栏图标颜色风格
* <p>
* 可以用来判断是否为Flyme用户
*
* @param window 需要设置的窗口
* @param dark 是否把状态栏字体及图标颜色设置为深色
* @return boolean 成功执行返回true
*/
public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class
.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
/**
* 设置小米手机状态栏字体图标颜色模式需要MIUIV6以上
*
* @param window 需要设置的窗口
* @param dark 是否把状态栏字体及图标颜色设置为深色
* @return boolean 成功执行返回true
*/
public static boolean MIUISetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
Class clazz = window.getClass();
try {
int darkModeFlag = 0;
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if (dark) {//状态栏透明且黑色字体
extraFlagField.invoke(window, darkModeFlag, darkModeFlag);
} else {//清除黑色字体
extraFlagField.invoke(window, 0, darkModeFlag);
}
result = true;
} catch (Exception e) {
}
}
return result;
}
/**
* 在不知道手机系统的情况下尝试设置状态栏字体模式为深色
* <p>
* 也可以根据此方法判断手机系统类型
* <p>
* 适配4.4以上版本MIUIVFlyme和6.0以上版本其他Android
*
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0 0:设置失败
*/
public static void statusBarLightMode(Activity activity) {
int result = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (MIUISetStatusBarLightMode(activity.getWindow(), true)) {
//result = 1;
StatusBarLightMode(activity, 1);
} else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {
//result = 2;
StatusBarLightMode(activity, 2);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
//result = 3;
StatusBarLightMode(activity, 3);
}
}
}
/**
* 已知系统类型时设置状态栏字体图标为深色
* <p>
* 适配4.4以上版本MIUIVFlyme和6.0以上版本其他Android
*
* @param activity
* @param type 1:MIUUI 2:Flyme 3:android6.0
*/
public static void StatusBarLightMode(Activity activity, int type) {
if (type == 1) {
MIUISetStatusBarLightMode(activity.getWindow(), true);
} else if (type == 2) {
FlymeSetStatusBarLightMode(activity.getWindow(), true);
} else if (type == 3) {
Window window = activity.getWindow();
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
/**
* 已知系统类型时清除MIUI或flyme或6.0以上版本状态栏字体深色模式
*
* @param activity
* @param type 1:MIUUI 2:Flyme 3:android6.0
*/
public static void StatusBarDarkMode(Activity activity, int type) {
if (type == 1) {
MIUISetStatusBarLightMode(activity.getWindow(), false);
} else if (type == 2) {
FlymeSetStatusBarLightMode(activity.getWindow(), false);
} else if (type == 3) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
/**
* 状态栏背景透明
*
* @param activity
*/
public static void StatusBarTransport(Activity activity) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
}
}
}

View File

@ -0,0 +1,97 @@
package com.pushdeer.os.util;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Locale;
public class SystemUtil {
/**
* 获取当前手机系统语言
*
* @return 返回当前系统语言例如当前设置的是中文-中国则返回zh-CN
*/
public static String getSystemLanguage() {
return Locale.getDefault().getLanguage();
}
/**
* 获取当前系统上的语言列表(Locale列表)
*
* @return 语言列表
*/
public static Locale[] getSystemLanguageList() {
return Locale.getAvailableLocales();
}
/**
* 获取当前手机系统版本号
*
* @return 系统版本号
*/
public static String getSystemVersion() {
return android.os.Build.VERSION.RELEASE;
}
/**
* 获取手机型号
*
* @return 手机型号
*/
public static String getDeviceModel() {
return android.os.Build.MODEL;
}
/**
* 获取手机厂商
*
* @return 手机厂商
*/
public static String getDeviceBrand() {
return android.os.Build.BRAND;
}
public static String getSystemProperty(String propName){
String line;
BufferedReader input = null;
try
{
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
}
catch (IOException ex)
{
Log.e("WH_", "Unable to read sysprop " + propName, ex);
return null;
}
finally
{
if(input != null)
{
try
{
input.close();
}
catch (IOException e)
{
Log.e("WH_", "Exception while closing InputStream", e);
}
}
}
return line;
}
public static String getMiuiVersion(){
return getSystemProperty("ro.miui.ui.version.name");
}
public static boolean isMiui(){
return !TextUtils.isEmpty(getMiuiVersion());
}
}

View File

@ -0,0 +1,12 @@
package com.pushdeer.os.values
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.unit.dp
object ConstValues {
val MainPageSidePadding = 37.dp
val MainPageSidePaddings = PaddingValues(horizontal = 37.dp)
val bigRoundCorner = RoundedCornerShape(8.dp)
}

View File

@ -0,0 +1,12 @@
package com.pushdeer.os.viewmodel
import androidx.lifecycle.ViewModel
import com.pushdeer.os.data.repository.LogDogRepository
class LogDogViewModel(private val logDogRepository: LogDogRepository): ViewModel() {
val all = logDogRepository.all
suspend fun clear(){
logDogRepository.clear()
}
}

View File

@ -0,0 +1,37 @@
package com.pushdeer.os.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pushdeer.os.data.api.PushDeerApi
import com.pushdeer.os.data.api.data.response.Message
import com.pushdeer.os.data.database.entity.MessageEntity
import com.pushdeer.os.data.repository.MessageRepository
import com.pushdeer.os.store.SettingStore
import kotlinx.coroutines.launch
class MessageViewModel(
private val messageRepository: MessageRepository,
private val settingStore: SettingStore,
private val pushDeerService: PushDeerApi
) : ViewModel() {
val all = messageRepository.all
fun insert(vararg message: Message) {
viewModelScope.launch {
messageRepository.insert(*message)
}
}
fun delete(vararg message: Message) {
viewModelScope.launch {
messageRepository.delete(*message)
}
}
fun delete(vararg messageEntity: MessageEntity){
viewModelScope.launch {
messageRepository.delete(*messageEntity)
}
}
}

View File

@ -0,0 +1,236 @@
package com.pushdeer.os.viewmodel
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.pushdeer.os.data.api.PushDeerApi
import com.pushdeer.os.data.api.data.request.DeviceInfo
import com.pushdeer.os.data.api.data.response.PushKey
import com.pushdeer.os.data.api.data.response.UserInfo
import com.pushdeer.os.data.repository.LogDogRepository
import com.pushdeer.os.data.repository.MessageRepository
import com.pushdeer.os.store.SettingStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class PushDeerViewModel(
private val settingStore: SettingStore,
private val logDogRepository: LogDogRepository,
private val pushDeerService:PushDeerApi,
private val messageRepository: MessageRepository
) : ViewModel() {
private val TAG = "WH_"
var token: String by mutableStateOf(settingStore.userToken)
var userInfo: UserInfo by mutableStateOf(UserInfo())
var deviceList = mutableStateListOf<DeviceInfo>()
val keyList = mutableStateListOf<PushKey>()
// var messageList = mutableStateListOf<Message>()
suspend fun login(onReturn: (String) -> Unit = {}) {
withContext(Dispatchers.IO) {
if (token == "") {
try {
pushDeerService.fakeLogin().let {
it.content?.let { tokenOnly ->
settingStore.userToken = tokenOnly.token
token = tokenOnly.token
}
}
} catch (e: Exception) {
Log.d(TAG, "login: ${e.localizedMessage}")
logDogRepository.loge("login", "", e.toString())
return@withContext
}
logDogRepository.logi("login","normally","nothing happened")
}
// Log.d(TAG, "login: token $token")
}
}
suspend fun userInfo(onReturn: (UserInfo) -> Unit = {}) {
withContext(Dispatchers.IO) {
try {
pushDeerService.userInfo(token).let {
it.content?.let { ita ->
userInfo = ita
}
}
} catch (e: Exception) {
Log.d(TAG, "userInfo: ${e.localizedMessage}")
logDogRepository.loge("userInfo", "", e.toString())
}
}
}
suspend fun deviceReg(deviceInfo: DeviceInfo, onReturn: (DeviceInfo) -> Unit = {}) {
withContext(Dispatchers.IO) {
try {
pushDeerService.deviceReg(deviceInfo.toRequestMap(token)).let {
it.content?.let {
deviceList.clear()
deviceList.addAll(it.devices)
deviceList.filter {
it.device_id == deviceInfo.device_id
}.let { dis ->
if (dis.isNotEmpty()) {
withContext(Dispatchers.Default) {
onReturn(dis.first())
}
}
}
}
}
} catch (e: Exception) {
Log.d(TAG, "deviceReg: ${e.localizedMessage}")
logDogRepository.loge("deviceReg", "", e.toString())
}
}
}
suspend fun deviceList(onReturn: (List<DeviceInfo>) -> Unit = {}) {
withContext(Dispatchers.IO) {
try {
pushDeerService.deviceList(token).let {
it.content?.let {
deviceList.clear()
deviceList.addAll(it.devices)
}
}
} catch (e: Exception) {
Log.d(TAG, "deviceList: ${e.localizedMessage}")
}
}
}
fun shouldRegDevice(): Boolean {
// Log.d(TAG, "isDeviceReged: current device id ${settingStore.thisDeviceId}")
return deviceList.none { it.device_id == settingStore.thisDeviceId }
}
suspend fun deviceRemove(deviceId: Int) {
withContext(Dispatchers.IO) {
try {
pushDeerService.deviceRemove(token, deviceId).let {
deviceList()
Log.d(TAG, "deviceRemove: $it")
}
} catch (e: Exception) {
Log.d(TAG, "deviceRemove: ${e.localizedMessage}")
}
}
}
suspend fun keyGen() {
withContext(Dispatchers.IO) {
try {
pushDeerService.keyGen(token).let {
it.content?.let {
keyList.clear()
keyList.addAll(it.keys)
}
}
} catch (e: Exception) {
Log.d(TAG, "keyGen: ${e.localizedMessage}")
}
}
}
suspend fun keyRegen(keyId: String) {
withContext(Dispatchers.IO) {
try {
pushDeerService.keyRegen(
mapOf(
"token" to token,
"id" to keyId
)
).let {
keyList()
}
} catch (e: Exception) {
Log.d(TAG, "keyRegen: ${e.localizedMessage}")
}
}
}
suspend fun keyList() {
withContext(Dispatchers.IO) {
try {
pushDeerService.keyList(token).let {
it.content?.let {
keyList.clear()
keyList.addAll(it.keys)
}
}
} catch (e: Exception) {
Log.d(TAG, "keyList: ${e.localizedMessage}")
}
}
}
suspend fun keyRemove(keyId: String) {
withContext(Dispatchers.IO) {
try {
pushDeerService.keyRemove(mapOf("token" to token, "id" to keyId)).let {
keyList()
}
} catch (e: Exception) {
Log.d(TAG, "keyRemove: ${e.localizedMessage}")
}
}
}
suspend fun messagePush(
text: String,
desp: String,
type: String = "markdown",
pushkey: String
) {
withContext(Dispatchers.IO) {
try {
pushDeerService.messagePush(
mapOf(
"pushkey" to pushkey,
"text" to text,
"desp" to desp,
"type" to type
)
).let {
messageList()
}
} catch (e: Exception) {
Log.d(TAG, "messagePush: ${e.localizedMessage}")
}
}
}
suspend fun messageList() {
withContext(Dispatchers.IO) {
try {
pushDeerService.messageList(token).let {
it.content?.let {
messageRepository.insert(*(it.messages.toTypedArray()))
}
}
} catch (e: Exception) {
Log.d(TAG, "messageList: ${e.localizedMessage}")
}
}
}
suspend fun messageRemove(messageId: Int) {
withContext(Dispatchers.IO) {
try {
pushDeerService.messageRemove(token, messageId).let {
messageList()
}
} catch (e: Exception) {
Log.d(TAG, "keyRemove: ${e.localizedMessage}")
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.pushdeer.os.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.pushdeer.os.store.SettingStore
class UiViewModel(private val settingStore: SettingStore): ViewModel() {
var showMessageSender by mutableStateOf(settingStore.showMessageSender)
}

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="233dp"
android:height="233dp"
android:viewportWidth="233"
android:viewportHeight="233">
<group>
<clip-path
android:pathData="M0,0h233v233h-233z"/>
<path
android:pathData="M-282.333,375.018h26.8l-0.7,-14.526c1.7,-5.491 13.527,-40.982 44.275,-61.545q9.332,-6.241 17.071,-11.58h0a69.072,69.072 0,0 0,-5.244 7.935c-1.149,2.3 -3.8,7.636 26.856,67.089l-1.1,12.629h29.6l0.6,-4.293c1.048,-7.437 -4.992,-19.965 -14.474,-38.586 -2.744,-5.39 -5.292,-10.431 -7.186,-14.624h0a13.171,13.171 0,0 1,-1.546 -9.586,3.626 3.626,0 0,1 1.845,-1.894l1.2,-0.349 43.627,-41.73 3.694,1.348c19.366,7.635 30.947,9.982 74.574,9.186 2.246,9.982 8.234,34.491 14.976,44.922 -2.844,15.473 -5.741,48.67 -5.839,49.916l-0.55,5.689h27.5v-12.03a383.644,383.644 0,0 0,5.439 -49.017c-0.349,-8.635 1.2,-28.5 2.145,-39.581l34.042,19.965h0a24.254,24.254 0,0 1,9.234 12.228c2.747,9.533 16.723,64.143 16.873,64.442l0.947,3.743h23.61l1.4,-1.2c4.342,-3.743 3.095,-13.127 -3.695,-27.9h0a432.137,432.137 0,0 1,-17.67 -56.851c0,-2.893 -2.245,-11.18 -15.922,-19.965l0,0a76.686,76.686 0,0 0,-8.934 -4.989l-2.545,-1.247v0a57.934,57.934 0,0 0,25.257 -16.97A112.324,112.324 0,0 1,75.363 235.307a56.1,56.1 0,0 0,20.564 -30.8l9.982,-42.329c4.342,-0.25 8.736,-0.647 13.127,-1.149 5.891,-0.647 11.929,-1.3 17.52,-1.4 17.869,0 23.011,-2.594 27.851,-7.786s4.992,-21.761 2.4,-27.5l0,0a6.684,6.684 0,0 0,-4.99 -4.289c-13.377,-4.143 -32.593,-12.827 -34.94,-16.622 -3.993,-6.239 -20.466,-7.138 -35.79,-6.539l-9.036,-11.629 7.337,-3.746L134.31,88.007v-0.449h54.411L203.697,68.287l-7.938,-6.288 -11.88,15.372L139.454,77.371l5.94,-11.73L170.351,37.887l-7.437,-6.641 -20.766,23.21 -8.784,-14.074 -8.535,4.993 10.731,17.569 -7.089,13.725L94.53,71.677l6.389,-27.848 26.653,-25.707 -6.689,-7.19L98.77,32.246l-5.539,-15.623 -9.436,3.294 7.44,21.664L84.147,72.827l-9.982,4.993L51.702,64.993l11.131,-16.574 -8.287,-5.54L44.165,58.353l-14.976,-18.07L50.202,22.207l-6.539,-7.587L24.147,31.599l-11.33,-24.46 -9.036,4.5 12.28,26.856 -0.849,0.749 25.957,31.448 29.8,16.768 0.449,0.55c-8.287,-4.641 -16.671,-8.287 -21.761,-7.486v0a8.588,8.588 0,0 0,-7.388 9.036,33.934 33.934,0 0,0 8.736,19.317l-32.4,37.537a513.11,513.11 0,0 0,-82.812 8.234c-17.221,3.346 -38.137,1.348 -58.4,-0.6 -28.75,-2.642 -56.005,-5.288 -71.73,7.34a59.131,59.131 0,0 0,-12.629 13.778c-7.838,1.8 -29.951,11.233 -29.951,59.2v4.992h4.992a19.5,19.5 0,0 0,13.377 -7.089c0,4.443 0,8.983 0.3,13.628v3.942a33.606,33.606 0,0 1,-4.042 9.185,17.928 17.928,0 0,0 -3.294,9.982 163.081,163.081 0,0 0,-21.263 13.078l-1.1,0.749L-282.336,358.05ZM-169.72,296.948a13.677,13.677 0,0 0,-6.689 6.64,21.454 21.454,0 0,0 1.448,17.97c1.995,4.394 4.641,9.586 7.388,14.976h0A210.546,210.546 0,0 1,-154.698 364.934h-8.934l0.4,-4.693 -0.4,-1.2c-12.629,-24.408 -26.2,-53.659 -27.054,-59.9 1.1,-1.845 3.444,-4.993 5.742,-8.234a83.879,83.879 0,0 0,11.33 -18.718c7.586,-5.492 12.43,-9.185 14.377,-10.682a82.932,82.932 0,0 1,26.9 -0.3ZM-16.176,361.839v3.245h-6.689c1,-10.881 3.4,-35.24 5.64,-45.724l0.5,-2.347 -1.547,-1.845c-4.693,-5.689 -10.532,-26.054 -13.976,-40.832l23.61,-0.551c-0.947,11.33 -2.5,31.4 -2.145,40.633h0a397.657,397.657 0,0 1,-5.39 47.322ZM-226.624,226.274c1.149,-20.366 7.037,-30.748 12.58,-36.041a99.151,99.151 0,0 0,-4.095 24.31,50.532 50.532,0 0,1 -8.485,11.73ZM-272.348,360.199 L-239.698,289.421a109.45,109.45 0,0 1,21.162 -12.382l4.592,-1.246 -1.1,-4.593c-0.7,-3.095 0,-3.944 1.6,-6.988a43.328,43.328 0,0 0,5.288 -12.479v-6.037c-1,-34.293 -1.7,-59.049 19.965,-76.419 12.629,-9.982 37.837,-7.688 64.539,-4.992 20.965,1.995 42.628,4.094 61.246,0.448l0,0a502.892,502.892 0,0 1,83.46 -8.336h2.3l34.94,-40.633 0,0a28.239,28.239 0,0 0,11.58 5.39l1.846,-9.982a27.909,27.909 0,0 1,-15.971 -12.879,21.824 21.824,0 0,1 -3.5,-8.085c5.891,0.749 22.712,10.784 35.988,20.616l3.043,-4.143c13.078,-0.449 24.958,0.449 27.005,1.947 4.592,7.239 24.16,14.976 34.443,18.718h0a6.434,6.434 0,0 0,5.491 7.935,29.034 29.034,0 0,1 -1.3,9.635c-2.2,2.347 -4.042,4.394 -20.665,4.592a178.548,178.548 0,0 0,-18.52 1.448c-12.13,1.348 -23.61,2.594 -31.246,-1 -9.982,-4.992 -11.281,-10.382 -11.33,-10.431l-9.982,1.2c0,1.1 1.748,11.131 16.97,18.269h0a37,37 0,0 0,13.179 3.1l-9.185,39.933v0a46.837,46.837 0,0 1,-17.02 24.958l-0.9,0.7A229.149,229.149 0,0 1,83.202 194.932l4.641,-8.635 -14.976,2.047 9.982,-20.766 -8.934,-4.393 -18.269,38.137 14.077,-1.947C63.036,212.707 55.802,229.326 56.396,238.907c-7.785,7.635 -14.227,13.827 -25.606,14.976v0a50.516,50.516 0,0 1,-0.7 -14.373l-9.982,-0.55c-1.246,24.011 5.39,28.5 16.424,33.791h0a69.288,69.288 0,0 1,7.838 4.245c10.634,7.036 11.629,12.479 11.629,12.479v1.3c0.4,1.646 9.982,40.281 18.618,59.3l0,0a57.23,57.23 0,0 1,4.99 14.976L70.122,365.051c-3.245,-12.629 -13.575,-52.712 -15.922,-60.946v0a34.29,34.29 0,0 0,-13.624 -17.918l-57.7,-34.341 -4.993,8.586 5.892,3.5c-67.088,1.647 -76.719,0 -97.086,-7.987l0,0a89.857,89.857 0,0 0,-49.017 -3.942l-1.047,0.25 -0.849,0.647S-182.845,267.407 -217.389,290.519c-36.389,24.359 -48.168,66.238 -48.666,68.035v0.749l0.3,5.69L-272.347,364.993Z"
android:fillColor="#3b4789"/>
<path
android:pathData="M110.502,119.904a5.39,5.39 0,1 1,-5.39 -5.394,5.39 5.39,0 0,1 5.39,5.394"
android:fillColor="#3b4789"/>
<path
android:pathData="M-152.554,175.257l1,-9.986v0a50.958,50.958 0,0 0,-50.911 33.994l9.335,3.545h0a40.393,40.393 0,0 1,40.584 -27.555Z"
android:fillColor="#3b4789"/>
<path
android:pathData="M153.592,151.55A2.54,2.54 83.016,0 0,150.072 151.982L124.335,184.905a2.54,2.54 83.016,0 0,0.431 3.52l53.994,42.208a2.54,2.54 83.016,0 0,3.52 -0.431L208.016,197.278A2.54,2.54 83.016,0 0,207.585 193.759ZM153.734,155.904L203.325,194.671L162.38,195.947ZM150.519,156.839 L155.87,181.674 128.916,184.473ZM203.196,198.018L181.593,225.652l-3.791,-26.831ZM156.586,184.954 L159.399,198.027a1.671,1.671 83.016,0 0,1.688 1.319L174.447,198.916 178.286,226.02L129.355,187.769Z"
android:strokeWidth="2.0003998"
android:fillColor="#3b4789"
android:strokeColor="#3b4789"/>
</group>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,9 @@
<vector android:height="279dp" android:viewportHeight="278.657"
android:viewportWidth="272.079" android:width="272.4139dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3b4789" android:pathData="M2.003,200.068L16.58,200.068l-0.381,-7.9c0.922,-2.986 7.357,-22.288 24.079,-33.471q5.075,-3.394 9.284,-6.3h0a37.563,37.563 0,0 0,-2.852 4.315c-0.625,1.248 -2.064,4.153 14.605,36.486l-0.6,6.868h16.1l0.326,-2.335c0.57,-4.045 -2.715,-10.858 -7.872,-20.985 -1.492,-2.931 -2.878,-5.673 -3.908,-7.953h0a7.163,7.163 0,0 1,-0.841 -5.213,1.972 1.972,0 0,1 1,-1.03l0.652,-0.19L89.904,139.668l2.009,0.733c10.532,4.152 16.83,5.429 40.557,5 1.222,5.429 4.478,18.758 8.144,24.431 -1.547,8.415 -3.122,26.469 -3.176,27.147l-0.3,3.094h14.957L152.095,193.528A208.643,208.643 0,0 0,155.055 166.868c-0.19,-4.7 0.652,-15.5 1.166,-21.526l18.513,10.858h0a13.19,13.19 0,0 1,5.022 6.65c1.494,5.184 9.1,34.884 9.177,35.047l0.515,2.036h12.84l0.76,-0.652c2.361,-2.035 1.683,-7.139 -2.009,-15.175h0a235.01,235.01 0,0 1,-9.61 -30.918c0,-1.574 -1.221,-6.08 -8.659,-10.858l0,0a41.707,41.707 0,0 0,-4.859 -2.713l-1.384,-0.678v0a31.508,31.508 0,0 0,13.736 -9.229,61.085 61.085,0 0,1 6.272,-5.62A30.51,30.51 0,0 0,207.72 107.338l5.429,-23.02c2.361,-0.136 4.751,-0.352 7.139,-0.625 3.2,-0.352 6.488,-0.706 9.528,-0.759 9.718,0 12.515,-1.411 15.147,-4.234s2.715,-11.835 1.3,-14.957l0,0a3.635,3.635 0,0 0,-2.714 -2.333c-7.275,-2.253 -17.726,-6.976 -19,-9.04 -2.172,-3.393 -11.131,-3.882 -19.464,-3.556l-4.914,-6.325 3.99,-2.037 24.431,3.53v-0.244h29.591l8.144,-10.479 -4.317,-3.419 -6.461,8.36h-24.16l3.23,-6.379 13.573,-15.094 -4.045,-3.611L232.857,25.734l-4.777,-7.654L223.44,20.795l5.836,9.555 -3.855,7.464L206.96,35.099l3.475,-15.145 14.5,-13.98 -3.638,-3.91L209.266,13.654l-3.012,-8.5 -5.132,1.791 4.046,11.782 -3.855,16.993 -5.429,2.716L183.669,31.468l6.054,-9.014 -4.507,-3.013 -5.646,8.415 -8.144,-9.828 11.428,-9.828L179.296,4.068l-10.614,9.231 -6.162,-13.3 -4.914,2.445 6.679,14.605 -0.462,0.407 14.117,17.1 16.206,9.119 0.244,0.3c-4.507,-2.524 -9.066,-4.507 -11.835,-4.071v0a4.671,4.671 0,0 0,-4.018 4.914,18.455 18.455,0 0,0 4.751,10.506L165.67,75.74a279.054,279.054 0,0 0,-45.037 4.478c-9.366,1.82 -20.74,0.733 -31.762,-0.326 -15.635,-1.437 -30.458,-2.876 -39.01,3.992a32.159,32.159 0,0 0,-6.868 7.493c-4.262,0.977 -16.289,6.109 -16.289,32.195v2.715h2.715a10.607,10.607 0,0 0,7.275 -3.855c0,2.417 0,4.885 0.163,7.411v2.144a18.277,18.277 0,0 1,-2.2 5,9.75 9.75,0 0,0 -1.791,5.429 88.7,88.7 0,0 0,-11.564 7.112l-0.6,0.407 -18.7,40.911ZM63.248,157.61a7.438,7.438 0,0 0,-3.638 3.611,11.668 11.668,0 0,0 0.788,9.773c1.085,2.39 2.524,5.213 4.018,8.144h0a114.508,114.508 0,0 1,7 15.446L66.56,194.584l0.218,-2.552 -0.218,-0.652c-6.868,-13.274 -14.251,-29.182 -14.713,-32.575 0.6,-1 1.873,-2.716 3.123,-4.478a45.618,45.618 0,0 0,6.162 -10.18c4.126,-2.987 6.76,-5 7.819,-5.809a45.1,45.1 0,0 1,14.632 -0.163ZM146.748,192.901L146.748,194.668h-3.638c0.543,-5.917 1.846,-19.165 3.068,-24.867l0.271,-1.276 -0.841,-1c-2.552,-3.094 -5.728,-14.17 -7.6,-22.206l12.84,-0.3c-0.515,6.162 -1.357,17.075 -1.166,22.1h0a216.26,216.26 0,0 1,-2.931 25.736ZM32.301,119.176c0.625,-11.076 3.827,-16.722 6.841,-19.6a53.923,53.923 0,0 0,-2.227 13.221,27.482 27.482,0 0,1 -4.614,6.379ZM7.434,192.011 L25.188,153.518a59.525,59.525 0,0 1,11.509 -6.734l2.5,-0.678 -0.6,-2.5c-0.381,-1.683 0,-2.145 0.869,-3.8a23.564,23.564 0,0 0,2.876 -6.787L42.342,129.738c-0.543,-18.65 -0.922,-32.113 10.858,-41.56 6.868,-5.429 20.578,-4.181 35.1,-2.715 11.4,1.085 23.183,2.226 33.308,0.244h0a273.5,273.5 0,0 1,45.39 -4.534h1.248l19,-22.1v0a15.358,15.358 0,0 0,6.3 2.931l1,-5.429a15.178,15.178 0,0 1,-8.686 -7,11.869 11.869,0 0,1 -1.9,-4.4c3.2,0.407 12.352,5.865 19.572,11.212l1.655,-2.253c7.112,-0.244 13.573,0.244 14.687,1.059 2.5,3.937 13.139,8.144 18.732,10.18h0a3.5,3.5 0,0 0,2.986 4.315,15.79 15.79,0 0,1 -0.706,5.24c-1.195,1.276 -2.2,2.39 -11.239,2.5a97.1,97.1 0,0 0,-10.072 0.788c-6.6,0.733 -12.84,1.411 -16.993,-0.543 -5.429,-2.715 -6.135,-5.646 -6.162,-5.673l-5.429,0.652c0,0.6 0.951,6.054 9.229,9.936h0a20.122,20.122 0,0 0,7.167 1.684l-5,21.718v0a25.472,25.472 0,0 1,-9.256 13.573l-0.489,0.381a124.622,124.622 0,0 1,8.144 -17.807l2.524,-4.7 -8.144,1.114 5.429,-11.293L195.749,84.868l-9.936,20.74 7.656,-1.059c-3.638,7.249 -7.574,16.289 -7.249,21.5 -4.234,4.152 -7.737,7.52 -13.926,8.144v0a27.473,27.473 0,0 1,-0.381 -7.817l-5.429,-0.3c-0.678,13.058 2.931,15.5 8.932,18.377h0a37.684,37.684 0,0 1,4.262 2.308c5.783,3.827 6.325,6.787 6.325,6.787v0.7c0.218,0.9 5.429,21.907 10.125,32.25l0,0a31.125,31.125 0,0 1,2.714 8.145h-5.158c-1.765,-6.868 -7.383,-28.667 -8.659,-33.146v0a18.649,18.649 0,0 0,-7.41 -9.745L146.234,133.078l-2.716,4.669 3.2,1.9c-36.486,0.9 -41.723,0 -52.8,-4.344h0a48.869,48.869 0,0 0,-26.658 -2.144l-0.57,0.136 -0.462,0.352s-10.125,7.9 -28.912,20.468c-19.79,13.248 -26.2,36.023 -26.467,37v0.407l0.162,3.094L7.435,194.616Z"/>
<path android:fillColor="#3b4789" android:pathData="M215.646,61.328a2.931,2.931 0,1 1,-2.931 -2.933,2.931 2.931,0 0,1 2.931,2.933"/>
<path android:fillColor="#3b4789" android:pathData="M72.584,91.43l0.541,-5.431v0a27.713,27.713 0,0 0,-27.688 18.487l5.077,1.928h0A21.968,21.968 0,0 1,72.584 91.431Z"/>
<path android:fillColor="#3b4789"
android:pathData="M240.236,78.538A1.381,1.381 83.016,0 0,238.322 78.772L224.325,96.678a1.381,1.381 83.016,0 0,0.234 1.914L253.924,121.547a1.381,1.381 83.016,0 0,1.914 -0.234L269.835,103.406A1.381,1.381 83.015,0 0,269.601 101.493ZM240.314,80.907L267.284,101.99L245.019,102.686ZM238.565,81.414l2.91,13.507L226.817,96.443ZM267.214,103.809L255.465,118.838L253.404,104.246ZM241.865,96.704l1.528,7.108a0.909,0.909 83.016,0 0,0.918 0.718l7.267,-0.233L253.667,119.039L227.055,98.236Z"
android:strokeColor="#3b4789" android:strokeWidth="2.0003998"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="76dp"
android:height="76dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M950.14,827.09L73.86,827.09a73.81,73.81 0,0 1,-73.81 -73.81L0.04,270.72a73.81,73.81 0,0 1,73.81 -73.81h876.29a73.81,73.81 0,0 1,73.81 73.81v482.47a73.81,73.81 0,0 1,-73.81 73.86zM246.14,679.38v-192l98.47,123.09 98.43,-123.09v192h98.47L541.53,344.66h-98.47l-98.43,123.09 -98.47,-123.09L147.67,344.66v334.81zM905.86,512h-98.47L807.38,344.62h-98.43L708.95,512h-98.47l147.67,172.33z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:ignore="NewApi">
<cn.bingoogolapple.qrcode.zxing.ZXingView
android:id="@+id/qrcode1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:qrcv_animTime="300"
app:qrcv_barCodeTipText="将条码放入框内,即可自动扫描"
app:qrcv_barcodeRectHeight="40dp"
app:qrcv_borderColor="@android:color/black"
app:qrcv_borderSize="0.1dp"
app:qrcv_cornerColor="@android:color/black"
app:qrcv_cornerLength="20dp"
app:qrcv_cornerSize="3dp"
app:qrcv_isBarcode="false"
app:qrcv_isCenterVertical="false"
app:qrcv_isOnlyDecodeScanBoxArea="true"
app:qrcv_isScanLineReverse="true"
app:qrcv_isShowDefaultGridScanLineDrawable="false"
app:qrcv_isShowDefaultScanLineDrawable="true"
app:qrcv_isShowTipBackground="false"
app:qrcv_isShowTipTextAsSingleLine="false"
app:qrcv_isTipTextBelowRect="true"
app:qrcv_maskColor="#DEBCBCBC"
app:qrcv_qrCodeTipText="请将QRCode放到扫描框中"
app:qrcv_rectWidth="240dp"
app:qrcv_scanLineColor="@android:color/black"
app:qrcv_scanLineMargin="0dp"
app:qrcv_scanLineSize="0.5dp"
app:qrcv_tipTextColor="#80ffffff"
app:qrcv_tipTextSize="14sp"
app:qrcv_toolbarHeight="40dp"
app:qrcv_topOffset="80dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PushDeer" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="main_blue">#3B4789</color>
<color name="main_green">#296C05</color>
<color name="main_bottom_btn">#8E8E8E</color>
</resources>

View File

@ -0,0 +1,8 @@
<resources>
<string name="app_name">PushDeer</string>
<string name="main_device">Device</string>
<string name="main_key">Key</string>
<string name="main_message">Message</string>
<string name="main_setting">Setting</string>
<string name="global_logdog">LogDog</string>
</resources>

View File

@ -0,0 +1,27 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PushDeer" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<!-- <item name="android:statusBarColor" tools:targetApi="l">@color/purple_500</item>-->
<!-- <item name="android:statusBarColor" tools:targetApi="l">@android:color/white</item>-->
<item name="android:statusBarColor" tools:targetApi="l">@android:color/transparent</item>
<!-- Customize your theme here. -->
</style>
<style name="Theme.PushDeer.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.PushDeer.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.PushDeer.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@ -0,0 +1,17 @@
package com.pushdeer.os
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)
}
}

Some files were not shown because too many files have changed in this diff Show More