Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions .github/workflows/release-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
jobs:
publish:
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
timeout-minutes: 10
timeout-minutes: 60
runs-on: ubuntu-latest

steps:
Expand All @@ -36,23 +36,49 @@ jobs:
- name: Find latest successful fork build for this PR
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
EXPECTED_SHA: ${{ github.event.pull_request.head.sha }}
run: |
mapfile -t PR_COMMITS < <(gh pr view "$PR_NUMBER" --json commits --jq '.commits[].oid')
test "${#PR_COMMITS[@]}" -gt 0 || { echo 'No PR commits found'; exit 1; }
test -n "$EXPECTED_SHA" || { echo 'No PR head commit found'; exit 1; }
echo "Waiting for the latest fork build of PR head $EXPECTED_SHA"

RUN_ID=""
while read -r ID SHA; do
for COMMIT in "${PR_COMMITS[@]}"; do
if [ "$SHA" = "$COMMIT" ]; then
RUN_ID="$ID"
break 2
BUILD_SUCCEEDED=false
for ATTEMPT in {1..12}; do
RUN_INFO=$(gh run list \
--workflow build-apk.yml \
--branch fork \
--commit "$EXPECTED_SHA" \
--limit 100 \
--json databaseId,headSha,status,conclusion,createdAt \
--jq 'sort_by(.createdAt) | last // empty')

if [ -n "$RUN_INFO" ]; then
RUN_ID=$(printf '%s' "$RUN_INFO" | jq -r '.databaseId')
STATUS=$(printf '%s' "$RUN_INFO" | jq -r '.status')
CONCLUSION=$(printf '%s' "$RUN_INFO" | jq -r '.conclusion // empty')
echo "Build $RUN_ID status=$STATUS conclusion=${CONCLUSION:-pending}"

if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
BUILD_SUCCEEDED=true
break
fi
echo "Latest fork build did not succeed: $CONCLUSION"
exit 1
fi
done
done < <(gh run list --workflow build-apk.yml --branch fork --status success --limit 100 --json databaseId,headSha --jq '.[] | [.databaseId, .headSha] | @tsv')
else
echo "No fork build found yet for $EXPECTED_SHA"
fi

echo 'Build is not finished; polling again in 5 minutes'
sleep 300
done

test -n "$RUN_ID" || { echo 'No successful fork build found for this PR'; exit 1; }
echo "Using successful fork build $RUN_ID"
test "$BUILD_SUCCEEDED" = true || {
echo 'Timed out waiting for the latest fork build'
exit 1
}
echo "Using fork build $RUN_ID for PR tip $EXPECTED_SHA"
gh run download "$RUN_ID" --dir downloaded-artifact
APK=$(find downloaded-artifact -type f -name '*.apk' | head -n 1)
test -n "$APK" || { echo 'APK not found in build artifact'; exit 1; }
Expand All @@ -74,18 +100,20 @@ jobs:
separators = r"\s,,、;;::|//\\"
label = re.compile(rf"(?:^|[{separators}])(?P<label>{'|'.join(categories)})(?=[{separators}]|$)")
trim = re.compile(rf"^[{separators}]+|[{separators}]+$")
subjects = subprocess.check_output(["git", "log", "--reverse", "--pretty=format:%s", log_range], text=True).splitlines()
for subject in subjects:
commits = subprocess.check_output(["git", "log", "--reverse", "--pretty=format:%H%x09%s", log_range], text=True).splitlines()
for commit in commits:
commit_sha, subject = commit.split("\t", 1)
short_sha = commit_sha[:7]
if subject.startswith("Merge pull request"): continue
matches = list(label.finditer(subject))
if not matches:
with open(f"{notes_dir}/其他", "a", encoding="utf-8") as f: f.write(f"- {subject}\n")
with open(f"{notes_dir}/其他", "a", encoding="utf-8") as f: f.write(f"- {subject} ({short_sha})\n")
continue
for i, match in enumerate(matches):
end = matches[i + 1].start() if i + 1 < len(matches) else len(subject)
content = trim.sub("", subject[match.end():end]).strip()
if content:
with open(f"{notes_dir}/{match.group('label')}", "a", encoding="utf-8") as f: f.write(f"- {content}\n")
with open(f"{notes_dir}/{match.group('label')}", "a", encoding="utf-8") as f: f.write(f"- {content} ({short_sha})\n")
PY

: > release-notes.md
Expand Down
43 changes: 39 additions & 4 deletions assets/proot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def proot_fix() -> None:
if not os.path.exists(target_file):
print(f"[proot_fix] 目标文件不存在: {target_file}")
return

try:
with open(target_file, 'r', encoding='utf-8') as f:
content = f.read()
Expand All @@ -39,10 +39,10 @@ def proot_fix() -> None:
r'\1 subprocess.Popen([executable, *argv[1:]], close_fds=True)\n'
r'\1 os._exit(0)\n'
r'\1else:\n'
r'\1 os.execv(executable, argv)'
r'\1 os.execv(executable, argv)\n'
)

new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
new_content, count = re.subn(pattern, replacement, content, flags=re.MULTILINE)

with open(target_file, 'w', encoding='utf-8') as f:
f.write(new_content)
Expand All @@ -53,4 +53,39 @@ def proot_fix() -> None:
print("[proot_fix] ✅ 修复完成")

except Exception as e:
print(f"[proot_fix] ❌ 修复失败: {e}")
print(f"[proot_fix] ❌ 修复失败: {e}")



"""
同时修改 python 日志级别。
避免输出大量来自 python 模块的 debug 日志到终端。
"""
target_file2 = "/root/AstrBot/astrbot/core/log.py"
try:
with open(target_file2, 'r', encoding='utf-8') as f:
content2 = f.read()

# 检查是否已修复
if not 'level="DEBUG"' in content2:
print("[proot_fix] python 日志级别已修改,跳过")
return

print("[proot_fix] 正在修改 python 日志级别...")

# 2. 替换日志配置部分
pattern2 = r'(level=")DEBUG(")'
replacement2 = r'\1INFO\2'

new_content2, count2 = re.subn(pattern2, replacement2, content2, flags=re.MULTILINE)

with open(target_file2, 'w', encoding='utf-8') as f:
f.write(new_content2)

if count2 < 1:
print(f"[proot_fix] ❌ 日志级别修改失败: 匹配数量为 {count}")
return
print("[proot_fix] ✅ 日志级别修改完成")

except Exception as e:
print(f"[proot_fix] ❌ 日志级别修改失败: {e}")
10 changes: 10 additions & 0 deletions lib/ui/controllers/terminal_controller.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:flutter_pty/flutter_pty.dart';
import 'package:get/get.dart';
Expand All @@ -19,6 +20,9 @@ import 'terminal_tab_manager.dart';

class HomeController extends GetxController {
static const _nativeWebViewChannel = MethodChannel('astrbot_native_webview');
static const double defaultTerminalFontSize = 12.0;
static const double minTerminalFontSize = 8.0;
static const double maxTerminalFontSize = 16.0;
// 终端标签页管理器
late final TerminalTabManager terminalTabManager;
// bool vsCodeStaring = false;
Expand All @@ -31,6 +35,8 @@ class HomeController extends GetxController {
final RxString napCatWebUiToken = ''.obs; // 存储 NapCat WebUI Token
final RxBool napCatWebUiEnabledRx = false.obs; // GetX 响应式变量用于导航栏更新
final RxBool showTerminalWhiteTextRx = false.obs; // GetX 响应式变量用于设置页更新
// 仅保存在当前运行期间,应用重启后恢复 xterm 默认字号
final RxDouble terminalFontSize = defaultTerminalFontSize.obs;
final RxList<Map<String, String>> customWebViews =
<Map<String, String>>[].obs; // 自定义 WebView 列表
final RxInt navigateToTab = (-1).obs; // 通知 WebViewPage 切换标签页
Expand Down Expand Up @@ -63,6 +69,10 @@ class HomeController extends GetxController {
double step = 14.0;
final RxString currentProgress = ''.obs;

void setTerminalFontSize(double size) {
terminalFontSize.value = size.clamp(minTerminalFontSize, maxTerminalFontSize).toDouble();
}

// 进度 +1
// Progress +1
void bumpProgress() {
Expand Down
4 changes: 1 addition & 3 deletions lib/ui/pages/terminal/terminal_keyboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ class _TerminalKeyboardState extends State<TerminalKeyboard> {
final bottom = MediaQuery.of(context).viewInsets.bottom;
final cs = Theme.of(context).colorScheme;

return AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
return Container(
margin: EdgeInsets.only(bottom: bottom),
decoration: BoxDecoration(
color: cs.surface,
Expand Down
93 changes: 92 additions & 1 deletion lib/ui/pages/terminal/terminal_tab_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,54 @@ class TerminalTabView extends StatefulWidget {
class _TerminalTabViewState extends State<TerminalTabView> {
final HomeController homeController = Get.find<HomeController>();
bool _isCopyDialogOpen = false;
final Map<int, Offset> _terminalPointers = <int, Offset>{};
final Map<TerminalTab, ScrollController> _terminalScrollControllers =
<TerminalTab, ScrollController>{};
double? _pinchStartDistance;
double? _pinchStartFontSize;
bool _isPinching = false;

static const double _fontSizeStep = 0.2;
static const double _distancePerFontSizeStep = 24.0;

ScrollController _scrollControllerFor(TerminalTab tab) {
return _terminalScrollControllers.putIfAbsent(
tab,
() => ScrollController(),
);
}

@override
void dispose() {
for (final controller in _terminalScrollControllers.values) {
controller.dispose();
}
super.dispose();
}

void _setTerminalFontSize(TerminalTab tab, double size) {
final scrollController = _scrollControllerFor(tab);
final oldFontSize = homeController.terminalFontSize.value;
final wasAtBottom = scrollController.hasClients &&
scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 1;
final oldOffset = scrollController.hasClients
? scrollController.position.pixels
: 0.0;

homeController.setTerminalFontSize(size);
final newFontSize = homeController.terminalFontSize.value;
if (!scrollController.hasClients || oldFontSize == newFontSize) return;

WidgetsBinding.instance.addPostFrameCallback((_) {
if (!scrollController.hasClients) return;
final maxScrollExtent = scrollController.position.maxScrollExtent;
final newOffset = wasAtBottom
? maxScrollExtent
: oldOffset * newFontSize / oldFontSize;
scrollController.jumpTo(newOffset.clamp(0.0, maxScrollExtent));
});
}

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -183,14 +231,57 @@ class _TerminalTabViewState extends State<TerminalTabView> {
children: [
Expanded(
child: Listener(
onPointerUp: (_) => _tryCopySelection(tab),
onPointerDown: (event) {
_terminalPointers[event.pointer] = event.localPosition;
if (_terminalPointers.length == 2) {
final points = _terminalPointers.values.toList();
_pinchStartDistance = (points[0] - points[1]).distance;
_pinchStartFontSize = homeController.terminalFontSize.value;
_isPinching = true;
}
},
onPointerMove: (event) {
if (!_terminalPointers.containsKey(event.pointer)) return;
_terminalPointers[event.pointer] = event.localPosition;
if (_terminalPointers.length != 2 ||
_pinchStartDistance == null ||
_pinchStartFontSize == null) {
return;
}

final points = _terminalPointers.values.toList();
final distance = (points[0] - points[1]).distance;
final distanceDelta = distance - _pinchStartDistance!;
final sizeDelta = (distanceDelta / _distancePerFontSizeStep).round() * _fontSizeStep;
_setTerminalFontSize(
tab,
_pinchStartFontSize! + sizeDelta,
);
},
onPointerUp: (event) {
_terminalPointers.remove(event.pointer);
final wasPinching = _isPinching;
if (_terminalPointers.isEmpty) {
_pinchStartDistance = null;
_pinchStartFontSize = null;
_isPinching = false;
}
if (!wasPinching) _tryCopySelection(tab);
},
onPointerCancel: (event) {
_terminalPointers.remove(event.pointer);
_pinchStartDistance = null;
_pinchStartFontSize = null;
},
child: ClipRect(
child: TerminalView(
tab.terminal,
controller: tab.controller,
readOnly: tab.type == TerminalTabType.fixed,
backgroundOpacity: 1,
theme: ManjaroTerminalTheme(),
scrollController: _scrollControllerFor(tab),
textStyle: TerminalStyle(fontSize: homeController.terminalFontSize.value),
),
),
),
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: astrbot_android
description: AstrBot Android App
publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.3+4
version: 1.0.4+5
environment:
sdk: ">=3.3.0 <4.0.0"

Expand Down
Loading