Compare commits

...

59 Commits
esp1.0 ... main

Author SHA1 Message Date
Easy b8fa724a9a
Update README.md 2024-02-26 21:46:50 +08:00
Easy 4ce68572b9
Update README.md 2023-12-29 12:33:49 +08:00
Easy 16aec6c468
Update README.md 2023-12-29 12:32:56 +08:00
Easy bfadc52fef 更新证书 2023-12-16 13:15:19 +08:00
Easy d7f3279458
Update README.md
remove core image
2023-11-10 18:11:31 +08:00
Easy f5841403c7
Update docker-compose.serverless.yml 2023-11-10 18:08:46 +08:00
Easy be51654558
Update README.md 2023-10-13 10:54:37 +08:00
Easy aa9a2a9dfa
Update README.md 2023-10-13 10:54:12 +08:00
Easy 1a9ad9e351 update 2023-02-02 23:27:03 +08:00
Easy 19011afdd0 更新推送证书 2023-01-15 16:24:03 +08:00
Easy f9779893ec
Update README.md 2022-12-20 08:26:35 +08:00
Easy 09acc7955e
Merge pull request #159 from abgelehnt/main
添加Rust SDK引用
2022-11-23 20:12:34 +08:00
Chi 6053cf624a
添加Rust SDK引用 2022-11-23 19:25:29 +08:00
Easy d15892727a
Update README.md 2022-09-24 11:00:22 +08:00
Easy b5e6605328
隐藏自架版mysql端口,避免和宿主机冲突 2022-09-21 11:25:41 +08:00
Easy 8e5292acfb
Merge pull request #148 from Hext123/main
获取用户信息时增加了simple_token字段,存下来,token过期以后调用 /login/simple_token 重新获得新的认证token
2022-09-14 00:38:19 +08:00
hext ed61f49eca 获取用户信息时增加了simple_token字段,存下来,token过期以后调用 /login/simple_token 重新获得新的认证token 2022-09-14 00:14:19 +08:00
Easy 6927a94860 添加SimpleToken的API实现和文档 2022-09-06 01:02:53 +08:00
Easy e5d715e06d update mipush sdk 2022-07-01 20:26:43 +08:00
Easy 3fb92270f3 添加key_debug demo 文件 2022-06-27 14:47:51 +08:00
Easy 1ca157b32e save 2022-06-27 14:47:34 +08:00
Easy fd901b04c8 添加key_debug demo文件 2022-06-27 14:47:07 +08:00
Easy c706d83e2f 添加用户禁用逻辑 2022-06-27 00:33:35 +08:00
Easy 034e93d3c7 add index 2022-06-27 00:05:17 +08:00
Easy a218d5c68d add mipush key 2022-06-21 22:29:33 +08:00
Easy 416cc716dd
Update README.md
添加浏览器插件链接
2022-06-20 12:37:52 +08:00
Easy d504a91dc4
Create AppKeys.kt 2022-06-11 18:21:05 +08:00
Easy 20697f77dd
Merge pull request #127 from chengyuhui/markdown-fix
Android 修复Markdown渲染错误,去除Android Studio相关文件
2022-06-11 18:19:24 +08:00
Harry Cheng 4673418d95 android: Fix list updating for image and QR code 2022-06-11 13:11:33 +08:00
Harry Cheng c6caf87a0a android: Fix #117 2022-06-11 11:47:57 +08:00
Harry Cheng 2b89ee699e android: Fix debug signing and remove IDE files 2022-06-11 11:47:22 +08:00
EasyChen d7a0c95382 remove subtitle 2022-05-27 19:04:57 +08:00
Easy 2f461cf8c6
Update README.md 2022-05-21 14:38:26 +08:00
Easy b874100b17
Update 安装文档.md 2022-05-21 12:24:22 +08:00
Easy a0d07f4362
Update 安装文档.md 2022-05-21 12:21:40 +08:00
Easy c2283e50f9
Update 安装文档.md 2022-05-21 12:06:43 +08:00
Easy 4c8b70310e
Update 安装文档.md 2022-05-21 00:19:21 +08:00
Easy 57e467b2e3
Update vhost.conf 2022-05-21 00:13:15 +08:00
Easy 6b097973c2
Update Dockerfile 2022-05-21 00:12:22 +08:00
Easy af112c230c
Update README.md 2022-05-12 14:42:13 +08:00
Easy 3c019e61f8
添加社区贡献的后端实现链接 2022-05-02 21:33:39 +08:00
Easy 914bd7ac1a 修改测试固件效果 2022-04-25 22:24:26 +08:00
Easy 3878706c9f change deeresp default text scale 2022-04-22 17:58:45 +08:00
Easy 628b67191d 添加烧录脚本 2022-04-22 14:12:30 +08:00
Easy fe15fa7593 添加测试用代码 2022-04-22 13:57:14 +08:00
Easy 2ea7fa6c22 更新文字说明 2022-04-22 01:59:18 +08:00
Easy ed3c753f6b 更新IOT文件 2022-04-22 01:54:15 +08:00
Easy 38162f0cbe
Merge pull request #106 from Hext123/main
删除多余图标; 优化逻辑; 添加日志
2022-04-20 00:33:45 +08:00
hext 65e2e729fc 删除多余图标; 优化逻辑; 添加日志 2022-04-20 00:27:26 +08:00
Easy 05d051dc8b
Update Helpers.php 2022-04-19 19:53:56 +08:00
Easy 411f768156
add mutable_content flag to ios push 2022-04-19 19:16:36 +08:00
Easy 6d2b30901b
Merge pull request #105 from Hext123/main
新增功能: 桌面小部件; 一键清空全部消息.
2022-04-19 00:38:25 +08:00
hext c3d0cd13f5 新增功能: 桌面小部件, 展示最近消息, 支持收到推送自动刷新;
新增功能: 1分钟内删除两条消息, 会提示是否一键清空全部消息.
2022-04-19 00:35:56 +08:00
Easy a685433be3
Update 调试文档.md 2022-04-14 10:43:56 +08:00
Easy 0808c2257c 修正时间显示(+0前缀) 2022-04-07 23:41:53 +08:00
Easy 4fbf408eae add screen rotation 2022-04-07 19:00:38 +08:00
Easy 91c2d64295 字体配置 2022-04-07 18:15:52 +08:00
Easy 828f6f5454 添加时钟和配置保存 2022-04-07 16:01:31 +08:00
Easy 331daec61a 添加时钟 2022-04-06 13:02:25 +08:00
94 changed files with 2255 additions and 453 deletions

View File

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

54
android/.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
storeFile=/Users/easy/Code/pushdeer-andorid/com.pushdeer.com
storePassword=pushdeer.com
keyAlias=pushdeer
keyPassword=pushdeer.com

View File

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

View File

@ -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();

View File

@ -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();

View File

@ -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');
});
}
}

View File

@ -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');
});
}
}

View File

@ -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');
// 删除消息

View File

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

View File

@ -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) 接口获得。

View File

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

View File

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

View File

@ -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.crtserver.key然后去掉下一行的注释
# ADD ssl /app/ssl
EXPOSE 80

View File

@ -24,6 +24,8 @@
Order allow,deny
allow from all
</Directory>
# 配置 https
# 去掉下边三行的注释
#SSLEngine on
#SSLCertificateFile /app/ssl/server.crt
#SSLCertificateKeyFile /app/ssl/server.key

View File

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

View File

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

View File

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

View File

@ -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 不支持的库

View File

@ -47,6 +47,6 @@ SPEC CHECKSUMS:
WechatOpenSDK: 6a4d1436c15b3b5fe2a0bd383f3046010186da44
WoodPeckeriOS: 12ec7f38c695e51cd94a476228888dfe85d9d916
PODFILE CHECKSUM: 1b349626994062a8291e3db07d3dbf087894c4d2
PODFILE CHECKSUM: 42e3d8abd976589c1043ff9f9e864c275a490160
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -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" : {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]? {

View File

@ -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)
// ,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

@ -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: ""),
]

View File

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

View File

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

View File

@ -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规范的就行内存需要4M32Mbits更大更好就能放中文字库了我选的是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.ino2000字中文字库手动填写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/3deeresp + 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/3deeresp + 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
```
#### 方案4NodeMCU + 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进行测试

View File

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

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

1
iot/upload.sh Normal file
View File

@ -0,0 +1 @@
esptool.py --chip esp8266 --port /dev/cu.wchusbserial1410 --baud 115200 --before default_reset --after hard_reset write_flash 0x0 xxx.bin

Binary file not shown.

Binary file not shown.