16
android/.gitignore
vendored
Normal 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
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
6
android/.idea/compiler.xml
Normal 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
@ -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>
|
20
android/.idea/inspectionProfiles/Project_Default.xml
Normal 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
@ -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
@ -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
@ -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
@ -0,0 +1,2 @@
|
||||
/build
|
||||
/src/main/java/com/pushdeer/os/values/AppKeys.kt
|
111
android/app/build.gradle
Normal 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'
|
||||
}
|
BIN
android/app/libs/MiPush_SDK_Client_4_9_0.jar
Normal file
21
android/app/proguard-rules.pro
vendored
Normal 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
|
@ -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)
|
||||
}
|
||||
}
|
119
android/app/src/main/AndroidManifest.xml
Normal 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>
|
80
android/app/src/main/java/com/pushdeer/os/App.kt
Normal 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"
|
||||
}
|
||||
}
|
169
android/app/src/main/java/com/pushdeer/os/MainActivity.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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>()
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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 = ""
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.pushdeer.os.data.repository
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
class MiPushRepository {
|
||||
val regId = MutableLiveData("")
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.pushdeer.os.holder
|
||||
|
||||
interface DataHolder {
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 ""
|
||||
// }
|
||||
//}
|
@ -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","")
|
||||
}
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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}")
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
12
android/app/src/main/java/com/pushdeer/os/ui/theme/Color.kt
Normal 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)
|
11
android/app/src/main/java/com/pushdeer/os/ui/theme/Shape.kt
Normal 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)
|
||||
)
|
71
android/app/src/main/java/com/pushdeer/os/ui/theme/Theme.kt
Normal 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
|
||||
)
|
||||
}
|
28
android/app/src/main/java/com/pushdeer/os/ui/theme/Type.kt
Normal 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
|
||||
)
|
||||
*/
|
||||
)
|
@ -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))
|
||||
}
|
||||
}
|
@ -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以上版本MIUIV、Flyme和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以上版本MIUIV、Flyme和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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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>
|
BIN
android/app/src/main/res/drawable/deer_placeholder.png
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
android/app/src/main/res/drawable/gearshape2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
24
android/app/src/main/res/drawable/ic_deer_head_with_mail.xml
Normal 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>
|
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
9
android/app/src/main/res/drawable/ic_logo_svg_1.xml
Normal 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>
|
9
android/app/src/main/res/drawable/ic_markdown.xml
Normal 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>
|
BIN
android/app/src/main/res/drawable/ipad_and_iphon2x.png
Normal file
After Width: | Height: | Size: 814 B |
BIN
android/app/src/main/res/drawable/ipad_landscape2x.png
Normal file
After Width: | Height: | Size: 588 B |
BIN
android/app/src/main/res/drawable/iphone2x.png
Normal file
After Width: | Height: | Size: 552 B |
BIN
android/app/src/main/res/drawable/key2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
android/app/src/main/res/drawable/logo_com_x2.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
android/app/src/main/res/drawable/logo_half.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
android/app/src/main/res/drawable/message2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
46
android/app/src/main/res/layout/activity_qr_scan.xml
Normal 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>
|
@ -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>
|
@ -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>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 982 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
16
android/app/src/main/res/values-night/themes.xml
Normal 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>
|
14
android/app/src/main/res/values/colors.xml
Normal 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>
|
8
android/app/src/main/res/values/strings.xml
Normal 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>
|
27
android/app/src/main/res/values/themes.xml
Normal 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>
|
17
android/app/src/test/java/com/pushdeer/os/ExampleUnitTest.kt
Normal 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)
|
||||
}
|
||||
}
|