Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da527b57af | |||
| 307f3c454a | |||
| b3057a72bb | |||
| 7a95af02b9 | |||
| 161d5dbcaf | |||
| 23b8260d87 | |||
| 9cce230f1f | |||
| 3185a0c66d | |||
| 759fdccc96 | |||
| 470c2eca39 | |||
| 52ac80a037 | |||
| 4f3bcf4e52 | |||
| 8b4ea9646d | |||
| a911f25a28 | |||
| 7b500dde68 | |||
| dc294c2eec | |||
| 425dd00208 | |||
| 5eaa8c2b49 | |||
| 6bbbba0343 | |||
| 2305f3d6d3 | |||
| e42e2dac79 | |||
| c876251950 | |||
| 04f3fadb62 | |||
| fd89f2a965 | |||
| 227e7e4ab9 | |||
| 7f6ca27e19 | |||
| 9f17517c20 | |||
| 8c1483da96 | |||
| a3c82260f2 | |||
| b94558069b | |||
| 849515fb0e | |||
| 4acdff0477 | |||
| 9d266f282c | |||
| 5e3a8f70f7 | |||
| cecdbd1b01 | |||
| 606b3aca90 | |||
| c418800664 | |||
| fe12e9ce33 | |||
| ebf60d4ce6 | |||
| 7773749c53 | |||
| 8eb287d32d | |||
| aa1dfe0e4b | |||
| d0b07f42de | |||
| b9fc411999 | |||
| 56c4caeaf3 | |||
| 012e71cc10 | |||
| 18f5c7093a | |||
| 103e7eb984 | |||
| d2131f1f9c | |||
| 6ba16654b1 | |||
| bc19fe5ef5 | |||
| 7ebe87e276 | |||
| 14d955951e | |||
| 621e34cc6a | |||
| 20b794122c | |||
| 1cad70e0b2 | |||
| c4c0cb3a7a | |||
| 0703c3aa07 | |||
| 999ee48e6b | |||
| 61d17ccbe2 | |||
| 882f74100e | |||
| 7e267072f3 | |||
| 1d0acc8f38 | |||
| df0aa6b9f9 | |||
| b8e2955ebe | |||
| dc0c6a0e09 | |||
| 5b044334ee | |||
| 3a96b19e0c | |||
| 31a8a2ba06 | |||
| c98b5df051 | |||
| a0e3b69ac5 | |||
| 5b8e815fe6 | |||
| a2523209b8 | |||
| f374e2ce3c | |||
| f2ef91036a | |||
| 4c70cd5f12 | |||
| 63ce949edc | |||
| 2080366d66 | |||
| 15e607dfc6 | |||
| 7ba0cec04f | |||
| 73a95e2c7a | |||
| 26c1cd4bcb | |||
| ec225e72bd | |||
| eca0f8a08f | |||
| 1b78c4b61c | |||
| 4fa894245c | |||
| 97f69f1a9c | |||
| 839a1627fd | |||
| 91b6620347 | |||
| e954bcc430 | |||
| 51a30184d7 | |||
| 267865c61d | |||
| c590135d68 |
@@ -0,0 +1,10 @@
|
||||
# Git LFS 配置 - iOS 大文件
|
||||
# Libbox.xcframework 二进制文件
|
||||
*.xcframework/**/*.framework/Versions/A/Libbox filter=lfs diff=lfs merge=lfs -text
|
||||
*.xcframework/**/*.framework/Libbox filter=lfs diff=lfs merge=lfs -text
|
||||
# 其他大型二进制文件
|
||||
*.a filter=lfs diff=lfs merge=lfs -text
|
||||
*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
*.framework/Versions/A/* filter=lfs diff=lfs merge=lfs -text
|
||||
iOS-SwiftUI-Code/Libbox.xcframework/**/*.framework/Versions/A/Libbox filter=lfs diff=lfs merge=lfs -text
|
||||
iOS-SwiftUI-Code/Libbox.xcframework/**/*.framework/Libbox filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,254 @@
|
||||
# CI/CD 配置检查报告
|
||||
|
||||
## ✅ 检查完成
|
||||
|
||||
已完成对 GitHub Actions CI/CD 配置文件的全面检查。
|
||||
|
||||
---
|
||||
|
||||
## 📁 已创建的文件
|
||||
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| build.yml | `.github/workflows/build.yml` | 主工作流配置文件 |
|
||||
| README.md | `.github/workflows/README.md` | 详细配置说明文档 |
|
||||
| QUICKSTART.md | `.github/workflows/QUICKSTART.md` | 快速入门指南 |
|
||||
| setup-secrets.sh | `.github/workflows/setup-secrets.sh` | macOS/Linux 配置脚本 |
|
||||
| setup-secrets.ps1 | `.github/workflows/setup-secrets.ps1` | Windows 配置脚本 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 已修复的问题
|
||||
|
||||
### 问题 1: iOS 签名身份 Secret 名称不一致
|
||||
- **位置**: build.yml 第 218 行
|
||||
- **问题**: 使用了 `IOS_SIGNING_identity` (小写 identity)
|
||||
- **修复**: 改为 `IOS_SIGNING_IDENTITY` (全大写,符合命名规范)
|
||||
|
||||
### 问题 2: 条件判断过于复杂
|
||||
- **位置**: 多处
|
||||
- **问题**: 使用了 `startsWith(github.ref, 'refs/tags/v') || github.event.inputs.release_type == 'release'` 复杂条件
|
||||
- **修复**: 简化为 `startsWith(github.ref, 'refs/tags/v')`,只在创建标签时触发 Release 构建
|
||||
|
||||
---
|
||||
|
||||
## ✅ 配置验证清单
|
||||
|
||||
### 工作流触发条件
|
||||
- ✅ Push 到 main 分支 - 触发构建
|
||||
- ✅ Push 到 develop 分支 - 触发 Debug 构建
|
||||
- ✅ Pull Request - 触发 Debug 构建
|
||||
- ✅ 创建版本标签 (v*) - 触发 Release 构建
|
||||
- ✅ 手动触发 - 可选择构建平台和类型
|
||||
|
||||
### Android 构建步骤
|
||||
- ✅ 检出代码和子模块
|
||||
- ✅ 安装 JDK 11
|
||||
- ✅ 安装 Go 1.23
|
||||
- ✅ 安装 Android SDK
|
||||
- ✅ 创建 local.properties
|
||||
- ✅ 配置签名密钥 (Release)
|
||||
- ✅ 构建 APK
|
||||
- ✅ 上传构建产物
|
||||
|
||||
### iOS 构建步骤
|
||||
- ✅ 检出代码和子模块
|
||||
- ✅ 安装 Xcode 15.0
|
||||
- ✅ 安装开发者证书 (Release)
|
||||
- ✅ 安装 Provisioning Profile (Release)
|
||||
- ✅ 解析 Swift Package 依赖
|
||||
- ✅ 构建 Debug 版本 (模拟器)
|
||||
- ✅ 构建并归档 Release 版本
|
||||
- ✅ 导出 IPA
|
||||
- ✅ 上传构建产物
|
||||
|
||||
### 发布步骤
|
||||
- ✅ 下载 Android APK
|
||||
- ✅ 下载 iOS IPA
|
||||
- ✅ 创建 GitHub Release
|
||||
- ✅ 上传文件到 Release
|
||||
- ✅ 上传到 TestFlight (Beta 和正式版)
|
||||
- ✅ 发送 Telegram 通知
|
||||
- ✅ 清理旧的构建产物
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 使用前必须配置的 Secrets
|
||||
|
||||
### Android 构建 (发布版本必需)
|
||||
|
||||
| Secret 名称 | 说明 | 如何获取 |
|
||||
|------------|------|---------|
|
||||
| `ANDROID_KEYSTORE_BASE64` | 签名密钥库的 Base64 编码 | 使用 base64 命令转换 |
|
||||
| `ANDROID_KEYSTORE_PASSWORD` | 密钥库密码 | 创建密钥库时设置 |
|
||||
| `ANDROID_KEY_ALIAS` | 密钥别名 | 创建密钥库时设置 |
|
||||
| `ANDROID_KEY_PASSWORD` | 密钥密码 | 创建密钥库时设置 |
|
||||
|
||||
**注意**: Debug 构建不需要配置这些 Secrets。
|
||||
|
||||
### iOS 构建 (发布版本必需)
|
||||
|
||||
| Secret 名称 | 说明 | 如何获取 |
|
||||
|------------|------|---------|
|
||||
| `IOS_CERTIFICATES_P12` | 开发者证书的 Base64 编码 | 从钥匙串导出 .p12 文件 |
|
||||
| `IOS_CERTIFICATES_PASSWORD` | 证书密码 | 导出时设置 |
|
||||
| `IOS_TEAM_ID` | Apple Team ID | Apple Developer 账号 |
|
||||
| `IOS_SIGNING_IDENTITY` | 签名身份 | 如: "Apple Distribution: Name (TEAM_ID)" |
|
||||
| `IOS_PROVISIONING_PROFILE` | Provisioning Profile 的 Base64 编码 | Apple Developer 网站下载 |
|
||||
|
||||
**注意**: Debug 构建不需要配置这些 Secrets。
|
||||
|
||||
### TestFlight 上传 (可选)
|
||||
|
||||
| Secret 名称 | 说明 | 如何获取 |
|
||||
|------------|------|---------|
|
||||
| `APP_STORE_CONNECT_ISSUER_ID` | Issuer ID | App Store Connect API Keys |
|
||||
| `APP_STORE_CONNECT_API_KEY_ID` | API Key ID | 创建 API Key 时获得 |
|
||||
| `APP_STORE_CONNECT_API_PRIVATE_KEY` | API 私钥 | 下载的 .p8 文件内容 |
|
||||
|
||||
### 通知 (可选)
|
||||
|
||||
| Secret 名称 | 说明 | 如何获取 |
|
||||
|------------|------|---------|
|
||||
| `TELEGRAM_BOT_TOKEN` | Bot Token | 通过 @BotFather 创建 |
|
||||
| `TELEGRAM_CHAT_ID` | Chat ID | 邀请 Bot 后获取 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始步骤
|
||||
|
||||
### 1. 配置 Secrets
|
||||
|
||||
**Windows 用户**:
|
||||
```powershell
|
||||
cd D:\Code\UUVPN\.github\workflows
|
||||
.\setup-secrets.ps1
|
||||
```
|
||||
|
||||
**macOS/Linux 用户**:
|
||||
```bash
|
||||
cd /path/to/UUVPN/.github/workflows
|
||||
chmod +x setup-secrets.sh
|
||||
./setup-secrets.sh
|
||||
```
|
||||
|
||||
### 2. 推送代码测试
|
||||
|
||||
```bash
|
||||
# 推送到 develop 分支触发 Debug 构建
|
||||
git checkout -b develop
|
||||
git push origin develop
|
||||
|
||||
# 查看 Actions 页面确认构建成功
|
||||
```
|
||||
|
||||
### 3. 创建发布版本
|
||||
|
||||
```bash
|
||||
# 创建 Alpha 版本
|
||||
git tag v1.0.0-alpha
|
||||
git push origin v1.0.0-alpha
|
||||
|
||||
# 创建 Beta 版本 (会自动上传到 TestFlight)
|
||||
git tag v1.0.0-beta
|
||||
git push origin v1.0.0-beta
|
||||
|
||||
# 创建正式版本
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 预期行为
|
||||
|
||||
### Debug 构建 (develop 分支或 PR)
|
||||
|
||||
**Android**:
|
||||
- ✅ 使用 Debug 签名
|
||||
- ✅ 构建 `app-meta-alpha-debug.apk`
|
||||
- ✅ 上传到 Artifacts (保留 30 天)
|
||||
|
||||
**iOS**:
|
||||
- ✅ 无签名
|
||||
- ✅ 构建模拟器版本
|
||||
- ✅ 上传 .app 文件到 Artifacts (保留 7 天)
|
||||
|
||||
### Release 构建 (创建标签)
|
||||
|
||||
**Android**:
|
||||
- ✅ 使用自定义签名密钥
|
||||
- ✅ 构建 Release APK
|
||||
- ✅ 重命名为 `UUVPN-Android-v1.0.0.apk`
|
||||
- ✅ 上传到 GitHub Release
|
||||
|
||||
**iOS**:
|
||||
- ✅ 使用开发者证书签名
|
||||
- ✅ 导出 IPA 文件
|
||||
- ✅ 重命名为 `UUVPN-iOS-v1.0.0.ipa`
|
||||
- ✅ 上传到 GitHub Release
|
||||
- ✅ Beta 和正式版上传到 TestFlight
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ 预计构建时间
|
||||
|
||||
| 平台 | Debug 构建 | Release 构建 |
|
||||
|------|-----------|-------------|
|
||||
| Android | 5-10 分钟 | 10-15 分钟 |
|
||||
| iOS | 15-20 分钟 | 20-30 分钟 |
|
||||
|
||||
**总计**: 完整构建约 25-45 分钟
|
||||
|
||||
---
|
||||
|
||||
## 💰 成本估算
|
||||
|
||||
GitHub Actions 免费账户每月 2000 分钟:
|
||||
|
||||
| 构建类型 | 消耗时间 | 每月可构建次数 |
|
||||
|---------|---------|--------------|
|
||||
| 仅 Android | ~10 分钟 | ~200 次 |
|
||||
| 仅 iOS | ~200 分钟 (按 10 倍计) | ~10 次 |
|
||||
| Android + iOS | ~210 分钟 | ~9 次 |
|
||||
|
||||
**建议**: 节省 macOS runner 时间,只在需要时构建 iOS 版本。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 下一步行动
|
||||
|
||||
1. ✅ **配置 Secrets**
|
||||
- 使用配置脚本或手动配置
|
||||
- 至少配置 Debug 构建所需的 Secrets
|
||||
|
||||
2. ✅ **测试 Debug 构建**
|
||||
- 推送代码到 develop 分支
|
||||
- 确认构建成功
|
||||
|
||||
3. ✅ **配置发布 Secrets**
|
||||
- 配置 Android 签名密钥
|
||||
- 配置 iOS 证书和 Profile
|
||||
|
||||
4. ✅ **创建第一个版本**
|
||||
- 创建标签触发 Release 构建
|
||||
- 验证 GitHub Release 创建成功
|
||||
|
||||
5. ✅ **配置 TestFlight** (可选)
|
||||
- 配置 App Store Connect API Keys
|
||||
- 验证自动上传到 TestFlight
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如遇到问题,请查看:
|
||||
|
||||
1. [GitHub Actions 日志](https://github.com/你的用户名/UUVPN/actions)
|
||||
2. [配置文档](.github/workflows/README.md)
|
||||
3. [快速入门](.github/workflows/QUICKSTART.md)
|
||||
4. [项目 Issues](https://github.com/nicolastinkl/UUVPN/issues)
|
||||
|
||||
---
|
||||
|
||||
**配置检查完成! 🎉**
|
||||
@@ -0,0 +1,363 @@
|
||||
# GitHub Actions CI/CD 快速入门指南
|
||||
|
||||
本指南帮助你快速配置和使用 UUVPN 的自动构建系统。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 5 分钟快速开始
|
||||
|
||||
### 第一步: 准备工作
|
||||
|
||||
确保你已拥有:
|
||||
|
||||
- ✅ GitHub 账号
|
||||
- ✅ UUVPN 代码仓库的写权限
|
||||
- ✅ Apple Developer 账号 (iOS 构建必需)
|
||||
- ✅ Android 签名密钥库 (Android Release 构建必需)
|
||||
|
||||
### 第二步: 安装 GitHub CLI
|
||||
|
||||
**Windows**:
|
||||
```powershell
|
||||
winget install GitHub.cli
|
||||
```
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
brew install gh
|
||||
```
|
||||
|
||||
**Linux**:
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
||||
sudo apt update
|
||||
sudo apt install gh
|
||||
```
|
||||
|
||||
### 第三步: 登录 GitHub
|
||||
|
||||
```bash
|
||||
gh auth login
|
||||
```
|
||||
|
||||
按照提示选择:
|
||||
- GitHub.com
|
||||
- HTTPS
|
||||
- 使用浏览器登录或粘贴 token
|
||||
|
||||
### 第四步: 配置 Secrets
|
||||
|
||||
#### 方法一: 使用自动配置脚本 (推荐)
|
||||
|
||||
**Windows**:
|
||||
```powershell
|
||||
cd D:\Code\UUVPN\.github\workflows
|
||||
.\setup-secrets.ps1
|
||||
```
|
||||
|
||||
**macOS/Linux**:
|
||||
```bash
|
||||
cd /path/to/UUVPN/.github/workflows
|
||||
chmod +x setup-secrets.sh
|
||||
./setup-secrets.sh
|
||||
```
|
||||
|
||||
#### 方法二: 手动配置
|
||||
|
||||
在 GitHub 仓库页面:
|
||||
|
||||
1. 进入 `Settings` → `Secrets and variables` → `Actions`
|
||||
2. 点击 `New repository secret`
|
||||
3. 添加所需的 secrets (详见下方)
|
||||
|
||||
### 第五步: 触发构建
|
||||
|
||||
```bash
|
||||
# 推送代码触发构建
|
||||
git push origin main
|
||||
|
||||
# 或创建标签触发发布
|
||||
git tag v1.0.0-alpha
|
||||
git push origin v1.0.0-alpha
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 必需的 Secrets 清单
|
||||
|
||||
### Android 构建
|
||||
|
||||
| Secret | 必需 | 说明 |
|
||||
|--------|------|------|
|
||||
| `ANDROID_KEYSTORE_BASE64` | ✅ | 签名密钥库的 Base64 编码 |
|
||||
| `ANDROID_KEYSTORE_PASSWORD` | ✅ | 密钥库密码 |
|
||||
| `ANDROID_KEY_ALIAS` | ✅ | 密钥别名 |
|
||||
| `ANDROID_KEY_PASSWORD` | ✅ | 密钥密码 |
|
||||
|
||||
### iOS 构建
|
||||
|
||||
| Secret | 必需 | 说明 |
|
||||
|--------|------|------|
|
||||
| `IOS_CERTIFICATES_P12` | ✅ | 开发者证书 Base64 编码 |
|
||||
| `IOS_CERTIFICATES_PASSWORD` | ✅ | 证书密码 |
|
||||
| `IOS_TEAM_ID` | ✅ | Apple Team ID |
|
||||
| `IOS_SIGNING_IDENTITY` | ✅ | 签名身份 |
|
||||
| `IOS_PROVISIONING_PROFILE` | ✅ | Provisioning Profile Base64 |
|
||||
|
||||
### TestFlight 上传 (可选)
|
||||
|
||||
| Secret | 必需 | 说明 |
|
||||
|--------|------|------|
|
||||
| `APP_STORE_CONNECT_ISSUER_ID` | ⭐ | App Store Connect Issuer ID |
|
||||
| `APP_STORE_CONNECT_API_KEY_ID` | ⭐ | API Key ID |
|
||||
| `APP_STORE_CONNECT_API_PRIVATE_KEY` | ⭐ | API 私钥 |
|
||||
|
||||
### 通知 (可选)
|
||||
|
||||
| Secret | 必需 | 说明 |
|
||||
|--------|------|------|
|
||||
| `TELEGRAM_BOT_TOKEN` | | Telegram Bot Token |
|
||||
| `TELEGRAM_CHAT_ID` | | Telegram Chat ID |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 详细配置步骤
|
||||
|
||||
### Android 签名密钥库
|
||||
|
||||
#### 1. 创建密钥库 (如果没有)
|
||||
|
||||
```bash
|
||||
keytool -genkey -v \
|
||||
-keystore release.keystore \
|
||||
-alias uuvpn \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000
|
||||
```
|
||||
|
||||
按提示输入:
|
||||
- 密钥库密码
|
||||
- 个人信息
|
||||
- 密钥密码
|
||||
|
||||
#### 2. 转换为 Base64
|
||||
|
||||
**macOS/Linux**:
|
||||
```bash
|
||||
base64 -i release.keystore -o release.keystore.base64
|
||||
```
|
||||
|
||||
**Windows PowerShell**:
|
||||
```powershell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) | Out-File release.keystore.base64
|
||||
```
|
||||
|
||||
#### 3. 添加到 GitHub Secrets
|
||||
|
||||
复制 `release.keystore.base64` 文件内容,添加为 `ANDROID_KEYSTORE_BASE64` secret。
|
||||
|
||||
### iOS 开发者证书
|
||||
|
||||
#### 1. 导出证书
|
||||
|
||||
1. 打开 "钥匙串访问" (Keychain Access)
|
||||
2. 找到 "Apple Distribution" 证书
|
||||
3. 右键 → 导出
|
||||
4. 选择 .p12 格式
|
||||
5. 设置密码
|
||||
|
||||
#### 2. 转换为 Base64
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
base64 -i certificate.p12 -o certificate.p12.base64
|
||||
```
|
||||
|
||||
#### 3. 下载 Provisioning Profile
|
||||
|
||||
1. 登录 [Apple Developer](https://developer.apple.com/)
|
||||
2. 进入 Certificates, Identifiers & Profiles
|
||||
3. 下载对应的 Provisioning Profile
|
||||
|
||||
#### 4. 转换为 Base64
|
||||
|
||||
```bash
|
||||
base64 -i uuvpn.mobileprovision -o uuvpn.mobileprovision.base64
|
||||
```
|
||||
|
||||
### App Store Connect API Key
|
||||
|
||||
#### 1. 创建 API Key
|
||||
|
||||
1. 登录 [App Store Connect](https://appstoreconnect.apple.com/)
|
||||
2. 进入 Users and Access → Keys
|
||||
3. 点击 "Generate API Key"
|
||||
4. 选择 "App Manager" 权限
|
||||
5. 下载 .p8 文件
|
||||
|
||||
#### 2. 记录信息
|
||||
|
||||
- **Issuer ID**: 在 Keys 页面顶部
|
||||
- **Key ID**: 创建后显示
|
||||
- **Private Key**: 下载的 .p8 文件内容
|
||||
|
||||
---
|
||||
|
||||
## 🎯 构建类型说明
|
||||
|
||||
### Debug 构建
|
||||
|
||||
**触发条件**:
|
||||
- Pull Request
|
||||
- 推送到 develop 分支
|
||||
|
||||
**特点**:
|
||||
- 无需签名配置
|
||||
- 快速构建
|
||||
- 不发布到 Release
|
||||
|
||||
### Release 构建
|
||||
|
||||
**触发条件**:
|
||||
- 推送到 main 分支
|
||||
- 创建版本标签
|
||||
|
||||
**特点**:
|
||||
- 需要签名配置
|
||||
- 自动发布到 GitHub Releases
|
||||
- 可选上传到 TestFlight
|
||||
|
||||
### 版本标签格式
|
||||
|
||||
```bash
|
||||
# Alpha 版本 (预发布)
|
||||
git tag v1.0.0-alpha
|
||||
git push origin v1.0.0-alpha
|
||||
|
||||
# Beta 版本 (预发布,上传 TestFlight)
|
||||
git tag v1.0.0-beta
|
||||
git push origin v1.0.0-beta
|
||||
|
||||
# 正式版本 (发布,上传 TestFlight)
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📥 下载构建产物
|
||||
|
||||
### 方法一: Actions Artifacts
|
||||
|
||||
1. 进入 GitHub 仓库
|
||||
2. 点击 "Actions" 标签
|
||||
3. 选择具体的 workflow 运行记录
|
||||
4. 在页面底部 "Artifacts" 区域下载
|
||||
|
||||
### 方法二: GitHub Releases
|
||||
|
||||
1. 进入 GitHub 仓库
|
||||
2. 点击 "Releases" 标签
|
||||
3. 找到对应版本
|
||||
4. 下载 APK 或 IPA 文件
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 故障排查
|
||||
|
||||
### 构建失败
|
||||
|
||||
1. **查看日志**:
|
||||
- 进入 Actions 页面
|
||||
- 点击失败的步骤
|
||||
- 查看详细错误信息
|
||||
|
||||
2. **常见错误**:
|
||||
- ❌ 签名错误: 检查 Secrets 配置
|
||||
- ❌ 证书过期: 更新证书和 Profile
|
||||
- ❌ 构建超时: 优化构建配置
|
||||
|
||||
### iOS 签名失败
|
||||
|
||||
检查清单:
|
||||
- [ ] 证书未过期
|
||||
- [ ] Provisioning Profile 包含正确 App ID
|
||||
- [ ] Team ID 正确
|
||||
- [ ] 证书和 Profile 匹配
|
||||
|
||||
### TestFlight 上传失败
|
||||
|
||||
检查清单:
|
||||
- [ ] API Key 有 App Manager 权限
|
||||
- [ ] App ID 在 App Store Connect 已创建
|
||||
- [ ] 版本号未重复
|
||||
|
||||
---
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 分支策略
|
||||
|
||||
```
|
||||
main (稳定版本)
|
||||
↑
|
||||
develop (开发版本)
|
||||
↑
|
||||
feature/* (功能分支)
|
||||
```
|
||||
|
||||
### 2. 版本号规范
|
||||
|
||||
使用语义化版本:
|
||||
- `v1.0.0-alpha`: 内部测试版本
|
||||
- `v1.0.0-beta`: 公开测试版本
|
||||
- `v1.0.0`: 正式发布版本
|
||||
|
||||
### 3. 节省构建时间
|
||||
|
||||
- 只在需要时构建 iOS (macOS runner 费用高)
|
||||
- 使用缓存加速构建
|
||||
- 手动触发选择性构建
|
||||
|
||||
### 4. 安全建议
|
||||
|
||||
- 定期更新签名密钥
|
||||
- 使用最小权限原则
|
||||
- 不要在代码中硬编码敏感信息
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
### 文档
|
||||
|
||||
- [GitHub Actions 文档](https://docs.github.com/en/actions)
|
||||
- [详细配置说明](./README.md)
|
||||
- [API 配置指南](../../API配置指南.md)
|
||||
|
||||
### 支持
|
||||
|
||||
- 查看仓库 [Issues](https://github.com/nicolastinkl/UUVPN/issues)
|
||||
- Telegram: @fastlink
|
||||
|
||||
---
|
||||
|
||||
## ✅ 配置验证清单
|
||||
|
||||
完成配置后,检查以下内容:
|
||||
|
||||
- [ ] 已安装 GitHub CLI
|
||||
- [ ] 已登录 GitHub CLI
|
||||
- [ ] 已配置 Android Secrets (如需 Android 构建)
|
||||
- [ ] 已配置 iOS Secrets (如需 iOS 构建)
|
||||
- [ ] 已配置 App Store Connect API (如需 TestFlight)
|
||||
- [ ] 已推送代码触发构建
|
||||
- [ ] 构建成功
|
||||
- [ ] 可以下载构建产物
|
||||
|
||||
---
|
||||
|
||||
**祝你构建顺利! 🎉**
|
||||
@@ -0,0 +1,525 @@
|
||||
# GitHub Actions CI/CD 配置说明
|
||||
|
||||
本文档说明如何配置和使用 UUVPN 的 GitHub Actions 自动构建流程。
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [功能特性](#功能特性)
|
||||
- [前置要求](#前置要求)
|
||||
- [配置 Secrets](#配置-secrets)
|
||||
- [使用方法](#使用方法)
|
||||
- [构建产物](#构建产物)
|
||||
- [自定义配置](#自定义配置)
|
||||
- [常见问题](#常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
### ✅ 自动构建
|
||||
|
||||
- **Android APK**: 支持 Debug 和 Release 构建
|
||||
- **iOS IPA**: 支持 Debug 和 Release 构建
|
||||
- **多架构支持**: Android 支持 armeabi-v7a, arm64-v8a, x86, x86_64
|
||||
|
||||
### ✅ 自动发布
|
||||
|
||||
- **GitHub Releases**: 自动创建 Release 并上传构建产物
|
||||
- **TestFlight**: 自动上传 iOS 版本到 TestFlight
|
||||
- **Telegram 通知**: 构建完成后发送通知
|
||||
|
||||
### ✅ 触发条件
|
||||
|
||||
- 推送到 main/develop 分支
|
||||
- 创建版本标签 (v*)
|
||||
- Pull Request
|
||||
- 手动触发
|
||||
|
||||
---
|
||||
|
||||
## 前置要求
|
||||
|
||||
### Android 构建
|
||||
|
||||
1. **JDK 11**: 已在 Actions 中自动配置
|
||||
2. **Android SDK**: 已在 Actions 中自动配置
|
||||
3. **Go 1.23**: 用于编译核心库
|
||||
4. **签名密钥**: Release 构建需要签名密钥库
|
||||
|
||||
### iOS 构建
|
||||
|
||||
1. **Apple Developer 账号**: $99/年
|
||||
2. **Xcode 15.0+**: 已在 Actions 中自动配置
|
||||
3. **开发者证书**: .p12 格式
|
||||
4. **Provisioning Profile**: .mobileprovision 文件
|
||||
5. **App Store Connect API Key**: 用于上传到 TestFlight
|
||||
|
||||
---
|
||||
|
||||
## 配置 Secrets
|
||||
|
||||
在 GitHub 仓库中配置以下 Secrets:
|
||||
|
||||
### Android Secrets
|
||||
|
||||
| Secret 名称 | 描述 | 获取方式 |
|
||||
|------------|------|---------|
|
||||
| `ANDROID_KEYSTORE_BASE64` | 签名密钥库的 Base64 编码 | 见下方说明 |
|
||||
| `ANDROID_KEYSTORE_PASSWORD` | 密钥库密码 | 创建密钥库时设置 |
|
||||
| `ANDROID_KEY_ALIAS` | 密钥别名 | 创建密钥库时设置 |
|
||||
| `ANDROID_KEY_PASSWORD` | 密钥密码 | 创建密钥库时设置 |
|
||||
|
||||
**生成 ANDROID_KEYSTORE_BASE64**:
|
||||
|
||||
```bash
|
||||
# 在本地执行
|
||||
keytool -genkey -v -keystore release.keystore -alias uuvpn -keyalg RSA -keysize 2048 -validity 10000
|
||||
|
||||
# 转换为 Base64
|
||||
base64 -i release.keystore -o release.keystore.base64
|
||||
|
||||
# 或者使用 PowerShell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) | Out-File release.keystore.base64
|
||||
```
|
||||
|
||||
### iOS Secrets
|
||||
|
||||
| Secret 名称 | 描述 | 获取方式 |
|
||||
|------------|------|---------|
|
||||
| `IOS_CERTIFICATES_P12` | 开发者证书的 Base64 编码 | 见下方说明 |
|
||||
| `IOS_CERTIFICATES_PASSWORD` | 证书密码 | 导出证书时设置 |
|
||||
| `IOS_SIGNING_IDENTITY` | 签名身份 | 如: "Apple Distribution: Your Name (TEAM_ID)" |
|
||||
| `IOS_PROVISIONING_PROFILE` | Provisioning Profile 的 Base64 编码 | 见下方说明 |
|
||||
| `IOS_TEAM_ID` | Apple Team ID | 在 Apple Developer 账号中查看 |
|
||||
|
||||
**生成 IOS_CERTIFICATES_P12**:
|
||||
|
||||
1. 在 Keychain Access 中导出证书
|
||||
2. 选择 "Apple Distribution" 证书
|
||||
3. 右键 → 导出 → 选择 .p12 格式
|
||||
4. 设置密码
|
||||
5. 转换为 Base64:
|
||||
|
||||
```bash
|
||||
base64 -i certificate.p12 -o certificate.p12.base64
|
||||
|
||||
# 或者使用 PowerShell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.p12")) | Out-File certificate.p12.base64
|
||||
```
|
||||
|
||||
**生成 IOS_PROVISIONING_PROFILE**:
|
||||
|
||||
1. 在 Apple Developer 网站下载 Provisioning Profile
|
||||
2. 转换为 Base64:
|
||||
|
||||
```bash
|
||||
base64 -i uuvpn.mobileprovision -o uuvpn.mobileprovision.base64
|
||||
|
||||
# 或者使用 PowerShell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes("uuvpn.mobileprovision")) | Out-File uuvpn.mobileprovision.base64
|
||||
```
|
||||
|
||||
### App Store Connect Secrets
|
||||
|
||||
| Secret 名称 | 描述 | 获取方式 |
|
||||
|------------|------|---------|
|
||||
| `APP_STORE_CONNECT_ISSUER_ID` | App Store Connect API Issuer ID | 在 App Store Connect 中创建 API Key |
|
||||
| `APP_STORE_CONNECT_API_KEY_ID` | App Store Connect API Key ID | 创建 API Key 时获得 |
|
||||
| `APP_STORE_CONNECT_API_PRIVATE_KEY` | App Store Connect API 私钥 | 创建 API Key 时下载的 .p8 文件内容 |
|
||||
|
||||
**获取方式**:
|
||||
|
||||
1. 登录 [App Store Connect](https://appstoreconnect.apple.com/)
|
||||
2. 进入 Users and Access → Keys
|
||||
3. 点击 "Generate API Key"
|
||||
4. 选择 "App Manager" 权限
|
||||
5. 下载 .p8 文件并保存私钥内容
|
||||
|
||||
### 通知 Secrets (可选)
|
||||
|
||||
| Secret 名称 | 描述 | 获取方式 |
|
||||
|------------|------|---------|
|
||||
| `TELEGRAM_BOT_TOKEN` | Telegram Bot Token | 通过 @BotFather 创建 Bot |
|
||||
| `TELEGRAM_CHAT_ID` | Telegram 群组/频道 ID | 邀请 Bot 到群组后获取 |
|
||||
|
||||
**获取方式**:
|
||||
|
||||
1. 在 Telegram 中找到 @BotFather
|
||||
2. 发送 `/newbot` 创建新 Bot
|
||||
3. 获取 Bot Token
|
||||
4. 创建群组或频道,邀请 Bot
|
||||
5. 获取 Chat ID: `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`
|
||||
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方法一: 自动触发
|
||||
|
||||
#### 推送到分支
|
||||
|
||||
```bash
|
||||
# 推送到 main 分支,触发构建
|
||||
git push origin main
|
||||
|
||||
# 推送到 develop 分支,触发 Debug 构建
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
#### 创建版本标签
|
||||
|
||||
```bash
|
||||
# 创建 Alpha 版本
|
||||
git tag v1.0.0-alpha
|
||||
git push origin v1.0.0-alpha
|
||||
|
||||
# 创建 Beta 版本
|
||||
git tag v1.0.0-beta
|
||||
git push origin v1.0.0-beta
|
||||
|
||||
# 创建正式版本
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
#### Pull Request
|
||||
|
||||
创建 Pull Request 到 main 分支会自动触发 Debug 构建。
|
||||
|
||||
### 方法二: 手动触发
|
||||
|
||||
1. 进入 GitHub 仓库
|
||||
2. 点击 "Actions" 标签
|
||||
3. 选择 "Build UUVPN" workflow
|
||||
4. 点击 "Run workflow"
|
||||
5. 选择构建选项:
|
||||
- `build_android`: 是否构建 Android APK
|
||||
- `build_ios`: 是否构建 iOS IPA
|
||||
- `release_type`: 构建类型 (alpha/beta/release)
|
||||
6. 点击 "Run workflow" 按钮
|
||||
|
||||
---
|
||||
|
||||
## 构建产物
|
||||
|
||||
### Android 构建产物
|
||||
|
||||
构建完成后,可以在以下位置找到 APK:
|
||||
|
||||
1. **Actions Artifacts**: 每个 workflow 运行都会保存 APK artifact
|
||||
2. **GitHub Releases**: 创建标签后,APK 会自动上传到 Release
|
||||
|
||||
**APK 文件命名**:
|
||||
- Debug: `app-meta-alpha-debug.apk`
|
||||
- Release: `UUVPN-Android-v1.0.0.apk`
|
||||
|
||||
### iOS 构建产物
|
||||
|
||||
构建完成后,可以在以下位置找到 IPA:
|
||||
|
||||
1. **Actions Artifacts**: 每个 workflow 运行都会保存 IPA artifact
|
||||
2. **GitHub Releases**: 创建标签后,IPA 会自动上传到 Release
|
||||
3. **TestFlight**: Beta 和正式版本会自动上传到 TestFlight
|
||||
|
||||
**IPA 文件命名**:
|
||||
- Release: `UUVPN-iOS-v1.0.0.ipa`
|
||||
|
||||
---
|
||||
|
||||
## 自定义配置
|
||||
|
||||
### 修改 Android 构建变体
|
||||
|
||||
编辑 `.github/workflows/build.yml` 中的构建命令:
|
||||
|
||||
```yaml
|
||||
- name: Build Android APK
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
# 修改为你需要的构建变体
|
||||
./gradlew app:assembleMeta-BetaRelease --stacktrace
|
||||
```
|
||||
|
||||
### 修改 iOS Scheme
|
||||
|
||||
编辑 `.github/workflows/build.yml` 中的 scheme:
|
||||
|
||||
```yaml
|
||||
- name: Build iOS App
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
xcodebuild build \
|
||||
-project uuvpn.xcodeproj \
|
||||
-scheme SFT \ # 修改为你需要的 scheme
|
||||
-sdk iphoneos \
|
||||
...
|
||||
```
|
||||
|
||||
### 添加构建缓存
|
||||
|
||||
在 workflow 中添加缓存以加速构建:
|
||||
|
||||
```yaml
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache Swift packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
.build
|
||||
key: ${{ runner.os }}-swift-${{ hashFiles('**/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-swift-
|
||||
```
|
||||
|
||||
### 添加自定义构建步骤
|
||||
|
||||
在 workflow 中添加自定义步骤:
|
||||
|
||||
```yaml
|
||||
- name: Custom build step
|
||||
run: |
|
||||
# 你的自定义命令
|
||||
echo "Running custom build step"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 工作流程说明
|
||||
|
||||
### 完整构建流程
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ 触发构建 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
├─────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ 构建 Android │ │ 构建 iOS │
|
||||
│ APK │ │ IPA │
|
||||
└────────┬────────┘ └────────┬────────┘
|
||||
│ │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ 创建 GitHub Release │
|
||||
│ (仅标签触发) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
├─────────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ 上传 APK 到 │ │ 上传 IPA 到 │
|
||||
│ GitHub Release │ │ TestFlight │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ 发送通知 │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### 构建类型说明
|
||||
|
||||
| 触发条件 | Android | iOS | 发布 |
|
||||
|---------|---------|-----|------|
|
||||
| Push to develop | Debug APK | Debug App | 否 |
|
||||
| Pull Request | Debug APK | Debug App | 否 |
|
||||
| Push to main | Release APK | Release IPA | 否 |
|
||||
| Tag v*-alpha | Release APK | Release IPA | GitHub Release (Pre-release) |
|
||||
| Tag v*-beta | Release APK | Release IPA | GitHub Release + TestFlight |
|
||||
| Tag v* | Release APK | Release IPA | GitHub Release + TestFlight |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: Android 构建失败,提示找不到签名密钥?
|
||||
|
||||
**A**: 检查以下内容:
|
||||
- 确认已配置 `ANDROID_KEYSTORE_BASE64` secret
|
||||
- 确认 Base64 编码正确
|
||||
- 确认密钥库密码、别名和密钥密码正确
|
||||
|
||||
### Q2: iOS 构建失败,提示签名错误?
|
||||
|
||||
**A**: 检查以下内容:
|
||||
- 确认证书未过期
|
||||
- 确认 Provisioning Profile 包含正确的 App ID
|
||||
- 确认 Team ID 正确
|
||||
- 确认证书和 Provisioning Profile 匹配
|
||||
|
||||
### Q3: iOS 构建时间过长?
|
||||
|
||||
**A**: iOS 构建通常需要 10-20 分钟,这是正常的。可以:
|
||||
- 使用缓存加速构建
|
||||
- 只在需要时构建 iOS 版本
|
||||
- 使用手动触发选择性构建
|
||||
|
||||
### Q4: 如何跳过某个平台的构建?
|
||||
|
||||
**A**: 使用手动触发时,取消勾选对应平台的构建选项。
|
||||
|
||||
### Q5: 构建产物在哪里下载?
|
||||
|
||||
**A**:
|
||||
1. 进入 Actions 页面
|
||||
2. 点击具体的 workflow 运行记录
|
||||
3. 在页面底部 "Artifacts" 区域下载
|
||||
|
||||
### Q6: 如何查看构建日志?
|
||||
|
||||
**A**:
|
||||
1. 进入 Actions 页面
|
||||
2. 点击具体的 workflow 运行记录
|
||||
3. 点击左侧的 job 名称
|
||||
4. 展开各个步骤查看详细日志
|
||||
|
||||
### Q7: TestFlight 上传失败?
|
||||
|
||||
**A**: 检查以下内容:
|
||||
- 确认 App Store Connect API Key 配置正确
|
||||
- 确认 API Key 有 App Manager 权限
|
||||
- 确认 App ID 在 App Store Connect 中已创建
|
||||
- 确认版本号未重复
|
||||
|
||||
### Q8: 如何修改构建触发条件?
|
||||
|
||||
**A**: 编辑 `.github/workflows/build.yml` 的 `on` 部分:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- feature/* # 添加更多分支
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 进阶配置
|
||||
|
||||
### 添加代码质量检查
|
||||
|
||||
在构建前添加代码检查:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
lint:
|
||||
name: Code Quality Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Android Lint
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew lint
|
||||
|
||||
- name: Swift Lint
|
||||
run: |
|
||||
brew install swiftlint
|
||||
cd iOS-SwiftUI-Code
|
||||
swiftlint
|
||||
```
|
||||
|
||||
### 添加单元测试
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Android Tests
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew test
|
||||
|
||||
- name: iOS Tests
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
xcodebuild test \
|
||||
-project uuvpn.xcodeproj \
|
||||
-scheme SFI \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 15'
|
||||
```
|
||||
|
||||
### 添加多版本构建
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: ['14.3', '15.0', '15.2']
|
||||
include:
|
||||
- xcode-version: '14.3'
|
||||
ios-deployment-target: '14.0'
|
||||
- xcode-version: '15.0'
|
||||
ios-deployment-target: '15.0'
|
||||
- xcode-version: '15.2'
|
||||
ios-deployment-target: '16.0'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成本估算
|
||||
|
||||
### GitHub Actions 免费额度
|
||||
|
||||
- **总时长**: 每月 2000 分钟 (免费账户)
|
||||
- **macOS runner**: 按 10 倍计算
|
||||
- **Linux runner**: 按 1 倍计算
|
||||
|
||||
### 实际消耗估算
|
||||
|
||||
| 构建类型 | Android | iOS | 总计 |
|
||||
|---------|---------|-----|------|
|
||||
| Debug 构建 | ~5 分钟 | ~100 分钟 | ~105 分钟 |
|
||||
| Release 构建 | ~10 分钟 | ~150 分钟 | ~160 分钟 |
|
||||
| 完整流程 | ~15 分钟 | ~200 分钟 | ~215 分钟 |
|
||||
|
||||
### 建议
|
||||
|
||||
- 免费账户每月可进行约 **9-10 次完整构建**
|
||||
- 建议只在必要时构建 iOS 版本
|
||||
- 使用手动触发选择性构建
|
||||
- 考虑升级 GitHub Pro 获得更多额度
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
通过 GitHub Actions CI/CD 配置,你可以:
|
||||
|
||||
✅ 无需本地 Mac 设备即可构建 iOS 应用
|
||||
✅ 自动化构建和发布流程
|
||||
✅ 支持 Android 和 iOS 双平台
|
||||
✅ 自动上传到 TestFlight 和 GitHub Releases
|
||||
✅ 接收构建通知
|
||||
|
||||
配置好 Secrets 后,只需推送代码或创建标签,即可自动完成构建和发布!
|
||||
@@ -0,0 +1,304 @@
|
||||
name: Build Debug
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# ==================== Android Debug 构建 ====================
|
||||
build-android-debug:
|
||||
name: Build Android Debug APK
|
||||
runs-on: ubuntu
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
|
||||
steps:
|
||||
- name: Install Node.js
|
||||
run: |
|
||||
export PATH="/toolcache/bin:$PATH"
|
||||
if ! command -v node &> /dev/null; then
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache nodejs npm
|
||||
else
|
||||
curl -fsSL https://nodejs.org/dist/v20.0.0/node-v20.0.0-linux-x64.tar.xz | tar -xJ -C /tmp
|
||||
mkdir -p /toolcache
|
||||
cp -r /tmp/node-v20.0.0-linux-x64/{bin,lib,share} /toolcache/
|
||||
fi
|
||||
fi
|
||||
node --version
|
||||
|
||||
- name: Install Git LFS
|
||||
run: |
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache git-lfs
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
apt-get update && apt-get install -y git-lfs
|
||||
else
|
||||
curl -sL https://github.com/git-lfs/git-lfs/releases/download/v3.5.1/git-lfs-linux-amd64-v3.5.1.tar.gz | tar xz -C /tmp
|
||||
mv /tmp/git-lfs-3.5.1/git-lfs /toolcache/bin/
|
||||
fi
|
||||
git lfs install
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Pull Git LFS files
|
||||
run: |
|
||||
git lfs install
|
||||
git lfs pull || echo "LFS pull failed, continuing without LFS files (Android build doesn't need them)"
|
||||
|
||||
- name: Install Java 17
|
||||
run: |
|
||||
if command -v apk &> /dev/null; then
|
||||
# Enable community repository for openjdk17-jdk
|
||||
sed -i 's/^#.*\/community\/.*$/\0/' /etc/apk/repositories || true
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
|
||||
apk update
|
||||
apk add --no-cache openjdk17-jdk
|
||||
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk" >> $GITHUB_ENV
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
apt-get update && apt-get install -y openjdk-17-jdk
|
||||
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64" >> $GITHUB_ENV
|
||||
else
|
||||
curl -sL https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz | tar xz -C /toolcache
|
||||
echo "JAVA_HOME=/toolcache/jdk-17.0.9+9" >> $GITHUB_ENV
|
||||
echo "/toolcache/jdk-17.0.9+9/bin" >> $GITHUB_PATH
|
||||
fi
|
||||
java -version
|
||||
|
||||
- name: Install Go 1.23
|
||||
run: |
|
||||
# Create toolcache directory if not exists
|
||||
mkdir -p /toolcache
|
||||
# Install curl if not available (Alpine uses apk)
|
||||
if ! command -v curl &> /dev/null; then
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache curl
|
||||
fi
|
||||
fi
|
||||
# Download Go 1.23 directly to ensure correct version
|
||||
curl -sL https://go.dev/dl/go1.23.0.linux-amd64.tar.gz | tar xz -C /toolcache
|
||||
echo "/toolcache/go/bin" >> $GITHUB_PATH
|
||||
echo "GOROOT=/toolcache/go" >> $GITHUB_ENV
|
||||
/toolcache/go/bin/go version
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
chmod +x gradlew
|
||||
|
||||
- name: Create local.properties
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
echo "sdk.dir=$ANDROID_HOME" > local.properties
|
||||
|
||||
- name: Build Android Debug APK
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew app:assembleDebug --stacktrace
|
||||
|
||||
- name: List APK files
|
||||
run: |
|
||||
echo "Listing APK files..."
|
||||
find Android-kotlin-Code/app/build/outputs/apk -name "*.apk" -type f || echo "No APK files found"
|
||||
ls -la Android-kotlin-Code/app/build/outputs/apk/ || true
|
||||
|
||||
- name: Copy APK to artifacts
|
||||
run: |
|
||||
mkdir -p /tmp/artifacts
|
||||
find Android-kotlin-Code/app/build/outputs/apk -name "*.apk" -type f -exec cp {} /tmp/artifacts/ \;
|
||||
ls -la /tmp/artifacts/
|
||||
echo "APK files copied to /tmp/artifacts"
|
||||
|
||||
# ==================== iOS Debug 构建 ====================
|
||||
build-ios-debug:
|
||||
name: Build iOS Debug App
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Git LFS
|
||||
run: |
|
||||
git lfs install
|
||||
git lfs pull
|
||||
|
||||
- name: Verify Libbox framework before build
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Checking Libbox.xcframework..."
|
||||
echo "========================================"
|
||||
cd iOS-SwiftUI-Code
|
||||
|
||||
if [ -d "Libbox.xcframework" ]; then
|
||||
echo "✅ Libbox.xcframework directory exists"
|
||||
ls -lh Libbox.xcframework/
|
||||
|
||||
# 检查模拟器版本
|
||||
if [ -d "Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A" ]; then
|
||||
echo "✅ Simulator framework structure exists"
|
||||
|
||||
# 检查二进制文件
|
||||
LIBBOX_BINARY="Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A/Libbox"
|
||||
if [ -f "$LIBBOX_BINARY" ]; then
|
||||
echo "✅ Libbox binary file exists"
|
||||
|
||||
# 获取文件大小 (macOS 和 Linux 兼容)
|
||||
FILE_SIZE=$(stat -f%z "$LIBBOX_BINARY" 2>/dev/null || stat -c%s "$LIBBOX_BINARY" 2>/dev/null)
|
||||
echo "Binary file size: $FILE_SIZE bytes ($(( FILE_SIZE / 1024 / 1024 )) MB)"
|
||||
|
||||
# 检查文件是否是真实的二进制文件 (不是 Git placeholder)
|
||||
if [ "$FILE_SIZE" -lt 1000 ]; then
|
||||
echo "❌ ERROR: File size is too small! This is likely a Git placeholder"
|
||||
echo "File content (first 200 bytes):"
|
||||
head -c 200 "$LIBBOX_BINARY"
|
||||
echo ""
|
||||
echo "This means Git did not download the actual binary file."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ File size is normal, binary appears to be downloaded correctly"
|
||||
echo "File type:"
|
||||
file "$LIBBOX_BINARY" || echo "Binary file exists"
|
||||
fi
|
||||
else
|
||||
echo "❌ ERROR: Libbox binary file NOT found at: $LIBBOX_BINARY"
|
||||
echo "Listing directory contents:"
|
||||
ls -la Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A/ || true
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ ERROR: Simulator framework structure NOT found"
|
||||
echo "Listing Libbox.xcframework contents:"
|
||||
find Libbox.xcframework -type d -name "*.framework" || true
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ ERROR: Libbox.xcframework directory NOT found!"
|
||||
echo "Listing iOS-SwiftUI-Code directory:"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.3'
|
||||
|
||||
- name: Show Xcode version
|
||||
run: |
|
||||
xcodebuild -version
|
||||
swift --version
|
||||
|
||||
- name: Resolve Swift Package Dependencies
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
xcodebuild -resolvePackageDependencies -project uuvpn.xcodeproj -scheme SFI
|
||||
|
||||
- name: List available schemes
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
xcodebuild -list -project uuvpn.xcodeproj
|
||||
|
||||
- name: Diagnose Libbox module
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
echo "=== Libbox.xcframework Info.plist ==="
|
||||
cat Libbox.xcframework/Info.plist
|
||||
echo ""
|
||||
echo "=== Simulator module.modulemap ==="
|
||||
cat Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A/Modules/module.modulemap
|
||||
echo ""
|
||||
echo "=== Simulator Headers ==="
|
||||
ls -la Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A/Headers/
|
||||
echo ""
|
||||
echo "=== Check for Swift module interface ==="
|
||||
find Libbox.xcframework/ios-arm64_x86_64-simulator -name "*.swiftmodule" -o -name "*.swiftinterface" 2>/dev/null || echo "No Swift module interface found"
|
||||
echo ""
|
||||
echo "=== Check Modules directory ==="
|
||||
ls -la Libbox.xcframework/ios-arm64_x86_64-simulator/Libbox.framework/Versions/A/Modules/
|
||||
|
||||
- name: Build iOS Debug App
|
||||
id: build
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
xcodebuild clean build \
|
||||
-project uuvpn.xcodeproj \
|
||||
-scheme SFI \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone Air,OS=26.2' \
|
||||
-configuration Debug \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
2>&1 | tee build_output.log
|
||||
|
||||
# 检查构建结果
|
||||
if grep -q "BUILD SUCCEEDED" build_output.log; then
|
||||
echo "✅ BUILD SUCCEEDED"
|
||||
echo "build_status=succeeded" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ BUILD FAILED"
|
||||
echo "=== Error lines from build log ==="
|
||||
grep -i "error:" build_output.log || echo "No 'error:' lines found"
|
||||
echo ""
|
||||
echo "=== Last 100 lines of build log ==="
|
||||
tail -100 build_output.log
|
||||
echo "build_status=failed" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Find and upload iOS Debug App
|
||||
if: success()
|
||||
run: |
|
||||
cd iOS-SwiftUI-Code
|
||||
|
||||
# 查找 .app 文件
|
||||
echo "Searching for .app files in DerivedData..."
|
||||
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "SFI.app" -type d | head -n 1)
|
||||
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "*.app" -type d | head -n 1)
|
||||
fi
|
||||
|
||||
if [ -n "$APP_PATH" ] && [ -d "$APP_PATH" ]; then
|
||||
echo "✅ Found app at: $APP_PATH"
|
||||
echo "App size:"
|
||||
du -sh "$APP_PATH"
|
||||
|
||||
# 复制到 build 目录
|
||||
mkdir -p build
|
||||
cp -R "$APP_PATH" build/
|
||||
|
||||
# 创建 zip 文件
|
||||
cd build
|
||||
APP_NAME=$(basename "$APP_PATH")
|
||||
zip -r ios-debug-app.zip "$APP_NAME"
|
||||
|
||||
echo "✅ Created zip file: ios-debug-app.zip"
|
||||
ls -lh ios-debug-app.zip
|
||||
else
|
||||
echo "❌ No .app file found in DerivedData"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload iOS Debug App
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-debug-app
|
||||
path: iOS-SwiftUI-Code/build/ios-debug-app.zip
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,283 @@
|
||||
name: Build iOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_debug:
|
||||
description: 'Build Debug IPA'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
build_release:
|
||||
description: 'Build Release IPA'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
IOS_XCODE_VERSION: "16.0"
|
||||
IOS_DEPLOYMENT_TARGET: "15.0"
|
||||
|
||||
jobs:
|
||||
build-ios:
|
||||
name: Build iOS IPA
|
||||
runs-on: ubuntu
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
|
||||
steps:
|
||||
- name: Install dependencies (Alpine)
|
||||
run: |
|
||||
# 添加 community 仓库
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
|
||||
apk update
|
||||
|
||||
# 安装基础依赖
|
||||
apk add --no-cache \
|
||||
wget \
|
||||
curl \
|
||||
git-lfs \
|
||||
bash \
|
||||
python3 \
|
||||
py3-pip \
|
||||
docker \
|
||||
openrc \
|
||||
sshpass \
|
||||
sshfs
|
||||
|
||||
# 启动 docker 服务
|
||||
rc-service docker start || true
|
||||
docker --version || echo "Docker not available"
|
||||
|
||||
- name: Check virtualization support
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Checking virtualization support..."
|
||||
echo "========================================"
|
||||
|
||||
# 检查 KVM
|
||||
ls -la /dev/kvm 2>/dev/null || echo "KVM device not found"
|
||||
cat /proc/cpuinfo | grep -E "vmx|svm" | head -5 || echo "No hardware virtualization detected"
|
||||
|
||||
echo ""
|
||||
echo "Docker version:"
|
||||
docker --version || echo "Docker not found"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Git LFS
|
||||
run: |
|
||||
git lfs install
|
||||
|
||||
# 配置 LFS 认证
|
||||
git config --global lfs.url "https://admin:73ffc0f4391a26bb327fbed379d63b5a087e5f10@git.viaeon.com/admin/UUVPN.git/info/lfs"
|
||||
|
||||
echo "Attempting LFS pull..."
|
||||
git lfs pull 2>&1 || echo "LFS pull failed, trying alternative methods..."
|
||||
|
||||
# 如果 LFS pull 失败,尝试直接 fetch 和 checkout
|
||||
git lfs fetch origin 2>&1 || echo "LFS fetch failed"
|
||||
git lfs checkout 2>&1 || echo "LFS checkout failed"
|
||||
|
||||
- name: Check if Libbox framework exists
|
||||
id: check_lfs
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Checking LFS files..."
|
||||
echo "========================================"
|
||||
|
||||
echo "Libbox.xcframework contents:"
|
||||
ls -la iOS-SwiftUI-Code/Libbox.xcframework/ 2>/dev/null || echo "Directory not found or empty"
|
||||
|
||||
# 检查关键文件是否是真正的二进制文件(不是 LFS 指针)
|
||||
if [ -f "iOS-SwiftUI-Code/Libbox.xcframework/ios-arm64/Libbox.framework/Libbox" ]; then
|
||||
file_size=$(stat -c%s "iOS-SwiftUI-Code/Libbox.xcframework/ios-arm64/Libbox.framework/Libbox" 2>/dev/null || stat -f%z "iOS-SwiftUI-Code/Libbox.xcframework/ios-arm64/Libbox.framework/Libbox" 2>/dev/null)
|
||||
if [ "$file_size" -gt 100 ]; then
|
||||
echo "lfs_ready=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ LFS files downloaded successfully (file size: ${file_size} bytes)"
|
||||
else
|
||||
echo "lfs_ready=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ LFS files are pointers, not actual binaries"
|
||||
fi
|
||||
else
|
||||
echo "lfs_ready=false" >> $GITHUB_OUTPUT
|
||||
echo "❌ LFS files not available"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
- name: Pull Docker-OSX image
|
||||
if: ${{ steps.check_lfs.outputs.lfs_ready == 'true' }}
|
||||
run: |
|
||||
echo "Pulling Docker-OSX image..."
|
||||
docker pull sickcodes/docker-osx:latest || echo "Failed to pull image"
|
||||
|
||||
- name: Start macOS VM (Sonoma)
|
||||
if: ${{ steps.check_lfs.outputs.lfs_ready == 'true' }}
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Starting macOS Sonoma VM with Docker-OSX..."
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "⚠️ 注意: 首次启动需要下载 macOS 安装镜像 (~15GB)"
|
||||
echo "⚠️ 这可能需要 30-60 分钟"
|
||||
echo ""
|
||||
|
||||
# 使用正确的 Docker-OSX 启动命令
|
||||
# 注意: 需要 --device /dev/kvm 来传递 KVM 设备
|
||||
# 注意: 需要 -it 交互式模式
|
||||
# 注意: Sonoma 需要特殊的 CPU 配置
|
||||
|
||||
# 检查 KVM 是否可用
|
||||
if [ -e "/dev/kvm" ]; then
|
||||
echo "✅ KVM is available"
|
||||
KVM_DEVICE="--device /dev/kvm"
|
||||
else
|
||||
echo "❌ KVM not available, will use QEMU emulation (very slow)"
|
||||
KVM_DEVICE=""
|
||||
fi
|
||||
|
||||
# 启动 macOS Sonoma 容器
|
||||
# 使用 -d 后台运行,并设置环境变量
|
||||
docker run -d \
|
||||
--name macos-vm \
|
||||
$KVM_DEVICE \
|
||||
--privileged \
|
||||
-p 50922:10022 \
|
||||
-e RAM=8192 \
|
||||
-e SMP=4 \
|
||||
-e CPU='Haswell-noTSX' \
|
||||
-e CPUID_FLAGS='kvm=on,vendor=GenuineIntel,+invtsc,vmware-cpuid-freq=on' \
|
||||
-e GENERATE_UNIQUE=true \
|
||||
-e MASTER_PLIST_URL='https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom-sonoma.plist' \
|
||||
-e SHORTNAME=sonoma \
|
||||
-e DISPLAY=dummy \
|
||||
sickcodes/docker-osx:latest || echo "Failed to start VM"
|
||||
|
||||
echo ""
|
||||
echo "VM started in background"
|
||||
echo "Waiting for macOS to boot (this may take several minutes)..."
|
||||
|
||||
# 等待 SSH 可用
|
||||
max_wait=600 # 10 分钟
|
||||
wait_count=0
|
||||
while [ $wait_count -lt $max_wait ]; do
|
||||
if sshpass -p "user" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -p 50922 user@localhost "echo 'SSH ready'" 2>/dev/null; then
|
||||
echo "✅ SSH is ready!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for SSH... ($wait_count/$max_wait)"
|
||||
sleep 10
|
||||
wait_count=$((wait_count + 10))
|
||||
done
|
||||
|
||||
if [ $wait_count -ge $max_wait ]; then
|
||||
echo "❌ SSH not available after $max_wait seconds"
|
||||
docker logs macos-vm 2>/dev/null | tail -50 || echo "No VM logs"
|
||||
fi
|
||||
|
||||
- name: Check macOS VM status
|
||||
if: ${{ steps.check_lfs.outputs.lfs_ready == 'true' }}
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "macOS VM Status"
|
||||
echo "========================================"
|
||||
|
||||
# 通过 SSH 连接到 macOS
|
||||
echo "Checking macOS via SSH..."
|
||||
|
||||
# 尝试连接 SSH
|
||||
sshpass -p "user" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p 50922 user@localhost "uname -a" 2>/dev/null || echo "Cannot connect via SSH"
|
||||
|
||||
# 检查 xcodebuild
|
||||
sshpass -p "user" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p 50922 user@localhost "xcodebuild -version" 2>/dev/null || echo "xcodebuild not found"
|
||||
|
||||
# 检查容器日志
|
||||
echo ""
|
||||
echo "Container logs (last 30 lines):"
|
||||
docker logs macos-vm 2>/dev/null | tail -30 || echo "No VM logs"
|
||||
|
||||
- name: Build iOS app (attempt)
|
||||
if: ${{ steps.check_lfs.outputs.lfs_ready == 'true' }}
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Attempting iOS build..."
|
||||
echo "========================================"
|
||||
|
||||
# 通过 SSHFS 挂载 macOS 文件系统,或者通过 SSH 直接执行命令
|
||||
# 默认用户: user, 密码: user
|
||||
|
||||
# 检查 SSH 是否可用
|
||||
if sshpass -p "user" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -p 50922 user@localhost "echo 'SSH ready'" 2>/dev/null; then
|
||||
echo "✅ SSH connection available"
|
||||
|
||||
# 将代码复制到 macOS
|
||||
echo "Copying code to macOS..."
|
||||
sshpass -p "user" scp -o StrictHostKeyChecking=no -P 50922 -r iOS-SwiftUI-Code user@localhost:/tmp/ 2>/dev/null || echo "Failed to copy code"
|
||||
|
||||
# 检查代码是否存在
|
||||
sshpass -p "user" ssh -o StrictHostKeyChecking=no -p 50922 user@localhost "ls -la /tmp/iOS-SwiftUI-Code/" 2>/dev/null || echo "Code not copied"
|
||||
|
||||
# 尝试构建
|
||||
echo ""
|
||||
echo "Building iOS app..."
|
||||
sshpass -p "user" ssh -o StrictHostKeyChecking=no -p 50922 user@localhost "
|
||||
cd /tmp/iOS-SwiftUI-Code || exit 1
|
||||
|
||||
# 检查 xcodebuild
|
||||
xcodebuild -version || echo 'xcodebuild not found'
|
||||
|
||||
# 尝试构建
|
||||
xcodebuild \
|
||||
-project uuvpn.xcodeproj \
|
||||
-scheme SFI \
|
||||
-sdk iphoneos \
|
||||
-configuration Debug \
|
||||
-derivedDataPath build \
|
||||
CODE_SIGN_IDENTITY='' \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
build 2>&1 || echo 'Build failed'
|
||||
" 2>&1 || echo "SSH command failed"
|
||||
|
||||
else
|
||||
echo "❌ SSH not available, cannot build"
|
||||
echo ""
|
||||
echo "Docker-OSX 问题总结:"
|
||||
echo "1. 首次启动需要下载 macOS 安装镜像 (~15GB)"
|
||||
echo "2. 需要等待 macOS 安装完成 (可能需要 1 小时+)"
|
||||
echo "3. 需要手动安装 Xcode"
|
||||
echo ""
|
||||
echo "建议:"
|
||||
echo "- 使用 GitHub Actions 的托管 macOS runner (免费)"
|
||||
echo "- 或者预先准备好 macOS 虚拟机镜像"
|
||||
fi
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up..."
|
||||
docker stop macos-vm 2>/dev/null || true
|
||||
docker rm macos-vm 2>/dev/null || true
|
||||
|
||||
- name: Build summary
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "iOS Build Summary"
|
||||
echo "========================================"
|
||||
echo "LFS ready: ${{ steps.check_lfs.outputs.lfs_ready }}"
|
||||
echo ""
|
||||
if [ "${{ steps.check_lfs.outputs.lfs_ready }}" != "true" ]; then
|
||||
echo "❌ Cannot build iOS without Libbox.xcframework"
|
||||
else
|
||||
echo "✅ LFS files available"
|
||||
echo "Build was attempted - check logs above for results"
|
||||
fi
|
||||
@@ -0,0 +1,307 @@
|
||||
name: Build UUVPN
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_android:
|
||||
description: 'Build Android APK'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
build_ios:
|
||||
description: 'Build iOS IPA'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
release_type:
|
||||
description: 'Release type'
|
||||
required: false
|
||||
default: 'alpha'
|
||||
type: choice
|
||||
options:
|
||||
- alpha
|
||||
- beta
|
||||
- release
|
||||
|
||||
env:
|
||||
# Android 配置
|
||||
ANDROID_COMPILE_SDK: "33"
|
||||
ANDROID_BUILD_TOOLS: "33.0.0"
|
||||
ANDROID_TARGET_SDK: "33"
|
||||
ANDROID_JDK_VERSION: "17"
|
||||
|
||||
# iOS 配置
|
||||
IOS_XCODE_VERSION: "16.0"
|
||||
IOS_DEPLOYMENT_TARGET: "15.0"
|
||||
|
||||
jobs:
|
||||
# ==================== Android 构建 ====================
|
||||
build-android:
|
||||
name: Build Android APK
|
||||
runs-on: ubuntu
|
||||
# 暂时禁用 Android 构建,使用 build-android.yml
|
||||
if: ${{ false && github.event.inputs.build_android != 'false' }}
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
|
||||
steps:
|
||||
- name: Install Node.js
|
||||
run: |
|
||||
export PATH="/toolcache/bin:$PATH"
|
||||
if ! command -v node &> /dev/null; then
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache nodejs npm
|
||||
else
|
||||
curl -fsSL https://nodejs.org/dist/v20.0.0/node-v20.0.0-linux-x64.tar.xz | tar -xJ -C /tmp
|
||||
mkdir -p /toolcache
|
||||
cp -r /tmp/node-v20.0.0-linux-x64/{bin,lib,share} /toolcache/
|
||||
fi
|
||||
fi
|
||||
node --version
|
||||
|
||||
- name: Install Git LFS
|
||||
run: |
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache git-lfs
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
apt-get update && apt-get install -y git-lfs
|
||||
else
|
||||
curl -sL https://github.com/git-lfs/git-lfs/releases/download/v3.5.1/git-lfs-linux-amd64-v3.5.1.tar.gz | tar xz -C /tmp
|
||||
mv /tmp/git-lfs-3.5.1/git-lfs /toolcache/bin/
|
||||
fi
|
||||
git lfs install
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Pull Git LFS files
|
||||
run: |
|
||||
git lfs pull || echo "LFS pull failed, continuing without LFS files (Android build doesn't need them)"
|
||||
|
||||
- name: Install Java 17
|
||||
run: |
|
||||
if command -v apk &> /dev/null; then
|
||||
# Enable community repository for openjdk17-jdk
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories
|
||||
apk update
|
||||
apk add --no-cache openjdk17-jdk
|
||||
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk" >> $GITHUB_ENV
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
apt-get update && apt-get install -y openjdk-17-jdk
|
||||
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64" >> $GITHUB_ENV
|
||||
else
|
||||
curl -sL https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz | tar xz -C /toolcache
|
||||
echo "JAVA_HOME=/toolcache/jdk-17.0.9+9" >> $GITHUB_ENV
|
||||
echo "/toolcache/jdk-17.0.9+9/bin" >> $GITHUB_PATH
|
||||
fi
|
||||
java -version
|
||||
|
||||
- name: Install Go 1.23
|
||||
run: |
|
||||
mkdir -p /toolcache
|
||||
if ! command -v curl &> /dev/null; then
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache curl
|
||||
fi
|
||||
fi
|
||||
curl -sL https://go.dev/dl/go1.23.0.linux-amd64.tar.gz | tar xz -C /toolcache
|
||||
echo "/toolcache/go/bin" >> $GITHUB_PATH
|
||||
echo "GOROOT=/toolcache/go" >> $GITHUB_ENV
|
||||
/toolcache/go/bin/go version
|
||||
|
||||
- name: Install gcompat (for NDK on Alpine)
|
||||
run: |
|
||||
# Install gcompat for running glibc binaries on Alpine (required for NDK clang)
|
||||
if command -v apk &> /dev/null; then
|
||||
apk add --no-cache gcompat libstdc++
|
||||
fi
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
chmod +x gradlew
|
||||
|
||||
- name: Create local.properties
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
echo "sdk.dir=$ANDROID_HOME" > local.properties
|
||||
|
||||
- name: Create signing.properties (Debug)
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
echo "keystore.path=debug.keystore" > signing.properties
|
||||
echo "keystore.password=android" >> signing.properties
|
||||
echo "key.alias=androiddebugkey" >> signing.properties
|
||||
echo "key.password=android" >> signing.properties
|
||||
|
||||
- name: Create signing.properties (Release)
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
echo "keystore.path=release.keystore" > signing.properties
|
||||
echo "keystore.password=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> signing.properties
|
||||
echo "key.alias=${{ secrets.ANDROID_KEY_ALIAS }}" >> signing.properties
|
||||
echo "key.password=${{ secrets.ANDROID_KEY_PASSWORD }}" >> signing.properties
|
||||
|
||||
- name: Decode Android keystore
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
|
||||
|
||||
- name: Build Android Debug APK
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew assembleAlphaDebug --stacktrace
|
||||
|
||||
- name: Build Android Release APK
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew assembleAlphaRelease --stacktrace
|
||||
|
||||
- name: Build Android APK (Default)
|
||||
if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') && github.ref != 'refs/heads/develop' }}
|
||||
run: |
|
||||
cd Android-kotlin-Code
|
||||
./gradlew assembleAlphaRelease --stacktrace
|
||||
|
||||
- name: Upload Android APK Artifact
|
||||
run: |
|
||||
# Copy APK to a known location for artifact upload
|
||||
mkdir -p /tmp/artifacts
|
||||
find Android-kotlin-Code/app/build/outputs/apk -name "*.apk" -type f -exec cp {} /tmp/artifacts/ \;
|
||||
ls -la /tmp/artifacts/
|
||||
echo "APK files copied to /tmp/artifacts"
|
||||
|
||||
- name: Get APK filename
|
||||
id: apk_name
|
||||
run: |
|
||||
if [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ]; then
|
||||
APK_NAME="UUVPN-Android-${{ github.ref_name }}.apk"
|
||||
else
|
||||
APK_NAME="UUVPN-Android-${{ github.sha }}.apk"
|
||||
fi
|
||||
echo "apk_name=$APK_NAME" >> $GITHUB_OUTPUT
|
||||
# Find the APK file
|
||||
APK_PATH=$(find Android-kotlin-Code/app/build/outputs/apk -name "*.apk" -type f | head -1)
|
||||
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
|
||||
echo "Found APK: $APK_PATH"
|
||||
|
||||
- name: Rename APK for release
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
cd Android-kotlin-Code/app/build/outputs/apk
|
||||
find . -name "*.apk" -type f -exec mv {} "${{ steps.apk_name.outputs.apk_name }}" \;
|
||||
|
||||
# ==================== iOS 构建 (Docker-OSX) ====================
|
||||
# 已移至 build-ios.yml
|
||||
build-ios:
|
||||
name: Build iOS IPA
|
||||
runs-on: ubuntu
|
||||
if: ${{ false }}
|
||||
steps:
|
||||
- name: Disabled
|
||||
run: echo "iOS build moved to build-ios.yml"
|
||||
|
||||
# ==================== 发布到 GitHub Releases ====================
|
||||
release:
|
||||
name: Create GitHub Release
|
||||
needs: [build-android]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Android APK
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: android-apk-${{ github.sha }}
|
||||
path: artifacts/android
|
||||
|
||||
- name: Get version from tag
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release Notes
|
||||
id: release_notes
|
||||
run: |
|
||||
cat > release_notes.md << EOF
|
||||
# UUVPN ${{ steps.get_version.outputs.version }}
|
||||
|
||||
## 🎉 新版本发布
|
||||
|
||||
### 📱 Android 版本
|
||||
- 支持 Android 5.0+ (推荐 Android 7.0+)
|
||||
- 支持 armeabi-v7a, arm64-v8a, x86, x86_64 架构
|
||||
- 基于 CLASH (mihomo) 核心
|
||||
|
||||
### 🍎 iOS 版本
|
||||
- 支持 iOS 15.0+
|
||||
- 基于 SINGBOX 核心
|
||||
- 支持 iPhone 和 iPad
|
||||
|
||||
## 📥 下载说明
|
||||
|
||||
### Android
|
||||
下载 APK 文件并安装,首次安装需要授予必要权限。
|
||||
|
||||
### iOS
|
||||
IPA 文件需要通过 TestFlight 或第三方工具安装。
|
||||
|
||||
## 🔄 更新内容
|
||||
|
||||
请查看 [Commits](https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}) 了解详细更新内容。
|
||||
|
||||
## 📝 问题反馈
|
||||
|
||||
如遇到问题,请在 [Issues](https://github.com/${{ github.repository }}/issues) 中反馈。
|
||||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: UUVPN ${{ steps.get_version.outputs.version }}
|
||||
body_path: release_notes.md
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
|
||||
files: |
|
||||
artifacts/android/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ==================== 通知 ====================
|
||||
notify:
|
||||
name: Send Notification
|
||||
needs: [build-android]
|
||||
runs-on: ubuntu
|
||||
if: ${{ always() && github.event_name == 'push' }}
|
||||
|
||||
steps:
|
||||
- name: Send Telegram Notification
|
||||
if: ${{ secrets.TELEGRAM_BOT_TOKEN != '' }}
|
||||
run: |
|
||||
echo "Telegram notification would be sent here"
|
||||
echo "Build status: ${{ needs.build-android.result }}"
|
||||
@@ -1,42 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Dart
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Note: This workflow uses the latest stable version of the Dart SDK.
|
||||
# You can specify other versions if desired, see documentation here:
|
||||
# https://github.com/dart-lang/setup-dart/blob/main/README.md
|
||||
# - uses: dart-lang/setup-dart@v1
|
||||
- uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
|
||||
# Uncomment this step to verify the use of 'dart format' on each commit.
|
||||
# - name: Verify formatting
|
||||
# run: dart format --output=none --set-exit-if-changed .
|
||||
|
||||
# Consider passing '--fatal-infos' for slightly stricter analysis.
|
||||
- name: Analyze project source
|
||||
run: dart analyze
|
||||
|
||||
# Your project will need to have tests in test/ and a dependency on
|
||||
# package:test for this step to succeed. Note that Flutter projects will
|
||||
# want to change this to 'flutter test'.
|
||||
- name: Run tests
|
||||
run: dart test
|
||||
@@ -0,0 +1,199 @@
|
||||
# UUVPN GitHub Secrets 配置脚本 (PowerShell)
|
||||
# 用于快速配置 GitHub Actions 所需的 Secrets
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "UUVPN GitHub Secrets 配置工具" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# 检查是否安装了 GitHub CLI
|
||||
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "❌ 错误: 未安装 GitHub CLI" -ForegroundColor Red
|
||||
Write-Host "请先安装 GitHub CLI: https://cli.github.com/"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 检查是否已登录 GitHub
|
||||
try {
|
||||
$authStatus = gh auth status 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "❌ 错误: 未登录 GitHub CLI" -ForegroundColor Red
|
||||
Write-Host "请先运行: gh auth login"
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host "❌ 错误: 未登录 GitHub CLI" -ForegroundColor Red
|
||||
Write-Host "请先运行: gh auth login"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 获取仓库信息
|
||||
try {
|
||||
$remoteUrl = git remote get-url origin
|
||||
if ($remoteUrl -match "github\.com[:/](.+?)\.git") {
|
||||
$repo = $matches[1]
|
||||
} else {
|
||||
Write-Host "❌ 错误: 无法获取仓库信息" -ForegroundColor Red
|
||||
Write-Host "请确保在仓库目录中运行此脚本"
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host "❌ 错误: 无法获取仓库信息" -ForegroundColor Red
|
||||
Write-Host "请确保在仓库目录中运行此脚本"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ 当前仓库: $repo" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 配置 Android Secrets
|
||||
Write-Host "📱 配置 Android Secrets" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
$configAndroid = Read-Host "是否配置 Android 签名密钥? (y/n)"
|
||||
if ($configAndroid -eq "y") {
|
||||
$keystorePath = Read-Host "密钥库文件路径 (默认: release.keystore)"
|
||||
if ([string]::IsNullOrWhiteSpace($keystorePath)) {
|
||||
$keystorePath = "release.keystore"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $keystorePath)) {
|
||||
Write-Host "❌ 错误: 密钥库文件不存在: $keystorePath" -ForegroundColor Red
|
||||
Write-Host "请先创建密钥库:"
|
||||
Write-Host "keytool -genkey -v -keystore release.keystore -alias uuvpn -keyalg RSA -keysize 2048 -validity 10000"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$keystorePassword = Read-Host "密钥库密码" -AsSecureString
|
||||
$keystorePasswordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($keystorePassword)
|
||||
)
|
||||
|
||||
$keyAlias = Read-Host "密钥别名 (默认: uuvpn)"
|
||||
if ([string]::IsNullOrWhiteSpace($keyAlias)) {
|
||||
$keyAlias = "uuvpn"
|
||||
}
|
||||
|
||||
$keyPassword = Read-Host "密钥密码" -AsSecureString
|
||||
$keyPasswordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($keyPassword)
|
||||
)
|
||||
|
||||
# Base64 编码密钥库
|
||||
$keystoreBytes = [IO.File]::ReadAllBytes($keystorePath)
|
||||
$keystoreBase64 = [Convert]::ToBase64String($keystoreBytes)
|
||||
|
||||
Write-Host "正在设置 Android Secrets..." -ForegroundColor Cyan
|
||||
$keystoreBase64 | gh secret set ANDROID_KEYSTORE_BASE64 --repo $repo
|
||||
$keystorePasswordPlain | gh secret set ANDROID_KEYSTORE_PASSWORD --repo $repo
|
||||
$keyAlias | gh secret set ANDROID_KEY_ALIAS --repo $repo
|
||||
$keyPasswordPlain | gh secret set ANDROID_KEY_PASSWORD --repo $repo
|
||||
|
||||
Write-Host "✅ Android Secrets 配置完成" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 配置 iOS Secrets
|
||||
Write-Host "🍎 配置 iOS Secrets" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
$configIOS = Read-Host "是否配置 iOS 签名证书? (y/n)"
|
||||
if ($configIOS -eq "y") {
|
||||
$certPath = Read-Host "证书文件路径 (.p12)"
|
||||
|
||||
if (-not (Test-Path $certPath)) {
|
||||
Write-Host "❌ 错误: 证书文件不存在: $certPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$certPassword = Read-Host "证书密码" -AsSecureString
|
||||
$certPasswordPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($certPassword)
|
||||
)
|
||||
|
||||
$teamId = Read-Host "Team ID"
|
||||
$signingIdentity = Read-Host "签名身份 (如: Apple Distribution: Your Name (TEAM_ID))"
|
||||
|
||||
$profilePath = Read-Host "Provisioning Profile 文件路径 (.mobileprovision)"
|
||||
|
||||
if (-not (Test-Path $profilePath)) {
|
||||
Write-Host "❌ 错误: Provisioning Profile 文件不存在: $profilePath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Base64 编码
|
||||
$certBytes = [IO.File]::ReadAllBytes($certPath)
|
||||
$certBase64 = [Convert]::ToBase64String($certBytes)
|
||||
|
||||
$profileBytes = [IO.File]::ReadAllBytes($profilePath)
|
||||
$profileBase64 = [Convert]::ToBase64String($profileBytes)
|
||||
|
||||
Write-Host "正在设置 iOS Secrets..." -ForegroundColor Cyan
|
||||
$certBase64 | gh secret set IOS_CERTIFICATES_P12 --repo $repo
|
||||
$certPasswordPlain | gh secret set IOS_CERTIFICATES_PASSWORD --repo $repo
|
||||
$teamId | gh secret set IOS_TEAM_ID --repo $repo
|
||||
$signingIdentity | gh secret set IOS_SIGNING_IDENTITY --repo $repo
|
||||
$profileBase64 | gh secret set IOS_PROVISIONING_PROFILE --repo $repo
|
||||
|
||||
Write-Host "✅ iOS Secrets 配置完成" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 配置 App Store Connect Secrets
|
||||
Write-Host "🚀 配置 App Store Connect Secrets" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
$configASC = Read-Host "是否配置 App Store Connect API? (y/n)"
|
||||
if ($configASC -eq "y") {
|
||||
$issuerId = Read-Host "Issuer ID"
|
||||
$apiKeyId = Read-Host "API Key ID"
|
||||
$p8Path = Read-Host "API Private Key 文件路径 (.p8)"
|
||||
|
||||
if (-not (Test-Path $p8Path)) {
|
||||
Write-Host "❌ 错误: API Private Key 文件不存在: $p8Path" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 读取私钥内容
|
||||
$apiKey = Get-Content $p8Path -Raw
|
||||
|
||||
Write-Host "正在设置 App Store Connect Secrets..." -ForegroundColor Cyan
|
||||
$issuerId | gh secret set APP_STORE_CONNECT_ISSUER_ID --repo $repo
|
||||
$apiKeyId | gh secret set APP_STORE_CONNECT_API_KEY_ID --repo $repo
|
||||
$apiKey | gh secret set APP_STORE_CONNECT_API_PRIVATE_KEY --repo $repo
|
||||
|
||||
Write-Host "✅ App Store Connect Secrets 配置完成" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# 配置通知 Secrets
|
||||
Write-Host "📢 配置通知 Secrets" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
$configTelegram = Read-Host "是否配置 Telegram 通知? (y/n)"
|
||||
if ($configTelegram -eq "y") {
|
||||
$botToken = Read-Host "Bot Token"
|
||||
$chatId = Read-Host "Chat ID"
|
||||
|
||||
Write-Host "正在设置 Telegram Secrets..." -ForegroundColor Cyan
|
||||
$botToken | gh secret set TELEGRAM_BOT_TOKEN --repo $repo
|
||||
$chatId | gh secret set TELEGRAM_CHAT_ID --repo $repo
|
||||
|
||||
Write-Host "✅ Telegram Secrets 配置完成" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "✅ 配置完成!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "当前配置的 Secrets:"
|
||||
gh secret list --repo $repo
|
||||
Write-Host ""
|
||||
Write-Host "下一步:" -ForegroundColor Yellow
|
||||
Write-Host "1. 推送代码到仓库触发构建"
|
||||
Write-Host "2. 查看构建状态: https://github.com/$repo/actions"
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/bin/bash
|
||||
|
||||
# UUVPN GitHub Secrets 配置脚本
|
||||
# 用于快速配置 GitHub Actions 所需的 Secrets
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo "UUVPN GitHub Secrets 配置工具"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 检查是否安装了 GitHub CLI
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "❌ 错误: 未安装 GitHub CLI"
|
||||
echo "请先安装 GitHub CLI: https://cli.github.com/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查是否已登录 GitHub
|
||||
if ! gh auth status &> /dev/null; then
|
||||
echo "❌ 错误: 未登录 GitHub CLI"
|
||||
echo "请先运行: gh auth login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 获取仓库信息
|
||||
REPO=$(git remote get-url origin | sed -E 's|.*github\.com[:/](.*)\.git|\1|')
|
||||
if [ -z "$REPO" ]; then
|
||||
echo "❌ 错误: 无法获取仓库信息"
|
||||
echo "请确保在仓库目录中运行此脚本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 当前仓库: $REPO"
|
||||
echo ""
|
||||
|
||||
# 配置 Android Secrets
|
||||
echo "📱 配置 Android Secrets"
|
||||
echo "----------------------------------------"
|
||||
|
||||
read -p "是否配置 Android 签名密钥? (y/n): " config_android
|
||||
if [ "$config_android" = "y" ]; then
|
||||
read -p "密钥库文件路径 (默认: release.keystore): " keystore_path
|
||||
keystore_path=${keystore_path:-"release.keystore"}
|
||||
|
||||
if [ ! -f "$keystore_path" ]; then
|
||||
echo "❌ 错误: 密钥库文件不存在: $keystore_path"
|
||||
echo "请先创建密钥库:"
|
||||
echo "keytool -genkey -v -keystore release.keystore -alias uuvpn -keyalg RSA -keysize 2048 -validity 10000"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -sp "密钥库密码: " keystore_password
|
||||
echo ""
|
||||
read -p "密钥别名 (默认: uuvpn): " key_alias
|
||||
key_alias=${key_alias:-"uuvpn"}
|
||||
read -sp "密钥密码: " key_password
|
||||
echo ""
|
||||
|
||||
# Base64 编码密钥库
|
||||
keystore_base64=$(base64 -i "$keystore_path")
|
||||
|
||||
echo "正在设置 Android Secrets..."
|
||||
gh secret set ANDROID_KEYSTORE_BASE64 --repo "$REPO" --body "$keystore_base64"
|
||||
gh secret set ANDROID_KEYSTORE_PASSWORD --repo "$REPO" --body "$keystore_password"
|
||||
gh secret set ANDROID_KEY_ALIAS --repo "$REPO" --body "$key_alias"
|
||||
gh secret set ANDROID_KEY_PASSWORD --repo "$REPO" --body "$key_password"
|
||||
|
||||
echo "✅ Android Secrets 配置完成"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 配置 iOS Secrets
|
||||
echo "🍎 配置 iOS Secrets"
|
||||
echo "----------------------------------------"
|
||||
|
||||
read -p "是否配置 iOS 签名证书? (y/n): " config_ios
|
||||
if [ "$config_ios" = "y" ]; then
|
||||
read -p "证书文件路径 (.p12): " cert_path
|
||||
|
||||
if [ ! -f "$cert_path" ]; then
|
||||
echo "❌ 错误: 证书文件不存在: $cert_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -sp "证书密码: " cert_password
|
||||
echo ""
|
||||
read -p "Team ID: " team_id
|
||||
read -p "签名身份 (如: Apple Distribution: Your Name (TEAM_ID)): " signing_identity
|
||||
|
||||
read -p "Provisioning Profile 文件路径 (.mobileprovision): " profile_path
|
||||
|
||||
if [ ! -f "$profile_path" ]; then
|
||||
echo "❌ 错误: Provisioning Profile 文件不存在: $profile_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Base64 编码
|
||||
cert_base64=$(base64 -i "$cert_path")
|
||||
profile_base64=$(base64 -i "$profile_path")
|
||||
|
||||
echo "正在设置 iOS Secrets..."
|
||||
gh secret set IOS_CERTIFICATES_P12 --repo "$REPO" --body "$cert_base64"
|
||||
gh secret set IOS_CERTIFICATES_PASSWORD --repo "$REPO" --body "$cert_password"
|
||||
gh secret set IOS_TEAM_ID --repo "$REPO" --body "$team_id"
|
||||
gh secret set IOS_SIGNING_IDENTITY --repo "$REPO" --body "$signing_identity"
|
||||
gh secret set IOS_PROVISIONING_PROFILE --repo "$REPO" --body "$profile_base64"
|
||||
|
||||
echo "✅ iOS Secrets 配置完成"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 配置 App Store Connect Secrets
|
||||
echo "🚀 配置 App Store Connect Secrets"
|
||||
echo "----------------------------------------"
|
||||
|
||||
read -p "是否配置 App Store Connect API? (y/n): " config_asc
|
||||
if [ "$config_asc" = "y" ]; then
|
||||
read -p "Issuer ID: " issuer_id
|
||||
read -p "API Key ID: " api_key_id
|
||||
read -p "API Private Key 文件路径 (.p8): " p8_path
|
||||
|
||||
if [ ! -f "$p8_path" ]; then
|
||||
echo "❌ 错误: API Private Key 文件不存在: $p8_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 读取私钥内容
|
||||
api_private_key=$(cat "$p8_path")
|
||||
|
||||
echo "正在设置 App Store Connect Secrets..."
|
||||
gh secret set APP_STORE_CONNECT_ISSUER_ID --repo "$REPO" --body "$issuer_id"
|
||||
gh secret set APP_STORE_CONNECT_API_KEY_ID --repo "$REPO" --body "$api_key_id"
|
||||
gh secret set APP_STORE_CONNECT_API_PRIVATE_KEY --repo "$REPO" --body "$api_private_key"
|
||||
|
||||
echo "✅ App Store Connect Secrets 配置完成"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 配置通知 Secrets
|
||||
echo "📢 配置通知 Secrets"
|
||||
echo "----------------------------------------"
|
||||
|
||||
read -p "是否配置 Telegram 通知? (y/n): " config_telegram
|
||||
if [ "$config_telegram" = "y" ]; then
|
||||
read -p "Bot Token: " bot_token
|
||||
read -p "Chat ID: " chat_id
|
||||
|
||||
echo "正在设置 Telegram Secrets..."
|
||||
gh secret set TELEGRAM_BOT_TOKEN --repo "$REPO" --body "$bot_token"
|
||||
gh secret set TELEGRAM_CHAT_ID --repo "$REPO" --body "$chat_id"
|
||||
|
||||
echo "✅ Telegram Secrets 配置完成"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "✅ 配置完成!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "当前配置的 Secrets:"
|
||||
gh secret list --repo "$REPO"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo "1. 推送代码到仓库触发构建"
|
||||
echo "2. 查看构建状态: https://github.com/$REPO/actions"
|
||||
echo ""
|
||||
@@ -1,6 +1,9 @@
|
||||
app-profile.apk
|
||||
.DS_Store
|
||||
|
||||
# Exclude kankanjiao project
|
||||
/kankanjiao/
|
||||
|
||||
# Do not remove or rename entries in this file, only add new ones
|
||||
# See https://github.com/flutter/flutter/issues/128635 for more context.
|
||||
|
||||
@@ -139,3 +142,4 @@ app.*.symbols
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
!.vscode/settings.json
|
||||
/Android-kotlin-Code/core/.cxx
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
# UUVPN API 端点配置指南
|
||||
|
||||
本文档详细说明如何配置 UUVPN 的 API 端点,包括 Android 和 iOS 平台的配置方法。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [API 配置概述](#api-配置概述)
|
||||
- [配置字段说明](#配置字段说明)
|
||||
- [Android 配置方法](#android-配置方法)
|
||||
- [iOS 配置方法](#ios-配置方法)
|
||||
- [测试与验证](#测试与验证)
|
||||
- [常见问题](#常见问题)
|
||||
|
||||
---
|
||||
|
||||
## API 配置概述
|
||||
|
||||
UUVPN 需要配置一个远程 API 端点,用于获取服务器配置信息。该配置采用 JSON 格式,包含 V2Board 面板的 API 地址、支付链接、客服支持等信息。
|
||||
|
||||
### 配置 URL 格式
|
||||
|
||||
```
|
||||
https://your-domain.com/api/config.php
|
||||
```
|
||||
|
||||
### 配置返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"baseURL": "https://api.0008.uk/api/v1/",
|
||||
"baseDYURL": "https://api.gooapis.com/api/vpnnodes.php",
|
||||
"mainregisterURL": "https://lelian.app/#/register?code=",
|
||||
"paymentURL": "https://your-payment-gateway.com/pay",
|
||||
"telegramurl": "https://t.me/fastlink",
|
||||
"kefuurl": "https://gooapis.com/fastlink/",
|
||||
"websiteURL": "https://gooapis.com/fastlink/",
|
||||
"crisptoken": "5546c6ea-4b1e-41bc-80e4-4b6648cbca76",
|
||||
"banners": [
|
||||
"https://image.gooapis.com/api/images/12-11-56.png",
|
||||
"https://image.gooapis.com/api/images/12-44-57.png",
|
||||
"https://image.gooapis.com/api/images/12-47-03.png"
|
||||
],
|
||||
"message": "OK",
|
||||
"code": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置字段说明
|
||||
|
||||
### 1. baseURL
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 是
|
||||
- **描述**: V2Board 面板的主要 API 端点
|
||||
- **示例**: `https://api.0008.uk/api/v1/`
|
||||
- **用途**: 所有与 V2Board 服务器的 API 请求都将基于此 URL,包括:
|
||||
- 用户登录/注册
|
||||
- 获取节点列表
|
||||
- 获取订阅信息
|
||||
- 用户信息管理
|
||||
|
||||
### 2. baseDYURL
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 是
|
||||
- **描述**: VPN 节点测试端点
|
||||
- **示例**: `https://api.gooapis.com/api/vpnnodes.php`
|
||||
- **用途**: 用于测试 VPN 节点的连通性和速度,通常用于应用内的节点测速功能
|
||||
|
||||
### 3. mainregisterURL
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 是
|
||||
- **描述**: 用户注册页面 URL(带邀请码参数)
|
||||
- **示例**: `https://lelian.app/#/register?code=`
|
||||
- **用途**: 引导用户注册,支持邀请码系统
|
||||
- **注意**: URL 末尾需要保留 `code=` 参数,应用会自动填充邀请码
|
||||
|
||||
### 4. paymentURL
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 是
|
||||
- **描述**: 支付网关 URL
|
||||
- **示例**: `https://your-payment-gateway.com/pay`
|
||||
- **用途**:
|
||||
- **正常模式** (URL 长度 > 3): 显示外部支付选项,启用订阅功能
|
||||
- **审核模式** (URL 长度 ≤ 3): 隐藏外部支付,遵守 App Store 指南
|
||||
- **iOS 重要说明**:
|
||||
- 正常值: `https://payment.example.com/pay` (显示支付功能)
|
||||
- 审核值: `xxx` 或 `xx` (隐藏支付功能,用于 App Store 审核)
|
||||
|
||||
### 5. telegramurl
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 否
|
||||
- **描述**: Telegram 支持频道/群组链接
|
||||
- **示例**: `https://t.me/fastlink`
|
||||
- **用途**: 提供用户 Telegram 客服支持入口
|
||||
|
||||
### 6. kefuurl
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 否
|
||||
- **描述**: 在线客服页面 URL
|
||||
- **示例**: `https://gooapis.com/fastlink/`
|
||||
- **用途**: 提供网页版在线客服入口
|
||||
|
||||
### 7. websiteURL
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 否
|
||||
- **描述**: 官方网站 URL
|
||||
- **示例**: `https://gooapis.com/fastlink/`
|
||||
- **用途**: 应用内关于页面或官网链接
|
||||
|
||||
### 8. crisptoken
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 否
|
||||
- **描述**: Crisp 聊天服务认证令牌
|
||||
- **示例**: `5546c6ea-4b1e-41bc-80e4-4b6648cbca76`
|
||||
- **用途**: 集成 Crisp 实时聊天功能
|
||||
- **获取方式**: 在 [Crisp 官网](https://crisp.chat/) 注册后获取
|
||||
|
||||
### 9. banners
|
||||
|
||||
- **类型**: Array of Strings
|
||||
- **必填**: 否
|
||||
- **描述**: 首页横幅图片 URL 数组
|
||||
- **示例**:
|
||||
```json
|
||||
[
|
||||
"https://image.gooapis.com/api/images/12-11-56.png",
|
||||
"https://image.gooapis.com/api/images/12-44-57.png"
|
||||
]
|
||||
```
|
||||
- **用途**: 应用首页轮播图展示
|
||||
- **建议尺寸**: 750x300 像素
|
||||
- **格式支持**: PNG, JPG, WebP
|
||||
|
||||
### 10. message
|
||||
|
||||
- **类型**: String
|
||||
- **必填**: 是
|
||||
- **描述**: API 响应状态消息
|
||||
- **示例**: `OK`
|
||||
- **用途**: 标识配置请求是否成功
|
||||
|
||||
### 11. code
|
||||
|
||||
- **类型**: Integer
|
||||
- **必填**: 是
|
||||
- **描述**: API 响应状态码
|
||||
- **示例**: `1` (成功) 或 `0` (失败)
|
||||
- **用途**: 应用根据此字段判断配置是否有效
|
||||
|
||||
---
|
||||
|
||||
## Android 配置方法
|
||||
|
||||
### 方法一: 修改配置文件 (推荐)
|
||||
|
||||
1. **定位配置文件**
|
||||
|
||||
在 Android 项目中找到以下文件:
|
||||
```
|
||||
Android-kotlin-Code/app/src/main/java/com/xxx/xxx/config/ApiConfig.kt
|
||||
```
|
||||
|
||||
2. **修改配置 URL**
|
||||
|
||||
找到 `configURL` 或类似的配置常量:
|
||||
```kotlin
|
||||
const val CONFIG_URL = "https://your-domain.com/api/config.php"
|
||||
```
|
||||
|
||||
3. **重新编译 APK**
|
||||
|
||||
```bash
|
||||
./gradlew app:assembleMeta-AlphaRelease
|
||||
```
|
||||
|
||||
### 方法二: 动态配置
|
||||
|
||||
如果支持动态配置,可以在应用启动时请求配置 URL:
|
||||
|
||||
```kotlin
|
||||
// 在 Application 或 MainActivity 中
|
||||
ConfigManager.init(this, "https://your-domain.com/api/config.php")
|
||||
```
|
||||
|
||||
### Android API 请求示例
|
||||
|
||||
```kotlin
|
||||
// ApiService.kt
|
||||
interface ApiService {
|
||||
@GET
|
||||
suspend fun getConfig(@Url url: String): Response<ConfigResponse>
|
||||
}
|
||||
|
||||
data class ConfigResponse(
|
||||
val baseURL: String,
|
||||
val baseDYURL: String,
|
||||
val mainregisterURL: String,
|
||||
val paymentURL: String,
|
||||
val telegramurl: String,
|
||||
val kefuurl: String,
|
||||
val websiteURL: String,
|
||||
val crisptoken: String,
|
||||
val banners: List<String>,
|
||||
val message: String,
|
||||
val code: Int
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## iOS 配置方法
|
||||
|
||||
### 方法一: 修改 StoreManager.swift (推荐)
|
||||
|
||||
1. **定位配置文件**
|
||||
|
||||
在 iOS 项目中找到:
|
||||
```
|
||||
iOS-SwiftUI-Code/ApplicationLibrary/Service/StoreManager.swift
|
||||
```
|
||||
|
||||
2. **修改配置 URL**
|
||||
|
||||
找到 `configURL` 常量并修改:
|
||||
```swift
|
||||
public let configURL = "https://your-domain.com/api/config.php"
|
||||
```
|
||||
|
||||
3. **重新编译应用**
|
||||
|
||||
在 Xcode 中按 `Cmd + B` 编译项目
|
||||
|
||||
### 方法二: 使用 Info.plist
|
||||
|
||||
1. **在 Info.plist 中添加配置**
|
||||
|
||||
```xml
|
||||
<key>ConfigURL</key>
|
||||
<string>https://your-domain.com/api/config.php</string>
|
||||
```
|
||||
|
||||
2. **在代码中读取**
|
||||
|
||||
```swift
|
||||
if let configURL = Bundle.main.infoDictionary?["ConfigURL"] as? String {
|
||||
// 使用配置 URL
|
||||
}
|
||||
```
|
||||
|
||||
### iOS 网络请求示例
|
||||
|
||||
```swift
|
||||
// StoreManager.swift
|
||||
func fetchConfig() async throws -> ConfigResponse {
|
||||
guard let url = URL(string: configURL) else {
|
||||
throw URLError(.badURL)
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue(Bundle.main.bundleIdentifier ?? "", forHTTPHeaderField: "bid")
|
||||
request.addValue(appVersion, forHTTPHeaderField: "appver")
|
||||
|
||||
let (data, _) = try await URLSession.shared.data(for: request)
|
||||
return try JSONDecoder().decode(ConfigResponse.self, from: data)
|
||||
}
|
||||
|
||||
// 数据模型
|
||||
struct ConfigResponse: Codable {
|
||||
let baseURL: String
|
||||
let baseDYURL: String
|
||||
let mainregisterURL: String
|
||||
let paymentURL: String
|
||||
let telegramurl: String
|
||||
let kefuurl: String
|
||||
let websiteURL: String
|
||||
let crisptoken: String
|
||||
let banners: [String]
|
||||
let message: String
|
||||
let code: Int
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试与验证
|
||||
|
||||
### 测试配置地址
|
||||
|
||||
项目提供了测试配置地址供开发调试使用:
|
||||
|
||||
```
|
||||
https://vungles.com/api/test/config
|
||||
```
|
||||
|
||||
**测试账号**:
|
||||
- 邮箱: `binance3980@gmail.com`
|
||||
- 密码: `Tinkl123`
|
||||
|
||||
### 验证配置是否正确
|
||||
|
||||
1. **浏览器测试**
|
||||
|
||||
在浏览器中访问你的配置 URL,确认返回正确的 JSON 格式
|
||||
|
||||
2. **Postman 测试**
|
||||
|
||||
使用 Postman 发送 GET 请求,验证响应内容
|
||||
|
||||
3. **应用内测试**
|
||||
|
||||
- 启动应用
|
||||
- 检查是否能正常加载首页横幅图
|
||||
- 测试登录/注册功能
|
||||
- 验证节点列表加载
|
||||
|
||||
### 错误排查
|
||||
|
||||
如果配置加载失败:
|
||||
|
||||
1. **检查 URL 是否正确**
|
||||
```
|
||||
https://your-domain.com/api/config.php
|
||||
```
|
||||
|
||||
2. **检查 HTTPS 证书**
|
||||
- 确保证书有效且未过期
|
||||
- 使用可信 CA 签发的证书
|
||||
|
||||
3. **检查 JSON 格式**
|
||||
- 使用 JSON 格式化工具验证
|
||||
- 确保所有必填字段都存在
|
||||
|
||||
4. **检查 CORS 配置** (Web 端)
|
||||
- 服务器需要配置正确的 CORS 头
|
||||
|
||||
---
|
||||
|
||||
## 部署配置服务器
|
||||
|
||||
### 方案一: 静态 JSON 文件 (推荐)
|
||||
|
||||
将配置 JSON 存储在 CDN 或对象存储服务:
|
||||
|
||||
1. **创建配置文件**
|
||||
|
||||
创建 `config.json`:
|
||||
```json
|
||||
{
|
||||
"baseURL": "https://api.yourdomain.com/api/v1/",
|
||||
"baseDYURL": "https://api.yourdomain.com/api/vpnnodes.php",
|
||||
"mainregisterURL": "https://yourdomain.com/#/register?code=",
|
||||
"paymentURL": "https://pay.yourdomain.com/",
|
||||
"telegramurl": "https://t.me/yourchannel",
|
||||
"kefuurl": "https://yourdomain.com/support/",
|
||||
"websiteURL": "https://yourdomain.com/",
|
||||
"crisptoken": "your-crisp-token",
|
||||
"banners": [
|
||||
"https://cdn.yourdomain.com/banner1.png"
|
||||
],
|
||||
"message": "OK",
|
||||
"code": 1
|
||||
}
|
||||
```
|
||||
|
||||
2. **上传到对象存储**
|
||||
|
||||
- 阿里云 OSS
|
||||
- 腾讯云 COS
|
||||
- AWS S3
|
||||
- Cloudflare R2
|
||||
|
||||
3. **启用 HTTPS**
|
||||
|
||||
确保存储桶启用 HTTPS 访问
|
||||
|
||||
### 方案二: 动态 API 接口
|
||||
|
||||
使用后端服务动态返回配置:
|
||||
|
||||
**PHP 示例**:
|
||||
```php
|
||||
<?php
|
||||
// config.php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$config = [
|
||||
'baseURL' => 'https://api.yourdomain.com/api/v1/',
|
||||
'baseDYURL' => 'https://api.yourdomain.com/api/vpnnodes.php',
|
||||
'mainregisterURL' => 'https://yourdomain.com/#/register?code=',
|
||||
'paymentURL' => 'https://pay.yourdomain.com/',
|
||||
'telegramurl' => 'https://t.me/yourchannel',
|
||||
'kefuurl' => 'https://yourdomain.com/support/',
|
||||
'websiteURL' => 'https://yourdomain.com/',
|
||||
'crisptoken' => 'your-crisp-token',
|
||||
'banners' => [
|
||||
'https://cdn.yourdomain.com/banner1.png',
|
||||
'https://cdn.yourdomain.com/banner2.png'
|
||||
],
|
||||
'message' => 'OK',
|
||||
'code' => 1
|
||||
];
|
||||
|
||||
echo json_encode($config, JSON_PRETTY_PRINT);
|
||||
?>
|
||||
```
|
||||
|
||||
**Node.js 示例**:
|
||||
```javascript
|
||||
// config.js
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.get('/api/config', (req, res) => {
|
||||
res.json({
|
||||
baseURL: 'https://api.yourdomain.com/api/v1/',
|
||||
baseDYURL: 'https://api.yourdomain.com/api/vpnnodes.php',
|
||||
mainregisterURL: 'https://yourdomain.com/#/register?code=',
|
||||
paymentURL: 'https://pay.yourdomain.com/',
|
||||
telegramurl: 'https://t.me/yourchannel',
|
||||
kefuurl: 'https://yourdomain.com/support/',
|
||||
websiteURL: 'https://yourdomain.com/',
|
||||
crisptoken: 'your-crisp-token',
|
||||
banners: [
|
||||
'https://cdn.yourdomain.com/banner1.png'
|
||||
],
|
||||
message: 'OK',
|
||||
code: 1
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Config server running on port 3000');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全建议
|
||||
|
||||
### 1. 使用 HTTPS
|
||||
|
||||
- 所有 API 端点必须使用 HTTPS
|
||||
- 使用可信 CA 签发的 SSL 证书
|
||||
- 禁用 HTTP 访问
|
||||
|
||||
### 2. 请求验证
|
||||
|
||||
在请求头中添加验证信息:
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
request.addHeader("bid", BuildConfig.APPLICATION_ID)
|
||||
request.addHeader("appver", BuildConfig.VERSION_NAME)
|
||||
request.addHeader("device", android.os.Build.MODEL)
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
request.addValue(Bundle.main.bundleIdentifier ?? "", forHTTPHeaderField: "bid")
|
||||
request.addValue(appVersion, forHTTPHeaderField: "appver")
|
||||
request.addValue(UIDevice.current.model, forHTTPHeaderField: "device")
|
||||
```
|
||||
|
||||
### 3. 服务器端验证
|
||||
|
||||
在服务器端验证请求来源:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$bid = $_SERVER['HTTP_BID'] ?? '';
|
||||
$appver = $_SERVER['HTTP_APPVER'] ?? '';
|
||||
|
||||
// 验证 Bundle ID
|
||||
$allowedBids = ['com.yourcompany.uuvpn', 'com.yourcompany.uuvpn.dev'];
|
||||
if (!in_array($bid, $allowedBids)) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['code' => 0, 'message' => 'Unauthorized']));
|
||||
}
|
||||
|
||||
// 返回配置
|
||||
// ...
|
||||
?>
|
||||
```
|
||||
|
||||
### 4. 敏感信息保护
|
||||
|
||||
- 不要在配置文件中存储密钥或密码
|
||||
- 使用 token 而非明文密码
|
||||
- 定期更新 Crisp token
|
||||
|
||||
---
|
||||
|
||||
## iOS App Store 审核配置
|
||||
|
||||
### 审核模式配置
|
||||
|
||||
iOS 应用提交 App Store 审核时,需要隐藏外部支付链接:
|
||||
|
||||
```json
|
||||
{
|
||||
"baseURL": "https://api.yourdomain.com/api/v1/",
|
||||
"baseDYURL": "https://api.yourdomain.com/api/vpnnodes.php",
|
||||
"mainregisterURL": "https://yourdomain.com/#/register?code=",
|
||||
"paymentURL": "xx",
|
||||
"telegramurl": "https://t.me/yourchannel",
|
||||
"kefuurl": "https://yourdomain.com/support/",
|
||||
"websiteURL": "https://yourdomain.com/",
|
||||
"crisptoken": "your-crisp-token",
|
||||
"banners": [],
|
||||
"message": "OK",
|
||||
"code": 1
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `paymentURL` 设置为 `"xx"` 或 `"xxx"` (长度 ≤ 3)
|
||||
- `banners` 设置为空数组,避免显示包含支付信息的横幅
|
||||
|
||||
### 审核通过后配置
|
||||
|
||||
审核通过后,将配置改回正常模式:
|
||||
|
||||
```json
|
||||
{
|
||||
"paymentURL": "https://pay.yourdomain.com/",
|
||||
"banners": [
|
||||
"https://cdn.yourdomain.com/banner1.png"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 修改配置后应用没有更新怎么办?
|
||||
|
||||
**A**: 清除应用缓存或重新安装应用
|
||||
|
||||
**Android**:
|
||||
```bash
|
||||
adb shell pm clear com.yourcompany.uuvpn
|
||||
```
|
||||
|
||||
**iOS**: 卸载重装应用
|
||||
|
||||
### Q2: 配置请求超时怎么办?
|
||||
|
||||
**A**: 检查以下内容:
|
||||
- 服务器是否正常运行
|
||||
- HTTPS 证书是否有效
|
||||
- 网络连接是否正常
|
||||
- 考虑使用 CDN 加速
|
||||
|
||||
### Q3: 如何在不重新编译的情况下更改配置?
|
||||
|
||||
**A**: 配置是从远程 URL 动态获取的,只需修改服务器端的配置 JSON 即可,应用会在下次启动时自动获取最新配置
|
||||
|
||||
### Q4: 能否使用多个配置 URL?
|
||||
|
||||
**A**: 可以,通过在不同的编译变体中使用不同的配置 URL:
|
||||
|
||||
**Android**:
|
||||
```kotlin
|
||||
object ApiConfig {
|
||||
val CONFIG_URL = when (BuildConfig.BUILD_TYPE) {
|
||||
"debug" -> "https://dev.yourdomain.com/api/config.php"
|
||||
"release" -> "https://yourdomain.com/api/config.php"
|
||||
else -> "https://yourdomain.com/api/config.php"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**iOS**:
|
||||
```swift
|
||||
#if DEBUG
|
||||
let configURL = "https://dev.yourdomain.com/api/config.php"
|
||||
#else
|
||||
let configURL = "https://yourdomain.com/api/config.php"
|
||||
#endif
|
||||
```
|
||||
|
||||
### Q5: 配置加载失败应用会崩溃吗?
|
||||
|
||||
**A**: 不会,应用应该有默认配置作为后备:
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
fun getConfig(): ConfigResponse {
|
||||
return try {
|
||||
apiService.getConfig(configURL)
|
||||
} catch (e: Exception) {
|
||||
// 返回默认配置
|
||||
getDefaultConfig()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func loadConfig() async {
|
||||
do {
|
||||
config = try await fetchConfig()
|
||||
} catch {
|
||||
// 使用默认配置
|
||||
config = getDefaultConfig()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
配置 API 端点是 UUVPN 应用的核心步骤,正确的配置能确保应用正常工作:
|
||||
|
||||
1. ✅ 准备好 V2Board 服务器
|
||||
2. ✅ 创建配置 JSON 文件
|
||||
3. ✅ 部署到 HTTPS 服务器
|
||||
4. ✅ 在应用中配置 URL
|
||||
5. ✅ 测试验证配置
|
||||
6. ✅ 针对iOS审核特殊处理
|
||||
|
||||
如有问题,请参考 [GitHub Issues](https://github.com/nicolastinkl/UUVPN/issues) 或联系技术支持。
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "clash-foss"]
|
||||
path = core/src/foss/golang/clash
|
||||
url = https://github.com/MetaCubeX/mihomo
|
||||
branch = Meta
|
||||
@@ -1,21 +1,15 @@
|
||||
## LiteVPN
|
||||
|
||||
A Graphical user interface of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) for Android
|
||||
|
||||
### Feature
|
||||
|
||||
Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/com.github.metacubex.clash.meta/)
|
||||
## UUVPN
|
||||
|
||||
[https://youtube.com/shorts/zI1hrpFJbtg?feature=share](https://youtube.com/shorts/zI1hrpFJbtg?feature=share)
|
||||
|
||||
### Requirement
|
||||
|
||||
- Android 5.0+ (minimum)
|
||||
- Android 7.0+ (recommend)
|
||||
- `armeabi-v7a` , `arm64-v8a`, `x86` or `x86_64` Architecture
|
||||
|
||||
|
||||
|
||||
### Build
|
||||
|
||||
1. Update submodules
|
||||
@@ -47,33 +41,104 @@ Feature of [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
|
||||
./gradlew app:assembleMeta-AlphaRelease
|
||||
```
|
||||
|
||||
### Automation
|
||||
|
||||
APP package name is `com.github.metacubex.clash.meta`
|
||||
6. 教程:修改 API 后台地址,api请求内容如下 , 可选增加订阅地址转换地址,方便调用。
|
||||
|
||||
- Toggle Clash.Meta service status
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.TOGGLE_CLASH`
|
||||
- Start Clash.Meta service
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.START_CLASH`
|
||||
- Stop Clash.Meta service
|
||||
- Send intent to activity `com.github.kr328.clash.ExternalControlActivity` with action `com.github.metacubex.clash.meta.action.STOP_CLASH`
|
||||
- Import a profile
|
||||
- URL Scheme `clash://install-config?url=<encoded URI>` or `clashmeta://install-config?url=<encoded URI>`
|
||||
Please read 服务器配置+Kotlin-Android 文档教程.docx
|
||||
|
||||
### Contribution and Project Maintenance
|
||||

|
||||
|
||||
#### Meta Kernel
|
||||
```
|
||||
返回值可以直接写在阿里云oss 中,速度更快
|
||||
{
|
||||
"baseURL": "https://api.0008.uk/api/v1/",
|
||||
"baseDYURL": "https://api.gooapis.com/api/vpnnodes.php",
|
||||
"mainregisterURL": "https://lelian.app/#/register?code=",
|
||||
"paymentURL": "xxxxx",
|
||||
"telegramurl": "https://t.me/fastlink",
|
||||
"kefuurl": "https://gooapis.com/fastlink/",
|
||||
"websiteURL": "https://gooapis.com/fastlink/",
|
||||
"crisptoken": "5546c6ea-4b1e-41bc-80e4-4b6648cbca76",
|
||||
"banners": [
|
||||
"https://image.gooapis.com/api/images/12-11-56.png",
|
||||
"https://image.gooapis.com/api/images/12-44-57.png",
|
||||
"https://image.gooapis.com/api/images/12-47-03.png"
|
||||
],
|
||||
"message": "OK",
|
||||
"code": 1
|
||||
}
|
||||
```
|
||||
|
||||
- CMFA uses the kernel from `android-real` branch under `MetaCubeX/Clash.Meta`, which is a merge of the main `Alpha` branch and `android-open`.
|
||||
- If you want to contribute to the kernel, make PRs to `Alpha` branch of the Meta kernel repository.
|
||||
- If you want to contribute Android-specific patches to the kernel, make PRs to `android-open` branch of the Meta kernel repository.
|
||||

|
||||
|
||||
#### Maintenance
|
||||
```
|
||||
测试的 URL 配置地址: https://vungles.com/api/test/config
|
||||
测试账号:
|
||||
binance3980@gmail.com
|
||||
Tinkl123
|
||||
```
|
||||
|
||||
- When `MetaCubeX/Clash.Meta` kernel is updated to a new version, the `Update Dependencies` actions in this repo will be triggered automatically.
|
||||
- It will pull the new version of the meta kernel, update all the golang dependencies, and create a PR without manual intervention.
|
||||
- If there is any compile error in PR, you need to fix it before merging. Alternatively, you may merge the PR directly.
|
||||
- Manually triggering `Build Pre-Release` actions will compile and publish a `PreRelease` version.
|
||||
- Manually triggering `Build Release` actions will compile, tag and publish a `Release` version.
|
||||
- You must fill the blank `Release Tag` with the tag you want to release in the format of `v1.2.3`.
|
||||
- `versionName` and `versionCode` in `build.gradle.kts` will be automatically bumped to the tag you filled above.
|
||||
上面字段的意思:
|
||||
以下是您提供的 JSON 配置文件的字段解释:
|
||||
|
||||
### 1. **baseURL**
|
||||
- **描述**:基础 API URL,用于访问主要的 API 端点。
|
||||
- **示例值**:`https://api.0008.uk/api/v1/`
|
||||
- **用途**:所有与服务器的 API 请求都将基于此 URL 进行。例如,可以在此基础上附加不同的 API 路径来访问特定资源。
|
||||
|
||||
### 2. **baseDYURL**
|
||||
- **描述**:另一个 API URL,与 `baseURL` 不同,用于访问特定的 VPN默认测试节点。
|
||||
- **示例值**:`https://api.gooapis.com/api/vpnnodes.php`
|
||||
- **用途**:通常用于请求特定的 VPN 节点数据或其他与代理/网络连接相关的信息。
|
||||
|
||||
### 3. **mainregisterURL**
|
||||
- **描述**:用户注册页面推广的 URL,通常包含一个唯一的代码参数,用于推广注册过程。
|
||||
- **示例值**:`https://lelian.app/#/register?code=`
|
||||
- **用途**:这个 URL 用于引导用户进行注册,`code` 参数可能是用户的注册邀请码或其他标识符。
|
||||
|
||||
### 4. **paymentURL**
|
||||
- **描述**:支付相关的 URL,可能指向支付网关或交易页面。
|
||||
- **示例值**:`xxxxx`
|
||||
- **用途**:用于支付的跳转链接,具体地址没有提供。
|
||||
|
||||
### 5. **telegramurl**
|
||||
- **描述**:指向 Telegram 频道或群组的 URL。
|
||||
- **示例值**:`https://t.me/fastlink`
|
||||
- **用途**:用户可以通过此链接访问 Telegram 频道,可能用于接收通知、更新或客服支持。
|
||||
|
||||
### 6. **kefuurl**
|
||||
- **描述**:客服支持页面的 URL。
|
||||
- **示例值**:`https://gooapis.com/fastlink/`
|
||||
- **用途**:提供给用户访问的在线客服页面,通常是支持问题解答和帮助解决问题的页面。
|
||||
|
||||
### 7. **websiteURL**
|
||||
- **描述**:指向网站首页的 URL。
|
||||
- **示例值**:`https://gooapis.com/fastlink/`
|
||||
- **用途**:指向官方网站,用户可以通过该链接访问相关产品或服务的详细信息。
|
||||
|
||||
### 8. **crisptoken**
|
||||
- **描述**:一个唯一的令牌,通常用于身份验证或授权。
|
||||
- **示例值**:`5546c6ea-4b1e-41bc-80e4-4b6648cbca76`
|
||||
- **用途**:令牌用于验证请求的合法性,防止未经授权的访问。可能用于 API 请求中传递身份信息。
|
||||
|
||||
### 9. **banners**
|
||||
- **描述**:一个包含多个图片 URL 的数组,通常用于在网页或应用中展示横幅广告或图片。
|
||||
- **示例值**:
|
||||
```json
|
||||
[
|
||||
"https://image.gooapis.com/api/images/12-11-56.png",
|
||||
"https://image.gooapis.com/api/images/12-44-57.png",
|
||||
"https://image.gooapis.com/api/images/12-47-03.png"
|
||||
]
|
||||
```
|
||||
- **用途**:这些链接指向可以展示在前端界面上的横幅图片,可以是广告、通知、促销等。
|
||||
|
||||
### 10. **message**
|
||||
- **描述**:一个消息字段,通常用于描述当前操作或请求的状态。
|
||||
- **示例值**:`OK`
|
||||
- **用途**:这是 API 响应中的状态信息,`OK` 表示操作成功。
|
||||
|
||||
### 11. **code**
|
||||
- **描述**:一个数字代码,表示响应的状态。
|
||||
- **示例值**:`1`
|
||||
- **用途**:通常用于表示请求是否成功,`1` 可能表示操作成功,其他数字可能表示错误或特定的状态。
|
||||
|
||||
@@ -6,11 +6,13 @@ plugins {
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.android.application")
|
||||
|
||||
// kotlin("jvm") version "1.8.10" // 使用兼容的 Kotlin 版本
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":hideapi"))
|
||||
|
||||
implementation(project(":core"))
|
||||
implementation(project(":service"))
|
||||
implementation(project(":design"))
|
||||
@@ -24,6 +26,25 @@ dependencies {
|
||||
implementation(libs.androidx.coordinator)
|
||||
implementation(libs.androidx.recyclerview)
|
||||
implementation(libs.google.material)
|
||||
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.appcompat/appcompat
|
||||
|
||||
// implementation(files("libs/appcompat-1.7.0.aar"))
|
||||
|
||||
// implementation("im.crisp:crisp-sdk:2.0.5")
|
||||
|
||||
// implementation("im.crisp:crisp-sdk:2.0.5")
|
||||
// If you're using AndroidX
|
||||
//implementation("androidx.multidex:multidex:2.0.1")
|
||||
// If you're not using AndroidX
|
||||
//implementation ("com.android.support:multidex:1.0.3")
|
||||
|
||||
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
||||
}
|
||||
|
||||
tasks.getByName("clean", type = Delete::class) {
|
||||
@@ -78,4 +99,14 @@ afterEvaluate {
|
||||
|
||||
tasks.getByName("clean", type = Delete::class) {
|
||||
delete(file(geoFilesDownloadDir))
|
||||
}
|
||||
}
|
||||
android {
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
getByName("debug") {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- 网络权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
@@ -21,6 +24,7 @@
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:fullBackupContent="@xml/full_backup_content"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -38,12 +42,15 @@
|
||||
android:name="releaseCode"
|
||||
android:value="@integer/release_code" />
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/launch_name"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/SplashTheme" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -66,9 +73,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="clash"/>
|
||||
<data android:scheme="clashmeta"/>
|
||||
<data android:host="install-config"/>
|
||||
<data android:scheme="clash-----------"/>
|
||||
<data android:scheme="clashmeta-----------"/>
|
||||
<data android:host="install-config-----------"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
|
||||
@@ -83,6 +90,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".ApkBrokenActivity"
|
||||
android:configChanges="uiMode"
|
||||
@@ -170,14 +180,109 @@
|
||||
android:exported="false"
|
||||
android:label="@string/files" />
|
||||
|
||||
<activity
|
||||
android:name=".ProfileActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="设置中心"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true" />
|
||||
|
||||
<activity
|
||||
android:name=".PlansActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="购买订阅"
|
||||
/>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".MyOrdersActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的订单"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ConfigOrderActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="配置订阅"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".SubmitOrderActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="订单详情"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".GongdanActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的工单"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".NewGongdanActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="新建工单"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".H5WebActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
/>
|
||||
<activity
|
||||
android:name=".TicketDetailActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label=""
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".RegisterActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="注册"
|
||||
/>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".InvitationActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的邀请"
|
||||
/>
|
||||
|
||||
<service
|
||||
android:name=".LogcatService"
|
||||
android:exported="false"
|
||||
android:label="@string/clash_logcat" />
|
||||
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_logo_service"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:label="@string/launch_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"v":"5.9.3","fr":29.9700012207031,"ip":0,"op":60.0000024438501,"w":1920,"h":1080,"nm":"1_Loading_Line","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Blue Line","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,539,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-1,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[800,800],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.212477396049,0.540471813725,0.898039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":80,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.9],"y":[0]},"t":15,"s":[0]},{"t":60.0000024438501,"s":[99.9]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.9],"y":[0]},"t":0,"s":[0.01]},{"t":45.0000018328876,"s":[100]}],"ix":2},"o":{"a":0,"k":-1,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150.000006109625,"st":0,"ct":1,"bm":0}],"markers":[{"tm":60.0000024438501,"cm":"END (No Loop)","dr":0}]}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -84,6 +84,7 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.BootstrapTheme)
|
||||
super.onCreate(savedInstanceState)
|
||||
applyDayNight()
|
||||
|
||||
@@ -189,11 +190,14 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
}
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
val dayNight = queryDayNight(config)
|
||||
// val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
@@ -209,7 +213,7 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = dayNight
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.ConfigOrderDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
//ConfigOrderDesign
|
||||
|
||||
class ConfigOrderActivity : BaseActivity<ConfigOrderDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
// 获取 Intent 并接收传递的 PlanData 对象
|
||||
// 使用 Parcelable 获取对象
|
||||
val planData = intent.getSerializableExtra("planData") as? PlanData
|
||||
|
||||
val design = ConfigOrderDesign(this)
|
||||
setContentDesign(design)
|
||||
design.fillData(planData)
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
ConfigOrderDesign.Request.SubmitOrder -> {
|
||||
startActivity(SubmitOrderActivity::class.intent.putExtra("trade_no",design.trade_no))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.view.Gravity
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.TicketsDataAdapter
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.TicketsData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class GongdanActivity : BaseListActivity<TicketsData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
|
||||
return TicketsDataAdapter(onItemClick = { planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
val intent = Intent(this, TicketDetailActivity::class.java)
|
||||
intent.putExtra("ticketid", planData.id ?: 0)
|
||||
startActivity(intent)
|
||||
}, onCloseItemClick = { planData ->
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "确定关闭当前工单吗?",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
// startActivity(PlansActivity::class.intent)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
val fieldMap = mutableMapOf<String, Int>()
|
||||
|
||||
fieldMap.put("id",planData.id ?: 0)
|
||||
|
||||
safeApiRequestCall {
|
||||
apiService.closeTicket(
|
||||
PreferenceManager.loginauthData,
|
||||
request = fieldMap
|
||||
)
|
||||
}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@GongdanActivity, "关闭成功", Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
refereshData()
|
||||
|
||||
}else{
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
this@GongdanActivity,
|
||||
"关闭失败",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
// Create the top-right icon
|
||||
val iconView = Button(this)
|
||||
iconView.setText("新建工单")
|
||||
iconView.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.TOP or Gravity.END
|
||||
).apply {
|
||||
marginEnd = 16 // Add margin to the right
|
||||
topMargin = 16 // Add margin to the top
|
||||
}
|
||||
// Set the background tint programmatically
|
||||
/* ViewCompat.setBackgroundTintList(
|
||||
iconView,
|
||||
ContextCompat.getColorStateList(this, R.color.blue)
|
||||
)
|
||||
*/
|
||||
val colorStateList: ColorStateList = ColorStateList(
|
||||
arrayOf<IntArray>(intArrayOf(android.R.attr.state_pressed)), intArrayOf(
|
||||
R.color.blue
|
||||
)
|
||||
)
|
||||
iconView.setBackgroundTintList(colorStateList)
|
||||
iconView.setOnClickListener {
|
||||
val intent = Intent(this@GongdanActivity, NewGongdanActivity::class.java)
|
||||
//startActivity(intent)
|
||||
resultLauncher.launch(intent)
|
||||
|
||||
}
|
||||
mainBinding?.activityBarLayout?.addView(iconView)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val data = result.data
|
||||
val resultData = data?.getStringExtra("resultKey")
|
||||
// Process the result
|
||||
println("registerForActivityResult : resultData : ${resultData} ")
|
||||
if (resultData == "shuaxin"){
|
||||
|
||||
refereshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadData(page: Int, callback: (List<TicketsData>?, Throwable?) -> Unit) {
|
||||
|
||||
|
||||
// android:backgroundTint="@color/blue"
|
||||
// android:background="@drawable/rounded_button_background"
|
||||
|
||||
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getTickets(PreferenceManager.loginauthData)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.design.H5WebViewDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
|
||||
|
||||
class H5WebActivity : BaseActivity<H5WebViewDesign>() {
|
||||
override suspend fun main() {
|
||||
|
||||
val design = H5WebViewDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
//Uri.parse(PreferenceManager.getConfigFromPreferences(this@ProfileActivity)?.telegramurl)
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
val url = intent.getStringExtra("url")
|
||||
|
||||
if (url != null && url.length > 1){
|
||||
title = url
|
||||
design.refereshApplyFrom()
|
||||
design.breaseURL(url)
|
||||
}else{
|
||||
|
||||
PreferenceManager.getConfigFromPreferences(this@H5WebActivity)?.kefuurl?.let {
|
||||
design.breaseURL(
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import kotlinx.coroutines.isActive
|
||||
|
||||
class HelpActivity : BaseActivity<HelpDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = HelpDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
while (isActive) {
|
||||
events.receive()
|
||||
}
|
||||
// val design = HelpDesign(this) {
|
||||
// startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
// }
|
||||
//
|
||||
// setContentDesign(design)
|
||||
//
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.InvitationDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class InvitationActivity : BaseActivity<InvitationDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
|
||||
val design = InvitationDesign(this)
|
||||
setContentDesign(design)
|
||||
design.requestdataDisible()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
|
||||
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
|
||||
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
|
||||
import com.github.kr328.clash.common.compat.isSystemBarsTranslucentCompat
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityProfileBinding
|
||||
import com.github.kr328.clash.design.ui.DayNight
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.design.util.layoutInflater
|
||||
import com.github.kr328.clash.design.util.resolveThemedBoolean
|
||||
import com.github.kr328.clash.design.util.resolveThemedColor
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.LoginResponse
|
||||
import com.github.kr328.clash.network.safeApiCall
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
|
||||
private var dayNight: DayNight = DayNight.Day
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
// 禁用返回键
|
||||
// 不调用 super.onBackPressed()
|
||||
}
|
||||
|
||||
|
||||
private var isPasswordVisible = false
|
||||
private var configRetryCount = 0
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val binding = ActivityLoginBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
applyDayNight()
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
binding.togglePasswordVisibility.setOnClickListener {
|
||||
|
||||
isPasswordVisible = !isPasswordVisible
|
||||
|
||||
if (isPasswordVisible) {
|
||||
// 显示明文密码
|
||||
binding.loginPasswordEditText.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
binding.togglePasswordVisibility.setImageResource(R.drawable.visibility_24px) // 更改图标为显示状态
|
||||
} else {
|
||||
// 显示星号密码
|
||||
binding.loginPasswordEditText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
binding.togglePasswordVisibility.setImageResource(R.drawable.visibility_off_24px) // 更改图标为隐藏状态
|
||||
}
|
||||
// 将光标移动到文本的末尾
|
||||
binding.loginPasswordEditText.setSelection(binding.loginPasswordEditText.text.length)
|
||||
|
||||
}
|
||||
binding.loginLoginButton.setOnClickListener {
|
||||
val email = binding.loginEmailEditText.text.toString()
|
||||
val password = binding.loginPasswordEditText.text.toString()
|
||||
val isAgreementChecked = binding.loginAgreementCheckBox.isChecked
|
||||
|
||||
|
||||
if (email.isEmpty() || password.isEmpty()) {
|
||||
Toast.makeText(this, "请输入邮箱和密码", Toast.LENGTH_SHORT).show()
|
||||
} else if (!isAgreementChecked) {
|
||||
Toast.makeText(this, "请同意隐私政策和用户协议", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
// Hide the keyboard
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val currentFocusView = currentFocus
|
||||
if (currentFocusView != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
|
||||
}
|
||||
performLogin(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
binding.loginForgotPassword.setOnClickListener {
|
||||
// Navigate to Forgot Password screen or show dialog
|
||||
}
|
||||
|
||||
binding.loginRegister.setOnClickListener {
|
||||
// Navigate to Register screen
|
||||
//startActivity(RegisterActivity::class::intent)
|
||||
// startActivity(RegisterActivity::class::intent)
|
||||
val intent = Intent(this@LoginActivity, RegisterActivity::class.java)
|
||||
startActivity(intent)
|
||||
// finish() // It
|
||||
|
||||
}
|
||||
|
||||
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
getconfig()
|
||||
}
|
||||
|
||||
private fun getconfig(){
|
||||
|
||||
runOnUiThread {
|
||||
LoadingDialog.show(this, "正在初始化...")
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
safeApiCall {apiService.getConfig()}.let {
|
||||
if (it != null) {
|
||||
if (it.code == 1 ) {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
PreferenceManager.saveConfigToPreferences( it)
|
||||
//重新初始化 ApiClint
|
||||
}
|
||||
}else{
|
||||
configRetryCount++
|
||||
if (configRetryCount < 3) {
|
||||
getconfig()
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
Toast.makeText(this@LoginActivity, "初始化失败,请检查配置是否正确", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* withContext(Dispatchers.Main) {
|
||||
* if (response.isSuccessful) {
|
||||
* val userData = response.body()
|
||||
* // 更新 UI
|
||||
* } else {
|
||||
* Toast.makeText(this@MainActivity, "Failed to fetch user data", Toast.LENGTH_SHORT).show()
|
||||
* }
|
||||
* }
|
||||
* */
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
// val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
|
||||
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
|
||||
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
private fun performLogin(email: String, password: String) {
|
||||
// Perform login logic here, possibly calling an API
|
||||
|
||||
// Show the loading indicator with a custom message
|
||||
LoadingDialog.show(this, "正在登录...")
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.loginUser(LoginRequest(email,password))}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
val response: LoginResponse? = it.body()
|
||||
println(response )
|
||||
println(it.errorBody()?.string() ?: "")
|
||||
if (response?.data != null){
|
||||
PreferenceManager.loginemail = email
|
||||
PreferenceManager.loginToken = response.data?.token ?: ""
|
||||
PreferenceManager.loginauthData = response.data?.auth_data ?: ""
|
||||
PreferenceManager.isLoginin = true
|
||||
// saveLoginMailToken(this@LoginActivity,email)
|
||||
// saveLoginToken(this@LoginActivity, response.data.token ?: "")
|
||||
// saveLoginAuthData(this@LoginActivity, response.data.auth_data ?: "")
|
||||
// saveLoginStatus(this@LoginActivity,true)
|
||||
//获取用户信息
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags =
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
finish() // It
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Handle error response, even if it's 422
|
||||
try {
|
||||
val errorResponse = it?.errorBody()?.string() ?: ""
|
||||
print(errorResponse)
|
||||
val errorJson = JSONObject(errorResponse)
|
||||
val message = errorJson.optString("message")
|
||||
// Process the error message or show it to the user
|
||||
if (!message.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@LoginActivity, "登录失败:${message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
println( "Message: $message, ")
|
||||
} catch (e: JSONException) {
|
||||
// Handle JSON parsing error
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@LoginActivity, "登录失败:${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
@@ -15,27 +17,54 @@ import com.github.kr328.clash.util.withClash
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import com.github.kr328.clash.core.bridge.*
|
||||
import com.github.kr328.clash.core.model.FetchStatus
|
||||
import com.github.kr328.clash.core.model.ProxySort
|
||||
import com.github.kr328.clash.core.model.TunnelState
|
||||
import com.github.kr328.clash.design.NewProfileDesign
|
||||
import com.github.kr328.clash.design.PreferenceLiveData
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.R
|
||||
import com.github.kr328.clash.design.component.ProxyMenu
|
||||
import com.github.kr328.clash.design.dialog.requestModelTextInput
|
||||
import com.github.kr328.clash.design.dialog.withModelProgressBar
|
||||
import com.github.kr328.clash.design.network.APIGlobalObject
|
||||
import com.github.kr328.clash.design.util.ValidatorHttpUrl
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.design.util.showExceptionToast
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.SubscribeData
|
||||
import com.github.kr328.clash.network.SubscribeResponse
|
||||
import com.github.kr328.clash.network.safeApiCall
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.service.ProfileManager
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.util.PreferenceManagerServer
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
private var subData: SubscribeData? = null
|
||||
|
||||
|
||||
|
||||
private val selectnodeName: LiveData<String> by lazy {
|
||||
PreferenceLiveData(this)
|
||||
@@ -44,100 +73,372 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = MainDesign(this)
|
||||
|
||||
PreferenceManager.init(this)
|
||||
if (PreferenceManager.isLoginin){
|
||||
setContentDesign(design)
|
||||
|
||||
setContentDesign(design)
|
||||
design.fetch()
|
||||
design.startBannsers()
|
||||
|
||||
design.fetch()
|
||||
design.startBannsers()
|
||||
|
||||
selectnodeName.observe(this, androidx.lifecycle.Observer { newValue ->
|
||||
})
|
||||
//1 : 查询 config 信息
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
// CoroutineScope(Dispatchers.IO).launch { }
|
||||
|
||||
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val cacheExpiryTime = 10 * 60 * 1000 // 10 minutes in milliseconds
|
||||
|
||||
|
||||
// 如果缓存数据存在且未超过 10 分钟,使用缓存数据;否则,重新请求数据
|
||||
if ( PreferenceManager.cached_data != null && (currentTime - PreferenceManager.cache_timestamp) <= cacheExpiryTime) {
|
||||
// 使用缓存数据
|
||||
val gson = Gson()
|
||||
val configResponse = gson.fromJson(PreferenceManager.cached_data, ConfigResponse::class.java)
|
||||
println(configResponse)
|
||||
println("Using cached data: ${PreferenceManager.cached_data}")
|
||||
println("Using cached_userSubscritedata : ${PreferenceManager.cached_userSubscritedata}")
|
||||
if (PreferenceManager.cached_userSubscritedata.length > 0){
|
||||
val subcache = gson.fromJson(PreferenceManager.cached_userSubscritedata, SubscribeResponse::class.java)
|
||||
subData = subcache.data
|
||||
|
||||
//存储到本地
|
||||
val plainid = subcache.data.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (subcache.data.u ?: 0) + (subcache.data.d ?: 0) > (subcache.data.transfer_enable ?: 0) && (subcache.data.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
//删除所有节点
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 0){
|
||||
allProfiles.forEach {
|
||||
delete(it.uuid)
|
||||
println("Deleted ${it.uuid} ${it.name}: ${it.source}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//删除所有节点文件
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "您的订阅已到期,请续费订阅开启服务",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
startActivity(PlansActivity::class.intent)
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.ActivityStart,
|
||||
Event.ServiceRecreated,
|
||||
Event.ClashStop, Event.ClashStart,
|
||||
Event.ProfileLoaded, Event.ProfileChanged -> design.fetch()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
}
|
||||
design.requests.onReceive {
|
||||
when (it) {
|
||||
MainDesign.Request.ToggleStatus -> {
|
||||
if (clashRunning)
|
||||
stopClashService()
|
||||
else
|
||||
design.startClash()
|
||||
}
|
||||
MainDesign.Request.OpenProxy ->
|
||||
startActivity(ProxyActivity::class.intent)
|
||||
MainDesign.Request.OpenProfiles ->
|
||||
startActivity(ProfilesActivity::class.intent)
|
||||
MainDesign.Request.OpenProviders ->
|
||||
startActivity(ProvidersActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs ->
|
||||
startActivity(LogsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettings ->
|
||||
startActivity(SettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenHelp ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
MainDesign.Request.OpenAbout ->
|
||||
design.showAbout(queryAppVersionName())
|
||||
MainDesign.Request.OpenSettingsDIY ->
|
||||
startActivity(NetworkSettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettingsKEFU ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenModeDirect ->
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
println("subData : ${subData}")
|
||||
|
||||
|
||||
|
||||
}else{
|
||||
// 缓存已过期,重新请求数据
|
||||
println("Cache expired, requesting new data")
|
||||
// 重新请求数据
|
||||
//requestNewData()
|
||||
LoadingDialog.show(this, "正在更新节点数据...")
|
||||
configRequesting()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
selectnodeName.observe(this, androidx.lifecycle.Observer { newValue ->
|
||||
})
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.ActivityStart,
|
||||
Event.ServiceRecreated,
|
||||
Event.ClashStop, Event.ClashStart,
|
||||
Event.ProfileLoaded, Event.ProfileChanged -> design.fetch()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
design.requests.onReceive {
|
||||
when (it) {
|
||||
MainDesign.Request.ToggleStatus -> {
|
||||
if (clashRunning)
|
||||
stopClashService()
|
||||
else
|
||||
design.startClash()
|
||||
}
|
||||
MainDesign.Request.OpenProxy ->
|
||||
startActivity(ProxyActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenProfiles ->
|
||||
startActivity(ProfilesActivity::class.intent)
|
||||
MainDesign.Request.OpenProviders ->
|
||||
startActivity(ProvidersActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs ->
|
||||
startActivity(LogsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettings ->
|
||||
startActivity(SettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenHelp ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
MainDesign.Request.OpenAbout ->
|
||||
design.showAbout(queryAppVersionName())
|
||||
MainDesign.Request.OpenSettingsDIY ->
|
||||
startActivity(ProfileActivity::class.intent)
|
||||
MainDesign.Request.OpenSettingsKEFU ->
|
||||
startActivity(PlansActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenModeDirect ->
|
||||
|
||||
withClash {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Direct.name
|
||||
val override = queryOverride(Clash.OverrideSlot.Session)
|
||||
override.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, override)
|
||||
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeGlobal -> withClash {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Global.name
|
||||
LoadingDialog.show(this@MainActivity, "正在切换中...")
|
||||
|
||||
val names = withClash { queryProxyGroupNames(uiStore.proxyExcludeNotSelectable) }
|
||||
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Global
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
|
||||
}
|
||||
startServer()
|
||||
|
||||
}
|
||||
}
|
||||
MainDesign.Request.OpenModeRule -> withClash {
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Rule.name
|
||||
LoadingDialog.show(this@MainActivity, "正在切换中...")
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Rule
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
}
|
||||
startServer()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeGlobal -> withClash {
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Global
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
MainDesign.Request.OpenModeMenu -> {
|
||||
design.OpenModeMenu()
|
||||
}
|
||||
}
|
||||
MainDesign.Request.OpenModeRule -> withClash {
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
MainDesign.Request.OpenChangeMode -> {
|
||||
design.OpenModeMenu()
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeSheet ->{
|
||||
design.OpenModeSheet()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clashRunning) {
|
||||
ticker.onReceive {
|
||||
design.fetchTraffic()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clashRunning) {
|
||||
ticker.onReceive {
|
||||
design.fetchTraffic()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}else{
|
||||
startActivity(LoginActivity::class.intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun configRequesting(retryCount: Int = 0){
|
||||
val gson = Gson()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
/*apiService.getConfig().let {
|
||||
if (it.isSuccessful) {
|
||||
val response: ConfigResponse = it.body() ?: throw Exception("Response body is null")
|
||||
|
||||
if (response.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences(response)
|
||||
//更新缓存数据
|
||||
PreferenceManager.cache_timestamp = currentTime
|
||||
PreferenceManager.cached_data = gson.toJson( it.body())
|
||||
println("API Response: ${gson.toJson( it.body())}")
|
||||
//重新初始化 ApiClint
|
||||
//2 : 获取订阅节点信息
|
||||
performSubscrite()
|
||||
}
|
||||
|
||||
} else {
|
||||
println("API Error: ${it.errorBody()}")
|
||||
configRequesting()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
safeApiCall {apiService.getConfig()}.let {
|
||||
if (it !=null){
|
||||
if (it.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences(it)
|
||||
//更新缓存数据
|
||||
PreferenceManager.cache_timestamp = currentTime
|
||||
PreferenceManager.cached_data = gson.toJson( it)
|
||||
println("API Response: ${it}")
|
||||
//重新初始化 ApiClint
|
||||
//2 : 获取订阅节点信息
|
||||
performSubscrite()
|
||||
}
|
||||
}else{
|
||||
if (retryCount < 3) {
|
||||
configRequesting(retryCount + 1)
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "初始化失败,请检查配置是否正确",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "",
|
||||
onPositiveClick = {
|
||||
},
|
||||
onNegativeClick = {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSubscrite(){
|
||||
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.getSubscribe(PreferenceManager.loginauthData)}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
println(" performSubscrite : ${it.body()?.data}")
|
||||
subData = it.body()?.data
|
||||
|
||||
val gson = Gson()
|
||||
PreferenceManager.cached_userSubscritedata = gson.toJson( it.body())
|
||||
APIGlobalObject.subData = it.body()?.data
|
||||
//存储到本地
|
||||
val plainid = it.body()?.data?.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (it.body()?.data?.u ?: 0) + (it.body()?.data?.d ?: 0) > (it.body()?.data?.transfer_enable ?: 0) && (it.body()?.data?.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){
|
||||
allProfiles.forEach { delete(it.uuid) }
|
||||
//删除所有节点
|
||||
}
|
||||
}
|
||||
|
||||
//删除所有节点文件
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "您的订阅已到期,请续费订阅开启服务",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
startActivity(PlansActivity::class.intent)
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}else{
|
||||
//更新本地节点
|
||||
withProfile {
|
||||
var profileActive = queryActive()
|
||||
if (profileActive != null){
|
||||
try {
|
||||
println(">>>>>>>>> 更新节点订阅地址:${profileActive.uuid} ${profileActive.source}")
|
||||
update(profileActive.uuid)
|
||||
}catch (e: Exception){
|
||||
println(">>>>>>>>> 更新节点订阅地址 catch:${e.message}")
|
||||
}
|
||||
finally {
|
||||
println(">>>>>>>>> 更新节点订阅地址 完成 finally")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun MainDesign.fetch() {
|
||||
setClashRunning(clashRunning)
|
||||
|
||||
val state = withClash {
|
||||
queryTunnelState()
|
||||
}
|
||||
|
||||
println("运行时查询系统mode模式: ${state}")
|
||||
|
||||
val providers = withClash {
|
||||
queryProviders()
|
||||
}
|
||||
android.util.Log.i("MainActivity","fetch Info: ${state.mode.name} ${providers.count()}")
|
||||
|
||||
if (providers.isNotEmpty()) {
|
||||
providers.forEach { android.util.Log.i("MainActivity", "The element is ${it.name}") }
|
||||
println("模式providers: ${providers}")
|
||||
providers.forEach { println("The element is ${it.name}") }
|
||||
}
|
||||
|
||||
setMode(state.mode)
|
||||
@@ -145,24 +446,27 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
withProfile {
|
||||
setProfileName(queryActive()?.name)
|
||||
|
||||
setSelectNodeName(PreferenceManager.selectnodeName)
|
||||
|
||||
setSelectNodeName(PreferenceManager.selectnodeName )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//查询上传速度和下载速度
|
||||
private suspend fun MainDesign.fetchTraffic() {
|
||||
withClash {
|
||||
setForwarded(queryTrafficTotal())
|
||||
|
||||
// setForwarded(queryTrafficTotal())
|
||||
|
||||
setUploadedSeep(queryTrafficTotal())
|
||||
setDownloadedSeep(queryTrafficTotal())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MainDesign.startClash() {
|
||||
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){ //删除超过多余的 1 个节点之外的节点
|
||||
allProfiles.forEach { delete(it.uuid) }
|
||||
//删除所有节点
|
||||
}
|
||||
@@ -180,26 +484,123 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
// }
|
||||
val activeProfile = withProfile { queryActive() }
|
||||
println("当前订阅文件:${activeProfile}")
|
||||
if (activeProfile == null ) { //|| !activeProfile.imported
|
||||
|
||||
if (activeProfile == null || !activeProfile.imported) {
|
||||
|
||||
showToast("正在请求节点数据....", ToastDuration.Long) {
|
||||
|
||||
}
|
||||
LoadingDialog.show(this@MainActivity, "正在请求节点数据...")
|
||||
|
||||
// showToast("正在请求节点数据....", ToastDuration.Long)
|
||||
|
||||
//create(Profile.Type.Url, "VPN")
|
||||
withProfile{
|
||||
val uuid: UUID = create(Profile.Type.Url,"VPN","https://api.0009.uk/api/v1/client/subscribe?token=5652e3eec20c553047dbe3fcf8754090")
|
||||
commit(uuid){
|
||||
android.util.Log.i("commit",">> ${it.progress} ${it.action} ${it.args.toString()} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
println(">>>>>>>>>${subData}")
|
||||
//https://hongxingdl.com/hxvip?token=bc2f2839ccc608f34d6a6fc90b6e7f15
|
||||
//https://hongxingdl.com/api/v1/client/subscribe?token=bc2f2839ccc608f34d6a6fc90b6e7f15
|
||||
//"https://api.0009.uk/api/v1/client/subscribe?token=5652e3eec20c553047dbe3fcf8754090"
|
||||
if (subData?.subscribe_url != null && subData?.plan?.name != null && subData?.plan?.id != null) {
|
||||
var suburl = subData?.subscribe_url ?: ""
|
||||
var encodeURL = java.net.URLEncoder.encode(suburl, "utf-8")
|
||||
encodeURL = encodeURL.replace("?","%3F")
|
||||
var newULR =suburl
|
||||
|
||||
println(">>>>>>>>> "+suburl)
|
||||
if( ApiClientConfig.ConfigNodeURL.length > 3){
|
||||
newULR = ApiClientConfig.ConfigNodeURL + encodeURL
|
||||
}
|
||||
|
||||
println(">>>>>>>>> "+newULR)
|
||||
|
||||
//val uuid: UUID = create(Profile.Type.Url,subData?.plan?.name ?: "VPN",newULR)
|
||||
|
||||
launch {
|
||||
val uuid2 = withProfile {
|
||||
val type = Profile.Type.Url
|
||||
val name = subData?.plan?.name ?: "VPN"
|
||||
|
||||
create(type, name).also {
|
||||
patch(it, name, newULR, 0)
|
||||
}
|
||||
}
|
||||
|
||||
withProcessing { updateStatus ->
|
||||
withProfile {
|
||||
|
||||
try {
|
||||
commit(uuid2) {
|
||||
|
||||
launch {
|
||||
activeProfile()
|
||||
updateStatus(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoadingDialog.hide()
|
||||
showExceptionToast("初始化节点数据失败,请检查网络重试")
|
||||
|
||||
}
|
||||
coroutineScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* coroutineScope {
|
||||
commit(uuid2) {
|
||||
println(">>>>>>>>>> ${it.progress} ${it.action} ${it.args.toString()} ${it} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* commit(uuid){
|
||||
println(">> ${it.progress} ${it.action} ${it.args.toString()} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}else{
|
||||
//重新请求网络
|
||||
performSubscrite()
|
||||
}
|
||||
|
||||
/*
|
||||
if (subData == null){
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
performSubscrite()
|
||||
}else{
|
||||
|
||||
val plainid =subData?.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (subData?.u ?: 0) + (subData?.d ?: 0) > (subData?.transfer_enable ?: 0) && (subData?.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
performSubscrite()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
@@ -209,9 +610,46 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
// }
|
||||
// }
|
||||
|
||||
android.util.Log.i("activeProfile",">> ${activeProfile.type.name} ${activeProfile.uuid} ${activeProfile.imported} ")
|
||||
println("当前订阅文件>> ${activeProfile.type.name} ${activeProfile.uuid} ${activeProfile.imported} ")
|
||||
withProfile {
|
||||
activeProfile.uuid
|
||||
}
|
||||
startServer()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
suspend fun withProcessing(executeTask: suspend (suspend (FetchStatus) -> Unit) -> Unit) {
|
||||
try {
|
||||
|
||||
|
||||
executeTask {
|
||||
// applyFrom(it)
|
||||
println("applyFrom ${it.progress} ${it}")
|
||||
if (it.action == FetchStatus.Action.Verifying) {
|
||||
//验证通过
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
//选中节点
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}else{
|
||||
//请求中
|
||||
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startServer(){
|
||||
|
||||
//读取配置文件
|
||||
|
||||
|
||||
val vpnRequest = startClashService()
|
||||
|
||||
@@ -223,13 +661,104 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
)
|
||||
|
||||
if (result.resultCode == RESULT_OK)
|
||||
{
|
||||
startClashService()
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
println("缓存配置模式:${PreferenceManager.modeName}")
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
// Delay for 3 seconds
|
||||
//第一次全部使用全局模式连接
|
||||
|
||||
delay(500)
|
||||
withClash {
|
||||
// Query the current mode
|
||||
val override = queryOverride(Clash.OverrideSlot.Session)
|
||||
|
||||
// Toggle between Rule and Global modes
|
||||
if (PreferenceManager.modeName == TunnelState.Mode.Global.name){
|
||||
override.mode = TunnelState.Mode.Global
|
||||
}else{
|
||||
override.mode = TunnelState.Mode.Rule //第一次默认使用智能 模式连接
|
||||
}
|
||||
// Apply the new mode`
|
||||
patchOverride(Clash.OverrideSlot.Session, override)
|
||||
//模式切换完毕
|
||||
// Optional: Log or display the current mode for verification
|
||||
println("模式切换 Clash mode is now set to: ${override.mode}")
|
||||
|
||||
delay(500)
|
||||
//如果切换全局模式,默认 直接主动选择节点 根据缓存来
|
||||
//if (override.mode == TunnelState.Mode.Global){
|
||||
val names = queryProxyGroupNames(uiStore.proxyExcludeNotSelectable)
|
||||
println("模式切换>>>>>>>>>>>>>"+names)
|
||||
if ( names.size > 0){
|
||||
var progxys = queryProxyGroup(names[0], ProxySort.Default)
|
||||
|
||||
if (progxys.proxies.first().title.length > 0){
|
||||
|
||||
|
||||
println("模式切换>>>>>>>>>>>>>: "+progxys.proxies.last())
|
||||
if ( progxys.proxies.size > 2)
|
||||
{
|
||||
|
||||
val zutuolist = progxys.proxies.filter { it.title == "自动选择" }
|
||||
var autoNode = ""
|
||||
if (PreferenceManager.selectnodeName.length > 0){
|
||||
autoNode = PreferenceManager.selectnodeName
|
||||
|
||||
} else{
|
||||
if (zutuolist.size > 0){
|
||||
//说明存在自动选择
|
||||
autoNode = "自动选择"
|
||||
}else{
|
||||
autoNode = progxys.proxies.first().title
|
||||
}
|
||||
PreferenceManager.selectnodeName = autoNode // progxys.proxies.last().title
|
||||
}
|
||||
|
||||
|
||||
patchSelector(names.first(),autoNode) // progxys.proxies.last().title
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
design?.setSelectNodeName(PreferenceManager.selectnodeName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
design?.showToast(R.string.unable_to_start_vpn, ToastDuration.Long)
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
//design?.showToast(R.string.unable_to_start_vpn, ToastDuration.Long)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private suspend fun activeProfile(){
|
||||
withProfile {
|
||||
setActive(queryAll().first())
|
||||
@@ -238,7 +767,7 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
}
|
||||
private suspend fun queryAppVersionName(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
packageManager.getPackageInfo(packageName, 0).versionName// + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.content.Context
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.compat.currentProcessName
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.remote.Remote
|
||||
import com.github.kr328.clash.service.util.PreferenceManagerServer
|
||||
import com.github.kr328.clash.service.util.sendServiceRecreated
|
||||
import com.github.kr328.clash.util.clashDir
|
||||
import java.io.File
|
||||
@@ -20,7 +22,11 @@ class MainApplication : Application() {
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
PreferenceManagerServer.init(this)
|
||||
PreferenceManager.init(this)
|
||||
|
||||
Global.init(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.OrdersDataAdapter
|
||||
import com.github.kr328.clash.design.adapter.PlanDataAdapter
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.OrderData
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
|
||||
|
||||
class MyOrdersActivity : BaseListActivity<OrderData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return OrdersDataAdapter(){ item ->
|
||||
|
||||
startActivity(SubmitOrderActivity::class.intent.putExtra("trade_no",item.trade_no))
|
||||
}
|
||||
}
|
||||
override fun onCreate() {}
|
||||
override suspend fun loadData(page: Int, callback: (List<OrderData>?, Throwable?) -> Unit) {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getOrders(PreferenceManager.loginauthData)}.let {
|
||||
if (it!= null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import com.github.kr328.clash.common.util.componentName
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.NetworkSettingsDesign
|
||||
import com.github.kr328.clash.design.model.Behavior
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
|
||||
class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>() {
|
||||
class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>(), Behavior {
|
||||
override suspend fun main() {
|
||||
val design = NetworkSettingsDesign(
|
||||
this,
|
||||
uiStore,
|
||||
this,
|
||||
ServiceStore(this),
|
||||
clashRunning,
|
||||
)
|
||||
@@ -36,4 +40,25 @@ class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>() {
|
||||
}
|
||||
}
|
||||
|
||||
override var autoRestart: Boolean
|
||||
get() {
|
||||
val status = packageManager.getComponentEnabledSetting(
|
||||
RestartReceiver::class.componentName
|
||||
)
|
||||
|
||||
return status == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
}
|
||||
set(value) {
|
||||
val status = if (value)
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
else
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
|
||||
packageManager.setComponentEnabledSetting(
|
||||
RestartReceiver::class.componentName,
|
||||
status,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.HelpDesign
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NewGongdanActivity : BaseActivity<HelpDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = HelpDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
//
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
|
||||
//val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
HelpDesign.Request.CommitSuccess -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("resultKey", "shuaxin")
|
||||
setResult(Activity.RESULT_OK, resultIntent)
|
||||
finish()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.PlanDataAdapter
|
||||
import com.github.kr328.clash.design.view.ActivityBarLayout
|
||||
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
|
||||
class PlansActivity : BaseListActivity<PlanData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return PlanDataAdapter(){ planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
val intent = Intent(this, ConfigOrderActivity::class.java)
|
||||
intent.putExtra("planData", planData) // 使用 Serializable 传递对象
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
//
|
||||
setRightButtonVisible {
|
||||
startActivity(MyOrdersActivity::class.intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadData(page: Int, callback: (List<PlanData>?, Throwable?) -> Unit) {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getplans(PreferenceManager.loginauthData)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.github.kr328.clash
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.core.bridge.Bridge
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class ProfileActivity : BaseActivity<ProfileDesign>() {
|
||||
|
||||
private suspend fun queryAppVersionName(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName //+ "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
val design = ProfileDesign(this)
|
||||
setContentDesign(design)
|
||||
|
||||
design.requestdataDisible()
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
design.queryVersion(queryAppVersionName())
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
|
||||
ProfileDesign.Request.OpenBuyPlan -> startActivity(PlansActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyOrders -> startActivity(MyOrdersActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyBlance -> startActivity(SubmitOrderActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyInvites ->startActivity(InvitationActivity::class.intent)
|
||||
ProfileDesign.Request.OpenCustomView ->{ startActivity(H5WebActivity::class.intent) }
|
||||
/*try {
|
||||
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setData(Uri.parse(PreferenceManager.getConfigFromPreferences(this@ProfileActivity)?.telegramurl))
|
||||
startActivity(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
design.showToast("当前手机未安装浏览器",ToastDuration.Long)
|
||||
|
||||
} */
|
||||
|
||||
ProfileDesign.Request.OpenLogout -> {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "确定要退出吗?",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
LoadingDialog.show(this@ProfileActivity, "正在退出...")
|
||||
// 执行确认操作
|
||||
stopClashService()
|
||||
// 清理缓存,清除节点信息,切换界面
|
||||
launch {
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 0) {
|
||||
allProfiles.forEach {
|
||||
delete(it.uuid)
|
||||
println("删除节点:${it.source}")
|
||||
}
|
||||
//删除所有节点
|
||||
}
|
||||
}
|
||||
}
|
||||
PreferenceManager.clearData()
|
||||
|
||||
//2s 后执行
|
||||
// 在协程作用域中执行
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
delay(2000) // 协程延迟2000毫秒(2秒)
|
||||
println("协程中的延迟执行")
|
||||
LoadingDialog.hide()
|
||||
startActivity(LoginActivity::class.intent)
|
||||
finish()
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
ProfileDesign.Request.OpenGSettings -> {
|
||||
//查看是否有更新
|
||||
design.showToast("当前版本是最新版本",ToastDuration.Long)
|
||||
}// startActivity(NetworkSettingsActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyGongdan ->startActivity(GongdanActivity::class.intent)
|
||||
ProfileDesign.Request.OpenCustomViewIPtest -> startActivity(H5WebActivity::class.intent.putExtra("url","https://ipcelou.com"))
|
||||
ProfileDesign.Request.OpenCustomViewSpeed -> startActivity(H5WebActivity::class.intent.putExtra("url","https://fast.com/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -95,6 +95,9 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
coroutineScope {
|
||||
commit(profile.uuid) {
|
||||
launch {
|
||||
withProfile {
|
||||
setActive(queryAll().first())
|
||||
}
|
||||
updateStatus(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.core.Clash
|
||||
@@ -8,11 +10,15 @@ import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProxyDesign
|
||||
import com.github.kr328.clash.design.model.ProxyState
|
||||
import com.github.kr328.clash.util.withClash
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
override suspend fun main() {
|
||||
@@ -22,6 +28,8 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
val unorderedStates = names.indices.map { names[it] to states[it] }.toMap()
|
||||
val reloadLock = Semaphore(10)
|
||||
|
||||
setTitle("选择节点")
|
||||
|
||||
val design = ProxyDesign(
|
||||
this,
|
||||
mode,
|
||||
@@ -31,7 +39,11 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
design.requests.send(ProxyDesign.Request.ReloadAll)
|
||||
|
||||
//刷新所以Groups
|
||||
//design.requests.send(ProxyDesign.Request.ReloadAll)
|
||||
|
||||
design.requests.send(ProxyDesign.Request.Reload(0))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
@@ -65,11 +77,52 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
is ProxyDesign.Request.Reload -> {
|
||||
launch {
|
||||
design.urlTesting = false
|
||||
val group = reloadLock.withPermit {
|
||||
withClash {
|
||||
queryProxyGroup(names[it.index], uiStore.proxySort)
|
||||
}
|
||||
}
|
||||
var state = states[it.index]
|
||||
|
||||
|
||||
println(">>>>>>>>>>>> ${names}")
|
||||
println(">>>>>>>>>>>> ${it.index} group: "+group + " ${group.type.name} ${group.proxies.size}")
|
||||
|
||||
if (group.proxies.first().name == "GLOBAL"){
|
||||
println(">>>>>>>>>>>> GLOBAL ")
|
||||
}
|
||||
|
||||
if (PreferenceManager.selectnodeName.length > 0){
|
||||
state.now = PreferenceManager.selectnodeName //group.now
|
||||
}else{
|
||||
state.now = group.now
|
||||
}
|
||||
|
||||
println(">>>>>>>>>>>>unorderedStates : ${unorderedStates}")
|
||||
println(">>>>>>>>>>>> state: "+state)
|
||||
//PreferenceManager.selectnodeName = state.now //刷新目前使用的节点名称
|
||||
// >>>>>>>>>>>> state: ProxyState(now=?)
|
||||
// >>>>>>>>>>>> 0 group: 距离下次重置剩余:18 天 Selector
|
||||
// >>>>>>>>>>>>{代理=ProxyState(now=距离下次重置剩余:18 天), 电报=ProxyState(now=?), 油管=ProxyState(now=?), 奈飞=ProxyState(now=?), b站=ProxyState(now=?), 规则外路由选择=ProxyState(now=?)}
|
||||
|
||||
|
||||
design.updateGroup(
|
||||
it.index,
|
||||
group.proxies,
|
||||
group.type == Proxy.Type.Selector,
|
||||
state,
|
||||
unorderedStates
|
||||
)
|
||||
|
||||
|
||||
design.finishreferesh()
|
||||
|
||||
/*val group = reloadLock.withPermit {
|
||||
withClash {
|
||||
queryProxyGroup(names[it.index], uiStore.proxySort)
|
||||
}
|
||||
}
|
||||
val state = states[it.index]
|
||||
|
||||
state.now = group.now
|
||||
@@ -80,7 +133,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
group.type == Proxy.Type.Selector,
|
||||
state,
|
||||
unorderedStates
|
||||
)
|
||||
) */
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.Select -> {
|
||||
@@ -88,9 +141,11 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
patchSelector(names[it.index], it.name)
|
||||
states[it.index].now = it.name
|
||||
}
|
||||
Log.i("selectnodeName","${it.name}")
|
||||
PreferenceManager.selectnodeName = it.name
|
||||
design.requestRedrawVisible()
|
||||
launch {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.UrlTest -> {
|
||||
launch {
|
||||
@@ -102,7 +157,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.PatchMode -> {
|
||||
design.showModeSwitchTips()
|
||||
// design.showModeSwitchTips()
|
||||
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
@@ -116,5 +171,21 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 使用协程延迟 500ms 后执行
|
||||
/* GlobalScope.launch(Dispatchers.Main) {
|
||||
delay(1500) // 延迟 500ms
|
||||
|
||||
launch {
|
||||
withClash {
|
||||
healthCheck(names[0])
|
||||
}
|
||||
design.requests.send(ProxyDesign.Request.Reload(0))
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
|
||||
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
|
||||
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
|
||||
import com.github.kr328.clash.common.compat.isSystemBarsTranslucentCompat
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityProfileBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityRegisterBinding
|
||||
import com.github.kr328.clash.design.ui.DayNight
|
||||
import com.github.kr328.clash.design.util.layoutInflater
|
||||
import com.github.kr328.clash.design.util.resolveThemedBoolean
|
||||
import com.github.kr328.clash.design.util.resolveThemedColor
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.LoginResponse
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class RegisterActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
|
||||
private var dayNight: DayNight = DayNight.Day
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val binding = ActivityRegisterBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
applyDayNight()
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
binding.loginLoginButton.setOnClickListener {
|
||||
val email = binding.loginEmailEditText.text.toString()
|
||||
val password = binding.loginPasswordEditText.text.toString()
|
||||
val password2 = binding.loginPasswordEditText2.text.toString()
|
||||
val isAgreementChecked = binding.loginAgreementCheckBox.isChecked
|
||||
|
||||
if (email.isEmpty() || password.isEmpty() || password2.isEmpty()) {
|
||||
Toast.makeText(this, "请输入邮箱和密码", Toast.LENGTH_SHORT).show()
|
||||
} else if (!isAgreementChecked) {
|
||||
Toast.makeText(this, "请同意隐私政策和用户协议", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
// Hide the keyboard
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val currentFocusView = currentFocus
|
||||
if (currentFocusView != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
|
||||
}
|
||||
|
||||
performLogin(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
binding.loginForgotPassword.setOnClickListener {
|
||||
// Navigate to Forgot Password screen or show dialog
|
||||
}
|
||||
|
||||
binding.loginRegister.setOnClickListener {
|
||||
// Navigate to Register screen
|
||||
finish()
|
||||
}
|
||||
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall { apiService.getConfig()}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
|
||||
val response: ConfigResponse = it.body() ?: throw Exception("Response body is null")
|
||||
if (response.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences( response)
|
||||
//重新初始化 ApiClint
|
||||
}
|
||||
println("API Response: ${it.body()}")
|
||||
} else {
|
||||
println("API Error: 请求失败")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* withContext(Dispatchers.Main) {
|
||||
* if (response.isSuccessful) {
|
||||
* val userData = response.body()
|
||||
* // 更新 UI
|
||||
* } else {
|
||||
* Toast.makeText(this@MainActivity, "Failed to fetch user data", Toast.LENGTH_SHORT).show()
|
||||
* }
|
||||
* }
|
||||
* */
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
//val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
|
||||
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
|
||||
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
private fun performLogin(email: String, password: String) {
|
||||
// Perform login logic here, possibly calling an API
|
||||
|
||||
// Show the loading indicator with a custom message
|
||||
LoadingDialog.show(this, "正在注册...")
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.registerUser(LoginRequest(email,password))}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
println("API Response: ${it.code()} ${it.body()} ${it.raw().body}")
|
||||
val response: LoginResponse = it.body() ?: throw Exception("Response body is null")
|
||||
if (response.data != null){
|
||||
PreferenceManager.loginemail = email
|
||||
PreferenceManager.loginToken = response.data?.token ?: ""
|
||||
PreferenceManager.loginauthData = response.data?.auth_data ?: ""
|
||||
PreferenceManager.isLoginin = true
|
||||
// saveLoginMailToken(this@LoginActivity,email)
|
||||
// saveLoginToken(this@LoginActivity, response.data.token ?: "")
|
||||
// saveLoginAuthData(this@LoginActivity, response.data.auth_data ?: "")
|
||||
// saveLoginStatus(this@LoginActivity,true)
|
||||
//获取用户信息
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(this@RegisterActivity, MainActivity::class.java)
|
||||
intent.flags =
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
finish() // It
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Handle error response, even if it's 422
|
||||
try {
|
||||
val errorResponse = it?.errorBody()?.string() ?: ""
|
||||
val errorJson = JSONObject(errorResponse)
|
||||
val message = errorJson.optString("message")
|
||||
// Process the error message or show it to the user
|
||||
if (!message.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@RegisterActivity, "注册失败:${message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
println( "Message: $message, ")
|
||||
} catch (e: JSONException) {
|
||||
// Handle JSON parsing error
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@RegisterActivity, "注册失败:${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivitySplashBinding
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
|
||||
|
||||
class SplashActivity : AppCompatActivity() {
|
||||
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivitySplashBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
PreferenceManager.init(this)
|
||||
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
// Hide the loading indicator
|
||||
startActivity(LoginActivity::class.intent)
|
||||
finish()
|
||||
|
||||
}, 1000) // Simulating a network delay
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.design.SubmitOrderViewDesign
|
||||
|
||||
class SubmitOrderActivity : BaseActivity<SubmitOrderViewDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
val design = SubmitOrderViewDesign(this)
|
||||
setContentDesign(design)
|
||||
|
||||
//
|
||||
val trade_no = intent.getStringExtra("trade_no")
|
||||
design.fetchTradeInfo(trade_no)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.provider.Settings.Global
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.TicketDetailAdapter
|
||||
import com.github.kr328.clash.design.adapter.TicketsDataAdapter
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.PublicResponse
|
||||
import com.github.kr328.clash.network.TicketMessage
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URLEncoder
|
||||
|
||||
class TicketDetailActivity : BaseListActivity<TicketMessage>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return TicketDetailAdapter(){ planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
}
|
||||
}
|
||||
override fun onCreate() {
|
||||
|
||||
}
|
||||
|
||||
fun encodeString(input: String): String {
|
||||
val encoded = StringBuilder()
|
||||
|
||||
input.forEach { char ->
|
||||
when {
|
||||
char.isWhitespace() -> {
|
||||
// 编码空格为 %20
|
||||
encoded.append("")
|
||||
}
|
||||
char.isLetterOrDigit() || char.toInt() in 0x4E00..0x9FFF -> {
|
||||
// 保留字母、数字和中文字符
|
||||
encoded.append(char)
|
||||
}
|
||||
else -> {
|
||||
// 其他字符使用 URLEncoder 编码
|
||||
encoded.append(URLEncoder.encode(char.toString(), "UTF-8"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encoded.toString()
|
||||
}
|
||||
|
||||
|
||||
fun onCreateInputView() {
|
||||
|
||||
|
||||
// 将 dp 转换为 px 的实用函数
|
||||
fun dpToPx(dp: Int): Int {
|
||||
return (dp * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
// 创建顶部分割线
|
||||
val topDivider = View(this).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
dpToPx(1) // 设置分割线高度为 1dp
|
||||
) .apply {
|
||||
gravity = Gravity.TOP // 将分割线置于顶部
|
||||
}
|
||||
setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.darker_gray)) // 设置分割线颜色
|
||||
}
|
||||
|
||||
val rootLayout = FrameLayout(this).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
addView(topDivider) // 将分割线添加到根布局
|
||||
|
||||
}
|
||||
|
||||
// Create the input container layout programmatically
|
||||
// Create the input container layout programmatically
|
||||
val inputContainer = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
setPadding(8, 8, 8, 8)
|
||||
// setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.white))
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM // Aligns the input container to the bottom
|
||||
)
|
||||
setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.white))
|
||||
}
|
||||
|
||||
// Create the EditText for message input
|
||||
val editText = EditText(this).apply {
|
||||
hint = "输入内容回复工单..."
|
||||
setHintTextColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.gray)) // Set the placeholder color
|
||||
// Utility function to convert dp to pixels
|
||||
fun dpToPx2(dp: Int): Int {
|
||||
return (dp * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
textSize = 14f
|
||||
|
||||
// Set other EditText properties
|
||||
setTextColor(Color.BLACK) // Text color
|
||||
inputType = android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
setTextColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.black))
|
||||
maxLines = 1
|
||||
// background = ContextCompat.getDrawable(this@TicketDetailActivity, R.drawable.rounded_button_background)
|
||||
|
||||
// 设置内边距为 4dp(上下左右)
|
||||
// val padding = dpToPx(8)
|
||||
// setPadding(padding,0 , padding, 0)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
1f // Weight to make it fill available width
|
||||
)
|
||||
}
|
||||
|
||||
// Create the Send Button
|
||||
|
||||
val sendButton = Button(this).apply {
|
||||
text = "回复"
|
||||
setTextColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.white)) // Set text color for better contrast
|
||||
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
// Set the background tint programmatically
|
||||
ViewCompat.setBackgroundTintList(
|
||||
sendButton,
|
||||
ContextCompat.getColorStateList(this, R.color.blue)
|
||||
)
|
||||
|
||||
|
||||
// Add EditText and Button to the input container
|
||||
inputContainer.addView(editText)
|
||||
inputContainer.addView(sendButton)
|
||||
|
||||
|
||||
sendButton.setOnClickListener {
|
||||
val message = editText.text.toString()
|
||||
if (message.isNullOrEmpty()){
|
||||
|
||||
}else{
|
||||
val fieldMap = mutableMapOf<String, String>()
|
||||
val messageProperty1 = encodeString(message)
|
||||
val ticketid = intent.getIntExtra("ticketid",0)
|
||||
fieldMap.put("message",""+messageProperty1)
|
||||
fieldMap.put("id",""+ticketid)
|
||||
|
||||
GlobalScope.launch {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.replyTicket(PreferenceManager.loginauthData, fieldMap)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复成功", Toast.LENGTH_SHORT).show()
|
||||
editText.setText("")
|
||||
}
|
||||
refereshData()
|
||||
|
||||
}else{
|
||||
val errorinfo = it?.errorBody()?.string()
|
||||
|
||||
if (errorinfo != null){
|
||||
//解析错错误信息
|
||||
|
||||
val gson = Gson()
|
||||
val submitResponse = gson.fromJson(errorinfo, PublicResponse::class.java)
|
||||
|
||||
if (submitResponse?.message != null){
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复失败: ${submitResponse.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 将分割线添加到输入容器顶部
|
||||
|
||||
|
||||
rootLayout.addView(inputContainer)
|
||||
// rootLayout.addView(topDivider) // 将分割线添加到根布局
|
||||
// Add the input container to the root layout
|
||||
mainBinding?.mainview?.addView(rootLayout)
|
||||
|
||||
rootLayout.viewTreeObserver.addOnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
rootLayout.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = rootLayout.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
|
||||
val params = inputContainer.layoutParams as FrameLayout.LayoutParams
|
||||
if (keypadHeight > screenHeight * 0.15) {
|
||||
// Keyboard is visible; adjust inputContainer to stay above it
|
||||
params.bottomMargin = keypadHeight
|
||||
} else {
|
||||
// Keyboard is hidden; reset inputContainer to bottom
|
||||
params.bottomMargin = 0
|
||||
}
|
||||
inputContainer.layoutParams = params
|
||||
}
|
||||
|
||||
}
|
||||
override suspend fun loadData(page: Int, callback: (List<TicketMessage>?, Throwable?) -> Unit) {
|
||||
val ticketid = intent.getIntExtra("ticketid",0)
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getTicketsByTID(PreferenceManager.loginauthData,ticketid)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
referTitle(it.body()?.data?.subject ?: "")
|
||||
}
|
||||
if(it.body()?.data?.status == 0){
|
||||
withContext(Dispatchers.Main) {
|
||||
onCreateInputView()
|
||||
}
|
||||
}
|
||||
callback(it.body()?.data?.message,null)
|
||||
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ object SystemLogcat {
|
||||
"Go",
|
||||
"DEBUG",
|
||||
"AndroidRuntime",
|
||||
"ClashMetaForAndroid",
|
||||
"UUVPN",
|
||||
"LwIP",
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.github.kr328.clash.common.constants.Intents
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import java.util.*
|
||||
@@ -88,7 +90,7 @@ class Broadcasts(private val context: Application) {
|
||||
return
|
||||
|
||||
try {
|
||||
context.registerReceiver(broadcastReceiver, IntentFilter().apply {
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(Intents.ACTION_SERVICE_RECREATED)
|
||||
addAction(Intents.ACTION_CLASH_STARTED)
|
||||
addAction(Intents.ACTION_CLASH_STOPPED)
|
||||
@@ -96,7 +98,15 @@ class Broadcasts(private val context: Application) {
|
||||
addAction(Intents.ACTION_PROFILE_UPDATE_COMPLETED)
|
||||
addAction(Intents.ACTION_PROFILE_UPDATE_FAILED)
|
||||
addAction(Intents.ACTION_PROFILE_LOADED)
|
||||
})
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
context.registerReceiver(broadcastReceiver, filter)
|
||||
}
|
||||
|
||||
registered = true
|
||||
|
||||
clashRunning = StatusClient(context).currentProfile() != null
|
||||
} catch (e: Exception) {
|
||||
@@ -110,10 +120,11 @@ class Broadcasts(private val context: Application) {
|
||||
|
||||
try {
|
||||
context.unregisterReceiver(broadcastReceiver)
|
||||
registered = false
|
||||
|
||||
clashRunning = false
|
||||
} catch (e: Exception) {
|
||||
Log.w("Unregister global receiver: $e", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/white" />
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/appstore"
|
||||
android:gravity="center" />
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#1e1f22"
|
||||
android:pathData="M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280Q405,280 352.5,332.5Q300,385 300,460Q300,535 352.5,587.5Q405,640 480,640ZM480,568Q435,568 403.5,536.5Q372,505 372,460Q372,415 403.5,383.5Q435,352 480,352Q525,352 556.5,383.5Q588,415 588,460Q588,505 556.5,536.5Q525,568 480,568ZM480,760Q334,760 214,678.5Q94,597 40,460Q94,323 214,241.5Q334,160 480,160Q626,160 746,241.5Q866,323 920,460Q866,597 746,678.5Q626,760 480,760ZM480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,680Q593,680 687.5,620.5Q782,561 832,460Q782,359 687.5,299.5Q593,240 480,240Q367,240 272.5,299.5Q178,359 128,460Q178,561 272.5,620.5Q367,680 480,680Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#1e1f22"
|
||||
android:pathData="M644,532L586,474Q595,427 559,386Q523,345 466,354L408,296Q425,288 442.5,284Q460,280 480,280Q555,280 607.5,332.5Q660,385 660,460Q660,480 656,497.5Q652,515 644,532ZM772,658L714,602Q752,573 781.5,538.5Q811,504 832,460Q782,359 688.5,299.5Q595,240 480,240Q451,240 423,244Q395,248 368,256L306,194Q347,177 390,168.5Q433,160 480,160Q631,160 749,243.5Q867,327 920,460Q897,519 859.5,569.5Q822,620 772,658ZM792,904L624,738Q589,749 553.5,754.5Q518,760 480,760Q329,760 211,676.5Q93,593 40,460Q61,407 93,361.5Q125,316 166,280L56,168L112,112L848,848L792,904ZM222,336Q193,362 169,393Q145,424 128,460Q178,561 271.5,620.5Q365,680 480,680Q500,680 519,677.5Q538,675 558,672L522,634Q511,637 501,638.5Q491,640 480,640Q405,640 352.5,587.5Q300,535 300,460Q300,449 301.5,439Q303,429 306,418L222,336ZM541,429L541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429ZM390,504Q390,504 390,504Q390,504 390,504L390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Z"/>
|
||||
</vector>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/appicon"/>
|
||||
<monochrome android:drawable="@drawable/appicon" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/appicon"/>
|
||||
<monochrome android:drawable="@drawable/appicon" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="BootstrapTheme" parent="AppThemeDark" />
|
||||
</resources>
|
||||
|
||||
<style name="SplashTheme" parent="BootstrapTheme" />
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="SplashTheme" parent="BootstrapTheme">
|
||||
<item name="android:windowSplashScreenBackground">@color/white</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/appstore</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">@color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="BootstrapTheme" parent="AppThemeLight" />
|
||||
|
||||
<style name="SplashTheme" parent="BootstrapTheme" />
|
||||
</resources>
|
||||
|
||||
@@ -12,6 +12,7 @@ buildscript {
|
||||
maven("https://raw.githubusercontent.com/MetaCubeX/maven-backup/main/releases")
|
||||
}
|
||||
dependencies {
|
||||
|
||||
classpath(libs.build.android)
|
||||
classpath(libs.build.kotlin.common)
|
||||
classpath(libs.build.kotlin.serialization)
|
||||
@@ -34,33 +35,39 @@ subprojects {
|
||||
extensions.configure<BaseExtension> {
|
||||
defaultConfig {
|
||||
if (isApp) {
|
||||
applicationId = "com.github.metacubex.clash"
|
||||
applicationId = "com.litevpn.fast"
|
||||
}
|
||||
|
||||
multiDexEnabled = true
|
||||
minSdk = 24
|
||||
|
||||
compileSdkVersion(33)
|
||||
// targetSdkVersion(33)
|
||||
versionName = "2.11.1"
|
||||
versionCode = 211001
|
||||
compileSdkVersion(34)
|
||||
targetSdkVersion(34)
|
||||
versionName = "4.0.0"
|
||||
versionCode = 423001
|
||||
|
||||
resValue("string", "release_name", "v$versionName")
|
||||
resValue("integer", "release_code", "$versionCode")
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("arm64-v8a", "armeabi-v7a",)
|
||||
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
}
|
||||
}
|
||||
|
||||
// compileOptions{
|
||||
// isCoreLibraryDesugaringEnabled = false
|
||||
// }
|
||||
|
||||
if (!isApp) {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
} else {
|
||||
setProperty("archivesBaseName", "LiteVPN-$versionName")
|
||||
setProperty("archivesBaseName", "UUVPN-$versionName")
|
||||
}
|
||||
}
|
||||
|
||||
ndkVersion = "23.0.7599858"
|
||||
// ndkVersion = "23.0.7599858"
|
||||
ndkVersion = "27.0.12077973"
|
||||
// compileSdkVersion(defaultConfig.targetSdk!!)
|
||||
|
||||
if (isApp) {
|
||||
@@ -81,8 +88,8 @@ subprojects {
|
||||
|
||||
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
|
||||
|
||||
resValue("string", "launch_name", "LiteVPN")
|
||||
resValue("string", "application_name", "LiteVPN")
|
||||
resValue("string", "launch_name", "UUVPN")
|
||||
resValue("string", "application_name", "UUVPN")
|
||||
|
||||
if (isApp) {
|
||||
applicationIdSuffix = ".alpha"
|
||||
@@ -92,15 +99,15 @@ subprojects {
|
||||
create("meta") {
|
||||
|
||||
dimension = flavorDimensionList[0]
|
||||
versionNameSuffix = ".Meta"
|
||||
// versionNameSuffix = ".Meta"
|
||||
|
||||
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
|
||||
|
||||
resValue("string", "launch_name", "LiteVPN")//@string/launch_name_meta
|
||||
resValue("string", "application_name", "LiteVPN")//@string/application_name_meta
|
||||
resValue("string", "launch_name", "UUVPN")//@string/launch_name_meta
|
||||
resValue("string", "application_name", "UUVPN")//@string/application_name_meta
|
||||
|
||||
if (isApp) {
|
||||
applicationIdSuffix = ".meta"
|
||||
// applicationIdSuffix = ".meta"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,8 +139,8 @@ subprojects {
|
||||
|
||||
buildTypes {
|
||||
named("release") {
|
||||
isMinifyEnabled = isApp
|
||||
isShrinkResources = isApp
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
@@ -141,7 +148,7 @@ subprojects {
|
||||
)
|
||||
}
|
||||
named("debug") {
|
||||
versionNameSuffix = ".debug"
|
||||
// versionNameSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.kr328.clash.common.log
|
||||
|
||||
object Log {
|
||||
private const val TAG = "ClashMetaForAndroid"
|
||||
private const val TAG = "UUVPN"
|
||||
|
||||
fun i(message: String, throwable: Throwable? = null) =
|
||||
android.util.Log.i(TAG, message, throwable)
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
{
|
||||
"inputs" :
|
||||
[
|
||||
{
|
||||
"path" : "CMakeLists.txt"
|
||||
},
|
||||
{
|
||||
"path" : "version.h.in"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeDetermineSystem.cmake"
|
||||
},
|
||||
{
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android.toolchain.cmake"
|
||||
},
|
||||
{
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android-legacy.toolchain.cmake"
|
||||
},
|
||||
{
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/platforms.cmake"
|
||||
},
|
||||
{
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/compiler_id.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android-Determine.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeSystem.cmake.in"
|
||||
},
|
||||
{
|
||||
"isGenerated" : true,
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/3.18.1-g262b901/CMakeSystem.cmake"
|
||||
},
|
||||
{
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android.toolchain.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeSystemSpecificInitialize.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android-Initialize.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeDetermineCCompiler.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeDetermineCompiler.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android-Determine-C.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android/Determine-Compiler.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeFindBinUtils.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/Clang-FindBinUtils.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCCompiler.cmake.in"
|
||||
},
|
||||
{
|
||||
"isGenerated" : true,
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/3.18.1-g262b901/CMakeCCompiler.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeSystemSpecificInformation.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeGenericSystem.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeInitializeConfigs.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Linux.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/UnixPaths.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCInformation.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeLanguageInformation.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/Clang-C.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/Clang.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/GNU.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Internal/CMakeCheckCompilerFlag.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCheckCompilerFlagCommonPatterns.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android-Clang-C.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Platform/Android-Clang.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCommonLanguageInclude.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeTestCCompiler.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeTestCompilerCommon.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeDetermineCompilerABI.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeParseImplicitIncludeInfo.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeParseImplicitLinkInfo.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeTestCompilerCommon.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCCompilerABI.c"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeDetermineCompileFeatures.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/Internal/FeatureTesting.cmake"
|
||||
},
|
||||
{
|
||||
"isCMake" : true,
|
||||
"isExternal" : true,
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18/Modules/CMakeCCompiler.cmake.in"
|
||||
},
|
||||
{
|
||||
"isGenerated" : true,
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/3.18.1-g262b901/CMakeCCompiler.cmake"
|
||||
}
|
||||
],
|
||||
"kind" : "cmakeFiles",
|
||||
"paths" :
|
||||
{
|
||||
"build" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"source" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"major" : 1,
|
||||
"minor" : 0
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"configurations" :
|
||||
[
|
||||
{
|
||||
"directories" :
|
||||
[
|
||||
{
|
||||
"build" : ".",
|
||||
"minimumCMakeVersion" :
|
||||
{
|
||||
"string" : "3.6.0"
|
||||
},
|
||||
"projectIndex" : 0,
|
||||
"source" : ".",
|
||||
"targetIndexes" :
|
||||
[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"name" : "Debug",
|
||||
"projects" :
|
||||
[
|
||||
{
|
||||
"directoryIndexes" :
|
||||
[
|
||||
0
|
||||
],
|
||||
"name" : "clash-bridge",
|
||||
"targetIndexes" :
|
||||
[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"targets" :
|
||||
[
|
||||
{
|
||||
"directoryIndex" : 0,
|
||||
"id" : "bridge::@6890427a1f51a3e7e1df",
|
||||
"jsonFile" : "target-bridge-Debug-a88af6526146ee2dd726.json",
|
||||
"name" : "bridge",
|
||||
"projectIndex" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"kind" : "codemodel",
|
||||
"paths" :
|
||||
{
|
||||
"build" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"source" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"major" : 2,
|
||||
"minor" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"cmake" :
|
||||
{
|
||||
"generator" :
|
||||
{
|
||||
"multiConfig" : false,
|
||||
"name" : "Ninja"
|
||||
},
|
||||
"paths" :
|
||||
{
|
||||
"cmake" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cmake",
|
||||
"cpack" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cpack",
|
||||
"ctest" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ctest",
|
||||
"root" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18"
|
||||
},
|
||||
"version" :
|
||||
{
|
||||
"isDirty" : false,
|
||||
"major" : 3,
|
||||
"minor" : 18,
|
||||
"patch" : 1,
|
||||
"string" : "3.18.1-g262b901",
|
||||
"suffix" : "g262b901"
|
||||
}
|
||||
},
|
||||
"objects" :
|
||||
[
|
||||
{
|
||||
"jsonFile" : "codemodel-v2-86c91a08090b1cd48c81.json",
|
||||
"kind" : "codemodel",
|
||||
"version" :
|
||||
{
|
||||
"major" : 2,
|
||||
"minor" : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"jsonFile" : "cache-v2-1139a18846e2431585f8.json",
|
||||
"kind" : "cache",
|
||||
"version" :
|
||||
{
|
||||
"major" : 2,
|
||||
"minor" : 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"jsonFile" : "cmakeFiles-v1-d5eba61b07fa62ce8c42.json",
|
||||
"kind" : "cmakeFiles",
|
||||
"version" :
|
||||
{
|
||||
"major" : 1,
|
||||
"minor" : 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply" :
|
||||
{
|
||||
"client-agp" :
|
||||
{
|
||||
"cache-v2" :
|
||||
{
|
||||
"jsonFile" : "cache-v2-1139a18846e2431585f8.json",
|
||||
"kind" : "cache",
|
||||
"version" :
|
||||
{
|
||||
"major" : 2,
|
||||
"minor" : 0
|
||||
}
|
||||
},
|
||||
"cmakeFiles-v1" :
|
||||
{
|
||||
"jsonFile" : "cmakeFiles-v1-d5eba61b07fa62ce8c42.json",
|
||||
"kind" : "cmakeFiles",
|
||||
"version" :
|
||||
{
|
||||
"major" : 1,
|
||||
"minor" : 0
|
||||
}
|
||||
},
|
||||
"codemodel-v2" :
|
||||
{
|
||||
"jsonFile" : "codemodel-v2-86c91a08090b1cd48c81.json",
|
||||
"kind" : "codemodel",
|
||||
"version" :
|
||||
{
|
||||
"major" : 2,
|
||||
"minor" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
{
|
||||
"artifacts" :
|
||||
[
|
||||
{
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a/libbridge.so"
|
||||
}
|
||||
],
|
||||
"backtrace" : 1,
|
||||
"backtraceGraph" :
|
||||
{
|
||||
"commands" :
|
||||
[
|
||||
"add_library",
|
||||
"link_directories",
|
||||
"target_link_libraries",
|
||||
"include_directories"
|
||||
],
|
||||
"files" :
|
||||
[
|
||||
"CMakeLists.txt"
|
||||
],
|
||||
"nodes" :
|
||||
[
|
||||
{
|
||||
"file" : 0
|
||||
},
|
||||
{
|
||||
"command" : 0,
|
||||
"file" : 0,
|
||||
"line" : 68,
|
||||
"parent" : 0
|
||||
},
|
||||
{
|
||||
"command" : 1,
|
||||
"file" : 0,
|
||||
"line" : 66,
|
||||
"parent" : 0
|
||||
},
|
||||
{
|
||||
"command" : 2,
|
||||
"file" : 0,
|
||||
"line" : 69,
|
||||
"parent" : 0
|
||||
},
|
||||
{
|
||||
"command" : 3,
|
||||
"file" : 0,
|
||||
"line" : 63,
|
||||
"parent" : 0
|
||||
},
|
||||
{
|
||||
"command" : 3,
|
||||
"file" : 0,
|
||||
"line" : 64,
|
||||
"parent" : 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"compileGroups" :
|
||||
[
|
||||
{
|
||||
"compileCommandFragments" :
|
||||
[
|
||||
{
|
||||
"fragment" : "-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O0 -fno-limit-debug-info -fPIC"
|
||||
}
|
||||
],
|
||||
"defines" :
|
||||
[
|
||||
{
|
||||
"define" : "bridge_EXPORTS"
|
||||
}
|
||||
],
|
||||
"includes" :
|
||||
[
|
||||
{
|
||||
"backtrace" : 4,
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang/alphaDebug/arm64-v8a"
|
||||
},
|
||||
{
|
||||
"backtrace" : 5,
|
||||
"path" : "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native"
|
||||
}
|
||||
],
|
||||
"language" : "C",
|
||||
"sourceIndexes" :
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"sysroot" :
|
||||
{
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id" : "bridge::@6890427a1f51a3e7e1df",
|
||||
"link" :
|
||||
{
|
||||
"commandFragments" :
|
||||
[
|
||||
{
|
||||
"fragment" : "-static-libstdc++ -Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments",
|
||||
"role" : "flags"
|
||||
},
|
||||
{
|
||||
"backtrace" : 2,
|
||||
"fragment" : "-L/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang/alphaDebug/arm64-v8a",
|
||||
"role" : "libraryPath"
|
||||
},
|
||||
{
|
||||
"backtrace" : 3,
|
||||
"fragment" : "-llog",
|
||||
"role" : "libraries"
|
||||
},
|
||||
{
|
||||
"backtrace" : 3,
|
||||
"fragment" : "-lclash",
|
||||
"role" : "libraries"
|
||||
},
|
||||
{
|
||||
"fragment" : "-latomic -lm",
|
||||
"role" : "libraries"
|
||||
}
|
||||
],
|
||||
"language" : "C",
|
||||
"sysroot" :
|
||||
{
|
||||
"path" : "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
|
||||
}
|
||||
},
|
||||
"name" : "bridge",
|
||||
"nameOnDisk" : "libbridge.so",
|
||||
"paths" :
|
||||
{
|
||||
"build" : ".",
|
||||
"source" : "."
|
||||
},
|
||||
"sourceGroups" :
|
||||
[
|
||||
{
|
||||
"name" : "Source Files",
|
||||
"sourceIndexes" :
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"sources" :
|
||||
[
|
||||
{
|
||||
"backtrace" : 1,
|
||||
"compileGroupIndex" : 0,
|
||||
"path" : "main.c",
|
||||
"sourceGroupIndex" : 0
|
||||
},
|
||||
{
|
||||
"backtrace" : 1,
|
||||
"compileGroupIndex" : 0,
|
||||
"path" : "jni_helper.c",
|
||||
"sourceGroupIndex" : 0
|
||||
},
|
||||
{
|
||||
"backtrace" : 1,
|
||||
"compileGroupIndex" : 0,
|
||||
"path" : "bridge_helper.c",
|
||||
"sourceGroupIndex" : 0
|
||||
}
|
||||
],
|
||||
"type" : "SHARED_LIBRARY"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# ninja log v5
|
||||
2 93 1729063389 CMakeFiles/bridge.dir/bridge_helper.c.o f028609ec8edd0d1
|
||||
1 114 1729063389 CMakeFiles/bridge.dir/jni_helper.c.o 83bc6809a04c5f79
|
||||
0 141 1729063389 CMakeFiles/bridge.dir/main.c.o 8e73b8907825dc18
|
||||
142 187 1729063389 /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a/libbridge.so 6c7340a32c892534
|
||||
0 9 0 clean 609cc99e61b9952c
|
||||
1 370 1729233248 CMakeFiles/bridge.dir/bridge_helper.c.o f028609ec8edd0d1
|
||||
0 371 1729233248 CMakeFiles/bridge.dir/jni_helper.c.o 83bc6809a04c5f79
|
||||
0 244 1729233373 CMakeFiles/bridge.dir/main.c.o 8e73b8907825dc18
|
||||
244 586 1729233374 /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a/libbridge.so 6c7340a32c892534
|
||||
1 63 1729693135 CMakeFiles/bridge.dir/main.c.o 8e73b8907825dc18
|
||||
63 119 1729693135 /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a/libbridge.so 6c7340a32c892534
|
||||
@@ -1,369 +0,0 @@
|
||||
# This is the CMakeCache file.
|
||||
# For build in directory: /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
# It was generated by CMake: /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cmake
|
||||
# You can edit this file to change values found and used by cmake.
|
||||
# If you do not want to change any of the values, simply exit the editor.
|
||||
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||
# The syntax for the file is as follows:
|
||||
# KEY:TYPE=VALUE
|
||||
# KEY is the name of a variable in the cache.
|
||||
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||
# VALUE is the current value for the KEY.
|
||||
|
||||
########################
|
||||
# EXTERNAL cache entries
|
||||
########################
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
ANDROID_ABI:UNINITIALIZED=arm64-v8a
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
ANDROID_NDK:UNINITIALIZED=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
ANDROID_PLATFORM:UNINITIALIZED=android-24
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_ADDR2LINE:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-addr2line
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_ANDROID_ARCH_ABI:UNINITIALIZED=arm64-v8a
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_ANDROID_NDK:UNINITIALIZED=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858
|
||||
|
||||
//Archiver
|
||||
CMAKE_AR:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar
|
||||
|
||||
//Flags used by the compiler during all build types.
|
||||
CMAKE_ASM_FLAGS:STRING=
|
||||
|
||||
//Flags used by the compiler during debug builds.
|
||||
CMAKE_ASM_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the compiler during release builds.
|
||||
CMAKE_ASM_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Choose the type of build, options are: None Debug Release RelWithDebInfo
|
||||
// MinSizeRel ...
|
||||
CMAKE_BUILD_TYPE:STRING=Debug
|
||||
|
||||
//Flags used by the compiler during all build types.
|
||||
CMAKE_CXX_FLAGS:STRING=
|
||||
|
||||
//Flags used by the compiler during debug builds.
|
||||
CMAKE_CXX_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the compiler during release builds.
|
||||
CMAKE_CXX_FLAGS_RELEASE:STRING=
|
||||
|
||||
//LLVM archiver
|
||||
CMAKE_C_COMPILER_AR:FILEPATH=CMAKE_C_COMPILER_AR-NOTFOUND
|
||||
|
||||
//Generate index for LLVM archive
|
||||
CMAKE_C_COMPILER_RANLIB:FILEPATH=CMAKE_C_COMPILER_RANLIB-NOTFOUND
|
||||
|
||||
//Flags used by the compiler during all build types.
|
||||
CMAKE_C_FLAGS:STRING=
|
||||
|
||||
//Flags used by the compiler during debug builds.
|
||||
CMAKE_C_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the C compiler during MINSIZEREL builds.
|
||||
CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
|
||||
|
||||
//Flags used by the compiler during release builds.
|
||||
CMAKE_C_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Flags used by the C compiler during RELWITHDEBINFO builds.
|
||||
CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
|
||||
|
||||
//Libraries linked by default with all C applications.
|
||||
CMAKE_C_STANDARD_LIBRARIES:STRING=-latomic -lm
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND
|
||||
|
||||
//Flags used by the linker.
|
||||
CMAKE_EXE_LINKER_FLAGS:STRING=
|
||||
|
||||
//Flags used by the linker during DEBUG builds.
|
||||
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the linker during MINSIZEREL builds.
|
||||
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||
|
||||
//Flags used by the linker during RELEASE builds.
|
||||
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Flags used by the linker during RELWITHDEBINFO builds.
|
||||
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_EXPORT_COMPILE_COMMANDS:UNINITIALIZED=ON
|
||||
|
||||
//Install path prefix, prepended onto install directories.
|
||||
CMAKE_INSTALL_PREFIX:PATH=/usr/local
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_LIBRARY_OUTPUT_DIRECTORY:UNINITIALIZED=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_LINKER:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld.lld
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_MAKE_PROGRAM:UNINITIALIZED=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja
|
||||
|
||||
//Flags used by the linker during the creation of modules.
|
||||
CMAKE_MODULE_LINKER_FLAGS:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of modules during
|
||||
// DEBUG builds.
|
||||
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of modules during
|
||||
// MINSIZEREL builds.
|
||||
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of modules during
|
||||
// RELEASE builds.
|
||||
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of modules during
|
||||
// RELWITHDEBINFO builds.
|
||||
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_NM:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-nm
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_OBJCOPY:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objcopy
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_OBJDUMP:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_DESCRIPTION:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_NAME:STATIC=clash-bridge
|
||||
|
||||
//Ranlib
|
||||
CMAKE_RANLIB:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_READELF:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-readelf
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_RUNTIME_OUTPUT_DIRECTORY:UNINITIALIZED=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a
|
||||
|
||||
//Flags used by the linker during the creation of dll's.
|
||||
CMAKE_SHARED_LINKER_FLAGS:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of shared libraries
|
||||
// during DEBUG builds.
|
||||
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of shared libraries
|
||||
// during MINSIZEREL builds.
|
||||
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of shared libraries
|
||||
// during RELEASE builds.
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of shared libraries
|
||||
// during RELWITHDEBINFO builds.
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||
|
||||
//If set, runtime paths are not added when installing shared libraries,
|
||||
// but are added when building.
|
||||
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
|
||||
|
||||
//If set, runtime paths are not added when using shared libraries.
|
||||
CMAKE_SKIP_RPATH:BOOL=NO
|
||||
|
||||
//Flags used by the linker during the creation of static libraries
|
||||
// during all build types.
|
||||
CMAKE_STATIC_LINKER_FLAGS:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of static libraries
|
||||
// during DEBUG builds.
|
||||
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of static libraries
|
||||
// during MINSIZEREL builds.
|
||||
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of static libraries
|
||||
// during RELEASE builds.
|
||||
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
|
||||
|
||||
//Flags used by the linker during the creation of static libraries
|
||||
// during RELWITHDEBINFO builds.
|
||||
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
|
||||
|
||||
//Strip
|
||||
CMAKE_STRIP:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_SYSTEM_NAME:UNINITIALIZED=Android
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
CMAKE_SYSTEM_VERSION:UNINITIALIZED=24
|
||||
|
||||
//The CMake toolchain file
|
||||
CMAKE_TOOLCHAIN_FILE:FILEPATH=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android.toolchain.cmake
|
||||
|
||||
//If this value is on, makefiles will be generated without the
|
||||
// .SILENT directive, and all commands will be echoed to the console
|
||||
// during the make. This is useful for debugging only. With Visual
|
||||
// Studio IDE projects all commands are done without /nologo.
|
||||
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
FLAVOR_NAME:STRING=alpha
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
GO_OUTPUT:STRING=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang
|
||||
|
||||
//No help, variable specified on the command line.
|
||||
GO_SOURCE:STRING=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native
|
||||
|
||||
//Dependencies for the target
|
||||
bridge_LIB_DEPENDS:STATIC=general;log;general;clash;
|
||||
|
||||
//Value Computed by CMake
|
||||
clash-bridge_BINARY_DIR:STATIC=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
|
||||
//Value Computed by CMake
|
||||
clash-bridge_SOURCE_DIR:STATIC=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp
|
||||
|
||||
|
||||
########################
|
||||
# INTERNAL cache entries
|
||||
########################
|
||||
|
||||
//ADVANCED property for variable: CMAKE_ADDR2LINE
|
||||
CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_AR
|
||||
CMAKE_AR-ADVANCED:INTERNAL=1
|
||||
//This is the directory where this CMakeCache.txt was created
|
||||
CMAKE_CACHEFILE_DIR:INTERNAL=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
//Major version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||
//Minor version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MINOR_VERSION:INTERNAL=18
|
||||
//Patch version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_PATCH_VERSION:INTERNAL=1
|
||||
//Path to CMake executable.
|
||||
CMAKE_COMMAND:INTERNAL=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cmake
|
||||
//Path to cpack program executable.
|
||||
CMAKE_CPACK_COMMAND:INTERNAL=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cpack
|
||||
//Path to ctest program executable.
|
||||
CMAKE_CTEST_COMMAND:INTERNAL=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ctest
|
||||
//ADVANCED property for variable: CMAKE_C_COMPILER_AR
|
||||
CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB
|
||||
CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_FLAGS
|
||||
CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
|
||||
CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
|
||||
CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_C_STANDARD_LIBRARIES
|
||||
CMAKE_C_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_DLLTOOL
|
||||
CMAKE_DLLTOOL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
|
||||
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
|
||||
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
|
||||
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
|
||||
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||
//Name of external makefile project generator.
|
||||
CMAKE_EXTRA_GENERATOR:INTERNAL=
|
||||
//Name of generator.
|
||||
CMAKE_GENERATOR:INTERNAL=Ninja
|
||||
//Generator instance identifier.
|
||||
CMAKE_GENERATOR_INSTANCE:INTERNAL=
|
||||
//Name of generator platform.
|
||||
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||
//Name of generator toolset.
|
||||
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||
//Source directory with the top level CMakeLists.txt file for this
|
||||
// project
|
||||
CMAKE_HOME_DIRECTORY:INTERNAL=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp
|
||||
//Install .so files without execute permission.
|
||||
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=0
|
||||
//ADVANCED property for variable: CMAKE_LINKER
|
||||
CMAKE_LINKER-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
|
||||
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
|
||||
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
|
||||
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
|
||||
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_NM
|
||||
CMAKE_NM-ADVANCED:INTERNAL=1
|
||||
//number of local generators
|
||||
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_OBJCOPY
|
||||
CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_OBJDUMP
|
||||
CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
|
||||
//Platform information initialized
|
||||
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_RANLIB
|
||||
CMAKE_RANLIB-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_READELF
|
||||
CMAKE_READELF-ADVANCED:INTERNAL=1
|
||||
//Path to CMake installation.
|
||||
CMAKE_ROOT:INTERNAL=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/share/cmake-3.18
|
||||
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
|
||||
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
|
||||
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
|
||||
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
|
||||
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_SKIP_RPATH
|
||||
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
|
||||
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
|
||||
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
|
||||
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
|
||||
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
|
||||
//ADVANCED property for variable: CMAKE_STRIP
|
||||
CMAKE_STRIP-ADVANCED:INTERNAL=1
|
||||
//uname command
|
||||
CMAKE_UNAME:INTERNAL=/usr/bin/uname
|
||||
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
|
||||
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
set(CMAKE_C_COMPILER "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang")
|
||||
set(CMAKE_C_COMPILER_ARG1 "")
|
||||
set(CMAKE_C_COMPILER_ID "Clang")
|
||||
set(CMAKE_C_COMPILER_VERSION "12.0.5")
|
||||
set(CMAKE_C_COMPILER_VERSION_INTERNAL "")
|
||||
set(CMAKE_C_COMPILER_WRAPPER "")
|
||||
set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "11")
|
||||
set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert")
|
||||
set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes")
|
||||
set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros")
|
||||
set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert")
|
||||
|
||||
set(CMAKE_C_PLATFORM_ID "")
|
||||
set(CMAKE_C_SIMULATE_ID "")
|
||||
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU")
|
||||
set(CMAKE_C_SIMULATE_VERSION "")
|
||||
|
||||
|
||||
|
||||
|
||||
set(CMAKE_AR "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar")
|
||||
set(CMAKE_C_COMPILER_AR "CMAKE_C_COMPILER_AR-NOTFOUND")
|
||||
set(CMAKE_RANLIB "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib")
|
||||
set(CMAKE_C_COMPILER_RANLIB "CMAKE_C_COMPILER_RANLIB-NOTFOUND")
|
||||
set(CMAKE_LINKER "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld.lld")
|
||||
set(CMAKE_MT "")
|
||||
set(CMAKE_COMPILER_IS_GNUCC )
|
||||
set(CMAKE_C_COMPILER_LOADED 1)
|
||||
set(CMAKE_C_COMPILER_WORKS TRUE)
|
||||
set(CMAKE_C_ABI_COMPILED TRUE)
|
||||
set(CMAKE_COMPILER_IS_MINGW )
|
||||
set(CMAKE_COMPILER_IS_CYGWIN )
|
||||
if(CMAKE_COMPILER_IS_CYGWIN)
|
||||
set(CYGWIN 1)
|
||||
set(UNIX 1)
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_COMPILER_ENV_VAR "CC")
|
||||
|
||||
if(CMAKE_COMPILER_IS_MINGW)
|
||||
set(MINGW 1)
|
||||
endif()
|
||||
set(CMAKE_C_COMPILER_ID_RUN 1)
|
||||
set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
|
||||
set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
|
||||
set(CMAKE_C_LINKER_PREFERENCE 10)
|
||||
|
||||
# Save compiler ABI information.
|
||||
set(CMAKE_C_SIZEOF_DATA_PTR "8")
|
||||
set(CMAKE_C_COMPILER_ABI "ELF")
|
||||
set(CMAKE_C_LIBRARY_ARCHITECTURE "")
|
||||
|
||||
if(CMAKE_C_SIZEOF_DATA_PTR)
|
||||
set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}")
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ABI)
|
||||
set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}")
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_LIBRARY_ARCHITECTURE)
|
||||
set(CMAKE_LIBRARY_ARCHITECTURE "")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "")
|
||||
if(CMAKE_C_CL_SHOWINCLUDES_PREFIX)
|
||||
set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}")
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/local/include;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/12.0.5/include;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/aarch64-linux-android;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include")
|
||||
set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "-l:libunwind.a;dl;c;-l:libunwind.a;dl")
|
||||
set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/12.0.5/lib/linux/aarch64;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/24;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android;/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib")
|
||||
set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "")
|
||||
@@ -1,15 +0,0 @@
|
||||
set(CMAKE_HOST_SYSTEM "Darwin-22.6.0")
|
||||
set(CMAKE_HOST_SYSTEM_NAME "Darwin")
|
||||
set(CMAKE_HOST_SYSTEM_VERSION "22.6.0")
|
||||
set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64")
|
||||
|
||||
include("/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android.toolchain.cmake")
|
||||
|
||||
set(CMAKE_SYSTEM "Android-1")
|
||||
set(CMAKE_SYSTEM_NAME "Android")
|
||||
set(CMAKE_SYSTEM_VERSION "1")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "aarch64")
|
||||
|
||||
set(CMAKE_CROSSCOMPILING "TRUE")
|
||||
|
||||
set(CMAKE_SYSTEM_LOADED 1)
|
||||
@@ -1,3 +0,0 @@
|
||||
/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/rebuild_cache.dir
|
||||
/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/edit_cache.dir
|
||||
/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/CMakeFiles/bridge.dir
|
||||
@@ -1 +0,0 @@
|
||||
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
|
||||
@@ -1,64 +0,0 @@
|
||||
# CMAKE generated file: DO NOT EDIT!
|
||||
# Generated by "Ninja" Generator, CMake Version 3.18
|
||||
|
||||
# This file contains all the rules used to get the outputs files
|
||||
# built from the input files.
|
||||
# It is included in the main 'build.ninja'.
|
||||
|
||||
# =============================================================================
|
||||
# Project: clash-bridge
|
||||
# Configurations: Debug
|
||||
# =============================================================================
|
||||
# =============================================================================
|
||||
|
||||
#############################################
|
||||
# Rule for running custom commands.
|
||||
|
||||
rule CUSTOM_COMMAND
|
||||
command = $COMMAND
|
||||
description = $DESC
|
||||
|
||||
|
||||
#############################################
|
||||
# Rule for compiling C files.
|
||||
|
||||
rule C_COMPILER__bridge_Debug
|
||||
depfile = $DEP_FILE
|
||||
deps = gcc
|
||||
command = /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=aarch64-none-linux-android24 --gcc-toolchain=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot $DEFINES $INCLUDES $FLAGS -MD -MT $out -MF $DEP_FILE -o $out -c $in
|
||||
description = Building C object $out
|
||||
|
||||
|
||||
#############################################
|
||||
# Rule for linking C shared library.
|
||||
|
||||
rule C_SHARED_LIBRARY_LINKER__bridge_Debug
|
||||
command = $PRE_LINK && /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=aarch64-none-linux-android24 --gcc-toolchain=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -fPIC $LANGUAGE_COMPILE_FLAGS $ARCH_FLAGS $LINK_FLAGS -shared $SONAME_FLAG$SONAME -o $TARGET_FILE $in $LINK_PATH $LINK_LIBRARIES && $POST_BUILD
|
||||
description = Linking C shared library $TARGET_FILE
|
||||
restat = $RESTAT
|
||||
|
||||
|
||||
#############################################
|
||||
# Rule for re-running cmake.
|
||||
|
||||
rule RERUN_CMAKE
|
||||
command = /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cmake --regenerate-during-build -S/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp -B/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
description = Re-running CMake...
|
||||
generator = 1
|
||||
|
||||
|
||||
#############################################
|
||||
# Rule for cleaning all built files.
|
||||
|
||||
rule CLEAN
|
||||
command = /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja $FILE_ARG -t clean $TARGETS
|
||||
description = Cleaning all built files...
|
||||
|
||||
|
||||
#############################################
|
||||
# Rule for printing all primary targets available.
|
||||
|
||||
rule HELP
|
||||
command = /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja -t targets
|
||||
description = All primary targets available:
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"buildFiles": [
|
||||
"/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/CMakeLists.txt"
|
||||
],
|
||||
"cleanCommandsComponents": [
|
||||
[
|
||||
"/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja",
|
||||
"-C",
|
||||
"/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"clean"
|
||||
]
|
||||
],
|
||||
"buildTargetsCommandComponents": [
|
||||
"/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja",
|
||||
"-C",
|
||||
"/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"{LIST_OF_TARGETS_TO_BUILD}"
|
||||
],
|
||||
"libraries": {
|
||||
"bridge::@6890427a1f51a3e7e1df": {
|
||||
"toolchain": "toolchain",
|
||||
"abi": "arm64-v8a",
|
||||
"artifactName": "bridge",
|
||||
"output": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a/libbridge.so",
|
||||
"runtimeFiles": []
|
||||
}
|
||||
},
|
||||
"toolchains": {
|
||||
"toolchain": {
|
||||
"cCompilerExecutable": "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang.lld",
|
||||
"cppCompilerExecutable": "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++.lld"
|
||||
}
|
||||
},
|
||||
"cFileExtensions": [
|
||||
"c"
|
||||
],
|
||||
"cppFileExtensions": []
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
Executable : /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja
|
||||
arguments :
|
||||
-C
|
||||
/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
bridge
|
||||
jvmArgs :
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
ninja: Entering directory `/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a'
|
||||
ninja: no work to do.
|
||||
@@ -1,54 +0,0 @@
|
||||
# Install script for directory: /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp
|
||||
|
||||
# Set the install prefix
|
||||
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local")
|
||||
endif()
|
||||
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
# Set the install configuration name.
|
||||
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
|
||||
if(BUILD_TYPE)
|
||||
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
|
||||
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
|
||||
else()
|
||||
set(CMAKE_INSTALL_CONFIG_NAME "Debug")
|
||||
endif()
|
||||
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
|
||||
endif()
|
||||
|
||||
# Set the component getting installed.
|
||||
if(NOT CMAKE_INSTALL_COMPONENT)
|
||||
if(COMPONENT)
|
||||
message(STATUS "Install component: \"${COMPONENT}\"")
|
||||
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
|
||||
else()
|
||||
set(CMAKE_INSTALL_COMPONENT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Install shared libraries without execute permission?
|
||||
if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
|
||||
set(CMAKE_INSTALL_SO_NO_EXE "0")
|
||||
endif()
|
||||
|
||||
# Is this installation the result of a crosscompile?
|
||||
if(NOT DEFINED CMAKE_CROSSCOMPILING)
|
||||
set(CMAKE_CROSSCOMPILING "TRUE")
|
||||
endif()
|
||||
|
||||
# Set default install directory permissions.
|
||||
if(NOT DEFINED CMAKE_OBJDUMP)
|
||||
set(CMAKE_OBJDUMP "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-objdump")
|
||||
endif()
|
||||
|
||||
if(CMAKE_INSTALL_COMPONENT)
|
||||
set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
|
||||
else()
|
||||
set(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
|
||||
endif()
|
||||
|
||||
string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
|
||||
"${CMAKE_INSTALL_MANIFEST_FILES}")
|
||||
file(WRITE "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a/${CMAKE_INSTALL_MANIFEST}"
|
||||
"${CMAKE_INSTALL_MANIFEST_CONTENT}")
|
||||
@@ -1,17 +0,0 @@
|
||||
[
|
||||
{
|
||||
"directory": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"command": "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=aarch64-none-linux-android24 --gcc-toolchain=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -Dbridge_EXPORTS -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang/alphaDebug/arm64-v8a -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O0 -fno-limit-debug-info -fPIC -o CMakeFiles/bridge.dir/main.c.o -c /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/main.c",
|
||||
"file": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/main.c"
|
||||
},
|
||||
{
|
||||
"directory": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"command": "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=aarch64-none-linux-android24 --gcc-toolchain=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -Dbridge_EXPORTS -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang/alphaDebug/arm64-v8a -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O0 -fno-limit-debug-info -fPIC -o CMakeFiles/bridge.dir/jni_helper.c.o -c /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/jni_helper.c",
|
||||
"file": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/jni_helper.c"
|
||||
},
|
||||
{
|
||||
"directory": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a",
|
||||
"command": "/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=aarch64-none-linux-android24 --gcc-toolchain=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -Dbridge_EXPORTS -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang/alphaDebug/arm64-v8a -I/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O0 -fno-limit-debug-info -fPIC -o CMakeFiles/bridge.dir/bridge_helper.c.o -c /Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/bridge_helper.c",
|
||||
"file": "/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp/bridge_helper.c"
|
||||
}
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
Executable : /Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/cmake
|
||||
arguments :
|
||||
-H/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/cpp
|
||||
-DCMAKE_SYSTEM_NAME=Android
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
-DCMAKE_SYSTEM_VERSION=24
|
||||
-DANDROID_PLATFORM=android-24
|
||||
-DANDROID_ABI=arm64-v8a
|
||||
-DCMAKE_ANDROID_ARCH_ABI=arm64-v8a
|
||||
-DANDROID_NDK=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858
|
||||
-DCMAKE_ANDROID_NDK=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858
|
||||
-DCMAKE_TOOLCHAIN_FILE=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/ndk/23.0.7599858/build/cmake/android.toolchain.cmake
|
||||
-DCMAKE_MAKE_PROGRAM=/Applications/Unity/Hub/Editor/2022.3.32f1/PlaybackEngines/AndroidPlayer/SDK/cmake/3.18.1/bin/ninja
|
||||
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a
|
||||
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/intermediates/cxx/Debug/1t22401e/obj/arm64-v8a
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
-B/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/.cxx/Debug/1t22401e/arm64-v8a
|
||||
-GNinja
|
||||
-DGO_SOURCE:STRING=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/src/main/golang/native
|
||||
-DGO_OUTPUT:STRING=/Users/zeus/Documents/GitHub/ClashMetaForAndroid/core/build/outputs/golang
|
||||
-DFLAVOR_NAME:STRING=alpha
|
||||
jvmArgs :
|
||||
|
||||
|
||||
Build command args: []
|
||||
Version: 2
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"packages": []
|
||||
}
|
||||