Compare commits
59 Commits
Author | SHA1 | Date |
---|---|---|
Easy | b8fa724a9a | |
Easy | 4ce68572b9 | |
Easy | 16aec6c468 | |
Easy | bfadc52fef | |
Easy | d7f3279458 | |
Easy | f5841403c7 | |
Easy | be51654558 | |
Easy | aa9a2a9dfa | |
Easy | 1a9ad9e351 | |
Easy | 19011afdd0 | |
Easy | f9779893ec | |
Easy | 09acc7955e | |
Chi | 6053cf624a | |
Easy | d15892727a | |
Easy | b5e6605328 | |
Easy | 8e5292acfb | |
hext | ed61f49eca | |
Easy | 6927a94860 | |
Easy | e5d715e06d | |
Easy | 3fb92270f3 | |
Easy | 1ca157b32e | |
Easy | fd901b04c8 | |
Easy | c706d83e2f | |
Easy | 034e93d3c7 | |
Easy | a218d5c68d | |
Easy | 416cc716dd | |
Easy | d504a91dc4 | |
Easy | 20697f77dd | |
Harry Cheng | 4673418d95 | |
Harry Cheng | c6caf87a0a | |
Harry Cheng | 2b89ee699e | |
EasyChen | d7a0c95382 | |
Easy | 2f461cf8c6 | |
Easy | b874100b17 | |
Easy | a0d07f4362 | |
Easy | c2283e50f9 | |
Easy | 4c8b70310e | |
Easy | 57e467b2e3 | |
Easy | 6b097973c2 | |
Easy | af112c230c | |
Easy | 3c019e61f8 | |
Easy | 914bd7ac1a | |
Easy | 3878706c9f | |
Easy | 628b67191d | |
Easy | fe15fa7593 | |
Easy | 2ea7fa6c22 | |
Easy | ed3c753f6b | |
Easy | 38162f0cbe | |
hext | 65e2e729fc | |
Easy | 05d051dc8b | |
Easy | 411f768156 | |
Easy | 6d2b30901b | |
hext | c3d0cd13f5 | |
Easy | a685433be3 | |
Easy | 0808c2257c | |
Easy | 4fbf408eae | |
Easy | 91c2d64295 | |
Easy | 828f6f5454 | |
Easy | 331daec61a |
64
README.md
|
@ -1,9 +1,11 @@
|
|||
> ⚠️ 目前,官方架设的Android版本因接口权限停止无法使用,[详情请点击](https://github.com/easychen/pushdeer/issues/150)
|
||||
|
||||
> ⚠️ 自架版服务器端需每年更新推送证书,如果之前架设的服务突然无法收到推送,请尝试拉取部署最新代码,或者[手动更新证书](https://github.com/easychen/pushdeer/tree/main/push)
|
||||
|
||||
PushDeer是一个可以自行架设的无APP推送服务,同时也为因为某些原因无法使用无APP推送方案的同学提供有APP/自制设备方案。
|
||||
|
||||
[🐙🐱 GitHub仓库](https://github.com/easychen/pushdeer) [🔮 中国大陆镜像仓库@Gitee](https://gitee.com/easychen/pushdeer)
|
||||
|
||||
> [🤖 Android自架版求开发者参与](doc/Android%E7%89%88self-hosted%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%AE%9E%E7%8E%B0.md)
|
||||
|
||||
本项目已经实现的方案/端包括:
|
||||
|
||||
- 无APP方案:
|
||||
|
@ -97,6 +99,7 @@ https://api2.pushdeer.com/message/push?pushkey=<key>&text=标题&desp=<markdown>
|
|||
|
||||
- [Python SDK](https://github.com/gaoliang/pypushdeer) by [Gao Liang](https://github.com/gaoliang)
|
||||
- [Go SDK](https://github.com/Luoxin/go-pushdeer-sdk) by [Luoxin](https://github.com/Luoxin)
|
||||
- [Rust SDK](https://github.com/abgelehnt/rupushdeer) by [Chi](https://github.com/abgelehnt)
|
||||
|
||||
PHP函数:
|
||||
|
||||
|
@ -158,20 +161,19 @@ function pushdeer_send($text, $desp = '', $type='text', $key = '[PUSHKEY]')
|
|||
|
||||
然后运行以下代码:
|
||||
|
||||
(大陆服务器使用)
|
||||
```
|
||||
git clone https://gitee.com/easychen/pushdeer.git
|
||||
cd pushdeer
|
||||
docker-compose -f docker-compose.self-hosted.yml up --build -d
|
||||
```
|
||||
|
||||
(海外服务器使用)
|
||||
```
|
||||
git clone https://github.com/easychen/pushdeer.git
|
||||
cd pushdeer
|
||||
docker-compose -f docker-compose.self-hosted.yml up --build -d
|
||||
```
|
||||
|
||||
如果你的服务器连接GitHub有困难,可以使用Gitee的代码,但需要核对是否为最新版本(有可能没同步)
|
||||
```
|
||||
git clone https://gitee.com/easychen/pushdeer.git
|
||||
cd pushdeer
|
||||
docker-compose -f docker-compose.self-hosted.yml up --build -d
|
||||
```
|
||||
|
||||
> 如提示docker服务未安装/找不到/未启动,可在 docker-compose 前加 sudo 再试
|
||||
|
||||
等待初始化完成后,访问 `$AAA(需替换为服务器端IP或域名):8800`,看到扫码提示和图片则说明容器已经启动。
|
||||
|
@ -180,6 +182,7 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d
|
|||
|
||||
如果您在部署中遇到问题,可按[调试文档](/doc/调试文档.md)定位并发现错误信息。
|
||||
|
||||
<!--
|
||||
#### 单一容器部署方案
|
||||
|
||||
对于很多不能运行docker-compose的容器环境,可以直接使用 pushdeer 镜像。该镜像中已经包含了 redis 服务,但需要通过环境变量指定数据库等信息:
|
||||
|
@ -188,7 +191,8 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d
|
|||
docker run -e DB_DATABASE=* -e DB_HOST=* -e DB_PORT=*28740* -e DB_USERNAME=* -e DB_PASSWORD=* -e DB_TIMEZONE=+08:00 -e WEB_PHP_SOCKET=127.0.0.1:8000 -p 9000:9000 ccr.ccs.tencentyun.com/ftqq/pushdeercore
|
||||
```
|
||||
|
||||
请将上诉命令中的`*`替换为对应的数据库信息。
|
||||
请将上述命令中的`*`替换为对应的数据库信息。
|
||||
-->
|
||||
|
||||
### 使用自架版客户端
|
||||
|
||||
|
@ -419,6 +423,38 @@ type 为 image 时,text 中为要发送图片的URL。
|
|||
|-|-|-|
|
||||
|token|认证token|
|
||||
|
||||
#### Simple token
|
||||
> 为了方便客户端永久保持登入状态,我们提供了一个永不失效的Token,即 Simple token
|
||||
|
||||
##### 获取 Simple token
|
||||
通过 上文中的「获得当前用户的基本信息」接口(`POST /user/info`) 得到
|
||||
|
||||
##### 通过 Simple token 登入
|
||||
|
||||
`POST /login/simple_token`
|
||||
|
||||
|参数|说明|备注|
|
||||
|-|-|-|
|
||||
|stoken|Simple token|
|
||||
|
||||
登入成功返回认证token。
|
||||
|
||||
##### 重置 Simple token
|
||||
|
||||
`POST /simple_token/regen`
|
||||
|
||||
|参数|说明|备注|
|
||||
|-|-|-|
|
||||
|token|认证token|
|
||||
|
||||
##### 清空 Simple token
|
||||
|
||||
`POST /simple_token/remove`
|
||||
|
||||
|参数|说明|备注|
|
||||
|-|-|-|
|
||||
|token|认证token|
|
||||
|
||||
|
||||
[更详细的请求和返回值可以参考这里](doc/api/PushDeerOS.md)
|
||||
|
||||
|
@ -521,7 +557,7 @@ PushDeer主要面向以下三类用户
|
|||
|
||||
# 授权
|
||||
|
||||
本项目禁止商用(包括但不限于搭建后挂广告或售卖会员、打包后上架商店销售等),对非商业用途采用 GPLV2 授权
|
||||
本项目禁止商用(包括但不限于搭建后挂广告或售卖会员、打包后上架商店销售等),在非商用的情况下遵循GPL v2,当两者冲突时,以非商用原则优先。
|
||||
|
||||
|
||||
# 相关项目
|
||||
|
@ -530,4 +566,6 @@ PushDeer主要面向以下三类用户
|
|||
- [Java SDK](https://gitee.com/mrbread/pushdeer-java-sdk) by [MrBread](https://gitee.com/mrbread)
|
||||
- [Python SDK](https://github.com/gaoliang/pypushdeer) by [Gao Liang](https://github.com/gaoliang)
|
||||
- [API的Go实现](https://github.com/iepngs/pushdeer-backend-go) by [iepngs](https://github.com/iepngs)
|
||||
|
||||
- [API的Node实现](https://github.com/xkrfer/pushdeer-node) by [DouDou](https://github.com/xkrfer)
|
||||
- [浏览器插件](https://github.com/xkrfer/pushdeer-crx) by [DouDou](https://github.com/xkrfer)
|
||||
- [Rust SDK](https://github.com/abgelehnt/rupushdeer) by [Chi](https://github.com/abgelehnt)
|
||||
|
|
|
@ -1,16 +1,42 @@
|
|||
*.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
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Misc Files
|
||||
.DS_Store
|
||||
|
||||
# API Keys
|
||||
/app/src/main/java/com/pushdeer/os/AppKeys.kt
|
||||
|
||||
# Signing Keys
|
||||
/key_debug.properties
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,25 +0,0 @@
|
|||
<?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$/app_self_hosted" />
|
||||
<option value="$PROJECT_DIR$/common" />
|
||||
<option value="$PROJECT_DIR$/compose" />
|
||||
<option value="$PROJECT_DIR$/pushdeerclient" />
|
||||
<option value="$PROJECT_DIR$/pushdeercommon" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -1,20 +0,0 @@
|
|||
<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>
|
|
@ -1,44 +0,0 @@
|
|||
<?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="../../../../layout/compose-model-1642733328920.xml" value="2.0" />
|
||||
<entry key="../../../../layout/compose-model-1642826587452.xml" value="2.0" />
|
||||
<entry key="../../../../layout/compose-model-1643039843013.xml" value="2.0" />
|
||||
<entry key="../../../../layout/compose-model-1643123274044.xml" value="0.2916666666666667" />
|
||||
<entry key="../../../../layout/compose-model-1643123509726.xml" value="4.0" />
|
||||
<entry key="../../../../layout/compose-model-1643595536124.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_apple_logo_svgrepo_com.xml" value="0.23802083333333332" />
|
||||
<entry key="app/src/main/res/drawable/ic_deer_head_with_mail.xml" value="0.23802083333333332" />
|
||||
<entry key="app/src/main/res/drawable/ic_launcher_foreground.xml" value="0.23802083333333332" />
|
||||
<entry key="app/src/main/res/drawable/ic_logo_svg_1.xml" value="0.23802083333333332" />
|
||||
<entry key="app/src/main/res/drawable/ic_markdown.xml" value="0.12962962962962962" />
|
||||
<entry key="app/src/main/res/drawable/ic_weixin_logo_svgrepo_com.xml" value="0.23802083333333332" />
|
||||
<entry key="app/src/main/res/layout/activity_qr_scan.xml" value="0.1" />
|
||||
<entry key="app/src/main/res/layout/activity_webview.xml" value="0.3691123188405797" />
|
||||
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1" />
|
||||
<entry key="app_self_hosted/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1925925925925926" />
|
||||
<entry key="common/src/main/res/layout/activity_qr_scan.xml" value="0.1" />
|
||||
<entry key="pushdeerclient/src/main/res/drawable-v24/ic_markdown.xml" value="0.11944444444444445" />
|
||||
</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>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -4,14 +4,19 @@ plugins {
|
|||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
def debugKeyProps = new Properties()
|
||||
def debugKeyPropsFile = rootProject.file('key_debug.properties')
|
||||
if (debugKeyPropsFile.exists()) {
|
||||
debugKeyProps.load(new FileInputStream(debugKeyPropsFile))
|
||||
}
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('/Users/wolf/Documents/com.wh.pushdeer')
|
||||
storePassword 'wh.pushdeer'
|
||||
keyAlias 'whpushdeer'
|
||||
keyPassword 'wh.pushdeer'
|
||||
storeFile file(debugKeyProps['storeFile'])
|
||||
storePassword debugKeyProps['storePassword']
|
||||
keyAlias debugKeyProps['keyAlias']
|
||||
keyPassword debugKeyProps['keyPassword']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +73,7 @@ dependencies {
|
|||
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')
|
||||
implementation files('libs/MiPush_SDK_Client_5_0_5-C_3rd.aar')
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.pushdeer.os.values
|
||||
|
||||
object AppKeys {
|
||||
|
||||
const val MiPush_Id = "2882303761520124028"
|
||||
const val MiPush_Key = "5512012428028"
|
||||
|
||||
const val WX_Id = "wx3ae07931d0555a24"
|
||||
}
|
|
@ -136,15 +136,16 @@ fun KeyItem(key: PushKey, requestHolder: RequestHolder) {
|
|||
) {
|
||||
AndroidView(
|
||||
factory = {
|
||||
ImageView(it).apply {
|
||||
this.setImageBitmap(
|
||||
QRCodeGenerator(
|
||||
key.key,
|
||||
400.dp.value.toInt(),
|
||||
400.dp.value.toInt()
|
||||
).qrCode
|
||||
)
|
||||
}
|
||||
ImageView(it)
|
||||
},
|
||||
update = { view ->
|
||||
view.setImageBitmap(
|
||||
QRCodeGenerator(
|
||||
key.key,
|
||||
400.dp.value.toInt(),
|
||||
400.dp.value.toInt()
|
||||
).qrCode
|
||||
)
|
||||
},
|
||||
modifier = Modifier.align(alignment = Alignment.Center)
|
||||
)
|
||||
|
|
|
@ -137,12 +137,17 @@ fun ImageMessageItem(message: MessageEntity, requestHolder: RequestHolder) {
|
|||
)
|
||||
}
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
AndroidView(factory = {
|
||||
ImageView(it).apply {
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
load(message.text, requestHolder.coilImageLoader)
|
||||
}
|
||||
}, modifier = Modifier.fillMaxWidth())
|
||||
AndroidView(
|
||||
factory = {
|
||||
ImageView(it).apply {
|
||||
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
view.load(message.text, requestHolder.coilImageLoader)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,7 +416,10 @@ fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) {
|
|||
CardItemWithContent {
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
android.widget.TextView(ctx).apply {
|
||||
android.widget.TextView(ctx)
|
||||
},
|
||||
update = { view ->
|
||||
view.apply {
|
||||
this.post {
|
||||
requestHolder.markdown.setMarkdown(
|
||||
this,
|
||||
|
@ -419,7 +427,8 @@ fun MarkdownMessageItem(message: MessageEntity, requestHolder: RequestHolder) {
|
|||
)
|
||||
}
|
||||
}
|
||||
}, modifier = Modifier
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
storeFile=/Users/easy/Code/pushdeer-andorid/com.pushdeer.com
|
||||
storePassword=pushdeer.com
|
||||
keyAlias=pushdeer
|
||||
keyPassword=pushdeer.com
|
|
@ -71,7 +71,10 @@ class PushDeerMessageController extends Controller
|
|||
|
||||
foreach ($keys as $thekey) {
|
||||
$key = PushDeerKey::where('key', $thekey)->get()->first();
|
||||
|
||||
$user = PushDeerUser::where('id', $key->uid)->get()->first();
|
||||
if ($user->level < 1) {
|
||||
return send_error('此账号已被停用', ErrorCode('ARGS'));
|
||||
}
|
||||
|
||||
if ($key) {
|
||||
$readkey = Str::random(32);
|
||||
|
|
|
@ -39,6 +39,8 @@ class PushDeerUserController extends Controller
|
|||
$the_user['level'] = 1;
|
||||
|
||||
$pd_user = PushDeerUser::create($the_user);
|
||||
$pd_user['simple_token'] = 'SP'.$pd_user['id'].'P'.md5(uniqid(rand(), true));
|
||||
$pd_user->save();
|
||||
}
|
||||
|
||||
// 将数据写到session
|
||||
|
@ -47,6 +49,7 @@ class PushDeerUserController extends Controller
|
|||
$_SESSION['name'] = $pd_user['name'];
|
||||
$_SESSION['email'] = $pd_user['email'];
|
||||
$_SESSION['level'] = $pd_user['level'];
|
||||
$_SESSION['simple_token'] = $pd_user['simple_token'];
|
||||
|
||||
session_regenerate_id(true);
|
||||
$token = session_id();
|
||||
|
@ -56,6 +59,56 @@ class PushDeerUserController extends Controller
|
|||
return send_error('id_token解析错误', ErrorCode('ARGS'));
|
||||
}
|
||||
|
||||
public function loginBySimpleToken(Request $request)
|
||||
{
|
||||
$validated = $request->validate(
|
||||
[
|
||||
'stoken' => 'required|string',
|
||||
]
|
||||
);
|
||||
|
||||
if (!$pd_user = PushDeerUser::where('simple_token', $validated['stoken'])->get()->first()) {
|
||||
return send_error('stoken无效', ErrorCode('ARGS'));
|
||||
}
|
||||
|
||||
if ($pd_user['level']<1) {
|
||||
return send_error('账号已被禁用', ErrorCode('ARGS'));
|
||||
}
|
||||
|
||||
// 将数据写到session
|
||||
session_start();
|
||||
$_SESSION['uid'] = $pd_user['id'];
|
||||
$_SESSION['name'] = $pd_user['name'];
|
||||
$_SESSION['email'] = $pd_user['email'];
|
||||
$_SESSION['level'] = $pd_user['level'];
|
||||
|
||||
session_regenerate_id(true);
|
||||
$token = session_id();
|
||||
return http_result(['token'=>$token]);
|
||||
}
|
||||
|
||||
public function simpleTokenRegen(Request $request)
|
||||
{
|
||||
// get user by session
|
||||
if (!$pd_user = PushDeerUser::where('id', $_SESSION['uid'])->get()->first()) {
|
||||
return send_error('用户不存在', ErrorCode('ARGS'));
|
||||
}
|
||||
$pd_user['simple_token'] = 'SP'.$pd_user['id'].'P'.md5(uniqid(rand(), true));
|
||||
$pd_user->save();
|
||||
return http_result(['stoken'=>$pd_user['simple_token']]);
|
||||
}
|
||||
|
||||
public function simpleTokenRemove(Request $request)
|
||||
{
|
||||
// get user by session
|
||||
if (!$pd_user = PushDeerUser::where('id', $_SESSION['uid'])->get()->first()) {
|
||||
return send_error('用户不存在', ErrorCode('ARGS'));
|
||||
}
|
||||
$pd_user['simple_token'] = '';
|
||||
$pd_user->save();
|
||||
return http_result(['stoken'=>$pd_user['simple_token']]);
|
||||
}
|
||||
|
||||
public function wecode2unionid(Request $request)
|
||||
{
|
||||
$validated = $request->validate(
|
||||
|
@ -133,6 +186,8 @@ class PushDeerUserController extends Controller
|
|||
$the_user['level'] = 1;
|
||||
|
||||
$pd_user = PushDeerUser::create($the_user);
|
||||
$pd_user['simple_token'] = 'SP'.$pd_user['id'].'P'.md5(uniqid(rand(), true));
|
||||
$pd_user->save();
|
||||
}
|
||||
|
||||
// 将数据写到session
|
||||
|
@ -141,6 +196,7 @@ class PushDeerUserController extends Controller
|
|||
$_SESSION['name'] = $pd_user['name'];
|
||||
$_SESSION['email'] = $pd_user['email'];
|
||||
$_SESSION['level'] = $pd_user['level'];
|
||||
$_SESSION['simple_token'] = $pd_user['simple_token'];
|
||||
|
||||
session_regenerate_id(true);
|
||||
$token = session_id();
|
||||
|
@ -175,6 +231,8 @@ class PushDeerUserController extends Controller
|
|||
$the_user['level'] = 1;
|
||||
|
||||
$pd_user = PushDeerUser::create($the_user);
|
||||
$pd_user['simple_token'] = 'SP'.$pd_user['id'].'P'.md5(uniqid(rand(), true));
|
||||
$pd_user->save();
|
||||
}
|
||||
|
||||
// 将数据写到session
|
||||
|
@ -183,6 +241,7 @@ class PushDeerUserController extends Controller
|
|||
$_SESSION['name'] = $pd_user['name'];
|
||||
$_SESSION['email'] = $pd_user['email'];
|
||||
$_SESSION['level'] = $pd_user['level'];
|
||||
$_SESSION['simple_token'] = $pd_user['simple_token'];
|
||||
|
||||
session_regenerate_id(true);
|
||||
$token = session_id();
|
||||
|
|
|
@ -115,6 +115,9 @@ function ios_send($is_clip, $device_token, $text, $desp = '', $dev = true)
|
|||
$topic = intval($is_clip) == 1 ? config('services.go_push.ios_clip_topic') : config('services.go_push.ios_topic');
|
||||
$notification->topic = $topic;
|
||||
$notification->sound = ['volume'=>2.0];
|
||||
$notification->mutable_content = true;
|
||||
$notification->alert= ['title'=>$text, 'body'=>$desp];
|
||||
// 'subtitle'=>'from PushDeer',
|
||||
|
||||
$json = ['notifications'=>[$notification]];
|
||||
$client = new GuzzleHttp\Client();
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexToMessageTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('push_deer_messages', function (Blueprint $table) {
|
||||
$table->index(['uid'], 'uid');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('push_deer_messages', function (Blueprint $table) {
|
||||
$table->dropIndex('uid');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddSimpleTokenToUserTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('push_deer_users', function (Blueprint $table) {
|
||||
$table->string('simple_token')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('push_deer_users', function (Blueprint $table) {
|
||||
$table->dropColumn('simple_token');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -23,6 +23,9 @@ use Illuminate\Support\Facades\Route;
|
|||
// 假登入,用于测试使用
|
||||
Route::any('/login/fake', 'App\Http\Controllers\PushDeerUserController@fakeLogin');
|
||||
|
||||
// 通过 simple_token 登入
|
||||
Route::any('/login/simple_token', 'App\Http\Controllers\PushDeerUserController@loginBySimpleToken');
|
||||
|
||||
// 通过 apple 返回的 idtoken 登入
|
||||
Route::post('/login/idtoken', 'App\Http\Controllers\PushDeerUserController@login');
|
||||
|
||||
|
@ -59,6 +62,11 @@ Route::middleware('auto.login')->group(function () {
|
|||
// 删除一个key
|
||||
Route::post('/key/remove', 'App\Http\Controllers\PushDeerKeyController@remove');
|
||||
|
||||
// simple_token
|
||||
Route::post('/simple_token/regen', 'App\Http\Controllers\PushDeerUserController@simpleTokenRegen');
|
||||
|
||||
Route::post('/simple_token/remove', 'App\Http\Controllers\PushDeerUserController@simpleTokenRemove');
|
||||
|
||||
// 消息列表
|
||||
Route::post('/message/list', 'App\Http\Controllers\PushDeerMessageController@list');
|
||||
// 删除消息
|
||||
|
|
|
@ -53,10 +53,15 @@ services:
|
|||
#### 放置证书文件
|
||||
|
||||
申请域名对应的 SSL 证书,获得证书文件( `example.crt` )和私钥文件( `example.key`)。
|
||||
在项目根目录下建立 `ssl` 目录,将证书文件重命名为 `server.crt` 和 私钥文件重命名为 `server.key` 。
|
||||
在[项目根目录](https://github.com/easychen/pushdeer)下建立 `ssl` 目录,将证书文件重命名为 `server.crt` 和 私钥文件重命名为 `server.key` 。
|
||||
|
||||
如果你下载的证书中还包含根证书(root_crt),可将根证书文本追加到 `server.crt` 之后。
|
||||
|
||||
<!-- #### 通过Dockfile将证书复制到镜像内
|
||||
|
||||
[按提示修改Dockerfile](https://github.com/easychen/pushdeer/blob/main/docker/web/Dockerfile#L27)
|
||||
-->
|
||||
|
||||
#### 修改虚拟host配置
|
||||
|
||||
修改 `docker/web/vhost.conf` 中被注释掉的[这三行](https://github.com/easychen/pushdeer/blob/10e4d3bb62d8d66d4739598a8f4af32eda4cceef/docker/web/vhost.conf#L27)。
|
||||
|
|
|
@ -115,7 +115,7 @@ sudo docker exec -it <id> /bin/bash
|
|||
接着就可以通过命令行进行推送了测试:
|
||||
|
||||
```bash
|
||||
cd /app/push && /data/gorush -ios -m "推送内容" -c "ios.yml" --topic "com.pushdeer.self.ios" -t "DeviceToken"
|
||||
cd /app/push && /data/gorush -ios -m "推送内容" -c "ios.yml" --topic "com.pushdeer.self.ios" -t "DeviceToken" --production
|
||||
```
|
||||
|
||||
其中 DeviceToken 可以通过[设备列表](https://github.com/easychen/pushdeer#%E8%AE%BE%E5%A4%87%E5%88%97%E8%A1%A8) 接口获得。
|
||||
其中 DeviceToken 可以通过[设备列表](https://github.com/easychen/pushdeer#%E8%AE%BE%E5%A4%87%E5%88%97%E8%A1%A8) 接口获得。
|
||||
|
|
|
@ -11,8 +11,8 @@ services:
|
|||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=theVeryp@ssw0rd
|
||||
- MYSQL_DATABASE=pushdeer
|
||||
ports:
|
||||
- '3306:3306'
|
||||
# ports:
|
||||
# - '3306:3306'
|
||||
redis:
|
||||
image: 'bitnami/redis:6.0.16'
|
||||
healthcheck:
|
||||
|
@ -53,4 +53,4 @@ services:
|
|||
# - MQTT_PASSWORD=y0urp@ss
|
||||
# - MQTT_BASE_TOPIC=default
|
||||
volumes:
|
||||
mariadb_data:
|
||||
mariadb_data:
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
version: '2'
|
||||
services:
|
||||
app:
|
||||
image: 'ccr.ccs.tencentyun.com/ftqq/pushdeercore'
|
||||
ports:
|
||||
- '9000:9000'
|
||||
environment:
|
||||
- DB_HOST=host.docker.internal
|
||||
- DB_PORT=3306
|
||||
- DB_USERNAME=root
|
||||
- DB_DATABASE=pushdeer_local
|
||||
- DB_PASSWORD=
|
||||
- DB_TIMEZONE=+08:00
|
||||
- GO_PUSH_IOS_TOPIC=com.pushdeer.self.ios
|
||||
- GO_PUSH_IOS_CLIP_TOPIC=com.pushdeer.self.ios.Clip
|
||||
- APP_DEBUG=false
|
||||
- WEB_PHP_SOCKET=127.0.0.1:8000
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
# 已废弃
|
||||
# version: '2'
|
||||
# services:
|
||||
# app:
|
||||
# image: 'ccr.ccs.tencentyun.com/ftqq/pushdeercore'
|
||||
# ports:
|
||||
# - '9000:9000'
|
||||
# environment:
|
||||
# - DB_HOST=host.docker.internal
|
||||
# - DB_PORT=3306
|
||||
# - DB_USERNAME=root
|
||||
# - DB_DATABASE=pushdeer_local
|
||||
# - DB_PASSWORD=
|
||||
# - DB_TIMEZONE=+08:00
|
||||
# - GO_PUSH_IOS_TOPIC=com.pushdeer.self.ios
|
||||
# - GO_PUSH_IOS_CLIP_TOPIC=com.pushdeer.self.ios.Clip
|
||||
# - APP_DEBUG=false
|
||||
# - WEB_PHP_SOCKET=127.0.0.1:8000
|
||||
# extra_hosts:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
|
|
|
@ -23,5 +23,9 @@ ADD supervisord-clip.conf /opt/docker/etc/supervisor.d/push-clip.conf
|
|||
ADD larave-cron /etc/cron.d
|
||||
RUN chmod +x /etc/cron.d/larave-cron
|
||||
|
||||
# 配置 https
|
||||
# 在本目录下创建ssl目录,放入证书(server.crt,server.key),然后去掉下一行的注释
|
||||
# ADD ssl /app/ssl
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
Order allow,deny
|
||||
allow from all
|
||||
</Directory>
|
||||
# 配置 https
|
||||
# 去掉下边三行的注释
|
||||
#SSLEngine on
|
||||
#SSLCertificateFile /app/ssl/server.crt
|
||||
#SSLCertificateKeyFile /app/ssl/server.key
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.usernotifications.service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// NotificationService.swift
|
||||
// Notification
|
||||
//
|
||||
// Created by HEXT on 2022/4/19.
|
||||
//
|
||||
|
||||
import UserNotifications
|
||||
import WidgetKit
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
|
||||
NSLog("push-userInfo: %@", bestAttemptContent?.userInfo ?? "")
|
||||
// 刷新所有桌面小部件
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
|
||||
if let bestAttemptContent = bestAttemptContent {
|
||||
// Modify the notification content here...
|
||||
// bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
|
||||
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,11 @@ target 'PushDeerClip' do
|
|||
|
||||
end
|
||||
|
||||
target 'PushDeerWidgetExtension' do
|
||||
# Pods for PushDeerWidgetExtension
|
||||
pod 'Moya', '~> 15.0'
|
||||
end
|
||||
|
||||
# define unsupported pods
|
||||
def unsupported_pods
|
||||
# macCatalyst 不支持的库
|
||||
|
|
|
@ -47,6 +47,6 @@ SPEC CHECKSUMS:
|
|||
WechatOpenSDK: 6a4d1436c15b3b5fe2a0bd383f3046010186da44
|
||||
WoodPeckeriOS: 12ec7f38c695e51cd94a476228888dfe85d9d916
|
||||
|
||||
PODFILE CHECKSUM: 1b349626994062a8291e3db07d3dbf087894c4d2
|
||||
PODFILE CHECKSUM: 42e3d8abd976589c1043ff9f9e864c275a490160
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -7,7 +7,21 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1C63523789DA4965F9F87DB8 /* Pods_PushDeerWidgetExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8DCAE5BD814D7C0D2F2722 /* Pods_PushDeerWidgetExtension.framework */; };
|
||||
4812F19BB0BFEFE089BC253E /* Pods_PushDeerClip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E03C2088F4CD9F4C0848E1A5 /* Pods_PushDeerClip.framework */; };
|
||||
5206008727CF749E00188431 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5206008627CF749E00188431 /* WidgetKit.framework */; };
|
||||
5206008927CF749E00188431 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5206008827CF749E00188431 /* SwiftUI.framework */; };
|
||||
5206008C27CF749E00188431 /* PushDeerWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5206008B27CF749E00188431 /* PushDeerWidget.swift */; };
|
||||
5206008F27CF749F00188431 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5206008E27CF749F00188431 /* Assets.xcassets */; };
|
||||
5206009127CF749F00188431 /* PushDeerWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 5206008D27CF749E00188431 /* PushDeerWidget.intentdefinition */; };
|
||||
5206009227CF749F00188431 /* PushDeerWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 5206008D27CF749E00188431 /* PushDeerWidget.intentdefinition */; };
|
||||
5206009527CF749F00188431 /* PushDeerWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5206008527CF749E00188431 /* PushDeerWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
5206009D27CF74C100188431 /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450F412784943F003652D8 /* HttpRequest.swift */; };
|
||||
5206009E27CF76BC00188431 /* PushDeerApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450F3727848243003652D8 /* PushDeerApi.swift */; };
|
||||
5206009F27CF76C400188431 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450F3E2784923D003652D8 /* Result.swift */; };
|
||||
520600A027CF76F900188431 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52450F3A278491F8003652D8 /* AppState.swift */; };
|
||||
520600A127CF770600188431 /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5243372627A6D86200190D00 /* Env.swift */; };
|
||||
520600A627D0AE2300188431 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90B22778DA4E0048E0ED /* Line.swift */; };
|
||||
52163EB327773F8400594190 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52163EB227773F8400594190 /* MainView.swift */; };
|
||||
52163EB52777413B00594190 /* MessageListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52163EB42777413B00594190 /* MessageListView.swift */; };
|
||||
52163EB72777415F00594190 /* DeviceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52163EB62777415F00594190 /* DeviceListView.swift */; };
|
||||
|
@ -71,6 +85,8 @@
|
|||
52B8CF86277E0C12004CB680 /* BaseNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90AD2778AFD60048E0ED /* BaseNavigationView.swift */; };
|
||||
52B8CF87277E0C5C004CB680 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5292F4FC2776BC7A00B9A7BB /* Assets.xcassets */; };
|
||||
52B8FB8727F36F9D00C29D13 /* BetterSafariView in Frameworks */ = {isa = PBXBuildFile; productRef = 52B8FB8627F36F9D00C29D13 /* BetterSafariView */; };
|
||||
52C4B434280DC28D009817EA /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52C4B433280DC28D009817EA /* NotificationService.swift */; };
|
||||
52C4B438280DC28D009817EA /* Notification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 52C4B431280DC28D009817EA /* Notification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
52E317D9279305BB000B8BB1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 52E317D7279305BB000B8BB1 /* InfoPlist.strings */; };
|
||||
52E317DC279305BB000B8BB1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 52E317DA279305BB000B8BB1 /* Localizable.strings */; };
|
||||
52E317DF279305BB000B8BB1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 52E317DD279305BB000B8BB1 /* InfoPlist.strings */; };
|
||||
|
@ -81,6 +97,9 @@
|
|||
52EB90B32778DA4E0048E0ED /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EB90B22778DA4E0048E0ED /* Line.swift */; };
|
||||
52EED71E27C9394D0086A804 /* WXDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EED71D27C9394D0086A804 /* WXDelegate.swift */; };
|
||||
52EED71F27C93B960086A804 /* WXDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EED71D27C9394D0086A804 /* WXDelegate.swift */; };
|
||||
52EF0AFC28CE081A00C99E4F /* CommonUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EF0AFB28CE081A00C99E4F /* CommonUtils.swift */; };
|
||||
52EF0AFD28CE081A00C99E4F /* CommonUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EF0AFB28CE081A00C99E4F /* CommonUtils.swift */; };
|
||||
52EF0AFE28CE08EC00C99E4F /* CommonUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EF0AFB28CE081A00C99E4F /* CommonUtils.swift */; };
|
||||
52F0243F277737470071D861 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F0243E277737470071D861 /* LoginView.swift */; };
|
||||
52F2C223277961D7006F08DC /* SettingsItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F2C222277961D7006F08DC /* SettingsItemView.swift */; };
|
||||
52F40D2F277CA05600766C24 /* MessageItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F40D2E277CA05600766C24 /* MessageItemView.swift */; };
|
||||
|
@ -91,6 +110,13 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
5206009327CF749F00188431 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5292F4ED2776BC7900B9A7BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 5206008427CF749E00188431;
|
||||
remoteInfo = PushDeerWidgetExtension;
|
||||
};
|
||||
52B8CF71277E0B46004CB680 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5292F4ED2776BC7900B9A7BB /* Project object */;
|
||||
|
@ -98,9 +124,28 @@
|
|||
remoteGlobalIDString = 52B8CF63277E0B44004CB680;
|
||||
remoteInfo = PushDeerClip;
|
||||
};
|
||||
52C4B436280DC28D009817EA /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5292F4ED2776BC7900B9A7BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 52C4B430280DC28D009817EA;
|
||||
remoteInfo = Notification;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5206009727CF749F00188431 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
5206009527CF749F00188431 /* PushDeerWidgetExtension.appex in Embed App Extensions */,
|
||||
52C4B438280DC28D009817EA /* Notification.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
52B8CF77277E0B46004CB680 /* Embed App Clips */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -115,6 +160,15 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5206008527CF749E00188431 /* PushDeerWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PushDeerWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5206008627CF749E00188431 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
5206008827CF749E00188431 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
5206008B27CF749E00188431 /* PushDeerWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushDeerWidget.swift; sourceTree = "<group>"; };
|
||||
5206008D27CF749E00188431 /* PushDeerWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = PushDeerWidget.intentdefinition; sourceTree = "<group>"; };
|
||||
5206008E27CF749F00188431 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
5206009027CF749F00188431 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
520600A427D085A100188431 /* PushDeerWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PushDeerWidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
520600A527D085C200188431 /* PushDeerWidgetExtension-SelfHosted.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "PushDeerWidgetExtension-SelfHosted.entitlements"; sourceTree = "<group>"; };
|
||||
52163EB227773F8400594190 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||
52163EB42777413B00594190 /* MessageListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListView.swift; sourceTree = "<group>"; };
|
||||
52163EB62777415F00594190 /* DeviceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListView.swift; sourceTree = "<group>"; };
|
||||
|
@ -152,6 +206,10 @@
|
|||
52B8CF6F277E0B46004CB680 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
52B8CF70277E0B46004CB680 /* PushDeerClip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PushDeerClip.entitlements; sourceTree = "<group>"; };
|
||||
52BE373227C236DD004AA630 /* PushDeer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PushDeer-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
52C4B431280DC28D009817EA /* Notification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Notification.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
52C4B433280DC28D009817EA /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
52C4B435280DC28D009817EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
52C4B439280DC28D009817EA /* Notification.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Notification.entitlements; sourceTree = "<group>"; };
|
||||
52E317D8279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
52E317DB279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
52E317DE279305BB000B8BB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
|
@ -163,24 +221,40 @@
|
|||
52EB90AF2778D67F0048E0ED /* KeyItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyItemView.swift; sourceTree = "<group>"; };
|
||||
52EB90B22778DA4E0048E0ED /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
|
||||
52EED71D27C9394D0086A804 /* WXDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WXDelegate.swift; sourceTree = "<group>"; };
|
||||
52EF0AFB28CE081A00C99E4F /* CommonUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonUtils.swift; sourceTree = "<group>"; };
|
||||
52F0243C277733CE0071D861 /* PushDeer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PushDeer.entitlements; sourceTree = "<group>"; };
|
||||
52F0243E277737470071D861 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
52F2C222277961D7006F08DC /* SettingsItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItemView.swift; sourceTree = "<group>"; };
|
||||
52F40D2E277CA05600766C24 /* MessageItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageItemView.swift; sourceTree = "<group>"; };
|
||||
52FB1FEB27CA9D7300367DE0 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
69F56B2711ED98819D474BE3 /* Pods-PushDeerClip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.debug.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7F8DCAE5BD814D7C0D2F2722 /* Pods_PushDeerWidgetExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PushDeerWidgetExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8B9D658D778AE868A0E052A8 /* Pods-PushDeer.debug-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeer.debug-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeer/Pods-PushDeer.debug-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
8CF87A70A3872FDE613FD228 /* Pods-PushDeerWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PushDeerWidgetExtension/Pods-PushDeerWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
9CC775BE0326BF31C6FACF06 /* Pods-PushDeerClip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.release.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B3763489EDE07E6970B0F8E5 /* Pods-PushDeerWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PushDeerWidgetExtension/Pods-PushDeerWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
B796E2A583611F7B6DC34726 /* Pods-PushDeerClip.release-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.release-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.release-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
CB57B55699850AD493C774D0 /* Pods-PushDeerClip.debug-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerClip.debug-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip.debug-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
CCCE1F6E56B157872E2C755F /* Pods-PushDeer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeer.release.xcconfig"; path = "Target Support Files/Pods-PushDeer/Pods-PushDeer.release.xcconfig"; sourceTree = "<group>"; };
|
||||
CE3005BD875FC9819A92466C /* Pods-PushDeer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeer.debug.xcconfig"; path = "Target Support Files/Pods-PushDeer/Pods-PushDeer.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D612A216E0469D1A050C3523 /* Pods-PushDeerWidgetExtension.release-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerWidgetExtension.release-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeerWidgetExtension/Pods-PushDeerWidgetExtension.release-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
E03C2088F4CD9F4C0848E1A5 /* Pods_PushDeerClip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PushDeerClip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E380A18349DE4D26071E913E /* Pods_PushDeer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PushDeer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E45C830227D3A7C7E965F94B /* Pods-PushDeerWidgetExtension.debug-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeerWidgetExtension.debug-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeerWidgetExtension/Pods-PushDeerWidgetExtension.debug-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
E60A7D4CA1149E1DBC55C672 /* Pods-PushDeer.release-selfhosted.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PushDeer.release-selfhosted.xcconfig"; path = "Target Support Files/Pods-PushDeer/Pods-PushDeer.release-selfhosted.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5206008227CF749E00188431 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5206008927CF749E00188431 /* SwiftUI.framework in Frameworks */,
|
||||
5206008727CF749E00188431 /* WidgetKit.framework in Frameworks */,
|
||||
1C63523789DA4965F9F87DB8 /* Pods_PushDeerWidgetExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5292F4F22776BC7900B9A7BB /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -201,6 +275,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
52C4B42E280DC28D009817EA /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
@ -215,10 +296,27 @@
|
|||
E60A7D4CA1149E1DBC55C672 /* Pods-PushDeer.release-selfhosted.xcconfig */,
|
||||
CB57B55699850AD493C774D0 /* Pods-PushDeerClip.debug-selfhosted.xcconfig */,
|
||||
B796E2A583611F7B6DC34726 /* Pods-PushDeerClip.release-selfhosted.xcconfig */,
|
||||
B3763489EDE07E6970B0F8E5 /* Pods-PushDeerWidgetExtension.debug.xcconfig */,
|
||||
E45C830227D3A7C7E965F94B /* Pods-PushDeerWidgetExtension.debug-selfhosted.xcconfig */,
|
||||
8CF87A70A3872FDE613FD228 /* Pods-PushDeerWidgetExtension.release.xcconfig */,
|
||||
D612A216E0469D1A050C3523 /* Pods-PushDeerWidgetExtension.release-selfhosted.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5206008A27CF749E00188431 /* PushDeerWidget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5206008B27CF749E00188431 /* PushDeerWidget.swift */,
|
||||
5206008D27CF749E00188431 /* PushDeerWidget.intentdefinition */,
|
||||
5206008E27CF749F00188431 /* Assets.xcassets */,
|
||||
5206009027CF749F00188431 /* Info.plist */,
|
||||
520600A427D085A100188431 /* PushDeerWidgetExtension.entitlements */,
|
||||
520600A527D085C200188431 /* PushDeerWidgetExtension-SelfHosted.entitlements */,
|
||||
);
|
||||
path = PushDeerWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52450F362784822C003652D8 /* Service */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -246,6 +344,8 @@
|
|||
children = (
|
||||
5292F4F72776BC7900B9A7BB /* PushDeer */,
|
||||
52B8CF65277E0B44004CB680 /* PushDeerClip */,
|
||||
5206008A27CF749E00188431 /* PushDeerWidget */,
|
||||
52C4B432280DC28D009817EA /* Notification */,
|
||||
5292F4F62776BC7900B9A7BB /* Products */,
|
||||
17D35B157765D96FC4DA6C39 /* Pods */,
|
||||
78FEAD2568FB92808C44E85A /* Frameworks */,
|
||||
|
@ -258,6 +358,8 @@
|
|||
children = (
|
||||
5292F4F52776BC7900B9A7BB /* PushDeer.app */,
|
||||
52B8CF64277E0B44004CB680 /* PushDeerClip.app */,
|
||||
5206008527CF749E00188431 /* PushDeerWidgetExtension.appex */,
|
||||
52C4B431280DC28D009817EA /* Notification.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -269,6 +371,7 @@
|
|||
527872C327CE0EE800AD79AD /* Info-SelfHosted.plist */,
|
||||
52E317DA279305BB000B8BB1 /* Localizable.strings */,
|
||||
52E317D7279305BB000B8BB1 /* InfoPlist.strings */,
|
||||
52EF0AFA28CE07A600C99E4F /* Tool */,
|
||||
52450F362784822C003652D8 /* Service */,
|
||||
52B8CF5D277DE5FF004CB680 /* Common */,
|
||||
52450F3D27849228003652D8 /* Model */,
|
||||
|
@ -327,6 +430,16 @@
|
|||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52C4B432280DC28D009817EA /* Notification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
52C4B439280DC28D009817EA /* Notification.entitlements */,
|
||||
52C4B433280DC28D009817EA /* NotificationService.swift */,
|
||||
52C4B435280DC28D009817EA /* Info.plist */,
|
||||
);
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52EB90B12778D9F90048E0ED /* Common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -339,6 +452,14 @@
|
|||
path = Common;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52EF0AFA28CE07A600C99E4F /* Tool */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
52EF0AFB28CE081A00C99E4F /* CommonUtils.swift */,
|
||||
);
|
||||
path = Tool;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
52F0243D2777370F0071D861 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -363,6 +484,9 @@
|
|||
children = (
|
||||
E380A18349DE4D26071E913E /* Pods_PushDeer.framework */,
|
||||
E03C2088F4CD9F4C0848E1A5 /* Pods_PushDeerClip.framework */,
|
||||
5206008627CF749E00188431 /* WidgetKit.framework */,
|
||||
5206008827CF749E00188431 /* SwiftUI.framework */,
|
||||
7F8DCAE5BD814D7C0D2F2722 /* Pods_PushDeerWidgetExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -370,6 +494,26 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
5206008427CF749E00188431 /* PushDeerWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5206009C27CF749F00188431 /* Build configuration list for PBXNativeTarget "PushDeerWidgetExtension" */;
|
||||
buildPhases = (
|
||||
97FE7C51BE9FCE0E275A4CA3 /* [CP] Check Pods Manifest.lock */,
|
||||
5206008127CF749E00188431 /* Sources */,
|
||||
5206008227CF749E00188431 /* Frameworks */,
|
||||
5206008327CF749E00188431 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = PushDeerWidgetExtension;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = PushDeerWidgetExtension;
|
||||
productReference = 5206008527CF749E00188431 /* PushDeerWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
5292F4F42776BC7900B9A7BB /* PushDeer */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5292F5032776BC7A00B9A7BB /* Build configuration list for PBXNativeTarget "PushDeer" */;
|
||||
|
@ -380,11 +524,14 @@
|
|||
5292F4F32776BC7900B9A7BB /* Resources */,
|
||||
52B8CF77277E0B46004CB680 /* Embed App Clips */,
|
||||
D5C7FAC44EC37CBCD945E0F2 /* [CP] Embed Pods Frameworks */,
|
||||
5206009727CF749F00188431 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
52B8CF72277E0B46004CB680 /* PBXTargetDependency */,
|
||||
5206009427CF749F00188431 /* PBXTargetDependency */,
|
||||
52C4B437280DC28D009817EA /* PBXTargetDependency */,
|
||||
);
|
||||
name = PushDeer;
|
||||
packageProductDependencies = (
|
||||
|
@ -418,6 +565,23 @@
|
|||
productReference = 52B8CF64277E0B44004CB680 /* PushDeerClip.app */;
|
||||
productType = "com.apple.product-type.application.on-demand-install-capable";
|
||||
};
|
||||
52C4B430280DC28D009817EA /* Notification */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 52C4B43E280DC28D009817EA /* Build configuration list for PBXNativeTarget "Notification" */;
|
||||
buildPhases = (
|
||||
52C4B42D280DC28D009817EA /* Sources */,
|
||||
52C4B42E280DC28D009817EA /* Frameworks */,
|
||||
52C4B42F280DC28D009817EA /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Notification;
|
||||
productName = Notification;
|
||||
productReference = 52C4B431280DC28D009817EA /* Notification.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
@ -425,9 +589,12 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastSwiftUpdateCheck = 1330;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
5206008427CF749E00188431 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
5292F4F42776BC7900B9A7BB = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
|
@ -436,6 +603,9 @@
|
|||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
};
|
||||
52C4B430280DC28D009817EA = {
|
||||
CreatedOnToolsVersion = 13.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 5292F4F02776BC7900B9A7BB /* Build configuration list for PBXProject "PushDeer" */;
|
||||
|
@ -458,11 +628,21 @@
|
|||
targets = (
|
||||
5292F4F42776BC7900B9A7BB /* PushDeer */,
|
||||
52B8CF63277E0B44004CB680 /* PushDeerClip */,
|
||||
5206008427CF749E00188431 /* PushDeerWidgetExtension */,
|
||||
52C4B430280DC28D009817EA /* Notification */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
5206008327CF749E00188431 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5206008F27CF749F00188431 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5292F4F32776BC7900B9A7BB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -485,6 +665,13 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
52C4B42F280DC28D009817EA /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
@ -527,6 +714,28 @@
|
|||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PushDeerClip/Pods-PushDeerClip-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
97FE7C51BE9FCE0E275A4CA3 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-PushDeerWidgetExtension-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
D5C7FAC44EC37CBCD945E0F2 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -569,6 +778,22 @@
|
|||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
5206008127CF749E00188431 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5206009127CF749F00188431 /* PushDeerWidget.intentdefinition in Sources */,
|
||||
5206009F27CF76C400188431 /* Result.swift in Sources */,
|
||||
5206008C27CF749E00188431 /* PushDeerWidget.swift in Sources */,
|
||||
520600A027CF76F900188431 /* AppState.swift in Sources */,
|
||||
5206009E27CF76BC00188431 /* PushDeerApi.swift in Sources */,
|
||||
520600A627D0AE2300188431 /* Line.swift in Sources */,
|
||||
5206009D27CF74C100188431 /* HttpRequest.swift in Sources */,
|
||||
52EF0AFE28CE08EC00C99E4F /* CommonUtils.swift in Sources */,
|
||||
520600A127CF770600188431 /* Env.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5292F4F12776BC7900B9A7BB /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -577,6 +802,7 @@
|
|||
52FB1FEC27CA9D7300367DE0 /* SafariView.swift in Sources */,
|
||||
523150DC2778762B00941EDC /* DeviceItemView.swift in Sources */,
|
||||
523150D9277875FB00941EDC /* DeletableView.swift in Sources */,
|
||||
5206009227CF749F00188431 /* PushDeerWidget.intentdefinition in Sources */,
|
||||
52163EBB277741AC00594190 /* SettingsView.swift in Sources */,
|
||||
5242C872278C8CBB00FDB27E /* EditableText.swift in Sources */,
|
||||
52163EB327773F8400594190 /* MainView.swift in Sources */,
|
||||
|
@ -598,6 +824,7 @@
|
|||
52EED71E27C9394D0086A804 /* WXDelegate.swift in Sources */,
|
||||
52EB90AE2778AFD60048E0ED /* BaseNavigationView.swift in Sources */,
|
||||
524E99E627B3CD0F00292396 /* EndpointView.swift in Sources */,
|
||||
52EF0AFC28CE081A00C99E4F /* CommonUtils.swift in Sources */,
|
||||
52EB90AC2778ADF80048E0ED /* CardView.swift in Sources */,
|
||||
52EB90B02778D67F0048E0ED /* KeyItemView.swift in Sources */,
|
||||
52AC5C2827B7FE1D00EEB185 /* ViewExtension.swift in Sources */,
|
||||
|
@ -641,6 +868,7 @@
|
|||
52FBA09427874879003308C2 /* ContentView.swift in Sources */,
|
||||
52B8CF85277E0C12004CB680 /* Line.swift in Sources */,
|
||||
52AC5C2927B7FE1D00EEB185 /* ViewExtension.swift in Sources */,
|
||||
52EF0AFD28CE081A00C99E4F /* CommonUtils.swift in Sources */,
|
||||
52450F402784923D003652D8 /* Result.swift in Sources */,
|
||||
526A1E712791E00400BA2177 /* PushDeerData.xcdatamodeld in Sources */,
|
||||
52450F432784943F003652D8 /* HttpRequest.swift in Sources */,
|
||||
|
@ -649,15 +877,33 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
52C4B42D280DC28D009817EA /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
52C4B434280DC28D009817EA /* NotificationService.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
5206009427CF749F00188431 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 5206008427CF749E00188431 /* PushDeerWidgetExtension */;
|
||||
targetProxy = 5206009327CF749F00188431 /* PBXContainerItemProxy */;
|
||||
};
|
||||
52B8CF72277E0B46004CB680 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
target = 52B8CF63277E0B44004CB680 /* PushDeerClip */;
|
||||
targetProxy = 52B8CF71277E0B46004CB680 /* PBXContainerItemProxy */;
|
||||
};
|
||||
52C4B437280DC28D009817EA /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 52C4B430280DC28D009817EA /* Notification */;
|
||||
targetProxy = 52C4B436280DC28D009817EA /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
|
@ -691,6 +937,130 @@
|
|||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
5206009827CF749F00188431 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B3763489EDE07E6970B0F8E5 /* Pods-PushDeerWidgetExtension.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = PushDeerWidget/PushDeerWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeerWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.PushDeerWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5206009927CF749F00188431 /* Debug-SelfHosted */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E45C830227D3A7C7E965F94B /* Pods-PushDeerWidgetExtension.debug-selfhosted.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = "PushDeerWidget/PushDeerWidgetExtension-SelfHosted.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeerWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.PushDeerWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = "Debug-SelfHosted";
|
||||
};
|
||||
5206009A27CF749F00188431 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 8CF87A70A3872FDE613FD228 /* Pods-PushDeerWidgetExtension.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = PushDeerWidget/PushDeerWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeerWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.PushDeerWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
5206009B27CF749F00188431 /* Release-SelfHosted */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D612A216E0469D1A050C3523 /* Pods-PushDeerWidgetExtension.release-selfhosted.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = "PushDeerWidget/PushDeerWidgetExtension-SelfHosted.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = PushDeerWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = PushDeerWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.PushDeerWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = "Release-SelfHosted";
|
||||
};
|
||||
524E99E827B3DDF000292396 /* Debug-SelfHosted */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -758,6 +1128,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 8B9D658D778AE868A0E052A8 /* Pods-PushDeer.debug-selfhosted.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
|
@ -901,6 +1272,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E60A7D4CA1149E1DBC55C672 /* Pods-PushDeer.release-selfhosted.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-SH";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
|
@ -1102,6 +1474,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CE3005BD875FC9819A92466C /* Pods-PushDeer.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
|
@ -1143,6 +1516,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CCCE1F6E56B157872E2C755F /* Pods-PushDeer.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
|
@ -1266,9 +1640,132 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
52C4B43A280DC28D009817EA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Notification/Notification.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Notification/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Notification;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.Notification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
52C4B43B280DC28D009817EA /* Debug-SelfHosted */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Notification/Notification.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Notification/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Notification;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.Notification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = "Debug-SelfHosted";
|
||||
};
|
||||
52C4B43C280DC28D009817EA /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Notification/Notification.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Notification/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Notification;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.app.ios.Notification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
52C4B43D280DC28D009817EA /* Release-SelfHosted */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Notification/Notification.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEVELOPMENT_TEAM = HUJ6HAE4VU;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Notification/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Notification;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pushdeer.self.ios.Notification;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = "Release-SelfHosted";
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
5206009C27CF749F00188431 /* Build configuration list for PBXNativeTarget "PushDeerWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5206009827CF749F00188431 /* Debug */,
|
||||
5206009927CF749F00188431 /* Debug-SelfHosted */,
|
||||
5206009A27CF749F00188431 /* Release */,
|
||||
5206009B27CF749F00188431 /* Release-SelfHosted */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5292F4F02776BC7900B9A7BB /* Build configuration list for PBXProject "PushDeer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -1302,6 +1799,17 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
52C4B43E280DC28D009817EA /* Build configuration list for PBXNativeTarget "Notification" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
52C4B43A280DC28D009817EA /* Debug */,
|
||||
52C4B43B280DC28D009817EA /* Debug-SelfHosted */,
|
||||
52C4B43C280DC28D009817EA /* Release */,
|
||||
52C4B43D280DC28D009817EA /* Release-SelfHosted */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
|
|
|
@ -56,7 +56,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
print("willPresent:", notification.request.content.userInfo)
|
||||
NSLog("willPresent: %@", notification.request.content.userInfo)
|
||||
Task {
|
||||
// 收到推送后, 刷新本地消息列表
|
||||
let messageItems = try await HttpRequest.getMessages().messages
|
||||
|
@ -66,7 +66,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
|
||||
print("didReceive:", response.notification.request.content.userInfo)
|
||||
NSLog("didReceive: %@", response.notification.request.content.userInfo)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 7.1 KiB |
|
@ -149,178 +149,6 @@
|
|||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "48.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "notificationCenter",
|
||||
"scale" : "2x",
|
||||
"size" : "24x24",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"filename" : "55.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "notificationCenter",
|
||||
"scale" : "2x",
|
||||
"size" : "27.5x27.5",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"filename" : "58.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "87.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"role" : "notificationCenter",
|
||||
"scale" : "2x",
|
||||
"size" : "33x33",
|
||||
"subtype" : "45mm"
|
||||
},
|
||||
{
|
||||
"filename" : "80.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "appLauncher",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"filename" : "88.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "appLauncher",
|
||||
"scale" : "2x",
|
||||
"size" : "44x44",
|
||||
"subtype" : "40mm"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"role" : "appLauncher",
|
||||
"scale" : "2x",
|
||||
"size" : "46x46",
|
||||
"subtype" : "41mm"
|
||||
},
|
||||
{
|
||||
"filename" : "100.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "appLauncher",
|
||||
"scale" : "2x",
|
||||
"size" : "50x50",
|
||||
"subtype" : "44mm"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"role" : "appLauncher",
|
||||
"scale" : "2x",
|
||||
"size" : "51x51",
|
||||
"subtype" : "45mm"
|
||||
},
|
||||
{
|
||||
"filename" : "172.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "quickLook",
|
||||
"scale" : "2x",
|
||||
"size" : "86x86",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"filename" : "196.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "quickLook",
|
||||
"scale" : "2x",
|
||||
"size" : "98x98",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"filename" : "216.png",
|
||||
"idiom" : "watch",
|
||||
"role" : "quickLook",
|
||||
"scale" : "2x",
|
||||
"size" : "108x108",
|
||||
"subtype" : "44mm"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"role" : "quickLook",
|
||||
"scale" : "2x",
|
||||
"size" : "117x117",
|
||||
"subtype" : "45mm"
|
||||
},
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "watch-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
|
|
@ -27,5 +27,13 @@ struct Env {
|
|||
static let wxUniversalLink = "https://vip.pushdeer.com/app/"
|
||||
/// PushDeer 官网地址
|
||||
static let officialWebsite = "https://www.pushdeer.com"
|
||||
/// 共享组名, 使用它来访问 App Clip Widget 共享数据
|
||||
static let appGroupId: String = {
|
||||
#if SELFHOSTED
|
||||
return "group.com.pushdeer.self.ios"
|
||||
#else
|
||||
return "group.com.pushdeer.app.ios"
|
||||
#endif
|
||||
}()
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,19 @@ extension MessageModel {
|
|||
static let _viewContext = PersistenceController.shared.container.viewContext
|
||||
static let _fetchRequest = MessageModel.fetchRequest()
|
||||
|
||||
/// 删除本地持久化的所有消息
|
||||
static func deleteAll() throws -> Void {
|
||||
// let fetchRequest: NSFetchRequest<NSFetchRequestResult> = MessageModel.fetchRequest()
|
||||
// let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
// try _viewContext.execute(deleteRequest)
|
||||
let fetchRequest = MessageModel.fetchRequest()
|
||||
let models = try _viewContext.fetch(fetchRequest)
|
||||
models.forEach { model in
|
||||
_viewContext.delete(model)
|
||||
}
|
||||
try _viewContext.save()
|
||||
}
|
||||
|
||||
/// 持久化保存和更新
|
||||
static func saveAndUpdate(messageItems: [MessageItem]) throws -> Void {
|
||||
try messageItems.forEach(saveAndUpdate)
|
||||
|
|
|
@ -29,6 +29,7 @@ struct UserInfoContent: Codable{
|
|||
let level: Int
|
||||
let created_at: String
|
||||
let updated_at: String
|
||||
let simple_token: String?
|
||||
}
|
||||
|
||||
struct DeviceItem: Codable, Identifiable{
|
||||
|
@ -78,6 +79,12 @@ struct ResultContent: Codable{
|
|||
let result: Array<String>
|
||||
}
|
||||
|
||||
|
||||
struct STokenContent: Codable{
|
||||
let stoken: String?
|
||||
}
|
||||
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
extension KeyItem {
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.self.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.photos-library</key>
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.app.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.photos-library</key>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
@main
|
||||
struct PushDeerApp: App {
|
||||
|
@ -39,6 +40,10 @@ struct PushDeerApp: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification), perform: { _ in
|
||||
// APP将要进入非活跃状态时, 刷新小部件
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
})
|
||||
.environmentObject(store)
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import AuthenticationServices
|
|||
|
||||
class AppState: ObservableObject {
|
||||
/// 账号 token
|
||||
@Published var token : String {
|
||||
@Published var token : String = "" {
|
||||
didSet {
|
||||
UserDefaults.standard.set(token, forKey: "PushDeer_token")
|
||||
getUserDefaults().set(token, forKey: "PushDeer_token")
|
||||
}
|
||||
}
|
||||
/// 设备列表
|
||||
|
@ -22,9 +22,9 @@ class AppState: ObservableObject {
|
|||
/// 消息列表
|
||||
// @Published var messages: [MessageItem] = []
|
||||
/// 选中的 tab 下标
|
||||
@Published var tabSelectedIndex: Int {
|
||||
@Published var tabSelectedIndex: Int = 2 {
|
||||
didSet {
|
||||
UserDefaults.standard.set(tabSelectedIndex, forKey: "PushDeer_tabSelectedIndex")
|
||||
getUserDefaults().set(tabSelectedIndex, forKey: "PushDeer_tabSelectedIndex")
|
||||
}
|
||||
}
|
||||
/// 设备推送 token
|
||||
|
@ -32,28 +32,28 @@ class AppState: ObservableObject {
|
|||
/// 用户信息
|
||||
@Published var userInfo: UserInfoContent?
|
||||
/// 是否显示测试发推送的 UI
|
||||
@Published var isShowTestPush: Bool {
|
||||
@Published var isShowTestPush: Bool = true {
|
||||
didSet {
|
||||
UserDefaults.standard.set(isShowTestPush, forKey: "PushDeer_isShowTestPush")
|
||||
getUserDefaults().set(isShowTestPush, forKey: "PushDeer_isShowTestPush")
|
||||
}
|
||||
}
|
||||
/// 是否使用内置浏览器打开链接
|
||||
@Published var isUseBuiltInBrowser: Bool {
|
||||
@Published var isUseBuiltInBrowser: Bool = true {
|
||||
didSet {
|
||||
UserDefaults.standard.set(isUseBuiltInBrowser, forKey: "PushDeer_isUseBuiltInBrowser")
|
||||
getUserDefaults().set(isUseBuiltInBrowser, forKey: "PushDeer_isUseBuiltInBrowser")
|
||||
}
|
||||
}
|
||||
/// MarkDown BaseURL
|
||||
@Published var markDownBaseURL: String? {
|
||||
didSet {
|
||||
UserDefaults.standard.set(markDownBaseURL, forKey: "PushDeer_markDownBaseURL")
|
||||
getUserDefaults().set(markDownBaseURL, forKey: "PushDeer_markDownBaseURL")
|
||||
}
|
||||
}
|
||||
|
||||
/// API endpoint
|
||||
@Published var api_endpoint : String {
|
||||
@Published var api_endpoint : String = "" {
|
||||
didSet {
|
||||
UserDefaults.standard.set(api_endpoint, forKey: "PushDeer_api_endpoint")
|
||||
getUserDefaults().set(api_endpoint, forKey: "PushDeer_api_endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,12 +68,26 @@ class AppState: ObservableObject {
|
|||
|
||||
static let shared = AppState()
|
||||
private init() {
|
||||
let _token = UserDefaults.standard.string(forKey: "PushDeer_token")
|
||||
let _tabSelectedIndex = UserDefaults.standard.integer(forKey: "PushDeer_tabSelectedIndex")
|
||||
let _isShowTestPush = UserDefaults.standard.object(forKey: "PushDeer_isShowTestPush")
|
||||
let _isUseBuiltInBrowser = UserDefaults.standard.object(forKey: "PushDeer_isUseBuiltInBrowser")
|
||||
let _markDownBaseURL = UserDefaults.standard.string(forKey: "PushDeer_markDownBaseURL")
|
||||
let _api_endpoint = UserDefaults.standard.string(forKey: "PushDeer_api_endpoint")
|
||||
reloadUserDefaults()
|
||||
moveOldUserDefaults()
|
||||
}
|
||||
|
||||
func getUserDefaults() -> UserDefaults {
|
||||
let ud = UserDefaults(suiteName: Env.appGroupId)
|
||||
if let ud = ud {
|
||||
return ud
|
||||
} else {
|
||||
return UserDefaults.standard
|
||||
}
|
||||
}
|
||||
|
||||
func reloadUserDefaults() -> Void {
|
||||
let _token = getUserDefaults().string(forKey: "PushDeer_token")
|
||||
let _tabSelectedIndex = getUserDefaults().integer(forKey: "PushDeer_tabSelectedIndex")
|
||||
let _isShowTestPush = getUserDefaults().object(forKey: "PushDeer_isShowTestPush")
|
||||
let _isUseBuiltInBrowser = getUserDefaults().object(forKey: "PushDeer_isUseBuiltInBrowser")
|
||||
let _markDownBaseURL = getUserDefaults().string(forKey: "PushDeer_markDownBaseURL")
|
||||
let _api_endpoint = getUserDefaults().string(forKey: "PushDeer_api_endpoint")
|
||||
token = _token ?? ""
|
||||
tabSelectedIndex = _tabSelectedIndex
|
||||
isShowTestPush = _isShowTestPush as? Bool ?? true
|
||||
|
@ -82,6 +96,64 @@ class AppState: ObservableObject {
|
|||
api_endpoint = _api_endpoint ?? ""
|
||||
}
|
||||
|
||||
/// 迁移老版本数据, 老版本不是存在共享组中, 需要迁移到共享组
|
||||
func moveOldUserDefaults() -> Void {
|
||||
let oldUserDefaults = UserDefaults.standard
|
||||
if let _token = oldUserDefaults.string(forKey: "PushDeer_token") {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_token")
|
||||
token = _token
|
||||
}
|
||||
if let _tabSelectedIndex = oldUserDefaults.object(forKey: "PushDeer_tabSelectedIndex") as? Int {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_tabSelectedIndex")
|
||||
tabSelectedIndex = _tabSelectedIndex
|
||||
}
|
||||
if let _isShowTestPush = oldUserDefaults.object(forKey: "PushDeer_isShowTestPush") as? Bool {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_isShowTestPush")
|
||||
isShowTestPush = _isShowTestPush
|
||||
}
|
||||
if let _isUseBuiltInBrowser = oldUserDefaults.object(forKey: "PushDeer_isUseBuiltInBrowser") as? Bool {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_isUseBuiltInBrowser")
|
||||
isUseBuiltInBrowser = _isUseBuiltInBrowser
|
||||
}
|
||||
if let _markDownBaseURL = oldUserDefaults.string(forKey: "PushDeer_markDownBaseURL") {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_markDownBaseURL")
|
||||
markDownBaseURL = _markDownBaseURL
|
||||
}
|
||||
if let _api_endpoint = oldUserDefaults.string(forKey: "PushDeer_api_endpoint") {
|
||||
oldUserDefaults.removeObject(forKey: "PushDeer_api_endpoint")
|
||||
api_endpoint = _api_endpoint
|
||||
}
|
||||
}
|
||||
|
||||
func loginAfter() {
|
||||
Task {
|
||||
// 查询 UserInfo
|
||||
self.userInfo = try await HttpRequest.getUserInfo()
|
||||
var stoken = self.userInfo?.simple_token
|
||||
if isEmpty(stoken) {
|
||||
// UserInfo 中的 stoken 为空, 就重新生成一个
|
||||
stoken = try await HttpRequest.stokenRegen().stoken
|
||||
}
|
||||
if isNotEmpty(stoken) {
|
||||
// 最后 stoken 不为空, 就保存到本地
|
||||
self.saveSTokenToLocal(stoken: stoken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveSTokenToLocal(stoken: String?) -> Void {
|
||||
let key = "stoken_\(self.api_endpoint)"
|
||||
getUserDefaults().set(stoken, forKey: key)
|
||||
}
|
||||
func getLocalSToken() -> String? {
|
||||
let key = "stoken_\(self.api_endpoint)"
|
||||
return getUserDefaults().string(forKey: key)
|
||||
}
|
||||
func deleteLocalSToken() -> Void {
|
||||
let key = "stoken_\(self.api_endpoint)"
|
||||
getUserDefaults().removeObject(forKey: key)
|
||||
}
|
||||
|
||||
func appleIdLogin(_ result: Result<ASAuthorization, Error>) async throws -> TokenContent {
|
||||
switch result {
|
||||
case let .success(authorization):
|
||||
|
|
|
@ -8,13 +8,63 @@
|
|||
import Foundation
|
||||
import Moya
|
||||
|
||||
struct TokenAuthorizationPlugin: PluginType {
|
||||
|
||||
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
|
||||
if
|
||||
let url = request.url,
|
||||
var components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
let queryItems = components.queryItems
|
||||
{
|
||||
let queryItems_new = queryItems.map { item -> URLQueryItem in
|
||||
if item.name == "token" {
|
||||
// 把请求参数中的 token 都换成最新的
|
||||
return URLQueryItem(name: "token", value: AppState.shared.token)
|
||||
}
|
||||
return item
|
||||
}
|
||||
components.queryItems = queryItems_new
|
||||
var request_mutable = request
|
||||
request_mutable.url = components.url
|
||||
return request_mutable
|
||||
}
|
||||
return request
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct HttpRequest {
|
||||
|
||||
static let provider = MoyaProvider<PushDeerApi>(callbackQueue: DispatchQueue.main)
|
||||
static let provider = MoyaProvider<PushDeerApi>(callbackQueue: DispatchQueue.main, plugins: [TokenAuthorizationPlugin()])
|
||||
|
||||
/// 统一处理接口请求, 并且封装成 Swift Concurrency 模式 (async / await)
|
||||
static func request<T: Codable>(_ targetType: PushDeerApi, resultType: T.Type) async throws -> T {
|
||||
do {
|
||||
return try await _request(targetType, resultType: resultType)
|
||||
} catch {
|
||||
if (error as NSError).code == 80403 {
|
||||
// token 失效处理
|
||||
let stoken = AppState.shared.getLocalSToken() // 取本地 stoken
|
||||
if isNotEmpty(stoken) {
|
||||
let token = try? await stokenLogin(stoken: stoken!).token // 用 stoken 登录换 token
|
||||
if isNotEmpty(token) {
|
||||
AppState.shared.token = token! // 更新 token
|
||||
do {
|
||||
return try await _request(targetType, resultType: resultType) // 用新 token 再次请求原接口
|
||||
} catch {
|
||||
throw error // 再次请求报错
|
||||
}
|
||||
}
|
||||
AppState.shared.deleteLocalSToken() // 到这来说明 stoken 已经失效, 删掉 stoken
|
||||
}
|
||||
// 退出登录
|
||||
AppState.shared.token = "" // 到这来 说明 stoken 不存在 或 已经失效, 清空 token 使其切换到登录界面
|
||||
}
|
||||
throw error // 抛出原请求的 error (stoken流程失败, 或者不是token失效的错误, 会到这儿来)
|
||||
}
|
||||
}
|
||||
|
||||
static func _request<T: Codable>(_ targetType: PushDeerApi, resultType: T.Type) async throws -> T {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
provider.request(targetType) { result in
|
||||
switch result {
|
||||
|
@ -31,7 +81,6 @@ struct HttpRequest {
|
|||
if let content = result.content, result.code == 0 {
|
||||
continuation.resume(returning: content)
|
||||
} else if result.code == 80403 {
|
||||
AppState.shared.token = ""
|
||||
continuation.resume(throwing: NSError(domain: result.error ?? NSLocalizedString("登录过期", comment: "token失效时提示"), code: result.code, userInfo: [NSLocalizedDescriptionKey: result.error ?? NSLocalizedString("登录过期", comment: "token失效时提示")]))
|
||||
} else {
|
||||
continuation.resume(throwing: NSError(domain: result.error ?? NSLocalizedString("接口报错", comment: "接口报错时提示"), code: result.code, userInfo: [NSLocalizedDescriptionKey: result.error ?? NSLocalizedString("接口报错", comment: "接口报错时提示")]))
|
||||
|
@ -135,4 +184,21 @@ struct HttpRequest {
|
|||
static func rmMessage(id: Int) async throws -> ActionContent {
|
||||
return try await request(.rmMessage(token: AppState.shared.token, id: id), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
static func rmAllMessage() async throws -> ActionContent {
|
||||
return try await request(.rmAllMessage(token: AppState.shared.token), resultType: ActionContent.self)
|
||||
}
|
||||
|
||||
|
||||
static func stokenLogin(stoken: String) async throws -> TokenContent {
|
||||
return try await request(.stokenLogin(stoken: stoken), resultType: TokenContent.self)
|
||||
}
|
||||
|
||||
static func stokenRegen() async throws -> STokenContent {
|
||||
return try await request(.stokenRegen(token: AppState.shared.token), resultType: STokenContent.self)
|
||||
}
|
||||
|
||||
static func stokenRemove() async throws -> STokenContent {
|
||||
return try await request(.stokenRemove(token: AppState.shared.token), resultType: STokenContent.self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,11 @@ enum PushDeerApi {
|
|||
|
||||
case getMessages(token: String, limit: Int)
|
||||
case rmMessage(token: String, id: Int)
|
||||
case rmAllMessage(token: String)
|
||||
|
||||
case stokenLogin(stoken: String)
|
||||
case stokenRegen(token: String)
|
||||
case stokenRemove(token: String)
|
||||
}
|
||||
|
||||
extension PushDeerApi: TargetType {
|
||||
|
@ -91,6 +95,15 @@ extension PushDeerApi: TargetType {
|
|||
return "/message/list"
|
||||
case .rmMessage:
|
||||
return "/message/remove"
|
||||
case .rmAllMessage:
|
||||
return "/message/clean"
|
||||
|
||||
case .stokenLogin:
|
||||
return "/login/simple_token"
|
||||
case .stokenRegen:
|
||||
return "/simple_token/regen"
|
||||
case .stokenRemove:
|
||||
return "/simple_token/remove"
|
||||
}
|
||||
}
|
||||
var method: Moya.Method {
|
||||
|
@ -139,7 +152,15 @@ extension PushDeerApi: TargetType {
|
|||
return .requestParameters(parameters: ["token": token, "limit": limit],encoding: URLEncoding.queryString)
|
||||
case let .rmMessage(token, id):
|
||||
return .requestParameters(parameters: ["token": token, "id": id],encoding: URLEncoding.queryString)
|
||||
case let .rmAllMessage(token):
|
||||
return .requestParameters(parameters: ["token": token],encoding: URLEncoding.queryString)
|
||||
|
||||
case let .stokenLogin(stoken):
|
||||
return .requestParameters(parameters: ["stoken": stoken],encoding: URLEncoding.queryString)
|
||||
case let .stokenRegen(token):
|
||||
return .requestParameters(parameters: ["token": token],encoding: URLEncoding.queryString)
|
||||
case let .stokenRemove(token):
|
||||
return .requestParameters(parameters: ["token": token],encoding: URLEncoding.queryString)
|
||||
}
|
||||
}
|
||||
var headers: [String: String]? {
|
||||
|
|
|
@ -30,6 +30,9 @@ class WXDelegate: NSObject, WXApiDelegate {
|
|||
if state == "login" {
|
||||
AppState.shared.token = try await HttpRequest.wechatLogin(code: code).token
|
||||
// 给 AppState 的 token 赋值后, SwiftUI 写的 ContentView 页面会监听到并自动进入主页
|
||||
// 登录成功后的处理
|
||||
AppState.shared.loginAfter()
|
||||
|
||||
} else if state == "bind" {
|
||||
_ = try await HttpRequest.mergeUser(type: "wechat", tokenorcode: code)
|
||||
// 合并成功, 更新数据
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// CommonUtils.swift
|
||||
// PushDeer
|
||||
//
|
||||
// Created by HEXT on 2022/9/11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 判断一个集合 **为空**
|
||||
/// - Parameter emptiable: 一个可能为空的集合, 需要实现了 Collection 协议, 如: String / Array / Dictionary / Set / Data 等
|
||||
/// - Returns: nil 或 空 为 true
|
||||
func isEmpty<T: Collection>(_ emptiable: T?) -> Bool {
|
||||
if emptiable == nil || emptiable!.isEmpty {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断一个集合 **不为空**
|
||||
/// - Parameter emptiable: 一个可能为空的集合, 需要实现了 Collection 协议, 如: String / Array / Dictionary / Set / Data 等
|
||||
/// - Returns: nil 或 空 为 false
|
||||
func isNotEmpty<T: Collection>(_ emptiable: T?) -> Bool {
|
||||
return !isEmpty(emptiable)
|
||||
}
|
|
@ -41,6 +41,8 @@ struct LoginView: View {
|
|||
showLoading = true
|
||||
store.token = try await store.appleIdLogin(result).token
|
||||
// 获取成功去主页
|
||||
// 登录成功后的处理
|
||||
store.loginAfter()
|
||||
} catch {
|
||||
showLoading = false
|
||||
if (error as NSError).code == 1001 {
|
||||
|
|
|
@ -15,6 +15,17 @@ struct MessageListView: View {
|
|||
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \MessageModel.created_at, ascending: false)], animation: .default)
|
||||
private var messages: FetchedResults<MessageModel>
|
||||
|
||||
@State private var showRemoveAllMessageView: Bool = false
|
||||
@State private var lastDeleteTime: TimeInterval = 0
|
||||
|
||||
func recordDeleteTime() -> Void {
|
||||
let currentDeleteTime = Date.timeIntervalSinceReferenceDate
|
||||
if currentDeleteTime - lastDeleteTime < 60 {
|
||||
showRemoveAllMessageView = true
|
||||
}
|
||||
lastDeleteTime = currentDeleteTime
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
BaseNavigationView(title: "消息") {
|
||||
ScrollView {
|
||||
|
@ -28,6 +39,7 @@ struct MessageListView: View {
|
|||
viewContext.delete(messageItem)
|
||||
try? viewContext.save()
|
||||
HToast.showSuccess(NSLocalizedString("已删除", comment: "删除设备/Key/消息时提示"))
|
||||
recordDeleteTime()
|
||||
Task {
|
||||
do {
|
||||
_ = try await HttpRequest.rmMessage(id: Int(id))
|
||||
|
@ -40,6 +52,14 @@ struct MessageListView: View {
|
|||
Spacer(minLength: 30)
|
||||
}
|
||||
}
|
||||
.overlay(
|
||||
Group {
|
||||
if (showRemoveAllMessageView) {
|
||||
RemoveAllMessageView{showRemoveAllMessageView = false}
|
||||
}
|
||||
},
|
||||
alignment: .bottom
|
||||
)
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
withAnimation(.easeOut) {
|
||||
store.isShowTestPush = !store.isShowTestPush
|
||||
|
@ -115,8 +135,51 @@ struct TestPushView: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct RemoveAllMessageView: View {
|
||||
/// 取消按钮点击的回调
|
||||
let closeAction : () -> ()
|
||||
var body: some View {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
HLine()
|
||||
.stroke(Color("borderColor"))
|
||||
.frame(height: 1)
|
||||
HStack(spacing: 12) {
|
||||
Button(NSLocalizedString("清除全部消息", comment: "")) {
|
||||
Task {
|
||||
do {
|
||||
try MessageModel.deleteAll()
|
||||
_ = try await HttpRequest.rmAllMessage()
|
||||
HToast.showSuccess(NSLocalizedString("已清空", comment: ""))
|
||||
self.closeAction()
|
||||
} catch {
|
||||
HToast.showError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.system(size: 20))
|
||||
.padding(.horizontal)
|
||||
.frame( height: 42)
|
||||
.overlay(RoundedRectangle(cornerRadius: 4).stroke())
|
||||
.foregroundColor(Color.accentColor)
|
||||
|
||||
Button(NSLocalizedString("取消", comment: "")) {
|
||||
self.closeAction()
|
||||
}
|
||||
.font(.system(size: 20))
|
||||
.padding(.horizontal)
|
||||
.frame( height: 42)
|
||||
.overlay(RoundedRectangle(cornerRadius: 4).stroke())
|
||||
.foregroundColor(Color.accentColor)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.background(Color("backgroundColor").opacity(0.9))
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MessageListView()
|
||||
.environmentObject(AppState.shared)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ struct SettingsView: View {
|
|||
VStack {
|
||||
SettingsItemView(title: NSLocalizedString("登录为", comment: "") + " " + userName(), button: NSLocalizedString("退出", comment: "退出登录按钮上的文字")) {
|
||||
store.token = ""
|
||||
store.deleteLocalSToken()
|
||||
HToast.showText(NSLocalizedString("退出", comment: "退出登录按钮上的文字"))
|
||||
}
|
||||
.padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20))
|
||||
|
||||
|
@ -36,6 +38,7 @@ struct SettingsView: View {
|
|||
SettingsItemView(title: NSLocalizedString("API endpoint", comment: ""), button: NSLocalizedString("重置", comment: "")) {
|
||||
store.api_endpoint = ""
|
||||
store.token = ""
|
||||
store.deleteLocalSToken()
|
||||
}
|
||||
.padding(EdgeInsets(top: 18, leading: 20, bottom: 0, trailing: 20))
|
||||
}
|
||||
|
|
|
@ -16,5 +16,9 @@
|
|||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.pushdeer.self.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.self.ios</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -16,5 +16,9 @@
|
|||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.pushdeer.app.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.app.ios</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.537",
|
||||
"green" : "0.278",
|
||||
"red" : "0.231"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.702",
|
||||
"green" : "0.365",
|
||||
"red" : "0.302"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
22
ios/PushDeer-iOS/PushDeerWidget/Assets.xcassets/Images/deer.gray.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "deer.gray.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "deer.gray@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ios/PushDeer-iOS/PushDeerWidget/Assets.xcassets/Images/deer.gray.imageset/deer.gray.png
vendored
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
ios/PushDeer-iOS/PushDeerWidget/Assets.xcassets/Images/deer.gray.imageset/deer.gray@2x.png
vendored
Normal file
After Width: | Height: | Size: 107 KiB |
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>INEnums</key>
|
||||
<array/>
|
||||
<key>INIntentDefinitionModelVersion</key>
|
||||
<string>1.2</string>
|
||||
<key>INIntentDefinitionNamespace</key>
|
||||
<string>88xZPY</string>
|
||||
<key>INIntentDefinitionSystemVersion</key>
|
||||
<string>20A294</string>
|
||||
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||
<string>12A6144</string>
|
||||
<key>INIntentDefinitionToolsVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>INIntents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentCategory</key>
|
||||
<string>information</string>
|
||||
<key>INIntentDescriptionID</key>
|
||||
<string>tVvJ9c</string>
|
||||
<key>INIntentEligibleForWidgets</key>
|
||||
<true/>
|
||||
<key>INIntentIneligibleForSuggestions</key>
|
||||
<true/>
|
||||
<key>INIntentName</key>
|
||||
<string>Configuration</string>
|
||||
<key>INIntentResponse</key>
|
||||
<dict>
|
||||
<key>INIntentResponseCodes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>success</string>
|
||||
<key>INIntentResponseCodeSuccess</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentResponseCodeName</key>
|
||||
<string>failure</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>INIntentTitle</key>
|
||||
<string>Configuration</string>
|
||||
<key>INIntentTitleID</key>
|
||||
<string>gpCwrM</string>
|
||||
<key>INIntentType</key>
|
||||
<string>Custom</string>
|
||||
<key>INIntentVerb</key>
|
||||
<string>View</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,182 @@
|
|||
//
|
||||
// PushDeerWidget.swift
|
||||
// PushDeerWidget
|
||||
//
|
||||
// Created by HEXT on 2022/3/2.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
import Intents
|
||||
|
||||
struct Provider: IntentTimelineProvider {
|
||||
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
|
||||
}
|
||||
|
||||
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date(), configuration: configuration)
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
NSLog("getTimeline")
|
||||
Task {
|
||||
let currentDate = Date()
|
||||
var entries: [SimpleEntry] = []
|
||||
var entry = SimpleEntry(date: currentDate, configuration: configuration)
|
||||
AppState.shared.reloadUserDefaults()
|
||||
print("token", AppState.shared.token)
|
||||
do {
|
||||
let messages = try await HttpRequest.getMessages().messages
|
||||
entry.messages = handleList(messages, context: context)
|
||||
NSLog("getMessages-success")
|
||||
} catch {
|
||||
NSLog("getMessages-catch")
|
||||
}
|
||||
entries.append(entry)
|
||||
|
||||
let nextDate = Calendar.current.date(byAdding: .minute, value: 10, to: currentDate)!
|
||||
let timeline = Timeline(entries: entries, policy: .after(nextDate))
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
func handleList(_ origList: [MessageItem], context: Context) -> [MessageItem] {
|
||||
var list = origList
|
||||
var limit = 0
|
||||
switch context.family {
|
||||
case .systemSmall, .systemMedium:
|
||||
limit = 4
|
||||
default:
|
||||
limit = 10
|
||||
}
|
||||
if list.count <= limit + 1 {
|
||||
return list;
|
||||
}
|
||||
list = list.prefix(limit) + []
|
||||
list.append(
|
||||
MessageItem(id: -1, uid: "", text: "+其它\(origList.count - limit)条", desp: "", type: "text", pushkey_name: "", created_at: "")
|
||||
)
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let configuration: ConfigurationIntent
|
||||
var messages: [MessageItem] = placeholderList
|
||||
}
|
||||
|
||||
struct PushDeerWidgetEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
HContentView {
|
||||
if AppState.shared.token.isEmpty {
|
||||
Text("未登录")
|
||||
} else if entry.messages.isEmpty {
|
||||
Text("无消息")
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
ForEach(entry.messages) {
|
||||
if entry.messages.first?.id != $0.id {
|
||||
Divider()
|
||||
}
|
||||
if $0.id == -1 {
|
||||
HText(text: $0.text)
|
||||
.foregroundColor(.accentColor)
|
||||
} else {
|
||||
HText(text: $0.text)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 15))
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding()
|
||||
.accentColor(Color("AccentColor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HContentView<Content : View>: View {
|
||||
/// 页面主体View
|
||||
@ViewBuilder let contentView: Content
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// VStack HStack Spacer 组合起来撑到最大
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
contentView
|
||||
}
|
||||
.background(
|
||||
Image("deer.gray")
|
||||
.opacity(colorScheme == .dark ? 0.4 : 1),
|
||||
alignment: .topTrailing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct HText: View {
|
||||
let text: String
|
||||
var body: some View {
|
||||
if #available(iOSApplicationExtension 15.0, *) {
|
||||
Group {
|
||||
if #available(iOSApplicationExtension 15.0, *) {
|
||||
Text(try! AttributedString(markdown: text))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(verbatim: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct PushDeerWidget: Widget {
|
||||
let kind: String = "PushDeerWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
|
||||
PushDeerWidgetEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("最近的PushDeer消息")
|
||||
.description("这个小部件可以快捷展示你最近收到的消息.")
|
||||
}
|
||||
}
|
||||
|
||||
struct PushDeerWidget_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AppState.shared.token = "1"
|
||||
return Group {
|
||||
PushDeerWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
|
||||
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
PushDeerWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
PushDeerWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
if #available(iOSApplicationExtension 15.0, *) {
|
||||
PushDeerWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
|
||||
.previewContext(WidgetPreviewContext(family: .systemExtraLarge))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let placeholderList = [
|
||||
MessageItem(id: 1, uid: "", text: "展示最新通知", desp: "", type: "text", pushkey_name: "", created_at: ""),
|
||||
MessageItem(id: 2, uid: "", text: "方便快速查看", desp: "", type: "text", pushkey_name: "", created_at: ""),
|
||||
MessageItem(id: 3, uid: "", text: "保持消息同步", desp: "", type: "text", pushkey_name: "", created_at: ""),
|
||||
MessageItem(id: 4, uid: "", text: "自动即时刷新", desp: "", type: "text", pushkey_name: "", created_at: ""),
|
||||
MessageItem(id: -1, uid: "", text: "+其它\(8)条", desp: "", type: "text", pushkey_name: "", created_at: ""),
|
||||
]
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.self.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.pushdeer.app.ios</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
178
iot/README.md
|
@ -1,21 +1,69 @@
|
|||
> PushDeer可以将消息推送到各种支持MQTT协议的智能设备。
|
||||
|
||||
DeerESP 是 PushDeer 在 IOT 方向的扩展项目,它是一个基于 ESP8266 的消息设备方案。
|
||||
DeerESP 是 PushDeer 在 IOT 方向的扩展项目,它是一个基于 ESP8266/ESP32 芯片的消息设备方案。
|
||||
|
||||
目前已经可以通过开发板自行组装使用。
|
||||
目前你可以通过以下方式获得这个终端:
|
||||
|
||||
本文将以 `NodeMCU` 1.0开发板和 1.44寸的 Arduino Black TFT屏幕为例,讲解如何组建一个成本35元人民币左右的硬件设备,并通过PushDeer将消息推送给它。
|
||||
- 方案1:直接购买为 PushDeer 设计的、组装好的、带3D打印外壳的硬件(价格为60左右)
|
||||
- 方案2:购买为 PushDeer 设计的、带3D打印外壳的、未焊接和组装的散件(价格为50左右)
|
||||
- 方案3:通过开源的硬件方案自行印制电路板、购买元件并焊接(十件均价25左右,屏幕10元左右)
|
||||
- 方案4:直接购买8266开发板、屏幕和蜂鸣器,连线使用(35元左右)
|
||||
|
||||
最终效果如下图:
|
||||
其中方案1和方案4无需焊接,主要区别在集成度和外壳;方案2和方案3需要较强的动手能力,适合会焊接的同学。
|
||||
|
||||
## 方案1/方案2效果:
|
||||
|
||||
![](image/20220422002608.png)
|
||||
|
||||
128*128 1.44寸屏幕效果
|
||||
|
||||
![](image/20220422003820.png)
|
||||
|
||||
240*240 1.3寸屏幕效果
|
||||
|
||||
![](image/20220422004159.png)
|
||||
|
||||
另有白色外壳版本。
|
||||
|
||||
## 方案3效果:
|
||||
|
||||
![](image/20220422005313.png)
|
||||
|
||||
![](image/20220422005232.png)
|
||||
|
||||
## 方案4效果:
|
||||
|
||||
![](image/20220422005744.png)
|
||||
|
||||
![](image/deeresp.gif)
|
||||
|
||||
[📼 点此查看视频版本,可以听到提示音♪](https://weibo.com/1088413295/LfUwivPoh)
|
||||
|
||||
PS:如果你有硬件量产的经验并有兴趣参与,可以在[微博](https://weibo.com/easy)私信或者评论@Easy。
|
||||
|
||||
以下我们将针对上述方案提供对应的教程。
|
||||
|
||||
|
||||
## 硬件的购买
|
||||
|
||||
### 方案1/2
|
||||
|
||||
在[淘宝店铺](https://item.taobao.com/item.htm?ft=t&id=673259250981)进行购买:
|
||||
![](image/20220422010516.png)
|
||||
|
||||
> 感谢店长`nickchen`贡献[开源硬件方案](https://gitee.com/nickchenss/deepesp-v2/tree/master/DEEP%20V2),硬件的品质将由店家自行负责。
|
||||
|
||||
### 方案3
|
||||
|
||||
在[硬创社](https://x.jlc.com/platform/detail/1a987c9a4d4347efaef4f425f3a408d0) 一键下单,通过嘉立创在线制作。
|
||||
|
||||
![](image/20220422011115.png)
|
||||
|
||||
相关资料也可以在[Gitee](https://gitee.com/nickchenss/deepesp-v2/tree/master/DEEP%20V2)上获得。
|
||||
|
||||
可参考:[B站嘉立创免费打样教程](https://www.bilibili.com/video/BV1eh41147ka?spm_id_from=333.337.search-card.all.click)
|
||||
|
||||
### 方案4
|
||||
|
||||
![](image/2022-02-17-00-43-58.png)
|
||||
|
||||
1. 开发板:只要是兼容NodeMCU1.0规范的就行,内存需要4M(32Mbits),更大更好(就能放中文字库了)我选的是CH340接口
|
||||
|
@ -26,6 +74,20 @@ PS:如果你有硬件量产的经验并有兴趣参与,可以在[微博](htt
|
|||
|
||||
## 硬件的连接
|
||||
|
||||
### 方案1
|
||||
|
||||
无需连接
|
||||
|
||||
### 方案2
|
||||
|
||||
店家会提供焊接和组装教学视频,请询问店家。
|
||||
|
||||
### 方案3
|
||||
|
||||
请参考开源硬件资料中的原理图进行连接。
|
||||
|
||||
### 方案4
|
||||
|
||||
首先把屏幕和开发板连起来,按下图操作:
|
||||
|
||||
![](image/2022-02-16-21-48-45.png)
|
||||
|
@ -62,7 +124,7 @@ PS:如果你有硬件量产的经验并有兴趣参与,可以在[微博](htt
|
|||
|
||||
![](image/2022-02-16-22-02-36.png)
|
||||
|
||||
然后选择 `工具`→`开发板`→`ESP8266 Boards`→`NodeMCU1.0`
|
||||
然后在 `工具`→`开发板管理`中搜索 `8266` 并安装,选择 `工具`→`开发板`→`ESP8266 Boards`→`NodeMCU1.0` 切换过来。
|
||||
|
||||
![](image/2022-02-16-22-04-25.png)
|
||||
|
||||
|
@ -169,13 +231,27 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d
|
|||
|
||||
## 烧录程序到设备
|
||||
|
||||
回到我们的设备这边来。首先用 `arduino IDE` 打开 `deeresp/deeresp.ino`,修改最上边的几行:
|
||||
### 选择要烧录的版本
|
||||
|
||||
因为8266的内存多为4M,难以同时放下配网和中文字库,我们提供了两个版本的源代码:
|
||||
|
||||
- deeresp/deeresp.ino:2000字中文字库,手动填写wifi和mqtt信息
|
||||
- deeresp32/deeresp32.ino:支持配网和保存配置文件、可选BG2312中文字库,建议8M以上设备使用
|
||||
|
||||
以上版本都包含了图片、文字推送,支持提示鸣音以及联网时钟功能。
|
||||
|
||||
### 具体操作
|
||||
|
||||
回到我们的设备这边来。首先用 `arduino IDE` 打开 `deeresp/deeresp.ino`,按提示修改最上边的几行:
|
||||
|
||||
```cpp
|
||||
#define SCREEN_WIDTH 128 // 如果是 240*240 的屏幕,请调整这两行
|
||||
#define SCREEN_HEIGHT 128
|
||||
#define SCREEN_ROTATION 0 // 这个是屏幕方向
|
||||
#define WIFI_SSID "wifi名称"
|
||||
#define WIFI_PASSWORD "wifi密码"
|
||||
#define MQTT_CLIENT_NAME "DeerEsp-001" // 多个同名设备连接同一台服务器会导致其他下线,所以起一个唯一的名字吧
|
||||
#define MQTT_TOPIC "LB2353" // 这里填PushDeer的Key
|
||||
#define MQTT_TOPIC "LB2312" // 这里填PushDeer的Key
|
||||
```
|
||||
|
||||
这里的信息我们现在都有了,把它们替换掉,然后点击上传图标(向右的箭头),就会编译并烧录程序到设备上了。不过别急,有两个问题需要处理。
|
||||
|
@ -203,13 +279,66 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d
|
|||
|
||||
![](image/2022-02-16-23-44-07.png)
|
||||
|
||||
注释掉默认驱动
|
||||
注释或删除掉默认驱动及其他内容
|
||||
|
||||
```cpp
|
||||
//#define ILI9341_DRIVER
|
||||
```
|
||||
|
||||
将以下行之前的注释去掉:
|
||||
然后根据不同的方案添加以下内容:
|
||||
|
||||
#### 方案1/2/3:deeresp + 1.44寸128*128 ST7735屏幕
|
||||
|
||||
```cpp
|
||||
#define ST7735_DRIVER
|
||||
#define TFT_RGB_ORDER TFT_BGR
|
||||
#define TFT_WIDTH 128
|
||||
#define TFT_HEIGHT 128
|
||||
#define ST7735_GREENTAB3
|
||||
#define TFT_MOSI PIN_D7
|
||||
#define TFT_SCLK PIN_D5
|
||||
#define TFT_CS PIN_D1
|
||||
#define TFT_DC PIN_D3
|
||||
#define TFT_RST PIN_D2
|
||||
#define LOAD_GLCD
|
||||
#define LOAD_FONT2
|
||||
#define LOAD_FONT4
|
||||
#define LOAD_FONT6
|
||||
#define LOAD_FONT7
|
||||
#define LOAD_FONT8
|
||||
#define LOAD_GFXFF
|
||||
#define SMOOTH_FONT
|
||||
#define SPI_FREQUENCY 27000000
|
||||
#define SPI_READ_FREQUENCY 20000000
|
||||
#define SPI_TOUCH_FREQUENCY 2500000
|
||||
```
|
||||
#### 方案1/2/3:deeresp + 1.3寸240*240 ST7789屏幕
|
||||
|
||||
```cpp
|
||||
#define CGRAM_OFFSET
|
||||
#define ST7789_DRIVER
|
||||
#define TFT_RGB_ORDER TFT_BGR
|
||||
#define TFT_WIDTH 240
|
||||
#define TFT_HEIGHT 240
|
||||
#define TFT_MOSI PIN_D7
|
||||
#define TFT_SCLK PIN_D5
|
||||
#define TFT_CS PIN_D1
|
||||
#define TFT_DC PIN_D3
|
||||
#define TFT_RST PIN_D2
|
||||
#define LOAD_GLCD
|
||||
#define LOAD_FONT2
|
||||
#define LOAD_FONT4
|
||||
#define LOAD_FONT6
|
||||
#define LOAD_FONT7
|
||||
#define LOAD_FONT8
|
||||
#define LOAD_GFXFF
|
||||
#define SMOOTH_FONT
|
||||
#define SPI_FREQUENCY 27000000
|
||||
#define SPI_READ_FREQUENCY 20000000
|
||||
#define SPI_TOUCH_FREQUENCY 2500000
|
||||
```
|
||||
|
||||
#### 方案4:NodeMCU + 1.44寸128*128 ST7735屏幕
|
||||
|
||||
```cpp
|
||||
#define ST7735_DRIVER
|
||||
|
@ -217,17 +346,16 @@ docker-compose -f docker-compose.self-hosted.yml up --build -d
|
|||
#define TFT_WIDTH 128
|
||||
#define TFT_HEIGHT 128
|
||||
#define ST7735_GREENTAB3
|
||||
#define TFT_CS PIN_D1 // Chip select control pin D8
|
||||
#define TFT_DC PIN_D3 // Data Command control pin
|
||||
#define TFT_RST PIN_D2 // Reset pin (could connect to NodeMCU RST, see next line)
|
||||
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
|
||||
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
|
||||
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
|
||||
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
|
||||
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
|
||||
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
|
||||
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
|
||||
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
|
||||
#define TFT_CS PIN_D1
|
||||
#define TFT_DC PIN_D3
|
||||
#define TFT_RST PIN_D2
|
||||
#define LOAD_GLCD
|
||||
#define LOAD_FONT2
|
||||
#define LOAD_FONT4
|
||||
#define LOAD_FONT6
|
||||
#define LOAD_FONT7
|
||||
#define LOAD_FONT8
|
||||
#define LOAD_GFXFF
|
||||
#define SMOOTH_FONT
|
||||
#define SPI_FREQUENCY 27000000
|
||||
#define SPI_READ_FREQUENCY 20000000
|
||||
|
@ -310,13 +438,13 @@ docker run -e API_KEY=9LKo3 -e MQTT_PORT=1883 -e MQTT_USER=easy -e MQTT_PASSWORD
|
|||
|
||||
如果使用默认的MQTT服务器账号,只需要将 MQTT TOPIC 改为对应的值,即可向设备推送文字和图片。
|
||||
|
||||
在网页上可以把剪贴板中的图片粘贴上,然后工具会进行缩放为设备需要的尺寸和格式。点击下载图片,可以获得处理好的图片。
|
||||
在网页上可以把剪贴板中的图片粘贴上,然后工具会进行缩放为设备需要的尺寸和格式。点击发送,会生成一个一分钟有效的临时图片链接,并自动推送到设备。
|
||||
|
||||
将图片上传到网上,在左侧`发送类型` 选择为 `图片`, `发送内容`粘贴为图片的URL即可推送给设备。
|
||||
也可点击下载,将处理好的图片上传到网上,在左侧`发送类型` 选择为 `图片`, `发送内容`粘贴为图片的URL即可推送给设备。
|
||||
|
||||
> 注意图片的URL必须为HTTP协议、JPG格式、不能有301/302等转向,最好为256*256。
|
||||
> 注意图片的URL必须为HTTP协议、JPG格式、不能有301/302等转向,最好为屏幕分辨率的两倍(256\*256、480*480)。
|
||||
|
||||
![](image/2022-03-26-17-38-02.png)
|
||||
![](image/20220422014021.png)
|
||||
|
||||
|
||||
## 用MQTTX进行测试
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 128
|
||||
#define SCREEN_ROTATION 0
|
||||
#define WIFI_SSID "wifi名称"
|
||||
#define WIFI_PASSWORD "wifi密码"
|
||||
#define MQTT_CLIENT_NAME "DeerEsp-001" // 多个同名设备连接同一台服务器会导致其他下线,所以起一个唯一的名字吧
|
||||
#define MQTT_TOPIC "LB2353" // 这里填PushDeer的Key
|
||||
|
||||
#define MQTT_IP "broker.emqx.io"
|
||||
#define MQTT_USER ""
|
||||
#define MQTT_PASSWORD ""
|
||||
|
@ -11,6 +15,9 @@
|
|||
// ====== 以下不用修改 ===============
|
||||
#define DOWNLOADED_IMG "/download.jpg"
|
||||
|
||||
#define IMG_SCALE 2
|
||||
#define TXT_SCALE 1
|
||||
|
||||
|
||||
#include <EspMQTTClient.h>
|
||||
|
||||
|
@ -23,32 +30,34 @@ EspMQTTClient mclient(
|
|||
MQTT_CLIENT_NAME,
|
||||
MQTT_PORT
|
||||
);
|
||||
|
||||
#include "cubic_12.h"
|
||||
#include "SPI.h"
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
#include <NTPClient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#define BEEP_PIN D8
|
||||
#define IMG_SCALE 2
|
||||
#define TXT_SCALE 2
|
||||
#define BEEP_PIN PIN_D8
|
||||
#else
|
||||
#include "SPIFFS.h" // Required for ESP32 only
|
||||
#define IMG_SCALE 1
|
||||
#define TXT_SCALE 4
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
#include <TJpg_Decoder.h>
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP,"ntp1.aliyun.com",60*60*8,60000);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
mclient.enableDebuggingMessages();
|
||||
|
||||
|
||||
tft.begin();
|
||||
// tft.setRotation(1); // 屏幕方向
|
||||
tft.setRotation(SCREEN_ROTATION); // 屏幕方向
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.setTextSize(TXT_SCALE);tft.println("Init ...");
|
||||
Serial.println("tft init");
|
||||
|
@ -64,13 +73,14 @@ void setup() {
|
|||
TJpgDec.setCallback(tft_output);
|
||||
|
||||
Serial.println("TJpgDec init");
|
||||
timeClient.begin();
|
||||
|
||||
}
|
||||
|
||||
void onConnectionEstablished()
|
||||
{
|
||||
Serial.println("connected");
|
||||
tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println("Waiting for messages ...");
|
||||
tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println("Waiting for messages at "+String(MQTT_TOPIC)+"...");
|
||||
|
||||
mclient.subscribe(String(MQTT_TOPIC)+"_text", [] (const String &payload)
|
||||
{
|
||||
|
@ -78,6 +88,8 @@ void onConnectionEstablished()
|
|||
|
||||
if (SPIFFS.exists(DOWNLOADED_IMG) == true) TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG);
|
||||
else tft.fillScreen( TFT_BLACK );
|
||||
|
||||
show_time(true);
|
||||
|
||||
#ifdef BEEP_PIN
|
||||
if(payload.indexOf("♪") >= 0) tone(BEEP_PIN, 1000, 100);
|
||||
|
@ -114,12 +126,57 @@ void onConnectionEstablished()
|
|||
bool ret = file_put_contents(payload, DOWNLOADED_IMG);
|
||||
if (SPIFFS.exists(DOWNLOADED_IMG) == true) {
|
||||
TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG);
|
||||
show_time(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String lastTime = "2020";
|
||||
String newTime = "";
|
||||
|
||||
void loop() {
|
||||
mclient.loop();
|
||||
show_time(false);
|
||||
}
|
||||
|
||||
void show_time(bool force)
|
||||
{
|
||||
timeClient.update();
|
||||
String hourStr = timeClient.getHours() < 10 ? "0" + String(timeClient.getHours()) : String(timeClient.getHours());
|
||||
String minStr = timeClient.getMinutes() < 10 ? "0" + String(timeClient.getMinutes()) : String(timeClient.getMinutes());
|
||||
newTime = hourStr + ":" + minStr ;
|
||||
if( lastTime != newTime )
|
||||
{
|
||||
echo_time( newTime );
|
||||
lastTime = newTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( force ) echo_time( newTime );
|
||||
}
|
||||
}
|
||||
|
||||
void echo_time( String thetime )
|
||||
{
|
||||
if( SCREEN_WIDTH == 128 )
|
||||
{
|
||||
tft.setCursor(96, 120, 1);
|
||||
tft.setTextSize(1);
|
||||
|
||||
}
|
||||
if( SCREEN_WIDTH == 240 )
|
||||
{
|
||||
tft.setCursor(180, 210, 1);
|
||||
tft.setTextSize(2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
tft.setTextColor(TFT_WHITE,TFT_BLACK);
|
||||
tft.println(thetime);
|
||||
|
||||
|
||||
tft.setTextSize(TXT_SCALE);
|
||||
}
|
||||
|
||||
bool file_put_contents(String url, String filename) {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 128
|
||||
#define SCREEN_ROTATION 0
|
||||
#define DOWNLOADED_IMG "/download.jpg"
|
||||
#define AA_FONT_CUBIC "Cubic1112"
|
||||
// #define FULL_FONT 1
|
||||
// #define CHINESE_FONT 1
|
||||
|
||||
#define BLK_PIN 19
|
||||
#define BTN_PIN 0
|
||||
|
||||
#include <WiFiManager.h>
|
||||
#include <EspMQTTClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Effortless_SPIFFS.h>
|
||||
|
||||
WiFiManager wm;
|
||||
|
||||
|
@ -21,7 +30,6 @@ String mqtt_user_value = "";
|
|||
String mqtt_client_value = "";
|
||||
String mqtt_password_value = "";
|
||||
|
||||
|
||||
//EspMQTTClient mclient(
|
||||
// WIFI_SSID,
|
||||
// WIFI_PASSWORD,
|
||||
|
@ -33,11 +41,21 @@ String mqtt_password_value = "";
|
|||
//);
|
||||
EspMQTTClient mclient;
|
||||
|
||||
// #include "cubic_12.h"
|
||||
#ifdef CHINESE_FONT
|
||||
#ifdef FULL_FONT
|
||||
#define AA_FONT_CUBIC "Cubic1112"
|
||||
#else
|
||||
#include "cubic_12.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// #include "SPI.h"
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
#include <NTPClient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#define BEEP_PIN D8
|
||||
|
@ -52,15 +70,35 @@ TFT_eSPI tft = TFT_eSPI();
|
|||
#endif
|
||||
|
||||
#include <TJpg_Decoder.h>
|
||||
#include <EasyButton.h>
|
||||
EasyButton clearbtn(BTN_PIN);
|
||||
|
||||
#include <WiFiUdp.h>
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP,"ntp1.aliyun.com",60*60*8,60000);
|
||||
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
Serial.println("Should save config");
|
||||
shouldSaveConfig = true;
|
||||
}
|
||||
|
||||
DynamicJsonDocument jsonDocument(1024);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
mclient.enableDebuggingMessages();
|
||||
clearbtn.begin();
|
||||
clearbtn.onPressed(clear_config);
|
||||
|
||||
tft.begin();
|
||||
pinMode(19, OUTPUT);
|
||||
digitalWrite(19, HIGH);
|
||||
// tft.setRotation(1); // 屏幕方向
|
||||
pinMode(BLK_PIN, OUTPUT);
|
||||
digitalWrite(BLK_PIN, HIGH);
|
||||
|
||||
// tft.setRotation(2); // 屏幕方向
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.setTextSize(TXT_SCALE);tft.println("Init ...");
|
||||
Serial.println("tft init");
|
||||
|
@ -71,56 +109,134 @@ void setup() {
|
|||
}
|
||||
Serial.println("SPIFFS init");
|
||||
|
||||
eSPIFFS fileSystem;
|
||||
|
||||
// Check Flash Size - Always try to incorrperate a check when not debugging to know if you have set the SPIFFS correctly
|
||||
if (!fileSystem.checkFlashConfig()) {
|
||||
Serial.println("Flash size was not correct! Please check your SPIFFS config and try again");
|
||||
delay(100000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
TJpgDec.setJpgScale(IMG_SCALE);
|
||||
TJpgDec.setSwapBytes(true);
|
||||
TJpgDec.setCallback(tft_output);
|
||||
|
||||
Serial.println("TJpgDec init");
|
||||
|
||||
tft.fillScreen( TFT_BLACK );
|
||||
tft.setCursor(0, 0, 1);
|
||||
tft.println("Connect to DeerEspWiFi, go 192.168.4.1");
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
wm.resetSettings();
|
||||
// 检测本地配置文件
|
||||
if( SPIFFS.exists( "/config.json" ) )
|
||||
{
|
||||
Serial.println("config.json exist");
|
||||
fileSystem.openFromFile("/config.json", jsonDocument);
|
||||
Serial.print("JSON Document is: ");
|
||||
serializeJson(jsonDocument, Serial);
|
||||
Serial.println();
|
||||
|
||||
// add a custom input field
|
||||
int customFieldLength = 40;
|
||||
|
||||
new (&mqtt_host_field) WiFiManagerParameter("mqtt_host", "MQTT IP", "broker.emqx.io", customFieldLength,"placeholder=\"MQTT server IP\"");
|
||||
new (&mqtt_port_field) WiFiManagerParameter("mqtt_port", "MQTT Port", "1883", customFieldLength,"placeholder=\"MQTT server port, 1883 as default\"");
|
||||
new (&mqtt_topic_field) WiFiManagerParameter("mqtt_topic", "MQTT Topic", "LB2312", customFieldLength,"placeholder=\"MQTT base topic\"");
|
||||
new (&mqtt_client_field) WiFiManagerParameter("mqtt_client", "MQTT Client ID", "DeerESP0001", customFieldLength,"placeholder=\"MQTT client id, can be empty\"");
|
||||
new (&mqtt_user_field) WiFiManagerParameter("mqtt_user", "MQTT User", "", customFieldLength,"placeholder=\"MQTT user, can be empty\"");
|
||||
new (&mqtt_password_field) WiFiManagerParameter("mqtt_password", "MQTT Password", "", customFieldLength,"placeholder=\"MQTT password, can be empty\"");
|
||||
|
||||
|
||||
wm.addParameter(&mqtt_host_field);
|
||||
wm.addParameter(&mqtt_port_field);
|
||||
wm.addParameter(&mqtt_topic_field);
|
||||
wm.addParameter(&mqtt_client_field);
|
||||
wm.addParameter(&mqtt_user_field);
|
||||
wm.addParameter(&mqtt_password_field);
|
||||
|
||||
wm.setSaveParamsCallback(saveParamCallback);
|
||||
|
||||
bool res;
|
||||
res = wm.autoConnect("DeerEspWiFi"); // anonymous ap
|
||||
Serial.println(jsonDocument["wifi_ssid"].as<String>());
|
||||
Serial.println(jsonDocument["wifi_password"].as<String>());
|
||||
|
||||
// 链接wifi
|
||||
WiFi.begin( jsonDocument["wifi_ssid"].as<const char*>(), jsonDocument["wifi_password"].as<const char*>() );
|
||||
|
||||
if(!res) {
|
||||
Serial.println("Failed to connect");
|
||||
// ESP.restart();
|
||||
}
|
||||
else {
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
mqtt_host_value = jsonDocument["mqtt_host"].as<String>();
|
||||
mqtt_port_value = jsonDocument["mqtt_port"].as<short>();
|
||||
mqtt_user_value = jsonDocument["mqtt_user"].as<String>();
|
||||
mqtt_password_value = jsonDocument["mqtt_password"].as<String>();
|
||||
mqtt_topic_value = jsonDocument["mqtt_topic"].as<String>();
|
||||
mqtt_client_value = jsonDocument["mqtt_client"].as<String>();
|
||||
|
||||
}else
|
||||
{
|
||||
Serial.println("config.json not exist");
|
||||
tft.fillScreen( TFT_BLACK );
|
||||
tft.setCursor(0, 0, 1);
|
||||
tft.println("Connect to DeerEspWiFi, go 192.168.4.1");
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
wm.resetSettings();
|
||||
|
||||
// add a custom input field
|
||||
int customFieldLength = 40;
|
||||
|
||||
new (&mqtt_host_field) WiFiManagerParameter("mqtt_host", "MQTT IP", "broker.emqx.io", customFieldLength,"placeholder=\"MQTT server IP\"");
|
||||
new (&mqtt_port_field) WiFiManagerParameter("mqtt_port", "MQTT Port", "1883", customFieldLength,"placeholder=\"MQTT server port, 1883 as default\"");
|
||||
new (&mqtt_topic_field) WiFiManagerParameter("mqtt_topic", "MQTT Topic", "LB2312", customFieldLength,"placeholder=\"MQTT base topic\"");
|
||||
new (&mqtt_client_field) WiFiManagerParameter("mqtt_client", "MQTT Client ID", "DeerESP0001", customFieldLength,"placeholder=\"MQTT client id, can be empty\"");
|
||||
new (&mqtt_user_field) WiFiManagerParameter("mqtt_user", "MQTT User", "", customFieldLength,"placeholder=\"MQTT user, can be empty\"");
|
||||
new (&mqtt_password_field) WiFiManagerParameter("mqtt_password", "MQTT Password", "", customFieldLength,"placeholder=\"MQTT password, can be empty\"");
|
||||
|
||||
|
||||
wm.addParameter(&mqtt_host_field);
|
||||
wm.addParameter(&mqtt_port_field);
|
||||
wm.addParameter(&mqtt_topic_field);
|
||||
wm.addParameter(&mqtt_client_field);
|
||||
wm.addParameter(&mqtt_user_field);
|
||||
wm.addParameter(&mqtt_password_field);
|
||||
|
||||
wm.setSaveParamsCallback(saveParamCallback);
|
||||
wm.setSaveConfigCallback(saveConfigCallback);
|
||||
|
||||
bool res;
|
||||
res = wm.autoConnect("DeerEspWiFi"); // anonymous ap
|
||||
|
||||
if(!res) {
|
||||
Serial.println("Failed to connect");
|
||||
ESP.restart();
|
||||
}
|
||||
else {
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
mclient.enableDebuggingMessages(true);
|
||||
mclient.setMqttClientName(mqtt_client_value.c_str());
|
||||
mclient.setMqttServer(mqtt_host_value.c_str(), mqtt_user_value.c_str(), mqtt_password_value.c_str(), mqtt_port_value);
|
||||
|
||||
|
||||
if (shouldSaveConfig)
|
||||
{
|
||||
|
||||
jsonDocument["mqtt_host"] = mqtt_host_value;
|
||||
jsonDocument["mqtt_port"] = mqtt_port_value;
|
||||
jsonDocument["mqtt_user"] = mqtt_user_value;
|
||||
jsonDocument["mqtt_password"] = mqtt_password_value;
|
||||
jsonDocument["mqtt_topic"] = mqtt_topic_value;
|
||||
jsonDocument["mqtt_client"] = mqtt_client_value;
|
||||
jsonDocument["wifi_ssid"] = WiFi.SSID();
|
||||
jsonDocument["wifi_password"] = WiFi.psk();
|
||||
|
||||
fileSystem.saveToFile("/config.json", jsonDocument);
|
||||
|
||||
Serial.print("JSON Document is: ");
|
||||
serializeJson(jsonDocument, Serial);
|
||||
Serial.println();
|
||||
|
||||
if(SPIFFS.exists("/config.json")) Serial.print("config.json exists ");
|
||||
else Serial.print("config.json not exists ");
|
||||
|
||||
|
||||
|
||||
// File root = SPIFFS.open("/","r");
|
||||
//
|
||||
// File file = root.openNextFile();
|
||||
//
|
||||
// while(file){
|
||||
//
|
||||
// Serial.print("FILE: ");
|
||||
// Serial.println(file.name());
|
||||
//
|
||||
// file = root.openNextFile();
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -149,7 +265,7 @@ String getParam(String name){
|
|||
void onConnectionEstablished()
|
||||
{
|
||||
Serial.println("connected");
|
||||
tft.fillScreen(TFT_BLACK);tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println("Waiting for messages ...");
|
||||
tft.fillScreen(TFT_BLACK);tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.println("Waiting for messages at "+mqtt_topic_value+"...");
|
||||
|
||||
mclient.subscribe(mqtt_topic_value+"_text", [] (const String &payload)
|
||||
{
|
||||
|
@ -158,8 +274,13 @@ void onConnectionEstablished()
|
|||
if (SPIFFS.exists(DOWNLOADED_IMG) == true) TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG);
|
||||
else tft.fillScreen( TFT_BLACK );
|
||||
|
||||
tft.loadFont(AA_FONT_CUBIC);
|
||||
// tft.loadFont(cubic_11);
|
||||
#ifdef CHINESE_FONT
|
||||
#ifdef FULL_FONT
|
||||
tft.loadFont(AA_FONT_CUBIC);
|
||||
#else
|
||||
tft.loadFont(cubic_11);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
if( payload.length() > 80 ) tft.setTextSize(TXT_SCALE/2);
|
||||
|
@ -179,7 +300,12 @@ void onConnectionEstablished()
|
|||
|
||||
}
|
||||
|
||||
tft.unloadFont();
|
||||
#ifdef CHINESE_FONT
|
||||
tft.unloadFont();
|
||||
#endif
|
||||
|
||||
|
||||
show_time(true);
|
||||
|
||||
#ifdef BEEP_PIN
|
||||
if(payload.indexOf("♪") >= 0) tone(BEEP_PIN, 1000, 100);
|
||||
|
@ -194,12 +320,61 @@ void onConnectionEstablished()
|
|||
bool ret = file_put_contents(payload, DOWNLOADED_IMG);
|
||||
if (SPIFFS.exists(DOWNLOADED_IMG) == true) {
|
||||
TJpgDec.drawFsJpg(0, 0, DOWNLOADED_IMG);
|
||||
show_time(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String lastTime = "2020";
|
||||
String newTime = "";
|
||||
|
||||
void loop() {
|
||||
mclient.loop();
|
||||
show_time(false);
|
||||
clearbtn.read();
|
||||
}
|
||||
|
||||
void clear_config()
|
||||
{
|
||||
SPIFFS.remove("/config.json");
|
||||
tone(BEEP_PIN, 1000, 100);
|
||||
}
|
||||
|
||||
void show_time(bool force)
|
||||
{
|
||||
timeClient.update();
|
||||
String hourStr = timeClient.getHours() < 10 ? "0" + String(timeClient.getHours()) : String(timeClient.getHours());
|
||||
String minStr = timeClient.getMinutes() < 10 ? "0" + String(timeClient.getMinutes()) : String(timeClient.getMinutes());
|
||||
newTime = hourStr + ":" + minStr ;
|
||||
if( lastTime != newTime )
|
||||
{
|
||||
echo_time( newTime );
|
||||
lastTime = newTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( force ) echo_time( newTime );
|
||||
}
|
||||
}
|
||||
|
||||
void echo_time( String thetime )
|
||||
{
|
||||
if( SCREEN_WIDTH == 128 )
|
||||
{
|
||||
tft.setCursor(96, 120, 1);
|
||||
tft.setTextSize(1);
|
||||
|
||||
}
|
||||
if( SCREEN_WIDTH == 240 )
|
||||
{
|
||||
tft.setCursor(180, 210, 1);
|
||||
tft.setTextSize(2);
|
||||
|
||||
}
|
||||
tft.setTextColor(TFT_WHITE,TFT_BLACK);
|
||||
tft.println(thetime);
|
||||
|
||||
tft.setTextSize(TXT_SCALE);
|
||||
}
|
||||
|
||||
bool file_put_contents(String url, String filename) {
|
||||
|
|
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,129 @@
|
|||
#define WIFI_SSID "wifi名称"
|
||||
#define WIFI_PASSWORD "wifi密码"
|
||||
#define SCREEN_WIDTH 240
|
||||
|
||||
|
||||
|
||||
#define BEEP_PIN PIN_D8 // 蜂鸣器
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <TFT_eSPI.h>
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
|
||||
#include <TJpg_Decoder.h>
|
||||
#include <EasyButton.h>
|
||||
EasyButton d0_btn(PIN_D0);
|
||||
EasyButton d3_btn(PIN_D3);
|
||||
EasyButton d6_btn(PIN_D6);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||
|
||||
d0_btn.begin();
|
||||
d0_btn.onPressed(d0_pressed);
|
||||
|
||||
d3_btn.begin();
|
||||
d3_btn.onPressed(d3_pressed);
|
||||
|
||||
d6_btn.begin();
|
||||
d6_btn.onPressed(d6_pressed);
|
||||
|
||||
|
||||
tft.begin();
|
||||
|
||||
tft.fillScreen(TFT_BLACK);
|
||||
tft.setTextColor(0xFFFF,0x0000);tft.setCursor(0, 0, 1);tft.setTextSize(2);tft.println("Init ...");
|
||||
Serial.println("tft init");
|
||||
|
||||
if (!SPIFFS.begin()) {
|
||||
Serial.println("SPIFFS initialisation failed!");
|
||||
while (1) yield(); // Stay here twiddling thumbs waiting
|
||||
}
|
||||
Serial.println("SPIFFS init");
|
||||
tft.println("SPIFFS init ...");
|
||||
|
||||
if( SCREEN_WIDTH == 128 )
|
||||
{
|
||||
TJpgDec.setJpgScale(2);
|
||||
}
|
||||
|
||||
TJpgDec.setSwapBytes(true);
|
||||
TJpgDec.setCallback(tft_output);
|
||||
|
||||
Serial.println("TJpgDec init");
|
||||
tft.println("TJpgDec init ...");
|
||||
|
||||
if( WiFi.status() == WL_CONNECTED )
|
||||
tft.println("Wifi connected ...");
|
||||
|
||||
tft.println("Press button ...");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
d0_btn.read();
|
||||
d3_btn.read();
|
||||
d6_btn.read();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
void d0_pressed()
|
||||
{
|
||||
btn_pressed("D0 button pressed");
|
||||
}
|
||||
|
||||
void d3_pressed()
|
||||
{
|
||||
btn_pressed("D3 button pressed");
|
||||
}
|
||||
|
||||
void d6_pressed()
|
||||
{
|
||||
btn_pressed("D6 button pressed");
|
||||
}
|
||||
|
||||
void btn_pressed(String name)
|
||||
{
|
||||
tone(BEEP_PIN, 1000, 100);
|
||||
|
||||
i++;
|
||||
short color = TFT_BLACK;
|
||||
if( i % 3 == 0 ){ color = TFT_RED; }
|
||||
if( i % 3 == 1 ){ color = TFT_YELLOW; }
|
||||
if( i % 3 == 2 ){ color = TFT_BLUE; }
|
||||
|
||||
tft.fillScreen(color);
|
||||
tft.setTextColor(0xFFFF,color);
|
||||
tft.setCursor(0, 0, 1);
|
||||
|
||||
tft.setCursor(0, 0, 1);
|
||||
tft.println(name + " button Pressed ...");
|
||||
if( WiFi.status() == WL_CONNECTED )
|
||||
tft.println("Wifi connected ...");
|
||||
|
||||
}
|
||||
|
||||
//void show_image()
|
||||
//{
|
||||
// if (SPIFFS.exists("/cover.jpeg") == true) {
|
||||
// TJpgDec.drawFsJpg(0, 0, "/cover.jpeg");
|
||||
// }
|
||||
// tft.setCursor(0, 0, 1);
|
||||
// tft.println( "image button Pressed ...");
|
||||
// if( WiFi.status() == WL_CONNECTED )
|
||||
// tft.println("Wifi connected ...");
|
||||
//
|
||||
//}
|
||||
|
||||
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
|
||||
{
|
||||
if ( y >= tft.height() ) return 0;
|
||||
tft.pushImage(x, y, w, h, bitmap);
|
||||
return 1;
|
||||
}
|
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 691 KiB |
After Width: | Height: | Size: 459 KiB |
After Width: | Height: | Size: 490 KiB |
After Width: | Height: | Size: 516 KiB |
After Width: | Height: | Size: 232 KiB |
After Width: | Height: | Size: 354 KiB |
After Width: | Height: | Size: 338 KiB |
|
@ -0,0 +1 @@
|
|||
esptool.py --chip esp8266 --port /dev/cu.wchusbserial1410 --baud 115200 --before default_reset --after hard_reset write_flash 0x0 xxx.bin
|