mirror of
https://github.com/hectorqin/reader.git
synced 2026-11-10 21:11:08 +08:00
chore: drop CNB config; Android package is com.htmake.readerpro
- Remove .cnb.yml and CNB references in the Dockerfile and Compose file; the Compose default image is hectorqin/reader-pro:latest. - Rename the Android applicationId/namespace and Kotlin package from cool.cnb.reader to com.htmake.readerpro (shortcuts, ProGuard, shell check). Installs under the old package are a different app and are not migrated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
32595c87a5
commit
415a9f5dd3
35 files changed
+74
-326
No files matched your search
@@ -1,250 +0,0 @@
|
||||
# CI:服务端(Node/TypeScript)、客户端(Web/TypeScript)、Android 壳(Kotlin)。
|
||||
#
|
||||
# 每个任务都用 ifModify 限定到自己那部分,避免改一行 README 就把三条流水线
|
||||
# 全跑一遍。
|
||||
|
||||
# 服务端与客户端都是 Node 生态,共用同一个镜像锚点。
|
||||
.node-env: &node-env
|
||||
docker:
|
||||
image: node:24
|
||||
|
||||
.server-check: &server-check
|
||||
- name: install
|
||||
script: cd server && npm ci
|
||||
- name: typecheck
|
||||
script: cd server && npm run typecheck
|
||||
- name: test
|
||||
script: cd server && npm test
|
||||
- name: build
|
||||
script: cd server && npm run build
|
||||
|
||||
.client-check: &client-check
|
||||
- name: install
|
||||
script: cd web && npm ci
|
||||
- name: typecheck
|
||||
script: cd web && npm run typecheck
|
||||
- name: test
|
||||
script: cd web && npm test
|
||||
- name: build
|
||||
script: cd web && npm run build
|
||||
|
||||
# UI 评审:把真实产物在真实 Chromium(390×844)里逐屏渲染、截图并测量。
|
||||
#
|
||||
# 和 `client-check` 分开,因为两者回答的问题不同:那里问「功能对不对」,
|
||||
# 这里问「看起来对不对」—— 面板是不是盖住了整页、底部页数和可翻次数是不是
|
||||
# 一个数、状态提示出现时正文会不会被推下去。这些都能在功能全绿的同时是错的,
|
||||
# 而它们的形态只有截图和量出来的数字能发现(见 docs/ui.md §8)。
|
||||
#
|
||||
# 截图作为制品留存,所以一次失败的评审可以被人看到,而不是只看到一行报错。
|
||||
.ui-review: &ui-review
|
||||
docker:
|
||||
# 镜像里带 Chromium。评审工具只用 CDP 协议驱动浏览器(见
|
||||
# `web/tools/ui-review/cdp.mjs`),所以这里只要一个能跑的 Chromium,
|
||||
# 不需要 playwright 的运行时依赖。
|
||||
#
|
||||
# `/usr/local/bin/chromium` 是镜像自己提供的路径之一,工具会按
|
||||
# CHROME_PATH → chromium → chromium-browser → google-chrome 的顺序找,
|
||||
# 找不到就直接报错,不会静默跳过整轮评审。
|
||||
image: mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
stages:
|
||||
- name: install
|
||||
script: cd web && npm ci
|
||||
# 生产产物,不是 dev server:被评审的必须是用户真正拿到的那份。
|
||||
- name: build
|
||||
script: cd web && npm run build
|
||||
- name: ui-review
|
||||
script: cd web && npm run ui:review:only
|
||||
- name: archive-screenshots
|
||||
script: |
|
||||
echo "UI 评审产物:artifacts/ui-review/react-app/"
|
||||
ls -la artifacts/ui-review/react-app/
|
||||
|
||||
main:
|
||||
push:
|
||||
- name: server-ci
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- server/**
|
||||
stages:
|
||||
- *server-check
|
||||
failStages:
|
||||
- name: report-failure
|
||||
script: echo "服务端 CI 失败:$CNB_BUILD_FAILED_MSG"
|
||||
|
||||
- name: client-ci
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- web/**
|
||||
stages:
|
||||
- *client-check
|
||||
|
||||
# UI 评审只在界面相关文件变化时跑:它要装 Chromium、渲染八屏、量尺寸,
|
||||
# 比 client-ci 慢一个量级,而它验证的每一条规则都只被样式与组件改动影响。
|
||||
- name: client-ui-review
|
||||
<<: *ui-review
|
||||
ifModify:
|
||||
- web/src/ui/**
|
||||
- web/src/styles/**
|
||||
- docs/ui.md
|
||||
- web/tools/ui-review/**
|
||||
- web/index.html
|
||||
failStages:
|
||||
- name: report-failure
|
||||
script: echo "客户端 CI 失败:$CNB_BUILD_FAILED_MSG"
|
||||
|
||||
# Android 壳。
|
||||
#
|
||||
# 先跑一个不依赖 JDK 的静态检查(秒级),再编译。原因是桥接契约的两半分别
|
||||
# 用 Kotlin 和 TypeScript 写,互相没法类型检查:客户端调用一个壳里没有的
|
||||
# 方法是最容易出、也最难在本地发现的错误。静态检查把它提前到这里。
|
||||
#
|
||||
# 触发条件包含 `web/src/formats/types.ts`:壳里分支的渲染模式词表来自它,
|
||||
# 改一个词就会让整条原生渲染通道静默失效,而 Kotlin 编译器不会知道。
|
||||
# 镜像走 Docker Hub 命名空间,CNB 会自动经 Docker Hub 代理注入只读令牌。
|
||||
#
|
||||
# 这里曾写 `androidsdk/android-35:latest`,而 Docker Hub 的 `androidsdk`
|
||||
# 命名空间**没有**这个仓库 —— 它只发布到 `android-31`(`android-33` 及以上
|
||||
# 都是 404)。仓库不存在时 Docker Hub 返回 `pull access denied / repository
|
||||
# does not exist or may require 'docker login'`,daemon 会重试一次,然后
|
||||
# prepare 阶段直接失败:`docker pull failed: 任务执行失败,返回码:1`。整条
|
||||
# 流水线还没进入任何 stage 就红了,所以 `shell-contract`/`assemble` 全是
|
||||
# skipped —— 失败信息指向 `docker pull`,但真正的问题是镜像名指向了一个
|
||||
# 不存在的仓库,与令牌、网络、代理都无关。
|
||||
#
|
||||
# 改用 `mobiledevops/android-sdk-image`:Docker Hub 上真实存在且在维护的
|
||||
# 社区镜像(2026-07 仍有更新,多架构 amd64/arm64)。它的 `packages.txt` 含
|
||||
# `platforms;android-35` 与 `build-tools;35.0.1`,正好匹配本项目的
|
||||
# compileSdk/targetSdk=35;基础镜像是 ubuntu:24.04 + openjdk-21(满足
|
||||
# Gradle 8.11 与 AGP 8.7 对 JDK 17+ 的要求),并已预接受 SDK 许可,
|
||||
# assemble 不需要再联网下载平台组件。
|
||||
#
|
||||
# 用 `latest` 而不是 `36.1.0` 之类的构建工具版本号:这个仓库的 tag 是按
|
||||
# build-tools 版本命名的,`latest` 始终指向包含 android-35 的最新一份,
|
||||
# 写死版本反而会在下一次平台升级后被遗漏。若要完全可复现可改钉具体 tag。
|
||||
- name: android-ci
|
||||
docker:
|
||||
image: mobiledevops/android-sdk-image:latest
|
||||
ifModify:
|
||||
- android/**
|
||||
- web/src/android-bridge.d.ts
|
||||
- web/src/formats/types.ts
|
||||
- web/src/ui/native-page.ts
|
||||
stages:
|
||||
- name: shell-contract
|
||||
script: sh android/scripts/check-shell.sh
|
||||
- name: assemble
|
||||
script: cd android && ./gradlew --no-daemon :app:assembleDebug
|
||||
failStages:
|
||||
- name: report-failure
|
||||
script: echo "Android 构建失败:$CNB_BUILD_FAILED_MSG"
|
||||
|
||||
# 客户端产物必须能被壳打包。这条单独跑,因为 `client-ci` 只证明 H5 能构建,
|
||||
# 不证明 assets 里有东西——而一个空的 assets 目录会产出一个启动即白屏的 APK。
|
||||
- name: web-assets-ci
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- web/**
|
||||
# 触发条件包含脚本自身与 assets 目录布局:改这段逻辑、或改 Gradle 里
|
||||
# `assets.srcDirs` 指到哪,本来不会跑任何验证,而它坏掉的形态是 APK 里
|
||||
# 没有客户端、启动即白屏 —— 只有跑一遍才知道。
|
||||
- android/scripts/build-web-assets.sh
|
||||
- android/scripts/verify-web-assets.sh
|
||||
- android/app/build.gradle.kts
|
||||
- android/app/src/main/assets/**
|
||||
stages:
|
||||
- name: stage-for-shell
|
||||
script: sh android/scripts/build-web-assets.sh
|
||||
# 只证明脚本写出了 assets 还不够,还要证明「写出来的东西真的会被打进 APK」。
|
||||
# 脚本只知道它写到哪,Gradle 只知道哪些目录算 assets,两者之间没有任何
|
||||
# 东西把它们连起来 —— 这两条路径此前就漂移过。
|
||||
- name: verify-assets-are-packaged
|
||||
script: sh android/scripts/verify-web-assets.sh
|
||||
|
||||
# 发布镜像到制品库 `docker.cnb.cool/hectorqin/reader`。
|
||||
#
|
||||
# 只在 `server/`、`web/` 或构建文件变更时跑:镜像里只有这两部分。文档改一行
|
||||
# 也重新发布一个镜像,会让制品库里堆满内容完全相同的标签。
|
||||
#
|
||||
# 为什么用 CNB 官方的 `services: docker`,而不是 `docker.build` + kaniko
|
||||
# 插件任务:`docker.build` 的产物只活在 runner 的本地 dockerd 里,而平台
|
||||
# 起插件任务容器走的是 `docker pull <image>`,不查本地镜像列表——
|
||||
# `docker.build` 那条路要求 `image:` 写成与 `dockerfile` 同名的裸名字或
|
||||
# 缓存仓库地址,靠平台去跟已构建的镜像做匹配。这条流水线因此在 `main` 上
|
||||
# 连错八次,报错覆盖了 `pull access denied`(被当成 Docker Hub 官方镜像)
|
||||
# 与 `invalid reference format`(名字里有大写或非法字符)两类;最后是靠把
|
||||
# `image:` 指向 `dockerfile-caches` 的完整仓库地址跑通的,代价是那次匹配
|
||||
# 依赖把一个**缓存 digest** 写死进流水线。
|
||||
#
|
||||
# 换成 dind 之后整条链路上只剩标准 Docker:
|
||||
# - 不再依赖平台对 `image:` 的匹配约定,也就不需要把缓存 digest 当镜像名;
|
||||
# - 没有 kaniko,就不必为了 `COPY server/...` 保住大小写而绕开工作区,
|
||||
# `GIT_CLONE_PATH` 那个补丁连同它带来的 `--context-sub-path` 需求一起没了;
|
||||
# - 也不需要单独 `docker login`,CNB 起 dind 服务时会自动登录制品库,
|
||||
# 用的是同一次构建的临时令牌,权限天然对齐。
|
||||
# 之前维护的根目录 `Dockerfile` / `kaniko-plugin.sh` 随之删除。
|
||||
- name: docker-image
|
||||
services:
|
||||
- docker
|
||||
ifModify:
|
||||
- server/**
|
||||
- web/**
|
||||
- server/Dockerfile
|
||||
- .dockerignore
|
||||
# 触发条件包含自身,否则改完构建逻辑没法验证。
|
||||
- .cnb.yml
|
||||
stages:
|
||||
- name: docker build and push
|
||||
# 镜像路径按「同名制品」规则走,即 `docker.cnb.cool/hectorqin/reader`,
|
||||
# 与 README、docker-compose.yml 里引用的地址一致。用
|
||||
# `CNB_REPO_SLUG_LOWERCASE` 而不是写死 `hectorqin/reader`:Docker 要求
|
||||
# 仓库名全小写,而 CNB 的仓库路径本身可能带大写。
|
||||
#
|
||||
# 构建上下文是仓库根,`server/Dockerfile` 里那句 `COPY web/...` 依赖
|
||||
# 这一点(客户端产物要打进服务端镜像)。`services: docker` 下的任务
|
||||
# 工作目录就是仓库根,不需要额外指定 `-f`/`--context`。
|
||||
#
|
||||
# 两个标签:分支名给「跟 main 走」的部署用,短 commit 给需要回滚到
|
||||
# 某个确切版本的情况用。`main` 分支上两者分别是 `main` 和当次提交。
|
||||
# 逐个 `docker push` 而不是 `--all-tags`:文档里的写法就是显式推标签,
|
||||
# 不想把这条流水线绑在一个没被文档覆盖的 flag 上。
|
||||
script: |
|
||||
set -eu
|
||||
image="$CNB_DOCKER_REGISTRY/$CNB_REPO_SLUG_LOWERCASE"
|
||||
docker build -t "$image:$CNB_BRANCH" -t "$image:$CNB_COMMIT_SHORT" \
|
||||
-f server/Dockerfile .
|
||||
docker push "$image:$CNB_BRANCH"
|
||||
docker push "$image:$CNB_COMMIT_SHORT"
|
||||
failStages:
|
||||
- name: report-failure
|
||||
script: echo "镜像发布失败:$CNB_BUILD_FAILED_MSG"
|
||||
|
||||
"**":
|
||||
pull_request:
|
||||
- name: server-checks
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- server/**
|
||||
stages:
|
||||
- *server-check
|
||||
|
||||
- name: client-checks
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- web/**
|
||||
stages:
|
||||
- *client-check
|
||||
|
||||
# PR 阶段只跑不依赖 JDK/SDK 的那半(秒级)。桥接契约断裂与渲染模式词表漂移
|
||||
# 是最容易出、最难在本地发现的错误 —— 两半分别用 Kotlin 和 TypeScript 写,
|
||||
# 互相没法类型检查 —— 而它们恰好都不需要编译就能查出来。完整 assembleDebug
|
||||
# 仍只在 main 的 push 上跑,PR 不该为一个 SDK 镜像等几分钟。
|
||||
- name: android-contract-checks
|
||||
<<: *node-env
|
||||
ifModify:
|
||||
- android/**
|
||||
- web/src/android-bridge.d.ts
|
||||
- web/src/formats/types.ts
|
||||
- web/src/ui/native-page.ts
|
||||
stages:
|
||||
- name: shell-contract
|
||||
script: sh android/scripts/check-shell.sh
|
||||
@@ -4,11 +4,11 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "cool.cnb.reader"
|
||||
namespace = "com.htmake.readerpro"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "cool.cnb.reader"
|
||||
applicationId = "com.htmake.readerpro"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
|
||||
Vendored
+1
-1
@@ -2,7 +2,7 @@
|
||||
# annotation. R8 cannot see those call sites, so without this the release build
|
||||
# would strip them and the client would silently lose connectivity reporting and
|
||||
# the device label.
|
||||
-keepclassmembers class cool.cnb.reader.bridge.ReaderBridge {
|
||||
-keepclassmembers class com.htmake.readerpro.bridge.ReaderBridge {
|
||||
public *;
|
||||
}
|
||||
-keepattributes JavascriptInterface
|
||||
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader
|
||||
package com.htmake.readerpro
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
@@ -12,11 +12,11 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import android.widget.FrameLayout
|
||||
import cool.cnb.reader.bridge.ConnectivityMonitor
|
||||
import cool.cnb.reader.bridge.ReaderBridge
|
||||
import cool.cnb.reader.bridge.SpeechBridge
|
||||
import cool.cnb.reader.web.NativePageView
|
||||
import cool.cnb.reader.web.WebHost
|
||||
import com.htmake.readerpro.bridge.ConnectivityMonitor
|
||||
import com.htmake.readerpro.bridge.ReaderBridge
|
||||
import com.htmake.readerpro.bridge.SpeechBridge
|
||||
import com.htmake.readerpro.web.NativePageView
|
||||
import com.htmake.readerpro.web.WebHost
|
||||
|
||||
/**
|
||||
* The only activity.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.bridge
|
||||
package com.htmake.readerpro.bridge
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.bridge
|
||||
package com.htmake.readerpro.bridge
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
@@ -7,7 +7,7 @@ import android.os.Looper
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import cool.cnb.reader.web.NativePageView
|
||||
import com.htmake.readerpro.web.NativePageView
|
||||
import org.json.JSONObject
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -68,7 +68,7 @@ class ReaderBridge(
|
||||
* the web layer decides when to pay that, by calling `init()` when the reader
|
||||
* first opens the朗读 settings rather than on every app start.
|
||||
*/
|
||||
private val speechBridge = SpeechBridge(context, webView) { cool.cnb.reader.media.MediaAudioCoordinator.pauseMedia?.invoke() }
|
||||
private val speechBridge = SpeechBridge(context, webView) { com.htmake.readerpro.media.MediaAudioCoordinator.pauseMedia?.invoke() }
|
||||
|
||||
/**
|
||||
* Runs a UI-thread block and waits for its result.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.bridge
|
||||
package com.htmake.readerpro.bridge
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
/** Called only on the main looper. Neither subsystem needs to know the other's implementation. */
|
||||
object MediaAudioCoordinator {
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
@@ -18,7 +18,7 @@ import androidx.media3.session.SessionCommand
|
||||
import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import cool.cnb.reader.web.WebHost
|
||||
import com.htmake.readerpro.web.WebHost
|
||||
import java.net.URI
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
/** Launcher shortcuts select fixed local routes, never caller-provided URLs. */
|
||||
object MediaLaunchRoute {
|
||||
fun fragmentForAction(action: String?): String? = when (action) {
|
||||
"com.htmake.readerpro.OPEN_VIDEO" -> "#/media/video"
|
||||
"com.htmake.readerpro.OPEN_MUSIC" -> "#/media/music"
|
||||
"com.htmake.readerpro.OPEN_AUDIOBOOK" -> "#/media/audiobook"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
@@ -19,7 +19,7 @@ import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import cool.cnb.reader.MainActivity
|
||||
import com.htmake.readerpro.MainActivity
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URI
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import java.net.URI
|
||||
import org.json.JSONArray
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import android.content.Context
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.app.PictureInPictureParams
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import androidx.media3.common.ForwardingPlayer
|
||||
import androidx.media3.common.Player
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
/** Main-thread ownership of subtitle UI callbacks; independent of player/network lifetime. */
|
||||
internal class SubtitleRequestGate {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
/** Android restricts the PiP aspect ratio to approximately 1:2.39 … 2.39:1. */
|
||||
internal object VideoPipPolicy {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.web
|
||||
package com.htmake.readerpro.web
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.web
|
||||
package com.htmake.readerpro.web
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
@@ -13,12 +13,12 @@ import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import cool.cnb.reader.BuildConfig
|
||||
import cool.cnb.reader.media.MediaLaunchRoute
|
||||
import cool.cnb.reader.bridge.ReaderBridge
|
||||
import cool.cnb.reader.bridge.SpeechBridge
|
||||
import cool.cnb.reader.media.MediaBridge
|
||||
import cool.cnb.reader.media.MediaAudioCoordinator
|
||||
import com.htmake.readerpro.BuildConfig
|
||||
import com.htmake.readerpro.media.MediaLaunchRoute
|
||||
import com.htmake.readerpro.bridge.ReaderBridge
|
||||
import com.htmake.readerpro.bridge.SpeechBridge
|
||||
import com.htmake.readerpro.media.MediaBridge
|
||||
import com.htmake.readerpro.media.MediaAudioCoordinator
|
||||
|
||||
/**
|
||||
* Configures the WebView and serves the bundled client to it.
|
||||
@@ -1,11 +0,0 @@
|
||||
package cool.cnb.reader.media
|
||||
|
||||
/** Launcher shortcuts select fixed local routes, never caller-provided URLs. */
|
||||
object MediaLaunchRoute {
|
||||
fun fragmentForAction(action: String?): String? = when (action) {
|
||||
"cool.cnb.reader.OPEN_VIDEO" -> "#/media/video"
|
||||
"cool.cnb.reader.OPEN_MUSIC" -> "#/media/music"
|
||||
"cool.cnb.reader.OPEN_AUDIOBOOK" -> "#/media/audiobook"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut android:shortcutId="media_video" android:enabled="true" android:icon="@mipmap/ic_launcher" android:shortcutShortLabel="@string/shortcut_video">
|
||||
<intent android:action="cool.cnb.reader.OPEN_VIDEO" android:targetPackage="cool.cnb.reader" android:targetClass="cool.cnb.reader.MainActivity" />
|
||||
<intent android:action="com.htmake.readerpro.OPEN_VIDEO" android:targetPackage="com.htmake.readerpro" android:targetClass="com.htmake.readerpro.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut android:shortcutId="media_music" android:enabled="true" android:icon="@mipmap/ic_launcher" android:shortcutShortLabel="@string/shortcut_music">
|
||||
<intent android:action="cool.cnb.reader.OPEN_MUSIC" android:targetPackage="cool.cnb.reader" android:targetClass="cool.cnb.reader.MainActivity" />
|
||||
<intent android:action="com.htmake.readerpro.OPEN_MUSIC" android:targetPackage="com.htmake.readerpro" android:targetClass="com.htmake.readerpro.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut android:shortcutId="media_audiobook" android:enabled="true" android:icon="@mipmap/ic_launcher" android:shortcutShortLabel="@string/shortcut_audiobook">
|
||||
<intent android:action="cool.cnb.reader.OPEN_AUDIOBOOK" android:targetPackage="cool.cnb.reader" android:targetClass="cool.cnb.reader.MainActivity" />
|
||||
<intent android:action="com.htmake.readerpro.OPEN_AUDIOBOOK" android:targetPackage="com.htmake.readerpro" android:targetClass="com.htmake.readerpro.MainActivity" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import java.net.ServerSocket
|
||||
import java.net.InetAddress
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
@@ -6,9 +6,9 @@ import org.junit.Test
|
||||
|
||||
class MediaLaunchRouteTest {
|
||||
@Test fun shortcutsSelectOnlyFixedMediaRoutes() {
|
||||
assertEquals("#/media/video", MediaLaunchRoute.fragmentForAction("cool.cnb.reader.OPEN_VIDEO"))
|
||||
assertEquals("#/media/music", MediaLaunchRoute.fragmentForAction("cool.cnb.reader.OPEN_MUSIC"))
|
||||
assertEquals("#/media/audiobook", MediaLaunchRoute.fragmentForAction("cool.cnb.reader.OPEN_AUDIOBOOK"))
|
||||
assertEquals("#/media/video", MediaLaunchRoute.fragmentForAction("com.htmake.readerpro.OPEN_VIDEO"))
|
||||
assertEquals("#/media/music", MediaLaunchRoute.fragmentForAction("com.htmake.readerpro.OPEN_MUSIC"))
|
||||
assertEquals("#/media/audiobook", MediaLaunchRoute.fragmentForAction("com.htmake.readerpro.OPEN_AUDIOBOOK"))
|
||||
}
|
||||
|
||||
@Test fun normalLaunchAndUntrustedActionsDoNotRedirectReading() {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import java.net.ServerSocket
|
||||
import java.net.InetAddress
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.*
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.*
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import androidx.media3.common.Player
|
||||
import java.lang.reflect.Proxy
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cool.cnb.reader.media
|
||||
package com.htmake.readerpro.media
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -32,14 +32,14 @@ done
|
||||
# 2. Every import of our own code must resolve to a file that exists.
|
||||
note "==> checking project imports resolve"
|
||||
for file in $(find "$android_dir/app/src" -name '*.kt'); do
|
||||
sed -n 's/^import cool\.cnb\.reader\.\(.*\)$/\1/p' "$file" | while read -r symbol; do
|
||||
sed -n 's/^import com\.htmake\.readerpro\.\(.*\)$/\1/p' "$file" | while read -r symbol; do
|
||||
# `BuildConfig` is generated, and a class name is the last dot segment.
|
||||
case "$symbol" in
|
||||
BuildConfig) continue ;;
|
||||
esac
|
||||
path="$android_dir/app/src/main/java/cool/cnb/reader/$(printf '%s' "$symbol" | tr '.' '/').kt"
|
||||
path="$android_dir/app/src/main/java/com/htmake/readerpro/$(printf '%s' "$symbol" | tr '.' '/').kt"
|
||||
if [ ! -f "$path" ]; then
|
||||
err "$file imports cool.cnb.reader.$symbol but $path does not exist"
|
||||
err "$file imports com.htmake.readerpro.$symbol but $path does not exist"
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -55,8 +55,8 @@ done
|
||||
# methods against `ReaderBridge.kt` is what produced eleven false failures the
|
||||
# first time this was run.
|
||||
note "==> checking the JS bridge contract in both directions"
|
||||
bridge_file="$android_dir/app/src/main/java/cool/cnb/reader/bridge/ReaderBridge.kt"
|
||||
speech_file="$android_dir/app/src/main/java/cool/cnb/reader/bridge/SpeechBridge.kt"
|
||||
bridge_file="$android_dir/app/src/main/java/com/htmake/readerpro/bridge/ReaderBridge.kt"
|
||||
speech_file="$android_dir/app/src/main/java/com/htmake/readerpro/bridge/SpeechBridge.kt"
|
||||
types_file="$repo_dir/web/src/android-bridge.d.ts"
|
||||
|
||||
# A method is "implemented" if either bridge class exposes it to the page: a
|
||||
@@ -119,7 +119,7 @@ note "==> checking the render-mode vocabulary matches"
|
||||
web_modes=$(sed -n "s/^export type RenderMode = \(.*\);/\1/p" "$repo_dir/web/src/formats/types.ts" \
|
||||
| tr -d "'" | tr -d ' ' | tr '|' '\n' | sort -u)
|
||||
shell_modes=$(sed -n 's/.*mode != "\([a-z]*\)".*/\1/p' \
|
||||
"$android_dir/app/src/main/java/cool/cnb/reader/bridge/ReaderBridge.kt" | sort -u)
|
||||
"$android_dir/app/src/main/java/com/htmake/readerpro/bridge/ReaderBridge.kt" | sort -u)
|
||||
if [ -z "$web_modes" ]; then
|
||||
err "cannot read RenderMode from web/src/formats/types.ts"
|
||||
fi
|
||||
@@ -130,7 +130,7 @@ for mode in $shell_modes; do
|
||||
done
|
||||
# The shell draws images natively, so it must know the client's image vocabulary.
|
||||
decodable=$(sed -n '/private val DECODABLE = setOf(/,/)/p' \
|
||||
"$android_dir/app/src/main/java/cool/cnb/reader/web/NativePageView.kt" \
|
||||
"$android_dir/app/src/main/java/com/htmake/readerpro/web/NativePageView.kt" \
|
||||
| sed -n 's/.*"\(image\/[a-z]*\)".*/\1/p' | sort -u)
|
||||
for mediaType in "image/jpeg" "image/png" "image/webp"; do
|
||||
if ! printf '%s\n' "$decodable" | grep -qx "$mediaType"; then
|
||||
|
||||
+3
-3
@@ -6,9 +6,9 @@
|
||||
# automatically becomes the instance admin.
|
||||
services:
|
||||
reader:
|
||||
# CI publishes branch and commit tags; it does not publish :latest.
|
||||
# Pin READER_IMAGE to a published commit tag for repeatable upgrades.
|
||||
image: ${READER_IMAGE:-cnb.cool/hectorqin/reader:main}
|
||||
# Releases (v* tags) publish x.y.z, x.y, latest and sha-* tags to Docker Hub.
|
||||
# Pin READER_IMAGE to a release version for repeatable upgrades.
|
||||
image: ${READER_IMAGE:-hectorqin/reader-pro:latest}
|
||||
# To build from source instead of pulling the published image. The context is
|
||||
# the repository root because the image bakes in the H5 client built from
|
||||
# ./web as well as the server.
|
||||
|
||||
+3
-5
@@ -8,13 +8,11 @@
|
||||
# fails, so the client stage is gated behind a build arg and the runtime stage
|
||||
# tolerates an absent bundle. A server-only deployment stays possible.
|
||||
#
|
||||
# The base image is overridable because `docker.cnb.cool` is an authenticated
|
||||
# mirror: a plain `docker build` on a machine whose token has no pull rights on
|
||||
# it fails at the very first stage. Docker Hub is the default; pass the mirror
|
||||
# where it is reachable (CNB pipelines, or anywhere Hub is slow):
|
||||
# The base image is overridable so a build can pull Node from a registry mirror
|
||||
# where Docker Hub is slow or unreachable. Docker Hub is the default:
|
||||
#
|
||||
# docker build . -f server/Dockerfile \
|
||||
# --build-arg NODE_IMAGE=docker.cnb.cool/dockerhub/node:24-alpine
|
||||
# --build-arg NODE_IMAGE=<mirror>/node:24-alpine
|
||||
ARG NODE_IMAGE=node:24-alpine
|
||||
|
||||
FROM ${NODE_IMAGE} AS web
|
||||
|
||||
Reference in new issue
Block a user