phone-lens 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  版本号三处联动:host `package.json` ↔ App `pubspec.yaml`(三段 = tag,build number 只增不减)↓ Release tag。App 版本号必须与 Gitee release tag 同步 bump,否则手机端会反复弹"发现新版本"。
4
4
 
5
+ ## 1.1.5(2026-11-07)
6
+
7
+ - **新增:裁剪页精细编辑(App 端)**。画笔/马赛克模式下双指捏合缩放 + 拖动平移整幅画面(1x~6x,以双指中心为锚点),放大后可做精细标注;右上角出现「回原比例」小按钮一键复位。缩放只是视图变换,笔画/裁剪框数据全部保持在图像空间,导出结果与屏幕所见严格一致,与裁剪、画笔、马赛克、旋转互不干扰(旋转/翻页自动复位缩放)。
8
+ - **修复:双指缩放残留误触笔画点**。双指落下时自动清扫 600ms 内未延伸的单点笔画(缩放前每根手指的落点都会留下一个小点),有意画的单指点不受影响。
9
+ - **改进:次级工具栏可收起**。画笔/马赛克激活后再次点击主工具栏按钮收起参数面板(工具保持激活),不再遮挡画面编辑区域;切换到其他工具时自动重新展开。
10
+ - **改进:笔画大小改滑条连续调节 + 六色画笔**。三档圆点按钮换成滑条(画笔宽度约图像宽度 0.4%~8%、马赛克块 1%~12% 拖动即见),新增红/蓝/黄/绿/黑/白色板,颜色贯通预览与最终导出(原固定红色)。
11
+ - **改进:上传画质四档 → 五档**:原图(原始尺寸)/ 高(2048px 超精细细节)/ 中(1280px,**默认改为中档**)/ 低(960px,约 130KB)/ 极低(320px 细节不可辨认);已保存的旧档位选择自动按新参数生效,无需重新设置。
12
+ - **修复:有标注内容时旋转直接清空无提示**——现在先弹「清除标注并旋转?」确认框,空画面旋转不弹窗。
13
+ - **修复:Gitee `releases/latest` 端点"最近编辑"陷阱**(双端三处:App `update_check`、host `apk-link`、host `update-check` 改用 releases 列表端点 + 客户端版本比较取最高 tag)。此前在 Gitee 网页上编辑/改名任何老发行版都会让 App 误判"已是最新版本"、二维码指向旧 APK,三处全部中招;现摆脱对该端点语义的依赖。
14
+
5
15
  ## 1.1.4(2026-11-03)
6
16
 
7
17
  - **新增:推流画质四档可调(App 端)**。设置 → 拍摄与上传 → 推流画质:极低(320P 8fps)/ 低(480P 10fps)/ 中(默认,640P 10fps,与升级前取景画质一致,跟随电脑端下发参数)/ 高(720P 10fps)。仅影响电脑端实时取景,不影响拍摄照片画质(照片仍由"上传画质"决定);切换下一帧即生效,无需重启预览或重连;帧协议不变(裸 JPEG 二进制),host 与 Web UI 零改动,新老版本双向兼容,无需版本门槛。推流画质存独立档位键,不被电脑端下发的预览参数在重连时覆盖("中"档则严格采用下发值)。
package/lib/dev.js CHANGED
@@ -399,7 +399,7 @@ const ERROR_CODES = {
399
399
 
400
400
  //#endregion
401
401
  //#region src/server/update-check.ts
402
- const GITEE_LATEST_API$1 = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases/latest";
402
+ const GITEE_RELEASES_API$1 = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases?per_page=10&direction=desc";
403
403
  /** A fresh result suppresses further API calls for 12h (host sits open all day). */
404
404
  const OK_TTL_MS = 12 * 60 * 60 * 1e3;
405
405
  /** A failed check retries after 30min instead of hammering the API per panel-open. */
@@ -431,15 +431,25 @@ function compareVersions(a, b) {
431
431
  }
432
432
  return 0;
433
433
  }
434
- /** Latest release tag from the trusted Gitee feed, or null on any failure. */
434
+ /** Highest release tag across the first page of the trusted Gitee feed, or
435
+ * null on any failure. Client-side max() over tag_name, so a stale-but-recently
436
+ * edited release can never shadow the real newest version. */
435
437
  async function fetchLatestGiteeTag() {
436
438
  try {
437
- const url = new URL(GITEE_LATEST_API$1);
439
+ const url = new URL(GITEE_RELEASES_API$1);
438
440
  if (url.protocol !== "https:" || url.host !== "gitee.com") return null;
439
441
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
440
442
  if (!res.ok) return null;
441
443
  const data = await res.json();
442
- return typeof data.tag_name === "string" && data.tag_name.trim() ? data.tag_name.trim() : null;
444
+ if (!Array.isArray(data)) return null;
445
+ let best = null;
446
+ for (const row of data) {
447
+ const tag = row?.tag_name;
448
+ if (typeof tag !== "string" || !tag.trim()) continue;
449
+ const clean = tag.trim();
450
+ if (best === null || compareVersions(clean, best) > 0) best = clean;
451
+ }
452
+ return best;
443
453
  } catch {
444
454
  return null;
445
455
  }
@@ -524,7 +534,7 @@ async function admitImage(input, attachments, fallbackDir) {
524
534
 
525
535
  //#endregion
526
536
  //#region src/server/apk-link.ts
527
- const GITEE_LATEST_API = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases/latest";
537
+ const GITEE_RELEASES_API = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases?per_page=10&direction=desc";
528
538
  const CACHE_TTL_MS = 10 * 60 * 1e3;
529
539
  const REQUEST_TIMEOUT_MS = 6e3;
530
540
  let cached = null;
@@ -545,15 +555,17 @@ async function fetchLatestGiteeApk() {
545
555
  const ctrl = new AbortController();
546
556
  const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
547
557
  try {
548
- const resp = await fetch(GITEE_LATEST_API, {
558
+ const resp = await fetch(GITEE_RELEASES_API, {
549
559
  signal: ctrl.signal,
550
560
  headers: { accept: "application/json" }
551
561
  });
552
562
  if (!resp.ok) return null;
553
563
  const data = await resp.json();
554
- const assets = data?.assets;
555
- if (!Array.isArray(assets)) return null;
556
- for (const asset of assets) {
564
+ if (!Array.isArray(data)) return null;
565
+ const rows = data.filter((row) => typeof row?.tag_name === "string" && !!row.tag_name.trim()).sort((a, b) => compareVersions(b.tag_name.trim(), a.tag_name.trim()));
566
+ const newest = rows[0];
567
+ if (!newest || !Array.isArray(newest.assets)) return null;
568
+ for (const asset of newest.assets) {
557
569
  const row = asset;
558
570
  if (typeof row.name === "string" && row.name.startsWith("app-release.apk") && isTrustedGiteeAsset(row.browser_download_url)) return row.browser_download_url;
559
571
  }
package/lib/index.js CHANGED
@@ -482,7 +482,7 @@ const ERROR_CODES = {
482
482
 
483
483
  //#endregion
484
484
  //#region src/server/update-check.ts
485
- const GITEE_LATEST_API$1 = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases/latest";
485
+ const GITEE_RELEASES_API$1 = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases?per_page=10&direction=desc";
486
486
  /** A fresh result suppresses further API calls for 12h (host sits open all day). */
487
487
  const OK_TTL_MS = 12 * 60 * 60 * 1e3;
488
488
  /** A failed check retries after 30min instead of hammering the API per panel-open. */
@@ -514,15 +514,25 @@ function compareVersions(a, b) {
514
514
  }
515
515
  return 0;
516
516
  }
517
- /** Latest release tag from the trusted Gitee feed, or null on any failure. */
517
+ /** Highest release tag across the first page of the trusted Gitee feed, or
518
+ * null on any failure. Client-side max() over tag_name, so a stale-but-recently
519
+ * edited release can never shadow the real newest version. */
518
520
  async function fetchLatestGiteeTag() {
519
521
  try {
520
- const url = new URL(GITEE_LATEST_API$1);
522
+ const url = new URL(GITEE_RELEASES_API$1);
521
523
  if (url.protocol !== "https:" || url.host !== "gitee.com") return null;
522
524
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
523
525
  if (!res.ok) return null;
524
526
  const data = await res.json();
525
- return typeof data.tag_name === "string" && data.tag_name.trim() ? data.tag_name.trim() : null;
527
+ if (!Array.isArray(data)) return null;
528
+ let best = null;
529
+ for (const row of data) {
530
+ const tag = row?.tag_name;
531
+ if (typeof tag !== "string" || !tag.trim()) continue;
532
+ const clean = tag.trim();
533
+ if (best === null || compareVersions(clean, best) > 0) best = clean;
534
+ }
535
+ return best;
526
536
  } catch {
527
537
  return null;
528
538
  }
@@ -607,7 +617,7 @@ async function admitImage(input, attachments, fallbackDir) {
607
617
 
608
618
  //#endregion
609
619
  //#region src/server/apk-link.ts
610
- const GITEE_LATEST_API = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases/latest";
620
+ const GITEE_RELEASES_API = "https://gitee.com/api/v5/repos/qianfengbingtang/phone-lens/releases?per_page=10&direction=desc";
611
621
  const CACHE_TTL_MS = 10 * 60 * 1e3;
612
622
  const REQUEST_TIMEOUT_MS = 6e3;
613
623
  let cached = null;
@@ -628,15 +638,17 @@ async function fetchLatestGiteeApk() {
628
638
  const ctrl = new AbortController();
629
639
  const timer = setTimeout(() => ctrl.abort(), REQUEST_TIMEOUT_MS);
630
640
  try {
631
- const resp = await fetch(GITEE_LATEST_API, {
641
+ const resp = await fetch(GITEE_RELEASES_API, {
632
642
  signal: ctrl.signal,
633
643
  headers: { accept: "application/json" }
634
644
  });
635
645
  if (!resp.ok) return null;
636
646
  const data = await resp.json();
637
- const assets = data?.assets;
638
- if (!Array.isArray(assets)) return null;
639
- for (const asset of assets) {
647
+ if (!Array.isArray(data)) return null;
648
+ const rows = data.filter((row) => typeof row?.tag_name === "string" && !!row.tag_name.trim()).sort((a, b) => compareVersions(b.tag_name.trim(), a.tag_name.trim()));
649
+ const newest = rows[0];
650
+ if (!newest || !Array.isArray(newest.assets)) return null;
651
+ for (const asset of newest.assets) {
640
652
  const row = asset;
641
653
  if (typeof row.name === "string" && row.name.startsWith("app-release.apk") && isTrustedGiteeAsset(row.browser_download_url)) return row.browser_download_url;
642
654
  }