Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7c65daeb89
|
|||
|
e76bcd709c
|
|||
|
e76ada9b4b
|
|||
|
eb23ef9d5d
|
|||
|
ed2629cb08
|
|||
|
20a25e8c49
|
|||
|
4b5d2b656b
|
|||
|
d1dd75f7ab
|
|||
|
f3adb56bd0
|
|||
|
d8ffc5eea5
|
|||
|
2a0f2901b4
|
|||
|
6ec897468f
|
|||
|
a2ad297dbc
|
|||
|
b4c15bf78c
|
|||
|
dbbfce1c4f
|
|||
|
5960a25309
|
|||
|
d9db99946d
|
|||
|
99c71f5179
|
|||
|
7a258d28a2
|
|||
|
6d8a8c4e2f
|
|||
|
dcba4c3e05
|
1
.gitignore
vendored
@@ -4,3 +4,4 @@ frontend/dist
|
|||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
build/linux/appimage/build
|
build/linux/appimage/build
|
||||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
||||||
|
dist
|
||||||
103
.goreleaser.yaml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
project_name: mesh-drop
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy
|
||||||
|
- wails3 generate bindings -ts
|
||||||
|
- sh -c 'cd frontend && npm run build'
|
||||||
|
- wails3 generate .desktop -name "{{.ProjectName}}" -exec "{{.ProjectName}}" -icon "{{.ProjectName}}.png" -outputfile "goreleaser/{{.ProjectName}}.desktop" -categories "Network;FileTransfer;" -keywords "mesh,transfer,file,network,drop"
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: linux-amd64
|
||||||
|
binary: mesh-drop
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
flags:
|
||||||
|
- -tags
|
||||||
|
- production
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X mesh-drop/internal/config.Version={{ .Version }}
|
||||||
|
|
||||||
|
- id: windows-amd64
|
||||||
|
binary: mesh-drop
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
flags:
|
||||||
|
- -tags
|
||||||
|
- production
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -s -w -H windowsgui -X mesh-drop/internal/config.Version={{ .Version }}
|
||||||
|
hooks:
|
||||||
|
pre:
|
||||||
|
- "wails3 generate icons -input goreleaser/icon.png -windowsfilename goreleaser/icon.ico"
|
||||||
|
- "wails3 generate syso -arch amd64 -icon goreleaser/icon.ico -manifest goreleaser/wails.exe.manifest -info goreleaser/info.json -out wails_windows_amd64.syso"
|
||||||
|
post: "rm -f wails_windows_amd64.syso"
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- formats: ["tar.gz"]
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
formats: ["zip"]
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
- id: default
|
||||||
|
package_name: mesh-drop
|
||||||
|
vendor: "nite"
|
||||||
|
homepage: "https://www.nite07.com"
|
||||||
|
maintainer: "Nite <nite@nite07.com>"
|
||||||
|
description: "A mesh-drop application"
|
||||||
|
license: "MIT"
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- archlinux
|
||||||
|
|
||||||
|
contents:
|
||||||
|
- src: "goreleaser/icon.png"
|
||||||
|
dst: "/usr/share/icons/hicolor/128x128/apps/mesh-drop.png"
|
||||||
|
- src: "build/linux/mesh-drop.desktop"
|
||||||
|
dst: "/usr/share/applications/mesh-drop.desktop"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- libgtk-3-0
|
||||||
|
- libwebkit2gtk-4.1-0
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
rpm:
|
||||||
|
dependencies:
|
||||||
|
- gtk3
|
||||||
|
- webkit2gtk4.1
|
||||||
|
archlinux:
|
||||||
|
dependencies:
|
||||||
|
- gtk3
|
||||||
|
- webkit2gtk-4.1
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "checksums.txt"
|
||||||
|
|
||||||
|
snapshot:
|
||||||
|
version_template: "{{ .Tag }}-{{ .ShortCommit }}"
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
|
|
||||||
|
upx:
|
||||||
|
- enabled: true
|
||||||
|
compress: best
|
||||||
|
lzma: true
|
||||||
3
.vscode/launch.json
vendored
@@ -9,7 +9,8 @@
|
|||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
"program": "${workspaceFolder}/main.go",
|
"program": "${workspaceFolder}",
|
||||||
|
"buildFlags": "-tags=gtk4",
|
||||||
"preLaunchTask": "build frontend"
|
"preLaunchTask": "build frontend"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
95
README.md
@@ -1,68 +1,91 @@
|
|||||||
# Mesh Drop
|
# Mesh Drop
|
||||||
|
|
||||||
简易、快速的局域网文件传输工具,基于 Wails 和 Vue 构建。
|
English | [中文](./README.zh.md)
|
||||||
|
|
||||||
## 功能特性
|
Simple, fast LAN file transfer tool, built with Wails and Vue.
|
||||||
|
|
||||||
- **文件传输**:支持多文件发送,轻松共享。
|
## Features
|
||||||
- **文件夹传输**:支持发送整个文件夹结构。
|
|
||||||
- **文本传输**:快速同步设备间的文本内容。
|
|
||||||
- **加密传输**:确保数据在传输过程中的安全性。
|
|
||||||
|
|
||||||
## 截图
|
- **File Transfer**: Support multi-file sending, easily share.
|
||||||
|
- **Folder Transfer**: Support sending entire folder structures.
|
||||||
|
- **Text Transfer**: Quickly sync text content between devices.
|
||||||
|
- **Encrypted Transmission**: Ensure data security during transmission.
|
||||||
|
- **Secure Identity**: Ed25519-based signature verification to prevent spoofing.
|
||||||
|
|
||||||
|
## Security Mechanisms
|
||||||
|
|
||||||
|
Mesh Drop uses a multi-layered security design to protect users from potential malicious attacks:
|
||||||
|
|
||||||
|
1. **Identity**
|
||||||
|
- Each device generates a unique pair of Ed25519 keys on first startup.
|
||||||
|
- All presence broadcasts are signed with the private key.
|
||||||
|
- The receiver verifies the signature with the public key to ensure the identity has not been tampered with.
|
||||||
|
|
||||||
|
2. **Trust**
|
||||||
|
- Uses TOFU (Trust On First Use) strategy.
|
||||||
|
- Users can choose to "Trust" a Peer. Once trusted, that Peer's public key is pinned.
|
||||||
|
- Subsequent packets from that Peer ID must be verified by the saved public key, otherwise they will be marked as **Mismatch**.
|
||||||
|
- **Anti-spoofing**: If someone tries to spoof a trusted Peer ID, the UI will display a clear "Mismatch" security warning and prevent metadata from being overwritten.
|
||||||
|
|
||||||
|
3. **Encryption**
|
||||||
|
- File transfer service uses HTTPS protocol.
|
||||||
|
- Automatically generates self-signed certificates for communication encryption to prevent eavesdropping.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|  |  |
|
|  |  |
|
||||||
| -------------------------------- | -------------------------------- |
|
| -------------------------------- | -------------------------------- |
|
||||||
|
|
||||||
## 待办事项
|
## Todo
|
||||||
|
|
||||||
- [x] 剪辑板传输
|
- [x] Clipboard transfer
|
||||||
- [x] 文件夹传输
|
- [x] Folder transfer
|
||||||
- [x] 取消传输
|
- [x] Cancel transfer
|
||||||
- [x] 多文件发送
|
- [x] Multi-file sending
|
||||||
- [x] 加密传输
|
- [x] Encrypted transmission
|
||||||
- [x] 设置页面
|
- [x] Settings page
|
||||||
- [x] 默认保存路径
|
- [x] Single instance mode
|
||||||
- [x] 设置主机名
|
- [x] System notifications
|
||||||
- [x] 保存历史
|
- [x] Clear history
|
||||||
- [x] 自动接收
|
- [x] Auto accept
|
||||||
- [x] 单例模式
|
- [x] App icon
|
||||||
- [x] 系统通知
|
- [x] Trust Peer
|
||||||
- [ ] 应用图标
|
- [x] Multi-language support
|
||||||
- [ ] 系统托盘(最小化到托盘)
|
- [ ] System tray (minimize to tray) badges https://github.com/wailsapp/wails/issues/4494
|
||||||
|
|
||||||
## 技术栈
|
## Tech Stack
|
||||||
|
|
||||||
本项目使用现代化的技术栈构建:
|
This project is built using a modern tech stack:
|
||||||
|
|
||||||
- **后端**: [Go](https://go.dev/) + [Wails v3](https://v3.wails.io/)
|
- **Backend**: [Go](https://go.dev/) + [Wails v3](https://v3.wails.io/)
|
||||||
- **前端**: [Vue 3](https://vuejs.org/) + [TypeScript](https://www.typescriptlang.org/)
|
- **Frontend**: [Vue 3](https://vuejs.org/) + [TypeScript](https://www.typescriptlang.org/)
|
||||||
- **UI 框架**: [Vuetify](https://vuetifyjs.com/)
|
- **UI Framework**: [Vuetify](https://vuetifyjs.com/)
|
||||||
|
|
||||||
## 开发
|
## Development
|
||||||
|
|
||||||
### 前置条件
|
### Prerequisites
|
||||||
|
|
||||||
在开始之前,请确保您的开发环境已安装以下工具:
|
Before starting, ensure your development environment has the following tools installed:
|
||||||
|
|
||||||
1. **Go** (版本 >= 1.25)
|
1. **Go** (version >= 1.25)
|
||||||
2. **Node.js**
|
2. **Node.js**
|
||||||
3. **Wails CLI**
|
3. **Wails CLI**
|
||||||
|
4. **UPX**
|
||||||
|
|
||||||
### 安装依赖
|
### Install Dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入项目目录
|
# Enter project directory
|
||||||
cd mesh-drop
|
cd mesh-drop
|
||||||
|
|
||||||
# 安装前端依赖 (通常 Wails 会自动处理,但手动安装可确保环境清晰)
|
# Install frontend dependencies (Wails usually handles this automatically, but manual installation ensures a clean environment)
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
cd ..
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
### 运行开发环境
|
### Run Development Environment
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wails dev
|
wails3 dev
|
||||||
```
|
```
|
||||||
|
|||||||
91
README.zh.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Mesh Drop
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
简易、快速的局域网文件传输工具,基于 Wails 和 Vue 构建。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **文件传输**:支持多文件发送,轻松共享。
|
||||||
|
- **文件夹传输**:支持发送整个文件夹结构。
|
||||||
|
- **文本传输**:快速同步设备间的文本内容。
|
||||||
|
- **加密传输**:确保数据在传输过程中的安全性。
|
||||||
|
- **安全身份**:基于 Ed25519 的签名验证,防止伪造。
|
||||||
|
|
||||||
|
## 安全机制
|
||||||
|
|
||||||
|
Mesh Drop 采用多层安全设计来保护用户免受潜在的恶意攻击:
|
||||||
|
|
||||||
|
1. **身份验证 (Identity)**
|
||||||
|
- 每个设备在首次启动时生成一对唯一的 Ed25519 密钥。
|
||||||
|
- 所有广播包(Presence Broadcast)都使用私钥签名。
|
||||||
|
- 接收端通过公钥验证签名,确保身份未被篡改。
|
||||||
|
|
||||||
|
2. **信任机制 (Trust)**
|
||||||
|
- 采用 TOFU (Trust On First Use) 策略。
|
||||||
|
- 用户可以选择“信任”某个 Peer,一旦信任,该 Peer 的公钥将被固定(Pinning)。
|
||||||
|
- 之后收到该 Peer ID 的所有数据包,必须通过已保存公钥的验证,否则会被标记为 **Mismatch**。
|
||||||
|
- **防欺骗**:如果有人试图伪造已信任 Peer 的 ID,UI 会显示明显的“Mismatch”安全警告,并阻止元数据被覆盖。
|
||||||
|
|
||||||
|
3. **传输加密 (Encryption)**
|
||||||
|
- 文件传输服务使用 HTTPS 协议。
|
||||||
|
- 自动生成自签名证书进行通信加密,防止传输内容被窃听。
|
||||||
|
|
||||||
|
## 截图
|
||||||
|
|
||||||
|
|  |  |
|
||||||
|
| -------------------------------- | -------------------------------- |
|
||||||
|
|
||||||
|
## 待办事项
|
||||||
|
|
||||||
|
- [x] 剪辑板传输
|
||||||
|
- [x] 文件夹传输
|
||||||
|
- [x] 取消传输
|
||||||
|
- [x] 多文件发送
|
||||||
|
- [x] 加密传输
|
||||||
|
- [x] 设置页面
|
||||||
|
- [x] 单例模式
|
||||||
|
- [x] 系统通知
|
||||||
|
- [x] 清理历史
|
||||||
|
- [x] 自动接收
|
||||||
|
- [x] 应用图标
|
||||||
|
- [x] 信任Peer
|
||||||
|
- [x] 多语言
|
||||||
|
- [ ] 系统托盘(最小化到托盘)徽章 https://github.com/wailsapp/wails/issues/4494
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
本项目使用现代化的技术栈构建:
|
||||||
|
|
||||||
|
- **后端**: [Go](https://go.dev/) + [Wails v3](https://v3.wails.io/)
|
||||||
|
- **前端**: [Vue 3](https://vuejs.org/) + [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
- **UI 框架**: [Vuetify](https://vuetifyjs.com/)
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
在开始之前,请确保您的开发环境已安装以下工具:
|
||||||
|
|
||||||
|
1. **Go** (版本 >= 1.25)
|
||||||
|
2. **Node.js**
|
||||||
|
3. **Wails CLI**
|
||||||
|
4. **UPX**
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd mesh-drop
|
||||||
|
|
||||||
|
# 安装前端依赖 (通常 Wails 会自动处理,但手动安装可确保环境清晰)
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行开发环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wails3 dev
|
||||||
|
```
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
common: ./build/Taskfile.yml
|
common: ./build/Taskfile.yml
|
||||||
windows: ./build/windows/Taskfile.yml
|
windows: ./build/windows/Taskfile.yml
|
||||||
darwin: ./build/darwin/Taskfile.yml
|
darwin: ./build/darwin/Taskfile.yml
|
||||||
linux: ./build/linux/Taskfile.yml
|
linux: ./build/linux/Taskfile.yml
|
||||||
ios: ./build/ios/Taskfile.yml
|
|
||||||
android: ./build/android/Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
APP_NAME: "mesh-drop"
|
APP_NAME: "mesh-drop"
|
||||||
BIN_DIR: "bin"
|
BIN_DIR: "bin"
|
||||||
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
VITE_PORT: "{{.WAILS_VITE_PORT | default 9245}}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
go:mod:tidy:
|
go:mod:tidy:
|
||||||
@@ -42,7 +42,6 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'
|
BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'
|
||||||
|
|
||||||
|
|
||||||
frontend:vendor:puppertino:
|
frontend:vendor:puppertino:
|
||||||
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
|
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
|
||||||
sources:
|
sources:
|
||||||
@@ -78,7 +77,6 @@ tasks:
|
|||||||
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
|
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
generate:bindings:
|
generate:bindings:
|
||||||
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
|
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
|
||||||
summary: Generates bindings for the frontend
|
summary: Generates bindings for the frontend
|
||||||
@@ -190,63 +188,3 @@ tasks:
|
|||||||
preconditions:
|
preconditions:
|
||||||
- sh: docker info > /dev/null 2>&1
|
- sh: docker info > /dev/null 2>&1
|
||||||
msg: "Docker is required. Please install Docker first."
|
msg: "Docker is required. Please install Docker first."
|
||||||
|
|
||||||
ios:device:list:
|
|
||||||
summary: Lists connected iOS devices (UDIDs)
|
|
||||||
cmds:
|
|
||||||
- xcrun xcdevice list
|
|
||||||
|
|
||||||
ios:run:device:
|
|
||||||
summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl)
|
|
||||||
vars:
|
|
||||||
PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/<YourProject>.xcodeproj
|
|
||||||
SCHEME: '{{.SCHEME}}' # e.g., ios.dev
|
|
||||||
CONFIG: '{{.CONFIG | default "Debug"}}'
|
|
||||||
DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}'
|
|
||||||
UDID: '{{.UDID}}' # from `task ios:device:list`
|
|
||||||
BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev
|
|
||||||
TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing
|
|
||||||
preconditions:
|
|
||||||
- sh: xcrun -f xcodebuild
|
|
||||||
msg: "xcodebuild not found. Please install Xcode."
|
|
||||||
- sh: xcrun -f devicectl
|
|
||||||
msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)."
|
|
||||||
- sh: test -n '{{.PROJECT}}'
|
|
||||||
msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)."
|
|
||||||
- sh: test -n '{{.SCHEME}}'
|
|
||||||
msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)."
|
|
||||||
- sh: test -n '{{.UDID}}'
|
|
||||||
msg: "Set UDID to your device UDID (see: task ios:device:list)."
|
|
||||||
- sh: test -n '{{.BUNDLE_ID}}'
|
|
||||||
msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)."
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}"
|
|
||||||
XCB_ARGS=(
|
|
||||||
-project "{{.PROJECT}}"
|
|
||||||
-scheme "{{.SCHEME}}"
|
|
||||||
-configuration "{{.CONFIG}}"
|
|
||||||
-destination "id={{.UDID}}"
|
|
||||||
-derivedDataPath "{{.DERIVED}}"
|
|
||||||
-allowProvisioningUpdates
|
|
||||||
-allowProvisioningDeviceRegistration
|
|
||||||
)
|
|
||||||
# Optionally inject signing identifiers if provided
|
|
||||||
if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi
|
|
||||||
if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi
|
|
||||||
xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true
|
|
||||||
# If xcpretty isn't installed, run without it
|
|
||||||
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
|
|
||||||
xcodebuild "${XCB_ARGS[@]}" build
|
|
||||||
fi
|
|
||||||
# Find built .app
|
|
||||||
APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1)
|
|
||||||
if [ -z "$APP_PATH" ]; then
|
|
||||||
echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Installing: $APP_PATH"
|
|
||||||
xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH"
|
|
||||||
echo "Launching: {{.BUNDLE_ID}}"
|
|
||||||
xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}"
|
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ../Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
APP_ID: '{{.APP_ID | default "com.wails.app"}}'
|
|
||||||
MIN_SDK: '21'
|
|
||||||
TARGET_SDK: '34'
|
|
||||||
NDK_VERSION: 'r26d'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
install:deps:
|
|
||||||
summary: Check and install Android development dependencies
|
|
||||||
cmds:
|
|
||||||
- go run build/android/scripts/deps/install_deps.go
|
|
||||||
env:
|
|
||||||
TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}'
|
|
||||||
prompt: This will check and install Android development dependencies. Continue?
|
|
||||||
|
|
||||||
build:
|
|
||||||
summary: Creates a build of the application for Android
|
|
||||||
deps:
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: generate:android:bindings
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
PRODUCTION:
|
|
||||||
ref: .PRODUCTION
|
|
||||||
- task: common:generate:icons
|
|
||||||
cmds:
|
|
||||||
- echo "Building Android app {{.APP_NAME}}..."
|
|
||||||
- task: compile:go:shared
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH | default "arm64"}}'
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}'
|
|
||||||
env:
|
|
||||||
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
|
||||||
|
|
||||||
compile:go:shared:
|
|
||||||
summary: Compile Go code to shared library (.so)
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
NDK_ROOT="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/{{.NDK_VERSION}}}"
|
|
||||||
if [ ! -d "$NDK_ROOT" ]; then
|
|
||||||
echo "Error: Android NDK not found at $NDK_ROOT"
|
|
||||||
echo "Please set ANDROID_NDK_HOME or install NDK {{.NDK_VERSION}} via Android Studio"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Determine toolchain based on host OS
|
|
||||||
case "$(uname -s)" in
|
|
||||||
Darwin) HOST_TAG="darwin-x86_64" ;;
|
|
||||||
Linux) HOST_TAG="linux-x86_64" ;;
|
|
||||||
*) echo "Unsupported host OS"; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$HOST_TAG"
|
|
||||||
|
|
||||||
# Set compiler based on architecture
|
|
||||||
case "{{.ARCH}}" in
|
|
||||||
arm64)
|
|
||||||
export CC="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang"
|
|
||||||
export CXX="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang++"
|
|
||||||
export GOARCH=arm64
|
|
||||||
JNI_DIR="arm64-v8a"
|
|
||||||
;;
|
|
||||||
amd64|x86_64)
|
|
||||||
export CC="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang"
|
|
||||||
export CXX="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang++"
|
|
||||||
export GOARCH=amd64
|
|
||||||
JNI_DIR="x86_64"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported architecture: {{.ARCH}}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
export CGO_ENABLED=1
|
|
||||||
export GOOS=android
|
|
||||||
|
|
||||||
mkdir -p {{.BIN_DIR}}
|
|
||||||
mkdir -p build/android/app/src/main/jniLibs/$JNI_DIR
|
|
||||||
|
|
||||||
go build -buildmode=c-shared {{.BUILD_FLAGS}} \
|
|
||||||
-o build/android/app/src/main/jniLibs/$JNI_DIR/libwails.so
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}'
|
|
||||||
|
|
||||||
compile:go:all-archs:
|
|
||||||
summary: Compile Go code for all Android architectures (fat APK)
|
|
||||||
cmds:
|
|
||||||
- task: compile:go:shared
|
|
||||||
vars:
|
|
||||||
ARCH: arm64
|
|
||||||
- task: compile:go:shared
|
|
||||||
vars:
|
|
||||||
ARCH: amd64
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages a production build of the application into an APK
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
PRODUCTION: "true"
|
|
||||||
cmds:
|
|
||||||
- task: assemble:apk
|
|
||||||
|
|
||||||
package:fat:
|
|
||||||
summary: Packages a production build for all architectures (fat APK)
|
|
||||||
cmds:
|
|
||||||
- task: compile:go:all-archs
|
|
||||||
- task: assemble:apk
|
|
||||||
|
|
||||||
assemble:apk:
|
|
||||||
summary: Assembles the APK using Gradle
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
cd build/android
|
|
||||||
./gradlew assembleDebug
|
|
||||||
cp app/build/outputs/apk/debug/app-debug.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
|
||||||
echo "APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
|
||||||
|
|
||||||
assemble:apk:release:
|
|
||||||
summary: Assembles a release APK using Gradle
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
cd build/android
|
|
||||||
./gradlew assembleRelease
|
|
||||||
cp app/build/outputs/apk/release/app-release-unsigned.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}-release.apk"
|
|
||||||
echo "Release APK created: {{.BIN_DIR}}/{{.APP_NAME}}-release.apk"
|
|
||||||
|
|
||||||
generate:android:bindings:
|
|
||||||
internal: true
|
|
||||||
summary: Generates bindings for Android
|
|
||||||
sources:
|
|
||||||
- "**/*.go"
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
generates:
|
|
||||||
- frontend/bindings/**/*
|
|
||||||
cmds:
|
|
||||||
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true
|
|
||||||
env:
|
|
||||||
GOOS: android
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
GOARCH: '{{.ARCH | default "arm64"}}'
|
|
||||||
|
|
||||||
ensure-emulator:
|
|
||||||
internal: true
|
|
||||||
summary: Ensure Android Emulator is running
|
|
||||||
silent: true
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
# Check if an emulator is already running
|
|
||||||
if adb devices | grep -q "emulator"; then
|
|
||||||
echo "Emulator already running"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get first available AVD
|
|
||||||
AVD_NAME=$(emulator -list-avds | head -1)
|
|
||||||
if [ -z "$AVD_NAME" ]; then
|
|
||||||
echo "No Android Virtual Devices found."
|
|
||||||
echo "Create one using: Android Studio > Tools > Device Manager"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Starting emulator: $AVD_NAME"
|
|
||||||
emulator -avd "$AVD_NAME" -no-snapshot-load &
|
|
||||||
|
|
||||||
# Wait for emulator to boot (max 60 seconds)
|
|
||||||
echo "Waiting for emulator to boot..."
|
|
||||||
adb wait-for-device
|
|
||||||
|
|
||||||
for i in {1..60}; do
|
|
||||||
BOOT_COMPLETED=$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')
|
|
||||||
if [ "$BOOT_COMPLETED" = "1" ]; then
|
|
||||||
echo "Emulator booted successfully"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Emulator boot timeout"
|
|
||||||
exit 1
|
|
||||||
preconditions:
|
|
||||||
- sh: command -v adb
|
|
||||||
msg: "adb not found. Please install Android SDK and add platform-tools to PATH"
|
|
||||||
- sh: command -v emulator
|
|
||||||
msg: "emulator not found. Please install Android SDK and add emulator to PATH"
|
|
||||||
|
|
||||||
deploy-emulator:
|
|
||||||
summary: Deploy to Android Emulator
|
|
||||||
deps: [package]
|
|
||||||
cmds:
|
|
||||||
- adb uninstall {{.APP_ID}} 2>/dev/null || true
|
|
||||||
- adb install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
|
||||||
- adb shell am start -n {{.APP_ID}}/.MainActivity
|
|
||||||
|
|
||||||
run:
|
|
||||||
summary: Run the application in Android Emulator
|
|
||||||
deps:
|
|
||||||
- task: ensure-emulator
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
ARCH: x86_64
|
|
||||||
cmds:
|
|
||||||
- task: assemble:apk
|
|
||||||
- adb uninstall {{.APP_ID}} 2>/dev/null || true
|
|
||||||
- adb install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
|
||||||
- adb shell am start -n {{.APP_ID}}/.MainActivity
|
|
||||||
|
|
||||||
logs:
|
|
||||||
summary: Stream Android logcat filtered to this app
|
|
||||||
cmds:
|
|
||||||
- adb logcat -v time | grep -E "(Wails|{{.APP_NAME}})"
|
|
||||||
|
|
||||||
logs:all:
|
|
||||||
summary: Stream all Android logcat (verbose)
|
|
||||||
cmds:
|
|
||||||
- adb logcat -v time
|
|
||||||
|
|
||||||
clean:
|
|
||||||
summary: Clean build artifacts
|
|
||||||
cmds:
|
|
||||||
- rm -rf {{.BIN_DIR}}
|
|
||||||
- rm -rf build/android/app/build
|
|
||||||
- rm -rf build/android/app/src/main/jniLibs/*/libwails.so
|
|
||||||
- rm -rf build/android/.gradle
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.wails.app'
|
|
||||||
compileSdk 34
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
buildConfig = true
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.wails.app"
|
|
||||||
minSdk 21
|
|
||||||
targetSdk 34
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
// Configure supported ABIs
|
|
||||||
ndk {
|
|
||||||
abiFilters 'arm64-v8a', 'x86_64'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
debug {
|
|
||||||
debuggable true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source sets configuration
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
// JNI libraries are in jniLibs folder
|
|
||||||
jniLibs.srcDirs = ['src/main/jniLibs']
|
|
||||||
// Assets for the WebView
|
|
||||||
assets.srcDirs = ['src/main/assets']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packaging options
|
|
||||||
packagingOptions {
|
|
||||||
// Don't strip Go symbols in debug builds
|
|
||||||
doNotStrip '*/arm64-v8a/libwails.so'
|
|
||||||
doNotStrip '*/x86_64/libwails.so'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
||||||
implementation 'androidx.webkit:webkit:1.9.0'
|
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
|
||||||
}
|
|
||||||
12
build/android/app/proguard-rules.pro
vendored
@@ -1,12 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
|
|
||||||
# Keep native methods
|
|
||||||
-keepclasseswithmembernames class * {
|
|
||||||
native <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep Wails bridge classes
|
|
||||||
-keep class com.wails.app.WailsBridge { *; }
|
|
||||||
-keep class com.wails.app.WailsJSBridge { *; }
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<!-- Internet permission for WebView -->
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.WailsApp"
|
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
tools:targetApi="31">
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
|
||||||
android:windowSoftInputMode="adjustResize">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
package com.wails.app;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebResourceRequest;
|
|
||||||
import android.webkit.WebResourceResponse;
|
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.webkit.WebViewAssetLoader;
|
|
||||||
import com.wails.app.BuildConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MainActivity hosts the WebView and manages the Wails application lifecycle.
|
|
||||||
* It uses WebViewAssetLoader to serve assets from the Go library without
|
|
||||||
* requiring a network server.
|
|
||||||
*/
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
private static final String TAG = "WailsActivity";
|
|
||||||
private static final String WAILS_SCHEME = "https";
|
|
||||||
private static final String WAILS_HOST = "wails.localhost";
|
|
||||||
|
|
||||||
private WebView webView;
|
|
||||||
private WailsBridge bridge;
|
|
||||||
private WebViewAssetLoader assetLoader;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
// Initialize the native Go library
|
|
||||||
bridge = new WailsBridge(this);
|
|
||||||
bridge.initialize();
|
|
||||||
|
|
||||||
// Set up WebView
|
|
||||||
setupWebView();
|
|
||||||
|
|
||||||
// Load the application
|
|
||||||
loadApplication();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
private void setupWebView() {
|
|
||||||
webView = findViewById(R.id.webview);
|
|
||||||
|
|
||||||
// Configure WebView settings
|
|
||||||
WebSettings settings = webView.getSettings();
|
|
||||||
settings.setJavaScriptEnabled(true);
|
|
||||||
settings.setDomStorageEnabled(true);
|
|
||||||
settings.setDatabaseEnabled(true);
|
|
||||||
settings.setAllowFileAccess(false);
|
|
||||||
settings.setAllowContentAccess(false);
|
|
||||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
|
||||||
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
|
|
||||||
|
|
||||||
// Enable debugging in debug builds
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
WebView.setWebContentsDebuggingEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up asset loader for serving local assets
|
|
||||||
assetLoader = new WebViewAssetLoader.Builder()
|
|
||||||
.setDomain(WAILS_HOST)
|
|
||||||
.addPathHandler("/", new WailsPathHandler(bridge))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Set up WebView client to intercept requests
|
|
||||||
webView.setWebViewClient(new WebViewClient() {
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
|
||||||
String url = request.getUrl().toString();
|
|
||||||
Log.d(TAG, "Intercepting request: " + url);
|
|
||||||
|
|
||||||
// Handle wails.localhost requests
|
|
||||||
if (request.getUrl().getHost() != null &&
|
|
||||||
request.getUrl().getHost().equals(WAILS_HOST)) {
|
|
||||||
|
|
||||||
// For wails API calls (runtime, capabilities, etc.), we need to pass the full URL
|
|
||||||
// including query string because WebViewAssetLoader.PathHandler strips query params
|
|
||||||
String path = request.getUrl().getPath();
|
|
||||||
if (path != null && path.startsWith("/wails/")) {
|
|
||||||
// Get full path with query string for runtime calls
|
|
||||||
String fullPath = path;
|
|
||||||
String query = request.getUrl().getQuery();
|
|
||||||
if (query != null && !query.isEmpty()) {
|
|
||||||
fullPath = path + "?" + query;
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Wails API call detected, full path: " + fullPath);
|
|
||||||
|
|
||||||
// Call bridge directly with full path
|
|
||||||
byte[] data = bridge.serveAsset(fullPath, request.getMethod(), "{}");
|
|
||||||
if (data != null && data.length > 0) {
|
|
||||||
java.io.InputStream inputStream = new java.io.ByteArrayInputStream(data);
|
|
||||||
java.util.Map<String, String> headers = new java.util.HashMap<>();
|
|
||||||
headers.put("Access-Control-Allow-Origin", "*");
|
|
||||||
headers.put("Cache-Control", "no-cache");
|
|
||||||
headers.put("Content-Type", "application/json");
|
|
||||||
|
|
||||||
return new WebResourceResponse(
|
|
||||||
"application/json",
|
|
||||||
"UTF-8",
|
|
||||||
200,
|
|
||||||
"OK",
|
|
||||||
headers,
|
|
||||||
inputStream
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Return error response if data is null
|
|
||||||
return new WebResourceResponse(
|
|
||||||
"application/json",
|
|
||||||
"UTF-8",
|
|
||||||
500,
|
|
||||||
"Internal Error",
|
|
||||||
new java.util.HashMap<>(),
|
|
||||||
new java.io.ByteArrayInputStream("{}".getBytes())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For regular assets, use the asset loader
|
|
||||||
return assetLoader.shouldInterceptRequest(request.getUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.shouldInterceptRequest(view, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView view, String url) {
|
|
||||||
super.onPageFinished(view, url);
|
|
||||||
Log.d(TAG, "Page loaded: " + url);
|
|
||||||
// Inject Wails runtime
|
|
||||||
bridge.injectRuntime(webView, url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add JavaScript interface for Go communication
|
|
||||||
webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadApplication() {
|
|
||||||
// Load the main page from the asset server
|
|
||||||
String url = WAILS_SCHEME + "://" + WAILS_HOST + "/";
|
|
||||||
Log.d(TAG, "Loading URL: " + url);
|
|
||||||
webView.loadUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute JavaScript in the WebView from the Go side
|
|
||||||
*/
|
|
||||||
public void executeJavaScript(final String js) {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
if (webView != null) {
|
|
||||||
webView.evaluateJavascript(js, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (bridge != null) {
|
|
||||||
bridge.onResume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
if (bridge != null) {
|
|
||||||
bridge.onPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (bridge != null) {
|
|
||||||
bridge.shutdown();
|
|
||||||
}
|
|
||||||
if (webView != null) {
|
|
||||||
webView.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (webView != null && webView.canGoBack()) {
|
|
||||||
webView.goBack();
|
|
||||||
} else {
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
package com.wails.app;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WailsBridge manages the connection between the Java/Android side and the Go native library.
|
|
||||||
* It handles:
|
|
||||||
* - Loading and initializing the native Go library
|
|
||||||
* - Serving asset requests from Go
|
|
||||||
* - Passing messages between JavaScript and Go
|
|
||||||
* - Managing callbacks for async operations
|
|
||||||
*/
|
|
||||||
public class WailsBridge {
|
|
||||||
private static final String TAG = "WailsBridge";
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Load the native Go library
|
|
||||||
System.loadLibrary("wails");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final AtomicInteger callbackIdGenerator = new AtomicInteger(0);
|
|
||||||
private final ConcurrentHashMap<Integer, AssetCallback> pendingAssetCallbacks = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<Integer, MessageCallback> pendingMessageCallbacks = new ConcurrentHashMap<>();
|
|
||||||
private WebView webView;
|
|
||||||
private volatile boolean initialized = false;
|
|
||||||
|
|
||||||
// Native methods - implemented in Go
|
|
||||||
private static native void nativeInit(WailsBridge bridge);
|
|
||||||
private static native void nativeShutdown();
|
|
||||||
private static native void nativeOnResume();
|
|
||||||
private static native void nativeOnPause();
|
|
||||||
private static native void nativeOnPageFinished(String url);
|
|
||||||
private static native byte[] nativeServeAsset(String path, String method, String headers);
|
|
||||||
private static native String nativeHandleMessage(String message);
|
|
||||||
private static native String nativeGetAssetMimeType(String path);
|
|
||||||
|
|
||||||
public WailsBridge(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the native Go library
|
|
||||||
*/
|
|
||||||
public void initialize() {
|
|
||||||
if (initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "Initializing Wails bridge...");
|
|
||||||
try {
|
|
||||||
nativeInit(this);
|
|
||||||
initialized = true;
|
|
||||||
Log.i(TAG, "Wails bridge initialized successfully");
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Failed to initialize Wails bridge", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdown the native Go library
|
|
||||||
*/
|
|
||||||
public void shutdown() {
|
|
||||||
if (!initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "Shutting down Wails bridge...");
|
|
||||||
try {
|
|
||||||
nativeShutdown();
|
|
||||||
initialized = false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error during shutdown", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the activity resumes
|
|
||||||
*/
|
|
||||||
public void onResume() {
|
|
||||||
if (initialized) {
|
|
||||||
nativeOnResume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the activity pauses
|
|
||||||
*/
|
|
||||||
public void onPause() {
|
|
||||||
if (initialized) {
|
|
||||||
nativeOnPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serve an asset from the Go asset server
|
|
||||||
* @param path The URL path requested
|
|
||||||
* @param method The HTTP method
|
|
||||||
* @param headers The request headers as JSON
|
|
||||||
* @return The asset data, or null if not found
|
|
||||||
*/
|
|
||||||
public byte[] serveAsset(String path, String method, String headers) {
|
|
||||||
if (!initialized) {
|
|
||||||
Log.w(TAG, "Bridge not initialized, cannot serve asset: " + path);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Serving asset: " + path);
|
|
||||||
try {
|
|
||||||
return nativeServeAsset(path, method, headers);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error serving asset: " + path, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the MIME type for an asset
|
|
||||||
* @param path The asset path
|
|
||||||
* @return The MIME type string
|
|
||||||
*/
|
|
||||||
public String getAssetMimeType(String path) {
|
|
||||||
if (!initialized) {
|
|
||||||
return "application/octet-stream";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String mimeType = nativeGetAssetMimeType(path);
|
|
||||||
return mimeType != null ? mimeType : "application/octet-stream";
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error getting MIME type for: " + path, e);
|
|
||||||
return "application/octet-stream";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a message from JavaScript
|
|
||||||
* @param message The message from JavaScript (JSON)
|
|
||||||
* @return The response to send back to JavaScript (JSON)
|
|
||||||
*/
|
|
||||||
public String handleMessage(String message) {
|
|
||||||
if (!initialized) {
|
|
||||||
Log.w(TAG, "Bridge not initialized, cannot handle message");
|
|
||||||
return "{\"error\":\"Bridge not initialized\"}";
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Handling message from JS: " + message);
|
|
||||||
try {
|
|
||||||
return nativeHandleMessage(message);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error handling message", e);
|
|
||||||
return "{\"error\":\"" + e.getMessage() + "\"}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject the Wails runtime JavaScript into the WebView.
|
|
||||||
* Called when the page finishes loading.
|
|
||||||
* @param webView The WebView to inject into
|
|
||||||
* @param url The URL that finished loading
|
|
||||||
*/
|
|
||||||
public void injectRuntime(WebView webView, String url) {
|
|
||||||
this.webView = webView;
|
|
||||||
// Notify Go side that page has finished loading so it can inject the runtime
|
|
||||||
Log.d(TAG, "Page finished loading: " + url + ", notifying Go side");
|
|
||||||
if (initialized) {
|
|
||||||
nativeOnPageFinished(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute JavaScript in the WebView (called from Go side)
|
|
||||||
* @param js The JavaScript code to execute
|
|
||||||
*/
|
|
||||||
public void executeJavaScript(String js) {
|
|
||||||
if (webView != null) {
|
|
||||||
webView.post(() -> webView.evaluateJavascript(js, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from Go when an event needs to be emitted to JavaScript
|
|
||||||
* @param eventName The event name
|
|
||||||
* @param eventData The event data (JSON)
|
|
||||||
*/
|
|
||||||
public void emitEvent(String eventName, String eventData) {
|
|
||||||
String js = String.format("window.wails && window.wails._emit('%s', %s);",
|
|
||||||
escapeJsString(eventName), eventData);
|
|
||||||
executeJavaScript(js);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String escapeJsString(String str) {
|
|
||||||
return str.replace("\\", "\\\\")
|
|
||||||
.replace("'", "\\'")
|
|
||||||
.replace("\n", "\\n")
|
|
||||||
.replace("\r", "\\r");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback interfaces
|
|
||||||
public interface AssetCallback {
|
|
||||||
void onAssetReady(byte[] data, String mimeType);
|
|
||||||
void onAssetError(String error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface MessageCallback {
|
|
||||||
void onResponse(String response);
|
|
||||||
void onError(String error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
package com.wails.app;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.JavascriptInterface;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import com.wails.app.BuildConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WailsJSBridge provides the JavaScript interface that allows the web frontend
|
|
||||||
* to communicate with the Go backend. This is exposed to JavaScript as the
|
|
||||||
* `window.wails` object.
|
|
||||||
*
|
|
||||||
* Similar to iOS's WKScriptMessageHandler but using Android's addJavascriptInterface.
|
|
||||||
*/
|
|
||||||
public class WailsJSBridge {
|
|
||||||
private static final String TAG = "WailsJSBridge";
|
|
||||||
|
|
||||||
private final WailsBridge bridge;
|
|
||||||
private final WebView webView;
|
|
||||||
|
|
||||||
public WailsJSBridge(WailsBridge bridge, WebView webView) {
|
|
||||||
this.bridge = bridge;
|
|
||||||
this.webView = webView;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to Go and return the response synchronously.
|
|
||||||
* Called from JavaScript: wails.invoke(message)
|
|
||||||
*
|
|
||||||
* @param message The message to send (JSON string)
|
|
||||||
* @return The response from Go (JSON string)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public String invoke(String message) {
|
|
||||||
Log.d(TAG, "Invoke called: " + message);
|
|
||||||
return bridge.handleMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to Go asynchronously.
|
|
||||||
* The response will be sent back via a callback.
|
|
||||||
* Called from JavaScript: wails.invokeAsync(callbackId, message)
|
|
||||||
*
|
|
||||||
* @param callbackId The callback ID to use for the response
|
|
||||||
* @param message The message to send (JSON string)
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public void invokeAsync(final String callbackId, final String message) {
|
|
||||||
Log.d(TAG, "InvokeAsync called: " + message);
|
|
||||||
|
|
||||||
// Handle in background thread to not block JavaScript
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
String response = bridge.handleMessage(message);
|
|
||||||
sendCallback(callbackId, response, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error in async invoke", e);
|
|
||||||
sendCallback(callbackId, null, e.getMessage());
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message from JavaScript to Android's logcat
|
|
||||||
* Called from JavaScript: wails.log(level, message)
|
|
||||||
*
|
|
||||||
* @param level The log level (debug, info, warn, error)
|
|
||||||
* @param message The message to log
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public void log(String level, String message) {
|
|
||||||
switch (level.toLowerCase()) {
|
|
||||||
case "debug":
|
|
||||||
Log.d(TAG + "/JS", message);
|
|
||||||
break;
|
|
||||||
case "info":
|
|
||||||
Log.i(TAG + "/JS", message);
|
|
||||||
break;
|
|
||||||
case "warn":
|
|
||||||
Log.w(TAG + "/JS", message);
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
Log.e(TAG + "/JS", message);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.v(TAG + "/JS", message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the platform name
|
|
||||||
* Called from JavaScript: wails.platform()
|
|
||||||
*
|
|
||||||
* @return "android"
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public String platform() {
|
|
||||||
return "android";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we're running in debug mode
|
|
||||||
* Called from JavaScript: wails.isDebug()
|
|
||||||
*
|
|
||||||
* @return true if debug build, false otherwise
|
|
||||||
*/
|
|
||||||
@JavascriptInterface
|
|
||||||
public boolean isDebug() {
|
|
||||||
return BuildConfig.DEBUG;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a callback response to JavaScript
|
|
||||||
*/
|
|
||||||
private void sendCallback(String callbackId, String result, String error) {
|
|
||||||
final String js;
|
|
||||||
if (error != null) {
|
|
||||||
js = String.format(
|
|
||||||
"window.wails && window.wails._callback('%s', null, '%s');",
|
|
||||||
escapeJsString(callbackId),
|
|
||||||
escapeJsString(error)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
js = String.format(
|
|
||||||
"window.wails && window.wails._callback('%s', %s, null);",
|
|
||||||
escapeJsString(callbackId),
|
|
||||||
result != null ? result : "null"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
webView.post(() -> webView.evaluateJavascript(js, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String escapeJsString(String str) {
|
|
||||||
if (str == null) return "";
|
|
||||||
return str.replace("\\", "\\\\")
|
|
||||||
.replace("'", "\\'")
|
|
||||||
.replace("\n", "\\n")
|
|
||||||
.replace("\r", "\\r");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package com.wails.app;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebResourceResponse;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.webkit.WebViewAssetLoader;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WailsPathHandler implements WebViewAssetLoader.PathHandler to serve assets
|
|
||||||
* from the Go asset server. This allows the WebView to load assets without
|
|
||||||
* using a network server, similar to iOS's WKURLSchemeHandler.
|
|
||||||
*/
|
|
||||||
public class WailsPathHandler implements WebViewAssetLoader.PathHandler {
|
|
||||||
private static final String TAG = "WailsPathHandler";
|
|
||||||
|
|
||||||
private final WailsBridge bridge;
|
|
||||||
|
|
||||||
public WailsPathHandler(WailsBridge bridge) {
|
|
||||||
this.bridge = bridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public WebResourceResponse handle(@NonNull String path) {
|
|
||||||
Log.d(TAG, "Handling path: " + path);
|
|
||||||
|
|
||||||
// Normalize path
|
|
||||||
if (path.isEmpty() || path.equals("/")) {
|
|
||||||
path = "/index.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get asset from Go
|
|
||||||
byte[] data = bridge.serveAsset(path, "GET", "{}");
|
|
||||||
|
|
||||||
if (data == null || data.length == 0) {
|
|
||||||
Log.w(TAG, "Asset not found: " + path);
|
|
||||||
return null; // Return null to let WebView handle 404
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine MIME type
|
|
||||||
String mimeType = bridge.getAssetMimeType(path);
|
|
||||||
Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)");
|
|
||||||
|
|
||||||
// Create response
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(data);
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
|
||||||
headers.put("Access-Control-Allow-Origin", "*");
|
|
||||||
headers.put("Cache-Control", "no-cache");
|
|
||||||
|
|
||||||
return new WebResourceResponse(
|
|
||||||
mimeType,
|
|
||||||
"UTF-8",
|
|
||||||
200,
|
|
||||||
"OK",
|
|
||||||
headers,
|
|
||||||
inputStream
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine MIME type from file extension
|
|
||||||
*/
|
|
||||||
private String getMimeType(String path) {
|
|
||||||
String lowerPath = path.toLowerCase();
|
|
||||||
|
|
||||||
if (lowerPath.endsWith(".html") || lowerPath.endsWith(".htm")) {
|
|
||||||
return "text/html";
|
|
||||||
} else if (lowerPath.endsWith(".js") || lowerPath.endsWith(".mjs")) {
|
|
||||||
return "application/javascript";
|
|
||||||
} else if (lowerPath.endsWith(".css")) {
|
|
||||||
return "text/css";
|
|
||||||
} else if (lowerPath.endsWith(".json")) {
|
|
||||||
return "application/json";
|
|
||||||
} else if (lowerPath.endsWith(".png")) {
|
|
||||||
return "image/png";
|
|
||||||
} else if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
|
|
||||||
return "image/jpeg";
|
|
||||||
} else if (lowerPath.endsWith(".gif")) {
|
|
||||||
return "image/gif";
|
|
||||||
} else if (lowerPath.endsWith(".svg")) {
|
|
||||||
return "image/svg+xml";
|
|
||||||
} else if (lowerPath.endsWith(".ico")) {
|
|
||||||
return "image/x-icon";
|
|
||||||
} else if (lowerPath.endsWith(".woff")) {
|
|
||||||
return "font/woff";
|
|
||||||
} else if (lowerPath.endsWith(".woff2")) {
|
|
||||||
return "font/woff2";
|
|
||||||
} else if (lowerPath.endsWith(".ttf")) {
|
|
||||||
return "font/ttf";
|
|
||||||
} else if (lowerPath.endsWith(".eot")) {
|
|
||||||
return "application/vnd.ms-fontobject";
|
|
||||||
} else if (lowerPath.endsWith(".xml")) {
|
|
||||||
return "application/xml";
|
|
||||||
} else if (lowerPath.endsWith(".txt")) {
|
|
||||||
return "text/plain";
|
|
||||||
} else if (lowerPath.endsWith(".wasm")) {
|
|
||||||
return "application/wasm";
|
|
||||||
} else if (lowerPath.endsWith(".mp3")) {
|
|
||||||
return "audio/mpeg";
|
|
||||||
} else if (lowerPath.endsWith(".mp4")) {
|
|
||||||
return "video/mp4";
|
|
||||||
} else if (lowerPath.endsWith(".webm")) {
|
|
||||||
return "video/webm";
|
|
||||||
} else if (lowerPath.endsWith(".webp")) {
|
|
||||||
return "image/webp";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "application/octet-stream";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/main_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<WebView
|
|
||||||
android:id="@+id/webview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="wails_blue">#3574D4</color>
|
|
||||||
<color name="wails_blue_dark">#2C5FB8</color>
|
|
||||||
<color name="wails_background">#1B2636</color>
|
|
||||||
<color name="white">#FFFFFFFF</color>
|
|
||||||
<color name="black">#FF000000</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Wails App</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<style name="Theme.WailsApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
|
||||||
<item name="colorPrimary">@color/wails_blue</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/wails_blue_dark</item>
|
|
||||||
<item name="colorOnPrimary">@android:color/white</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor">@color/wails_background</item>
|
|
||||||
<item name="android:navigationBarColor">@color/wails_background</item>
|
|
||||||
<!-- Window background -->
|
|
||||||
<item name="android:windowBackground">@color/wails_background</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
plugins {
|
|
||||||
id 'com.android.application' version '8.7.3' apply false
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. For more details, visit
|
|
||||||
# https://developer.android.com/build/optimize-your-build#parallel
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
||||||
# Android operating system, and which are packaged with your app's APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
|
||||||
|
|
||||||
# Enables namespacing of each library's R class so that its R class includes only the
|
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
|
||||||
# thereby reducing the size of the R class for that library
|
|
||||||
android.nonTransitiveRClass=true
|
|
||||||
BIN
build/android/gradle/wrapper/gradle-wrapper.jar
vendored
@@ -1,7 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
248
build/android/gradlew
vendored
@@ -1,248 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015 the original authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# This is normally unused
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
if ! command -v java >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
|
||||||
# and any embedded shellness will be escaped.
|
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
|
||||||
# treated as '${Hostname}' itself on the command line.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
93
build/android/gradlew.bat
vendored
@@ -1,93 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
|
||||||
|
|
||||||
echo. 1>&2
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
|
||||||
echo. 1>&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
|
||||||
echo location of your Java installation. 1>&2
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo. 1>&2
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
|
||||||
echo. 1>&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
|
||||||
echo location of your Java installation. 1>&2
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build android
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Register main function to be called when the Android app initializes
|
|
||||||
// This is necessary because in c-shared build mode, main() is not automatically called
|
|
||||||
application.RegisterAndroidMain(main)
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Checking Android development dependencies...")
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
errors := []string{}
|
|
||||||
|
|
||||||
// Check Go
|
|
||||||
if !checkCommand("go", "version") {
|
|
||||||
errors = append(errors, "Go is not installed. Install from https://go.dev/dl/")
|
|
||||||
} else {
|
|
||||||
fmt.Println("✓ Go is installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check ANDROID_HOME
|
|
||||||
androidHome := os.Getenv("ANDROID_HOME")
|
|
||||||
if androidHome == "" {
|
|
||||||
androidHome = os.Getenv("ANDROID_SDK_ROOT")
|
|
||||||
}
|
|
||||||
if androidHome == "" {
|
|
||||||
// Try common default locations
|
|
||||||
home, _ := os.UserHomeDir()
|
|
||||||
possiblePaths := []string{
|
|
||||||
filepath.Join(home, "Android", "Sdk"),
|
|
||||||
filepath.Join(home, "Library", "Android", "sdk"),
|
|
||||||
"/usr/local/share/android-sdk",
|
|
||||||
}
|
|
||||||
for _, p := range possiblePaths {
|
|
||||||
if _, err := os.Stat(p); err == nil {
|
|
||||||
androidHome = p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if androidHome == "" {
|
|
||||||
errors = append(errors, "ANDROID_HOME not set. Install Android Studio and set ANDROID_HOME environment variable")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("✓ ANDROID_HOME: %s\n", androidHome)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check adb
|
|
||||||
if !checkCommand("adb", "version") {
|
|
||||||
if androidHome != "" {
|
|
||||||
platformTools := filepath.Join(androidHome, "platform-tools")
|
|
||||||
errors = append(errors, fmt.Sprintf("adb not found. Add %s to PATH", platformTools))
|
|
||||||
} else {
|
|
||||||
errors = append(errors, "adb not found. Install Android SDK Platform-Tools")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("✓ adb is installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check emulator
|
|
||||||
if !checkCommand("emulator", "-list-avds") {
|
|
||||||
if androidHome != "" {
|
|
||||||
emulatorPath := filepath.Join(androidHome, "emulator")
|
|
||||||
errors = append(errors, fmt.Sprintf("emulator not found. Add %s to PATH", emulatorPath))
|
|
||||||
} else {
|
|
||||||
errors = append(errors, "emulator not found. Install Android Emulator via SDK Manager")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("✓ Android Emulator is installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check NDK
|
|
||||||
ndkHome := os.Getenv("ANDROID_NDK_HOME")
|
|
||||||
if ndkHome == "" && androidHome != "" {
|
|
||||||
// Look for NDK in default location
|
|
||||||
ndkDir := filepath.Join(androidHome, "ndk")
|
|
||||||
if entries, err := os.ReadDir(ndkDir); err == nil {
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.IsDir() {
|
|
||||||
ndkHome = filepath.Join(ndkDir, entry.Name())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ndkHome == "" {
|
|
||||||
errors = append(errors, "Android NDK not found. Install NDK via Android Studio > SDK Manager > SDK Tools > NDK (Side by side)")
|
|
||||||
} else {
|
|
||||||
fmt.Printf("✓ Android NDK: %s\n", ndkHome)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check Java
|
|
||||||
if !checkCommand("java", "-version") {
|
|
||||||
errors = append(errors, "Java not found. Install JDK 11+ (OpenJDK recommended)")
|
|
||||||
} else {
|
|
||||||
fmt.Println("✓ Java is installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for AVD (Android Virtual Device)
|
|
||||||
if checkCommand("emulator", "-list-avds") {
|
|
||||||
cmd := exec.Command("emulator", "-list-avds")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err == nil && len(strings.TrimSpace(string(output))) > 0 {
|
|
||||||
avds := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
||||||
fmt.Printf("✓ Found %d Android Virtual Device(s)\n", len(avds))
|
|
||||||
} else {
|
|
||||||
fmt.Println("⚠ No Android Virtual Devices found. Create one via Android Studio > Tools > Device Manager")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
fmt.Println("❌ Missing dependencies:")
|
|
||||||
for _, err := range errors {
|
|
||||||
fmt.Printf(" - %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Setup instructions:")
|
|
||||||
fmt.Println("1. Install Android Studio: https://developer.android.com/studio")
|
|
||||||
fmt.Println("2. Open SDK Manager and install:")
|
|
||||||
fmt.Println(" - Android SDK Platform (API 34)")
|
|
||||||
fmt.Println(" - Android SDK Build-Tools")
|
|
||||||
fmt.Println(" - Android SDK Platform-Tools")
|
|
||||||
fmt.Println(" - Android Emulator")
|
|
||||||
fmt.Println(" - NDK (Side by side)")
|
|
||||||
fmt.Println("3. Set environment variables:")
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
fmt.Println(" export ANDROID_HOME=$HOME/Library/Android/sdk")
|
|
||||||
} else {
|
|
||||||
fmt.Println(" export ANDROID_HOME=$HOME/Android/Sdk")
|
|
||||||
}
|
|
||||||
fmt.Println(" export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator")
|
|
||||||
fmt.Println("4. Create an AVD via Android Studio > Tools > Device Manager")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("✓ All Android development dependencies are installed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommand(name string, args ...string) bool {
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
return cmd.Run() == nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "WailsApp"
|
|
||||||
include ':app'
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 583 533" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<g transform="matrix(1,0,0,1,-246,-251)">
|
|
||||||
<g id="Ebene1">
|
|
||||||
<path d="M246,251L265,784L401,784L506,450L507,450L505,784L641,784L829,251L682,251L596,567L595,567L596,251L478,251L378,568L391,251L246,251Z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 698 B |
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"fill" : {
|
|
||||||
"automatic-gradient" : "extended-gray:1.00000,1.00000"
|
|
||||||
},
|
|
||||||
"groups" : [
|
|
||||||
{
|
|
||||||
"layers" : [
|
|
||||||
{
|
|
||||||
"fill-specializations" : [
|
|
||||||
{
|
|
||||||
"appearance" : "dark",
|
|
||||||
"value" : {
|
|
||||||
"solid" : "srgb:0.92143,0.92145,0.92144,1.00000"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"appearance" : "tinted",
|
|
||||||
"value" : {
|
|
||||||
"solid" : "srgb:0.83742,0.83744,0.83743,1.00000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"image-name" : "wails_icon_vector.svg",
|
|
||||||
"name" : "wails_icon_vector",
|
|
||||||
"position" : {
|
|
||||||
"scale" : 1.25,
|
|
||||||
"translation-in-points" : [
|
|
||||||
36.890625,
|
|
||||||
4.96875
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shadow" : {
|
|
||||||
"kind" : "neutral",
|
|
||||||
"opacity" : 0.5
|
|
||||||
},
|
|
||||||
"specular" : true,
|
|
||||||
"translucency" : {
|
|
||||||
"enabled" : true,
|
|
||||||
"value" : 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"supported-platforms" : {
|
|
||||||
"circles" : [
|
|
||||||
"watchOS"
|
|
||||||
],
|
|
||||||
"squares" : "shared"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,7 +1,7 @@
|
|||||||
# This file contains the configuration for this project.
|
# This file contains the configuration for this project.
|
||||||
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
|
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
|
||||||
# Note that this will overwrite any changes you have made to the assets.
|
# Note that this will overwrite any changes you have made to the assets.
|
||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
# This information is used to generate the build assets.
|
# This information is used to generate the build assets.
|
||||||
info:
|
info:
|
||||||
@@ -12,23 +12,6 @@ info:
|
|||||||
copyright: "(c) 2025, My Company" # Copyright text
|
copyright: "(c) 2025, My Company" # Copyright text
|
||||||
comments: "Some Product Comments" # Comments
|
comments: "Some Product Comments" # Comments
|
||||||
version: "0.0.1" # The application version
|
version: "0.0.1" # The application version
|
||||||
# cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional)
|
|
||||||
# # Should match the name of your .icon file without the extension
|
|
||||||
# # If not set and Assets.car exists, defaults to "appicon"
|
|
||||||
|
|
||||||
# iOS build configuration (uncomment to customise iOS project generation)
|
|
||||||
# Note: Keys under `ios` OVERRIDE values under `info` when set.
|
|
||||||
# ios:
|
|
||||||
# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier)
|
|
||||||
# bundleID: "com.mycompany.myproduct"
|
|
||||||
# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName)
|
|
||||||
# displayName: "My Product"
|
|
||||||
# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion)
|
|
||||||
# version: "0.0.1"
|
|
||||||
# # The company/organisation name for templates and project settings
|
|
||||||
# company: "My Company"
|
|
||||||
# # Additional comments to embed in Info.plist metadata
|
|
||||||
# comments: "Some Product Comments"
|
|
||||||
|
|
||||||
# Dev mode configuration
|
# Dev mode configuration
|
||||||
dev_mode:
|
dev_mode:
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
},
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "icon-20@2x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-20@3x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-29@2x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-29@3x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-40@2x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-40@3x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-60@2x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-60@3x.png",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"scale" : "3x",
|
|
||||||
"size" : "60x60"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-20.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-20@2x.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "20x20"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-29.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-29@2x.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "29x29"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-40.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-40@2x.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "40x40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-76.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-76@2x.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "76x76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-83.5@2x.png",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"scale" : "2x",
|
|
||||||
"size" : "83.5x83.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "icon-1024.png",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"scale" : "1x",
|
|
||||||
"size" : "1024x1024"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>mesh-drop</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.example.meshdrop.dev</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>My Product (Dev)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>My Product (Dev)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>0.1.0-dev</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0.1.0</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>15.0</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>armv7</string>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSAllowsLocalNetworking</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<!-- Development mode enabled -->
|
|
||||||
<key>WailsDevelopmentMode</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© 2026, My Company</string>
|
|
||||||
|
|
||||||
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>This is a comment</string>
|
|
||||||
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>mesh-drop</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.example.meshdrop</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>My Product</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>My Product</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>0.1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0.1.0</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>15.0</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>armv7</string>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSAllowsLocalNetworking</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© 2026, My Company</string>
|
|
||||||
|
|
||||||
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>This is a comment</string>
|
|
||||||
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
|
||||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
|
||||||
<dependencies>
|
|
||||||
<deployment identifier="iOS"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<!--View Controller-->
|
|
||||||
<scene sceneID="EHf-IW-A2E">
|
|
||||||
<objects>
|
|
||||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
|
||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="My Product" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
|
|
||||||
<rect key="frame" x="0.0" y="397" width="393" height="43"/>
|
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A mesh-drop application" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
|
|
||||||
<rect key="frame" x="0.0" y="448" width="393" height="21"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
</subviews>
|
|
||||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
|
|
||||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/2" constant="-20" id="moa-c2-u7t"/>
|
|
||||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
|
|
||||||
|
|
||||||
<constraint firstItem="MN2-I3-ftu" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="8" symbolic="YES" id="cPy-rs-vsC"/>
|
|
||||||
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="OQL-iM-xY6"/>
|
|
||||||
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="Dti-5h-tvW"/>
|
|
||||||
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="53" y="375"/>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
</document>
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ../Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
BUNDLE_ID: '{{.BUNDLE_ID | default "com.wails.app"}}'
|
|
||||||
# SDK_PATH is computed lazily at task-level to avoid errors on non-macOS systems
|
|
||||||
# Each task that needs it defines SDK_PATH in its own vars section
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
install:deps:
|
|
||||||
summary: Check and install iOS development dependencies
|
|
||||||
cmds:
|
|
||||||
- go run build/ios/scripts/deps/install_deps.go
|
|
||||||
env:
|
|
||||||
TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}'
|
|
||||||
prompt: This will check and install iOS development dependencies. Continue?
|
|
||||||
|
|
||||||
# Note: Bindings generation may show CGO warnings for iOS C imports.
|
|
||||||
# These warnings are harmless and don't affect the generated bindings,
|
|
||||||
# as the generator only needs to parse Go types, not C implementations.
|
|
||||||
build:
|
|
||||||
summary: Creates a build of the application for iOS
|
|
||||||
deps:
|
|
||||||
- task: generate:ios:overlay
|
|
||||||
- task: generate:ios:xcode
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: generate:ios:bindings
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
PRODUCTION:
|
|
||||||
ref: .PRODUCTION
|
|
||||||
- task: common:generate:icons
|
|
||||||
cmds:
|
|
||||||
- echo "Building iOS app {{.APP_NAME}}..."
|
|
||||||
- go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json {{.BUILD_FLAGS}} -o {{.OUTPUT}}.a
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,ios -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags ios,debug -buildvcs=false -gcflags=all="-l"{{end}}'
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
SDK_PATH:
|
|
||||||
sh: xcrun --sdk iphonesimulator --show-sdk-path
|
|
||||||
env:
|
|
||||||
GOOS: ios
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
GOARCH: '{{.ARCH | default "arm64"}}'
|
|
||||||
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
|
||||||
CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0'
|
|
||||||
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator'
|
|
||||||
|
|
||||||
compile:objc:
|
|
||||||
summary: Compile Objective-C iOS wrapper
|
|
||||||
vars:
|
|
||||||
SDK_PATH:
|
|
||||||
sh: xcrun --sdk iphonesimulator --show-sdk-path
|
|
||||||
cmds:
|
|
||||||
- xcrun -sdk iphonesimulator clang -target arm64-apple-ios15.0-simulator -isysroot {{.SDK_PATH}} -framework Foundation -framework UIKit -framework WebKit -o {{.BIN_DIR}}/{{.APP_NAME}} build/ios/main.m
|
|
||||||
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}"
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages a production build of the application into a `.app` bundle
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
PRODUCTION: "true"
|
|
||||||
cmds:
|
|
||||||
- task: create:app:bundle
|
|
||||||
|
|
||||||
create:app:bundle:
|
|
||||||
summary: Creates an iOS `.app` bundle
|
|
||||||
cmds:
|
|
||||||
- rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
- cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
|
|
||||||
- cp build/ios/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/"
|
|
||||||
- |
|
|
||||||
# Compile asset catalog and embed icons in the app bundle
|
|
||||||
APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
AC_IN="build/ios/xcode/main/Assets.xcassets"
|
|
||||||
if [ -d "$AC_IN" ]; then
|
|
||||||
TMP_AC=$(mktemp -d)
|
|
||||||
xcrun actool \
|
|
||||||
--compile "$TMP_AC" \
|
|
||||||
--app-icon AppIcon \
|
|
||||||
--platform iphonesimulator \
|
|
||||||
--minimum-deployment-target 15.0 \
|
|
||||||
--product-type com.apple.product-type.application \
|
|
||||||
--target-device iphone \
|
|
||||||
--target-device ipad \
|
|
||||||
--output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \
|
|
||||||
"$AC_IN"
|
|
||||||
if [ -f "$TMP_AC/Assets.car" ]; then
|
|
||||||
cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car"
|
|
||||||
fi
|
|
||||||
rm -rf "$TMP_AC"
|
|
||||||
if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then
|
|
||||||
/usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
|
|
||||||
deploy-simulator:
|
|
||||||
summary: Deploy to iOS Simulator
|
|
||||||
deps: [package]
|
|
||||||
cmds:
|
|
||||||
- xcrun simctl terminate booted {{.BUNDLE_ID}} 2>/dev/null || true
|
|
||||||
- xcrun simctl uninstall booted {{.BUNDLE_ID}} 2>/dev/null || true
|
|
||||||
- xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
- xcrun simctl launch booted {{.BUNDLE_ID}}
|
|
||||||
|
|
||||||
compile:ios:
|
|
||||||
summary: Compile the iOS executable from Go archive and main.m
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
SDK_PATH:
|
|
||||||
sh: xcrun --sdk iphonesimulator --show-sdk-path
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
MAIN_M=build/ios/xcode/main/main.m
|
|
||||||
if [ ! -f "$MAIN_M" ]; then
|
|
||||||
MAIN_M=build/ios/main.m
|
|
||||||
fi
|
|
||||||
xcrun -sdk iphonesimulator clang \
|
|
||||||
-target arm64-apple-ios15.0-simulator \
|
|
||||||
-isysroot {{.SDK_PATH}} \
|
|
||||||
-framework Foundation -framework UIKit -framework WebKit \
|
|
||||||
-framework Security -framework CoreFoundation \
|
|
||||||
-lresolv \
|
|
||||||
-o "{{.BIN_DIR}}/{{.APP_NAME | lower}}" \
|
|
||||||
"$MAIN_M" "{{.BIN_DIR}}/{{.APP_NAME}}.a"
|
|
||||||
|
|
||||||
generate:ios:bindings:
|
|
||||||
internal: true
|
|
||||||
summary: Generates bindings for iOS with proper CGO flags
|
|
||||||
sources:
|
|
||||||
- "**/*.go"
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
generates:
|
|
||||||
- frontend/bindings/**/*
|
|
||||||
vars:
|
|
||||||
SDK_PATH:
|
|
||||||
sh: xcrun --sdk iphonesimulator --show-sdk-path
|
|
||||||
cmds:
|
|
||||||
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true
|
|
||||||
env:
|
|
||||||
GOOS: ios
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
GOARCH: '{{.ARCH | default "arm64"}}'
|
|
||||||
CGO_CFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0'
|
|
||||||
CGO_LDFLAGS: '-isysroot {{.SDK_PATH}} -target arm64-apple-ios15.0-simulator'
|
|
||||||
|
|
||||||
ensure-simulator:
|
|
||||||
internal: true
|
|
||||||
summary: Ensure iOS Simulator is running and booted
|
|
||||||
silent: true
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
if ! xcrun simctl list devices booted | grep -q "Booted"; then
|
|
||||||
echo "Starting iOS Simulator..."
|
|
||||||
# Get first available iPhone device
|
|
||||||
DEVICE_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -o "[A-F0-9-]\{36\}" || true)
|
|
||||||
if [ -z "$DEVICE_ID" ]; then
|
|
||||||
echo "No iPhone simulator found. Creating one..."
|
|
||||||
RUNTIME=$(xcrun simctl list runtimes | grep iOS | tail -1 | awk '{print $NF}')
|
|
||||||
DEVICE_ID=$(xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" "$RUNTIME")
|
|
||||||
fi
|
|
||||||
# Boot the device
|
|
||||||
echo "Booting device $DEVICE_ID..."
|
|
||||||
xcrun simctl boot "$DEVICE_ID" 2>/dev/null || true
|
|
||||||
# Open Simulator app
|
|
||||||
open -a Simulator
|
|
||||||
# Wait for boot (max 30 seconds)
|
|
||||||
for i in {1..30}; do
|
|
||||||
if xcrun simctl list devices booted | grep -q "Booted"; then
|
|
||||||
echo "Simulator booted successfully"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
# Final check
|
|
||||||
if ! xcrun simctl list devices booted | grep -q "Booted"; then
|
|
||||||
echo "Failed to boot simulator after 30 seconds"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
preconditions:
|
|
||||||
- sh: command -v xcrun
|
|
||||||
msg: "xcrun not found. Please run 'wails3 task ios:install:deps' to install iOS development dependencies"
|
|
||||||
|
|
||||||
generate:ios:overlay:
|
|
||||||
internal: true
|
|
||||||
summary: Generate Go build overlay and iOS shim
|
|
||||||
sources:
|
|
||||||
- build/config.yml
|
|
||||||
generates:
|
|
||||||
- build/ios/xcode/overlay.json
|
|
||||||
- build/ios/xcode/gen/main_ios.gen.go
|
|
||||||
cmds:
|
|
||||||
- wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml
|
|
||||||
|
|
||||||
generate:ios:xcode:
|
|
||||||
internal: true
|
|
||||||
summary: Generate iOS Xcode project structure and assets
|
|
||||||
sources:
|
|
||||||
- build/config.yml
|
|
||||||
- build/appicon.png
|
|
||||||
generates:
|
|
||||||
- build/ios/xcode/main/main.m
|
|
||||||
- build/ios/xcode/main/Assets.xcassets/**/*
|
|
||||||
- build/ios/xcode/project.pbxproj
|
|
||||||
cmds:
|
|
||||||
- wails3 ios xcode:gen -outdir build/ios/xcode -config build/config.yml
|
|
||||||
|
|
||||||
run:
|
|
||||||
summary: Run the application in iOS Simulator
|
|
||||||
deps:
|
|
||||||
- task: ensure-simulator
|
|
||||||
- task: compile:ios
|
|
||||||
cmds:
|
|
||||||
- rm -rf "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
- cp "{{.BIN_DIR}}/{{.APP_NAME | lower}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/{{.APP_NAME | lower}}"
|
|
||||||
- cp build/ios/Info.dev.plist "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Info.plist"
|
|
||||||
- |
|
|
||||||
# Compile asset catalog and embed icons for dev bundle
|
|
||||||
APP_BUNDLE="{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
AC_IN="build/ios/xcode/main/Assets.xcassets"
|
|
||||||
if [ -d "$AC_IN" ]; then
|
|
||||||
TMP_AC=$(mktemp -d)
|
|
||||||
xcrun actool \
|
|
||||||
--compile "$TMP_AC" \
|
|
||||||
--app-icon AppIcon \
|
|
||||||
--platform iphonesimulator \
|
|
||||||
--minimum-deployment-target 15.0 \
|
|
||||||
--product-type com.apple.product-type.application \
|
|
||||||
--target-device iphone \
|
|
||||||
--target-device ipad \
|
|
||||||
--output-partial-info-plist "$APP_BUNDLE/assetcatalog_generated_info.plist" \
|
|
||||||
"$AC_IN"
|
|
||||||
if [ -f "$TMP_AC/Assets.car" ]; then
|
|
||||||
cp -f "$TMP_AC/Assets.car" "$APP_BUNDLE/Assets.car"
|
|
||||||
fi
|
|
||||||
rm -rf "$TMP_AC"
|
|
||||||
if [ -f "$APP_BUNDLE/assetcatalog_generated_info.plist" ]; then
|
|
||||||
/usr/libexec/PlistBuddy -c "Merge $APP_BUNDLE/assetcatalog_generated_info.plist" "$APP_BUNDLE/Info.plist" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
- codesign --force --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
- xcrun simctl terminate booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true
|
|
||||||
- xcrun simctl uninstall booted "com.wails.{{.APP_NAME | lower}}.dev" 2>/dev/null || true
|
|
||||||
- xcrun simctl install booted "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
- xcrun simctl launch booted "com.wails.{{.APP_NAME | lower}}.dev"
|
|
||||||
|
|
||||||
xcode:
|
|
||||||
summary: Open the generated Xcode project for this app
|
|
||||||
cmds:
|
|
||||||
- task: generate:ios:xcode
|
|
||||||
- open build/ios/xcode/main.xcodeproj
|
|
||||||
|
|
||||||
logs:
|
|
||||||
summary: Stream iOS Simulator logs filtered to this app
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
xcrun simctl spawn booted log stream \
|
|
||||||
--level debug \
|
|
||||||
--style compact \
|
|
||||||
--predicate 'senderImagePath CONTAINS[c] "{{.APP_NAME | lower}}.app/" OR composedMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR eventMessage CONTAINS[c] "{{.APP_NAME | lower}}" OR process == "{{.APP_NAME | lower}}" OR category CONTAINS[c] "{{.APP_NAME | lower}}"'
|
|
||||||
|
|
||||||
logs:dev:
|
|
||||||
summary: Stream logs for the dev bundle (used by `task ios:run`)
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
xcrun simctl spawn booted log stream \
|
|
||||||
--level debug \
|
|
||||||
--style compact \
|
|
||||||
--predicate 'senderImagePath CONTAINS[c] ".dev.app/" OR subsystem == "com.wails.{{.APP_NAME | lower}}.dev" OR process == "{{.APP_NAME | lower}}"'
|
|
||||||
|
|
||||||
logs:wide:
|
|
||||||
summary: Wide log stream to help discover the exact process/bundle identifiers
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
xcrun simctl spawn booted log stream \
|
|
||||||
--level debug \
|
|
||||||
--style compact \
|
|
||||||
--predicate 'senderImagePath CONTAINS[c] ".app/"'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
//go:build !ios
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
|
|
||||||
// modifyOptionsForIOS is a no-op on non-iOS platforms
|
|
||||||
func modifyOptionsForIOS(opts *application.Options) {
|
|
||||||
// No modifications needed for non-iOS platforms
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
|
||||||
|
|
||||||
// modifyOptionsForIOS adjusts the application options for iOS
|
|
||||||
func modifyOptionsForIOS(opts *application.Options) {
|
|
||||||
// Disable signal handlers on iOS to prevent crashes
|
|
||||||
opts.DisableDefaultSignalHandler = true
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Build configuration
|
|
||||||
APP_NAME="mesh-drop"
|
|
||||||
BUNDLE_ID="com.example.meshdrop"
|
|
||||||
VERSION="0.1.0"
|
|
||||||
BUILD_NUMBER="0.1.0"
|
|
||||||
BUILD_DIR="build/ios"
|
|
||||||
TARGET="simulator"
|
|
||||||
|
|
||||||
echo "Building iOS app: $APP_NAME"
|
|
||||||
echo "Bundle ID: $BUNDLE_ID"
|
|
||||||
echo "Version: $VERSION ($BUILD_NUMBER)"
|
|
||||||
echo "Target: $TARGET"
|
|
||||||
|
|
||||||
# Ensure build directory exists
|
|
||||||
mkdir -p "$BUILD_DIR"
|
|
||||||
|
|
||||||
# Determine SDK and target architecture
|
|
||||||
if [ "$TARGET" = "simulator" ]; then
|
|
||||||
SDK="iphonesimulator"
|
|
||||||
ARCH="arm64-apple-ios15.0-simulator"
|
|
||||||
elif [ "$TARGET" = "device" ]; then
|
|
||||||
SDK="iphoneos"
|
|
||||||
ARCH="arm64-apple-ios15.0"
|
|
||||||
else
|
|
||||||
echo "Unknown target: $TARGET"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get SDK path
|
|
||||||
SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path)
|
|
||||||
|
|
||||||
# Compile the application
|
|
||||||
echo "Compiling with SDK: $SDK"
|
|
||||||
xcrun -sdk $SDK clang \
|
|
||||||
-target $ARCH \
|
|
||||||
-isysroot "$SDK_PATH" \
|
|
||||||
-framework Foundation \
|
|
||||||
-framework UIKit \
|
|
||||||
-framework WebKit \
|
|
||||||
-framework CoreGraphics \
|
|
||||||
-o "$BUILD_DIR/$APP_NAME" \
|
|
||||||
"$BUILD_DIR/main.m"
|
|
||||||
|
|
||||||
# Create app bundle
|
|
||||||
echo "Creating app bundle..."
|
|
||||||
APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
|
|
||||||
rm -rf "$APP_BUNDLE"
|
|
||||||
mkdir -p "$APP_BUNDLE"
|
|
||||||
|
|
||||||
# Move executable
|
|
||||||
mv "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/"
|
|
||||||
|
|
||||||
# Copy Info.plist
|
|
||||||
cp "$BUILD_DIR/Info.plist" "$APP_BUNDLE/"
|
|
||||||
|
|
||||||
# Sign the app
|
|
||||||
echo "Signing app..."
|
|
||||||
codesign --force --sign - "$APP_BUNDLE"
|
|
||||||
|
|
||||||
echo "Build complete: $APP_BUNDLE"
|
|
||||||
|
|
||||||
# Deploy to simulator if requested
|
|
||||||
if [ "$TARGET" = "simulator" ]; then
|
|
||||||
echo "Deploying to simulator..."
|
|
||||||
xcrun simctl terminate booted "$BUNDLE_ID" 2>/dev/null || true
|
|
||||||
xcrun simctl install booted "$APP_BUNDLE"
|
|
||||||
xcrun simctl launch booted "$BUNDLE_ID"
|
|
||||||
echo "App launched on simulator"
|
|
||||||
fi
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<!-- Development entitlements -->
|
|
||||||
<key>get-task-allow</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<!-- App Sandbox -->
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<!-- Network access -->
|
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<!-- File access (read-only) -->
|
|
||||||
<key>com.apple.security.files.user-selected.read-only</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# iOS Icon Placeholder
|
|
||||||
# This file should be replaced with the actual app icon (1024x1024 PNG)
|
|
||||||
# The build process will generate all required icon sizes from this base icon
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
// Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate)
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// External Go initialization function from the c-archive (declare before use)
|
|
||||||
extern void WailsIOSMain();
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
// Disable buffering so stdout/stderr from Go log.Printf flush immediately
|
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
|
||||||
setvbuf(stderr, NULL, _IONBF, 0);
|
|
||||||
|
|
||||||
// Start Go runtime on a background queue to avoid blocking main thread/UI
|
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
||||||
WailsIOSMain();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run UIApplicationMain using WailsAppDelegate provided by the Go archive
|
|
||||||
return UIApplicationMain(argc, argv, nil, @"WailsAppDelegate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"C"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For iOS builds, we need to export a function that can be called from Objective-C
|
|
||||||
// This wrapper allows us to keep the original main.go unmodified
|
|
||||||
|
|
||||||
//export WailsIOSMain
|
|
||||||
func WailsIOSMain() {
|
|
||||||
// DO NOT lock the goroutine to the current OS thread on iOS!
|
|
||||||
// This causes signal handling issues:
|
|
||||||
// "signal 16 received on thread with no signal stack"
|
|
||||||
// "fatal error: non-Go code disabled sigaltstack"
|
|
||||||
// iOS apps run in a sandboxed environment where the Go runtime's
|
|
||||||
// signal handling doesn't work the same way as desktop platforms.
|
|
||||||
|
|
||||||
// Call the actual main function from main.go
|
|
||||||
// This ensures all the user's code is executed
|
|
||||||
main()
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {};
|
|
||||||
objectVersion = 56;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; };
|
|
||||||
C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; };
|
|
||||||
C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; };
|
|
||||||
C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; };
|
|
||||||
C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; };
|
|
||||||
C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; };
|
|
||||||
C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; };
|
|
||||||
C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* My Product.a */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
|
||||||
C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
C0DEBEEF0000000000000004 /* My Product.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "My Product.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
|
||||||
C0DEBEEF0000000000000107 /* My Product.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "My Product.a"; path = ../../../bin/My Product.a; sourceTree = SOURCE_ROOT; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
C0DEBEEF0000000000000010 = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C0DEBEEF0000000000000020 /* Products */,
|
|
||||||
C0DEBEEF0000000000000045 /* Frameworks */,
|
|
||||||
C0DEBEEF0000000000000030 /* main */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
C0DEBEEF0000000000000020 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C0DEBEEF0000000000000004 /* My Product.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
C0DEBEEF0000000000000030 /* main */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C0DEBEEF0000000000000002 /* main.m */,
|
|
||||||
C0DEBEEF0000000000000003 /* Info.plist */,
|
|
||||||
);
|
|
||||||
path = main;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
C0DEBEEF0000000000000045 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C0DEBEEF0000000000000101 /* UIKit.framework */,
|
|
||||||
C0DEBEEF0000000000000102 /* Foundation.framework */,
|
|
||||||
C0DEBEEF0000000000000103 /* WebKit.framework */,
|
|
||||||
C0DEBEEF0000000000000104 /* Security.framework */,
|
|
||||||
C0DEBEEF0000000000000105 /* CoreFoundation.framework */,
|
|
||||||
C0DEBEEF0000000000000106 /* libresolv.tbd */,
|
|
||||||
C0DEBEEF0000000000000107 /* My Product.a */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
C0DEBEEF0000000000000040 /* My Product */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */;
|
|
||||||
buildPhases = (
|
|
||||||
C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */,
|
|
||||||
C0DEBEEF0000000000000050 /* Sources */,
|
|
||||||
C0DEBEEF0000000000000056 /* Frameworks */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = "My Product";
|
|
||||||
productName = "My Product";
|
|
||||||
productReference = C0DEBEEF0000000000000004 /* My Product.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
C0DEBEEF0000000000000060 /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
LastUpgradeCheck = 1500;
|
|
||||||
ORGANIZATIONNAME = "My Company";
|
|
||||||
TargetAttributes = {
|
|
||||||
C0DEBEEF0000000000000040 = {
|
|
||||||
CreatedOnToolsVersion = 15.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */;
|
|
||||||
compatibilityVersion = "Xcode 15.0";
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
);
|
|
||||||
mainGroup = C0DEBEEF0000000000000010;
|
|
||||||
productRefGroup = C0DEBEEF0000000000000020 /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
C0DEBEEF0000000000000040 /* My Product */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
C0DEBEEF0000000000000056 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
C0DEBEEF00000000000000F7 /* My Product.a in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */,
|
|
||||||
C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Prebuild: Wails Go Archive";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nSDK_PATH=$(xcrun --sdk iphonesimulator --show-sdk-path)\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator -mios-simulator-version-min=15.0\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target arm64-apple-ios15.0-simulator\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Build Go c-archive if missing or older than sources\nif [ ! -f bin/My Product.a ]; then\n echo \"Building Go c-archive...\"\n go build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o bin/My Product.a\nfi\n";
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
C0DEBEEF0000000000000050 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
C0DEBEEF0000000000000001 /* main.m in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
C0DEBEEF0000000000000090 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
INFOPLIST_FILE = main/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.meshdrop";
|
|
||||||
PRODUCT_NAME = "My Product";
|
|
||||||
CODE_SIGNING_ALLOWED = NO;
|
|
||||||
SDKROOT = iphonesimulator;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
C0DEBEEF00000000000000A0 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
INFOPLIST_FILE = main/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.meshdrop";
|
|
||||||
PRODUCT_NAME = "My Product";
|
|
||||||
CODE_SIGNING_ALLOWED = NO;
|
|
||||||
SDKROOT = iphonesimulator;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "My Product" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
C0DEBEEF0000000000000090 /* Debug */,
|
|
||||||
C0DEBEEF00000000000000A0 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Debug;
|
|
||||||
};
|
|
||||||
C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
C0DEBEEF0000000000000090 /* Debug */,
|
|
||||||
C0DEBEEF00000000000000A0 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Debug;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = C0DEBEEF0000000000000060 /* Project object */;
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
// install_deps.go - iOS development dependency checker
|
|
||||||
// This script checks for required iOS development tools.
|
|
||||||
// It's designed to be portable across different shells by using Go instead of shell scripts.
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// go run install_deps.go # Interactive mode
|
|
||||||
// TASK_FORCE_YES=true go run install_deps.go # Auto-accept prompts
|
|
||||||
// CI=true go run install_deps.go # CI mode (auto-accept)
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Dependency struct {
|
|
||||||
Name string
|
|
||||||
CheckFunc func() (bool, string) // Returns (success, details)
|
|
||||||
Required bool
|
|
||||||
InstallCmd []string
|
|
||||||
InstallMsg string
|
|
||||||
SuccessMsg string
|
|
||||||
FailureMsg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Checking iOS development dependencies...")
|
|
||||||
fmt.Println("=" + strings.Repeat("=", 50))
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
hasErrors := false
|
|
||||||
dependencies := []Dependency{
|
|
||||||
{
|
|
||||||
Name: "Xcode",
|
|
||||||
CheckFunc: func() (bool, string) {
|
|
||||||
// Check if xcodebuild exists
|
|
||||||
if !checkCommand([]string{"xcodebuild", "-version"}) {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
// Get version info
|
|
||||||
out, err := exec.Command("xcodebuild", "-version").Output()
|
|
||||||
if err != nil {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
if len(lines) > 0 {
|
|
||||||
return true, strings.TrimSpace(lines[0])
|
|
||||||
}
|
|
||||||
return true, ""
|
|
||||||
},
|
|
||||||
Required: true,
|
|
||||||
InstallMsg: "Please install Xcode from the Mac App Store:\n https://apps.apple.com/app/xcode/id497799835\n Xcode is REQUIRED for iOS development (includes iOS SDKs, simulators, and frameworks)",
|
|
||||||
SuccessMsg: "✅ Xcode found",
|
|
||||||
FailureMsg: "❌ Xcode not found (REQUIRED)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Xcode Developer Path",
|
|
||||||
CheckFunc: func() (bool, string) {
|
|
||||||
// Check if xcode-select points to a valid Xcode path
|
|
||||||
out, err := exec.Command("xcode-select", "-p").Output()
|
|
||||||
if err != nil {
|
|
||||||
return false, "xcode-select not configured"
|
|
||||||
}
|
|
||||||
path := strings.TrimSpace(string(out))
|
|
||||||
|
|
||||||
// Check if path exists and is in Xcode.app
|
|
||||||
if _, err := os.Stat(path); err != nil {
|
|
||||||
return false, "Invalid Xcode path"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify it's pointing to Xcode.app (not just Command Line Tools)
|
|
||||||
if !strings.Contains(path, "Xcode.app") {
|
|
||||||
return false, fmt.Sprintf("Points to %s (should be Xcode.app)", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, path
|
|
||||||
},
|
|
||||||
Required: true,
|
|
||||||
InstallCmd: []string{"sudo", "xcode-select", "-s", "/Applications/Xcode.app/Contents/Developer"},
|
|
||||||
InstallMsg: "Xcode developer path needs to be configured",
|
|
||||||
SuccessMsg: "✅ Xcode developer path configured",
|
|
||||||
FailureMsg: "❌ Xcode developer path not configured correctly",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "iOS SDK",
|
|
||||||
CheckFunc: func() (bool, string) {
|
|
||||||
// Get the iOS Simulator SDK path
|
|
||||||
cmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false, "Cannot find iOS SDK"
|
|
||||||
}
|
|
||||||
sdkPath := strings.TrimSpace(string(output))
|
|
||||||
|
|
||||||
// Check if the SDK path exists
|
|
||||||
if _, err := os.Stat(sdkPath); err != nil {
|
|
||||||
return false, "iOS SDK path not found"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for UIKit framework (essential for iOS development)
|
|
||||||
uikitPath := fmt.Sprintf("%s/System/Library/Frameworks/UIKit.framework", sdkPath)
|
|
||||||
if _, err := os.Stat(uikitPath); err != nil {
|
|
||||||
return false, "UIKit.framework not found"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get SDK version
|
|
||||||
versionCmd := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-version")
|
|
||||||
versionOut, _ := versionCmd.Output()
|
|
||||||
version := strings.TrimSpace(string(versionOut))
|
|
||||||
|
|
||||||
return true, fmt.Sprintf("iOS %s SDK", version)
|
|
||||||
},
|
|
||||||
Required: true,
|
|
||||||
InstallMsg: "iOS SDK comes with Xcode. Please ensure Xcode is properly installed.",
|
|
||||||
SuccessMsg: "✅ iOS SDK found with UIKit framework",
|
|
||||||
FailureMsg: "❌ iOS SDK not found or incomplete",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "iOS Simulator Runtime",
|
|
||||||
CheckFunc: func() (bool, string) {
|
|
||||||
if !checkCommand([]string{"xcrun", "simctl", "help"}) {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
// Check if we can list runtimes
|
|
||||||
out, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
|
|
||||||
if err != nil {
|
|
||||||
return false, "Cannot access simulator"
|
|
||||||
}
|
|
||||||
// Count iOS runtimes
|
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
count := 0
|
|
||||||
var versions []string
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
|
|
||||||
count++
|
|
||||||
// Extract version number
|
|
||||||
if parts := strings.Fields(line); len(parts) > 2 {
|
|
||||||
for _, part := range parts {
|
|
||||||
if strings.HasPrefix(part, "(") && strings.HasSuffix(part, ")") {
|
|
||||||
versions = append(versions, strings.Trim(part, "()"))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return true, fmt.Sprintf("%d runtime(s): %s", count, strings.Join(versions, ", "))
|
|
||||||
}
|
|
||||||
return false, "No iOS runtimes installed"
|
|
||||||
},
|
|
||||||
Required: true,
|
|
||||||
InstallMsg: "iOS Simulator runtimes come with Xcode. You may need to download them:\n Xcode → Settings → Platforms → iOS",
|
|
||||||
SuccessMsg: "✅ iOS Simulator runtime available",
|
|
||||||
FailureMsg: "❌ iOS Simulator runtime not available",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each dependency
|
|
||||||
for _, dep := range dependencies {
|
|
||||||
success, details := dep.CheckFunc()
|
|
||||||
if success {
|
|
||||||
msg := dep.SuccessMsg
|
|
||||||
if details != "" {
|
|
||||||
msg = fmt.Sprintf("%s (%s)", dep.SuccessMsg, details)
|
|
||||||
}
|
|
||||||
fmt.Println(msg)
|
|
||||||
} else {
|
|
||||||
fmt.Println(dep.FailureMsg)
|
|
||||||
if details != "" {
|
|
||||||
fmt.Printf(" Details: %s\n", details)
|
|
||||||
}
|
|
||||||
if dep.Required {
|
|
||||||
hasErrors = true
|
|
||||||
if len(dep.InstallCmd) > 0 {
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" " + dep.InstallMsg)
|
|
||||||
fmt.Printf(" Fix command: %s\n", strings.Join(dep.InstallCmd, " "))
|
|
||||||
if promptUser("Do you want to run this command?") {
|
|
||||||
fmt.Println("Running command...")
|
|
||||||
cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
fmt.Printf("Command failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("✅ Command completed. Please run this check again.")
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" Please run manually: %s\n", strings.Join(dep.InstallCmd, " "))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println(" " + dep.InstallMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for iPhone simulators
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Checking for iPhone simulator devices...")
|
|
||||||
if !checkCommand([]string{"xcrun", "simctl", "list", "devices"}) {
|
|
||||||
fmt.Println("❌ Cannot check for iPhone simulators")
|
|
||||||
hasErrors = true
|
|
||||||
} else {
|
|
||||||
out, err := exec.Command("xcrun", "simctl", "list", "devices").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("❌ Failed to list simulator devices")
|
|
||||||
hasErrors = true
|
|
||||||
} else if !strings.Contains(string(out), "iPhone") {
|
|
||||||
fmt.Println("⚠️ No iPhone simulator devices found")
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// Get the latest iOS runtime
|
|
||||||
runtimeOut, err := exec.Command("xcrun", "simctl", "list", "runtimes").Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(" Failed to get iOS runtimes:", err)
|
|
||||||
} else {
|
|
||||||
lines := strings.Split(string(runtimeOut), "\n")
|
|
||||||
var latestRuntime string
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.Contains(line, "iOS") && !strings.Contains(line, "unavailable") {
|
|
||||||
// Extract runtime identifier
|
|
||||||
parts := strings.Fields(line)
|
|
||||||
if len(parts) > 0 {
|
|
||||||
latestRuntime = parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if latestRuntime == "" {
|
|
||||||
fmt.Println(" No iOS runtime found. Please install iOS simulators in Xcode:")
|
|
||||||
fmt.Println(" Xcode → Settings → Platforms → iOS")
|
|
||||||
} else {
|
|
||||||
fmt.Println(" Would you like to create an iPhone 15 Pro simulator?")
|
|
||||||
createCmd := []string{"xcrun", "simctl", "create", "iPhone 15 Pro", "iPhone 15 Pro", latestRuntime}
|
|
||||||
fmt.Printf(" Command: %s\n", strings.Join(createCmd, " "))
|
|
||||||
if promptUser("Create simulator?") {
|
|
||||||
cmd := exec.Command(createCmd[0], createCmd[1:]...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
fmt.Printf(" Failed to create simulator: %v\n", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println(" ✅ iPhone 15 Pro simulator created")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println(" Skipping simulator creation")
|
|
||||||
fmt.Printf(" Create manually: %s\n", strings.Join(createCmd, " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Count iPhone devices
|
|
||||||
count := 0
|
|
||||||
lines := strings.Split(string(out), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("✅ %d iPhone simulator device(s) available\n", count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final summary
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("=" + strings.Repeat("=", 50))
|
|
||||||
if hasErrors {
|
|
||||||
fmt.Println("❌ Some required dependencies are missing or misconfigured.")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Quick setup guide:")
|
|
||||||
fmt.Println("1. Install Xcode from Mac App Store (if not installed)")
|
|
||||||
fmt.Println("2. Open Xcode once and agree to the license")
|
|
||||||
fmt.Println("3. Install additional components when prompted")
|
|
||||||
fmt.Println("4. Run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer")
|
|
||||||
fmt.Println("5. Download iOS simulators: Xcode → Settings → Platforms → iOS")
|
|
||||||
fmt.Println("6. Run this check again")
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
fmt.Println("✅ All required dependencies are installed!")
|
|
||||||
fmt.Println(" You're ready for iOS development with Wails!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCommand(args []string) bool {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
err := cmd.Run()
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func promptUser(question string) bool {
|
|
||||||
// Check if we're in a non-interactive environment
|
|
||||||
if os.Getenv("CI") != "" || os.Getenv("TASK_FORCE_YES") == "true" {
|
|
||||||
fmt.Printf("%s [y/N]: y (auto-accepted)\n", question)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
fmt.Printf("%s [y/N]: ", question)
|
|
||||||
|
|
||||||
response, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
response = strings.ToLower(strings.TrimSpace(response))
|
|
||||||
return response == "y" || response == "yes"
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
common: ../Taskfile.yml
|
common: ../Taskfile.yml
|
||||||
@@ -23,14 +23,14 @@ tasks:
|
|||||||
# 3. Target architecture differs from host architecture (cross-arch compilation)
|
# 3. Target architecture differs from host architecture (cross-arch compilation)
|
||||||
- task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}'
|
- task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}'
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH}}'
|
ARCH: "{{.ARCH}}"
|
||||||
DEV: '{{.DEV}}'
|
DEV: "{{.DEV}}"
|
||||||
OUTPUT: '{{.OUTPUT}}'
|
OUTPUT: "{{.OUTPUT}}"
|
||||||
vars:
|
vars:
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
DEFAULT_OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}"
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
OUTPUT: "{{ .OUTPUT | default .DEFAULT_OUTPUT }}"
|
||||||
# Determine target architecture (defaults to host ARCH if not specified)
|
# Determine target architecture (defaults to host ARCH if not specified)
|
||||||
TARGET_ARCH: '{{.ARCH | default ARCH}}'
|
TARGET_ARCH: "{{.ARCH | default ARCH}}"
|
||||||
# Check if a C compiler is available (gcc or clang)
|
# Check if a C compiler is available (gcc or clang)
|
||||||
HAS_CC:
|
HAS_CC:
|
||||||
sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"'
|
sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"'
|
||||||
@@ -51,13 +51,15 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
||||||
vars:
|
vars:
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
VERSION:
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
sh: git describe --tags --always
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -X mesh-drop/internal/config.Version={{.VERSION}}"{{end}}'
|
||||||
|
DEFAULT_OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}"
|
||||||
|
OUTPUT: "{{ .OUTPUT | default .DEFAULT_OUTPUT }}"
|
||||||
env:
|
env:
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
CGO_ENABLED: 1
|
CGO_ENABLED: 1
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
GOARCH: "{{.ARCH | default ARCH}}"
|
||||||
|
|
||||||
build:docker:
|
build:docker:
|
||||||
summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available)
|
summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available)
|
||||||
@@ -80,8 +82,8 @@ tasks:
|
|||||||
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
||||||
vars:
|
vars:
|
||||||
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
|
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
DEFAULT_OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}"
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
OUTPUT: "{{ .OUTPUT | default .DEFAULT_OUTPUT }}"
|
||||||
# Mount Go module cache for faster builds
|
# Mount Go module cache for faster builds
|
||||||
GO_CACHE_MOUNT:
|
GO_CACHE_MOUNT:
|
||||||
sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"'
|
sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"'
|
||||||
@@ -121,11 +123,11 @@ tasks:
|
|||||||
- cp ../../appicon.png "{{.APP_NAME}}.png"
|
- cp ../../appicon.png "{{.APP_NAME}}.png"
|
||||||
- wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
|
- wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
|
||||||
vars:
|
vars:
|
||||||
APP_NAME: '{{.APP_NAME}}'
|
APP_NAME: "{{.APP_NAME}}"
|
||||||
APP_BINARY: '../../../bin/{{.APP_NAME}}'
|
APP_BINARY: "../../../bin/{{.APP_NAME}}"
|
||||||
ICON: '{{.APP_NAME}}.png'
|
ICON: "{{.APP_NAME}}.png"
|
||||||
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
|
DESKTOP_FILE: "../{{.APP_NAME}}.desktop"
|
||||||
OUTPUT_DIR: '../../../bin'
|
OUTPUT_DIR: "../../../bin"
|
||||||
|
|
||||||
create:deb:
|
create:deb:
|
||||||
summary: Creates a deb package
|
summary: Creates a deb package
|
||||||
@@ -171,17 +173,18 @@ tasks:
|
|||||||
dir: build
|
dir: build
|
||||||
cmds:
|
cmds:
|
||||||
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
|
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
|
||||||
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}"
|
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}" -keywords "{{.KEYWORDS}}"
|
||||||
vars:
|
vars:
|
||||||
APP_NAME: '{{.APP_NAME}}'
|
APP_NAME: "{{.APP_NAME}}"
|
||||||
EXEC: '{{.APP_NAME}}'
|
EXEC: "{{.APP_NAME}}"
|
||||||
ICON: '{{.APP_NAME}}'
|
ICON: "{{.APP_NAME}}"
|
||||||
CATEGORIES: 'Development;'
|
CATEGORIES: '{{.CATEGORIES | default .ENV.CATEGORIES | default "GTK;Utility"}}'
|
||||||
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
|
OUTPUTFILE: "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop"
|
||||||
|
KEYWORDS: '{{.KEYWORDS | default .ENV.KEYWORDS | default "utility"}}'
|
||||||
|
|
||||||
run:
|
run:
|
||||||
cmds:
|
cmds:
|
||||||
- '{{.BIN_DIR}}/{{.APP_NAME}}'
|
- "{{.BIN_DIR}}/{{.APP_NAME}}"
|
||||||
|
|
||||||
sign:deb:
|
sign:deb:
|
||||||
summary: Signs the DEB package
|
summary: Signs the DEB package
|
||||||
|
|||||||
BIN
build/linux/appimage/mesh-drop
Executable file
BIN
build/linux/appimage/mesh-drop.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
@@ -1,13 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Name=My Product
|
|
||||||
Comment=A mesh-drop application
|
|
||||||
# The Exec line includes %u to pass the URL to the application
|
|
||||||
Exec=/usr/local/bin/mesh-drop %u
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Icon=mesh-drop
|
|
||||||
Categories=Utility;
|
|
||||||
StartupWMClass=mesh-drop
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3,8 +3,8 @@ Type=Application
|
|||||||
Name=mesh-drop
|
Name=mesh-drop
|
||||||
Exec=mesh-drop
|
Exec=mesh-drop
|
||||||
Icon=mesh-drop
|
Icon=mesh-drop
|
||||||
Categories=Development;
|
Categories=GTK;Utility
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Keywords=wails
|
Keywords=utility
|
||||||
Version=1.0
|
Version=1.0
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
name: "mesh-drop"
|
name: "mesh-drop"
|
||||||
arch: ${GOARCH}
|
arch: ${GOARCH}
|
||||||
platform: "linux"
|
platform: "linux"
|
||||||
version: "0.1.0"
|
version: "0.0.3"
|
||||||
section: "default"
|
section: "default"
|
||||||
priority: "extra"
|
priority: "extra"
|
||||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||||
description: "A mesh-drop application"
|
description: "A mesh-drop application"
|
||||||
vendor: "My Company"
|
vendor: "nite"
|
||||||
homepage: "https://wails.io"
|
homepage: "https://www.nite07.com"
|
||||||
license: "MIT"
|
license: "MIT"
|
||||||
release: "1"
|
release: "1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
common: ../Taskfile.yml
|
common: ../Taskfile.yml
|
||||||
@@ -21,8 +21,8 @@ tasks:
|
|||||||
# Auto-detect CGO: if CGO_ENABLED=1, use Docker; otherwise use native Go cross-compile
|
# Auto-detect CGO: if CGO_ENABLED=1, use Docker; otherwise use native Go cross-compile
|
||||||
- task: '{{if and (ne OS "windows") (eq .CGO_ENABLED "1")}}build:docker{{else}}build:native{{end}}'
|
- task: '{{if and (ne OS "windows") (eq .CGO_ENABLED "1")}}build:docker{{else}}build:native{{end}}'
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH}}'
|
ARCH: "{{.ARCH}}"
|
||||||
DEV: '{{.DEV}}'
|
DEV: "{{.DEV}}"
|
||||||
vars:
|
vars:
|
||||||
# Default to CGO_ENABLED=0 if not explicitly set
|
# Default to CGO_ENABLED=0 if not explicitly set
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
||||||
@@ -46,12 +46,16 @@ tasks:
|
|||||||
platforms: [windows]
|
platforms: [windows]
|
||||||
- cmd: rm -f *.syso
|
- cmd: rm -f *.syso
|
||||||
platforms: [linux, darwin]
|
platforms: [linux, darwin]
|
||||||
|
- cmd: upx -t "{{.BIN_DIR}}/{{.APP_NAME}}.exe" || upx "{{.BIN_DIR}}/{{.APP_NAME}}.exe"
|
||||||
|
platforms: [linux]
|
||||||
vars:
|
vars:
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
|
VERSION:
|
||||||
|
sh: git describe --tags --always
|
||||||
|
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui -X mesh-drop/internal/config.Version={{.VERSION}}"{{end}}'
|
||||||
env:
|
env:
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
GOARCH: "{{.ARCH | default ARCH}}"
|
||||||
|
|
||||||
build:docker:
|
build:docker:
|
||||||
summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows)
|
summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows)
|
||||||
@@ -104,7 +108,7 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: "{{.ARCH | default ARCH}}"
|
||||||
|
|
||||||
create:nsis:installer:
|
create:nsis:installer:
|
||||||
summary: Creates an NSIS installer
|
summary: Creates an NSIS installer
|
||||||
@@ -121,7 +125,7 @@ tasks:
|
|||||||
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
|
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
|
||||||
{{end}}
|
{{end}}
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: "{{.ARCH | default ARCH}}"
|
||||||
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
||||||
|
|
||||||
create:msix:package:
|
create:msix:package:
|
||||||
@@ -140,7 +144,7 @@ tasks:
|
|||||||
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
||||||
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: "{{.ARCH | default ARCH}}"
|
||||||
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
||||||
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
||||||
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
||||||
@@ -152,7 +156,7 @@ tasks:
|
|||||||
|
|
||||||
run:
|
run:
|
||||||
cmds:
|
cmds:
|
||||||
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
|
- "{{.BIN_DIR}}/{{.APP_NAME}}.exe"
|
||||||
|
|
||||||
sign:
|
sign:
|
||||||
summary: Signs the Windows executable
|
summary: Signs the Windows executable
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
import * as NotificationService from "./notificationservice.js";
|
||||||
|
export {
|
||||||
|
NotificationService
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
NotificationAction,
|
||||||
|
NotificationCategory,
|
||||||
|
NotificationOptions
|
||||||
|
} from "./models.js";
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotificationAction represents an action button for a notification.
|
||||||
|
*/
|
||||||
|
export class NotificationAction {
|
||||||
|
"id"?: string;
|
||||||
|
"title"?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (macOS-specific)
|
||||||
|
*/
|
||||||
|
"destructive"?: boolean;
|
||||||
|
|
||||||
|
/** Creates a new NotificationAction instance. */
|
||||||
|
constructor($$source: Partial<NotificationAction> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NotificationAction instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): NotificationAction {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new NotificationAction($$parsedSource as Partial<NotificationAction>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotificationCategory groups actions for notifications.
|
||||||
|
*/
|
||||||
|
export class NotificationCategory {
|
||||||
|
"id"?: string;
|
||||||
|
"actions"?: NotificationAction[];
|
||||||
|
"hasReplyField"?: boolean;
|
||||||
|
"replyPlaceholder"?: string;
|
||||||
|
"replyButtonTitle"?: string;
|
||||||
|
|
||||||
|
/** Creates a new NotificationCategory instance. */
|
||||||
|
constructor($$source: Partial<NotificationCategory> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NotificationCategory instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): NotificationCategory {
|
||||||
|
const $$createField1_0 = $$createType1;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("actions" in $$parsedSource) {
|
||||||
|
$$parsedSource["actions"] = $$createField1_0($$parsedSource["actions"]);
|
||||||
|
}
|
||||||
|
return new NotificationCategory($$parsedSource as Partial<NotificationCategory>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotificationOptions contains configuration for a notification
|
||||||
|
*/
|
||||||
|
export class NotificationOptions {
|
||||||
|
"id": string;
|
||||||
|
"title": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (macOS and Linux only)
|
||||||
|
*/
|
||||||
|
"subtitle"?: string;
|
||||||
|
"body"?: string;
|
||||||
|
"categoryId"?: string;
|
||||||
|
"data"?: { [_ in string]?: any };
|
||||||
|
|
||||||
|
/** Creates a new NotificationOptions instance. */
|
||||||
|
constructor($$source: Partial<NotificationOptions> = {}) {
|
||||||
|
if (!("id" in $$source)) {
|
||||||
|
this["id"] = "";
|
||||||
|
}
|
||||||
|
if (!("title" in $$source)) {
|
||||||
|
this["title"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NotificationOptions instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): NotificationOptions {
|
||||||
|
const $$createField5_0 = $$createType2;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("data" in $$parsedSource) {
|
||||||
|
$$parsedSource["data"] = $$createField5_0($$parsedSource["data"]);
|
||||||
|
}
|
||||||
|
return new NotificationOptions($$parsedSource as Partial<NotificationOptions>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = NotificationAction.createFrom;
|
||||||
|
const $$createType1 = $Create.Array($$createType0);
|
||||||
|
const $$createType2 = $Create.Map($Create.Any, $Create.Any);
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service represents the notifications service
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
export function CheckNotificationAuthorization(): $CancellablePromise<boolean> {
|
||||||
|
return $Call.ByID(2216952893);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RegisterNotificationCategory(category: $models.NotificationCategory): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(2917562919, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveAllDeliveredNotifications(): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(3956282340);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveAllPendingNotifications(): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(108821341);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveDeliveredNotification(identifier: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(975691940, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveNotification(identifier: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(3966653866, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveNotificationCategory(categoryID: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(2032615554, categoryID);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemovePendingNotification(identifier: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(3729049703, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public methods that delegate to the implementation.
|
||||||
|
*/
|
||||||
|
export function RequestNotificationAuthorization(): $CancellablePromise<boolean> {
|
||||||
|
return $Call.ByID(3933442950);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SendNotification(options: $models.NotificationOptions): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(3968228732, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SendNotificationWithActions(options: $models.NotificationOptions): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(1886542847, options);
|
||||||
|
}
|
||||||
@@ -9,10 +9,18 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as $models from "./models.js";
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
export function AddTrust(peerID: string, publicKey: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(2986105628, peerID, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
export function GetAutoAccept(): $CancellablePromise<boolean> {
|
export function GetAutoAccept(): $CancellablePromise<boolean> {
|
||||||
return $Call.ByID(2605668438);
|
return $Call.ByID(2605668438);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetCloseToSystray(): $CancellablePromise<boolean> {
|
||||||
|
return $Call.ByID(3671455511);
|
||||||
|
}
|
||||||
|
|
||||||
export function GetHostName(): $CancellablePromise<string> {
|
export function GetHostName(): $CancellablePromise<string> {
|
||||||
return $Call.ByID(972342140);
|
return $Call.ByID(972342140);
|
||||||
}
|
}
|
||||||
@@ -21,6 +29,18 @@ export function GetID(): $CancellablePromise<string> {
|
|||||||
return $Call.ByID(4240411568);
|
return $Call.ByID(4240411568);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetLanguage(): $CancellablePromise<$models.Language> {
|
||||||
|
return $Call.ByID(480133131);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetPrivateKey(): $CancellablePromise<string> {
|
||||||
|
return $Call.ByID(353744619);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetPublicKey(): $CancellablePromise<string> {
|
||||||
|
return $Call.ByID(2506498735);
|
||||||
|
}
|
||||||
|
|
||||||
export function GetSaveHistory(): $CancellablePromise<boolean> {
|
export function GetSaveHistory(): $CancellablePromise<boolean> {
|
||||||
return $Call.ByID(2178923392);
|
return $Call.ByID(2178923392);
|
||||||
}
|
}
|
||||||
@@ -29,16 +49,30 @@ export function GetSavePath(): $CancellablePromise<string> {
|
|||||||
return $Call.ByID(4081533263);
|
return $Call.ByID(4081533263);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetTrusted(): $CancellablePromise<{ [_ in string]?: string }> {
|
||||||
|
return $Call.ByID(800326956).then(($result: any) => {
|
||||||
|
return $$createType0($result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function GetVersion(): $CancellablePromise<string> {
|
export function GetVersion(): $CancellablePromise<string> {
|
||||||
return $Call.ByID(3578438023);
|
return $Call.ByID(3578438023);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetWindowState(): $CancellablePromise<$models.WindowState> {
|
export function GetWindowState(): $CancellablePromise<$models.WindowState> {
|
||||||
return $Call.ByID(341414414).then(($result: any) => {
|
return $Call.ByID(341414414).then(($result: any) => {
|
||||||
return $$createType0($result);
|
return $$createType1($result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function IsTrusted(peerID: string): $CancellablePromise<boolean> {
|
||||||
|
return $Call.ByID(1255607538, peerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RemoveTrust(peerID: string): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(732981195, peerID);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save 保存配置到磁盘
|
* Save 保存配置到磁盘
|
||||||
*/
|
*/
|
||||||
@@ -50,10 +84,18 @@ export function SetAutoAccept(autoAccept: boolean): $CancellablePromise<void> {
|
|||||||
return $Call.ByID(3371961138, autoAccept);
|
return $Call.ByID(3371961138, autoAccept);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SetCloseToSystray(closeToSystray: boolean): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(2558495467, closeToSystray);
|
||||||
|
}
|
||||||
|
|
||||||
export function SetHostName(hostName: string): $CancellablePromise<void> {
|
export function SetHostName(hostName: string): $CancellablePromise<void> {
|
||||||
return $Call.ByID(1580131496, hostName);
|
return $Call.ByID(1580131496, hostName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SetLanguage(language: $models.Language): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(933959199, language);
|
||||||
|
}
|
||||||
|
|
||||||
export function SetSaveHistory(saveHistory: boolean): $CancellablePromise<void> {
|
export function SetSaveHistory(saveHistory: boolean): $CancellablePromise<void> {
|
||||||
return $Call.ByID(3779587628, saveHistory);
|
return $Call.ByID(3779587628, saveHistory);
|
||||||
}
|
}
|
||||||
@@ -70,4 +112,5 @@ export function SetWindowState(state: $models.WindowState): $CancellablePromise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = $models.WindowState.createFrom;
|
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
|
||||||
|
const $$createType1 = $models.WindowState.createFrom;
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
Language,
|
||||||
WindowState
|
WindowState
|
||||||
} from "./models.js";
|
} from "./models.js";
|
||||||
|
|||||||
@@ -5,32 +5,30 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import { Create as $Create } from "@wailsio/runtime";
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
export enum Language {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
LanguageEnglish = "en",
|
||||||
|
LanguageChinese = "zh-Hans",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WindowState 定义窗口状态
|
* WindowState 定义窗口状态
|
||||||
*/
|
*/
|
||||||
export class WindowState {
|
export class WindowState {
|
||||||
"Width": number;
|
"width": number;
|
||||||
"Height": number;
|
"height": number;
|
||||||
"X": number;
|
|
||||||
"Y": number;
|
|
||||||
"Maximised": boolean;
|
|
||||||
|
|
||||||
/** Creates a new WindowState instance. */
|
/** Creates a new WindowState instance. */
|
||||||
constructor($$source: Partial<WindowState> = {}) {
|
constructor($$source: Partial<WindowState> = {}) {
|
||||||
if (!("Width" in $$source)) {
|
if (!("width" in $$source)) {
|
||||||
this["Width"] = 0;
|
this["width"] = 0;
|
||||||
}
|
}
|
||||||
if (!("Height" in $$source)) {
|
if (!("height" in $$source)) {
|
||||||
this["Height"] = 0;
|
this["height"] = 0;
|
||||||
}
|
|
||||||
if (!("X" in $$source)) {
|
|
||||||
this["X"] = 0;
|
|
||||||
}
|
|
||||||
if (!("Y" in $$source)) {
|
|
||||||
this["Y"] = 0;
|
|
||||||
}
|
|
||||||
if (!("Maximised" in $$source)) {
|
|
||||||
this["Maximised"] = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
|
|||||||
@@ -40,13 +40,20 @@ export class Peer {
|
|||||||
* Routes 记录了设备的 IP 地址和状态。
|
* Routes 记录了设备的 IP 地址和状态。
|
||||||
* Key: ip, Value: *RouteState
|
* Key: ip, Value: *RouteState
|
||||||
*/
|
*/
|
||||||
"routes": { [_: string]: RouteState | null };
|
"routes": { [_ in string]?: RouteState | null };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port 是文件传输服务的监听端口。
|
* Port 是文件传输服务的监听端口。
|
||||||
*/
|
*/
|
||||||
"port": number;
|
"port": number;
|
||||||
"os": OS;
|
"os": OS;
|
||||||
|
"pk": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TrustMismatch 指示该节点的公钥与本地信任列表中的公钥不匹配
|
||||||
|
* 如果为 true,说明可能存在 ID 欺骗或密钥轮换
|
||||||
|
*/
|
||||||
|
"trust_mismatch": boolean;
|
||||||
|
|
||||||
/** Creates a new Peer instance. */
|
/** Creates a new Peer instance. */
|
||||||
constructor($$source: Partial<Peer> = {}) {
|
constructor($$source: Partial<Peer> = {}) {
|
||||||
@@ -65,6 +72,12 @@ export class Peer {
|
|||||||
if (!("os" in $$source)) {
|
if (!("os" in $$source)) {
|
||||||
this["os"] = OS.$zero;
|
this["os"] = OS.$zero;
|
||||||
}
|
}
|
||||||
|
if (!("pk" in $$source)) {
|
||||||
|
this["pk"] = "";
|
||||||
|
}
|
||||||
|
if (!("trust_mismatch" in $$source)) {
|
||||||
|
this["trust_mismatch"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,21 @@ export function GetID(): $CancellablePromise<string> {
|
|||||||
return $Call.ByID(1539451205);
|
return $Call.ByID(1539451205);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetPeerByIP(ip: string): $CancellablePromise<$models.Peer | null> {
|
export function GetLocalIPInSameSubnet(receiverIP: string): $CancellablePromise<[string, boolean]> {
|
||||||
|
return $Call.ByID(3089425954, receiverIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetPeerByID(id: string): $CancellablePromise<[$models.Peer | null, boolean]> {
|
||||||
|
return $Call.ByID(1962377788, id).then(($result: any) => {
|
||||||
|
$result[0] = $$createType1($result[0]);
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetPeerByIP(ip: string): $CancellablePromise<[$models.Peer | null, boolean]> {
|
||||||
return $Call.ByID(1626825408, ip).then(($result: any) => {
|
return $Call.ByID(1626825408, ip).then(($result: any) => {
|
||||||
return $$createType1($result);
|
$result[0] = $$createType1($result[0]);
|
||||||
|
return $result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +37,12 @@ export function GetPeers(): $CancellablePromise<$models.Peer[]> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetSelf(): $CancellablePromise<$models.Peer> {
|
||||||
|
return $Call.ByID(3599633538).then(($result: any) => {
|
||||||
|
return $$createType0($result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function Start(): $CancellablePromise<void> {
|
export function Start(): $CancellablePromise<void> {
|
||||||
return $Call.ByID(1014177536);
|
return $Call.ByID(1014177536);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export {
|
|||||||
export {
|
export {
|
||||||
ContentType,
|
ContentType,
|
||||||
Progress,
|
Progress,
|
||||||
Sender,
|
|
||||||
Transfer,
|
Transfer,
|
||||||
TransferStatus,
|
TransferStatus,
|
||||||
TransferType
|
TransferType
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import { Create as $Create } from "@wailsio/runtime";
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as discovery$0 from "../discovery/models.js";
|
||||||
|
|
||||||
export enum ContentType {
|
export enum ContentType {
|
||||||
/**
|
/**
|
||||||
* The Go zero value for the underlying type of the enum.
|
* The Go zero value for the underlying type of the enum.
|
||||||
@@ -59,38 +63,6 @@ export class Progress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Sender {
|
|
||||||
/**
|
|
||||||
* 发送者 ID
|
|
||||||
*/
|
|
||||||
"id": string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送者名称
|
|
||||||
*/
|
|
||||||
"name": string;
|
|
||||||
|
|
||||||
/** Creates a new Sender instance. */
|
|
||||||
constructor($$source: Partial<Sender> = {}) {
|
|
||||||
if (!("id" in $$source)) {
|
|
||||||
this["id"] = "";
|
|
||||||
}
|
|
||||||
if (!("name" in $$source)) {
|
|
||||||
this["name"] = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Sender instance from a string or object.
|
|
||||||
*/
|
|
||||||
static createFrom($$source: any = {}): Sender {
|
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
|
||||||
return new Sender($$parsedSource as Partial<Sender>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transfer
|
* Transfer
|
||||||
*/
|
*/
|
||||||
@@ -108,9 +80,10 @@ export class Transfer {
|
|||||||
/**
|
/**
|
||||||
* 发送者
|
* 发送者
|
||||||
*/
|
*/
|
||||||
"sender": Sender;
|
"sender": discovery$0.Peer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* FileName 如果 ContentType 为 file,文件名;如果 ContentType 为 folder,文件夹名;如果 ContentType 为 text,空
|
||||||
* 文件名
|
* 文件名
|
||||||
*/
|
*/
|
||||||
"file_name": string;
|
"file_name": string;
|
||||||
@@ -169,7 +142,7 @@ export class Transfer {
|
|||||||
this["create_time"] = 0;
|
this["create_time"] = 0;
|
||||||
}
|
}
|
||||||
if (!("sender" in $$source)) {
|
if (!("sender" in $$source)) {
|
||||||
this["sender"] = (new Sender());
|
this["sender"] = (new discovery$0.Peer());
|
||||||
}
|
}
|
||||||
if (!("file_name" in $$source)) {
|
if (!("file_name" in $$source)) {
|
||||||
this["file_name"] = "";
|
this["file_name"] = "";
|
||||||
@@ -248,5 +221,5 @@ export enum TransferType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = Sender.createFrom;
|
const $$createType0 = discovery$0.Peer.createFrom;
|
||||||
const $$createType1 = Progress.createFrom;
|
const $$createType1 = Progress.createFrom;
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as discovery$0 from "../discovery/models.js";
|
import * as discovery$0 from "../discovery/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as sync$0 from "../../../sync/models.js";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
@@ -20,8 +23,8 @@ export function CancelTransfer(transferID: string): $CancellablePromise<void> {
|
|||||||
/**
|
/**
|
||||||
* CleanTransferList 清理完成的 transfer
|
* CleanTransferList 清理完成的 transfer
|
||||||
*/
|
*/
|
||||||
export function CleanTransferList(): $CancellablePromise<void> {
|
export function CleanFinishedTransferList(): $CancellablePromise<void> {
|
||||||
return $Call.ByID(3775121017);
|
return $Call.ByID(1852624467);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeleteTransfer(transferID: string): $CancellablePromise<void> {
|
export function DeleteTransfer(transferID: string): $CancellablePromise<void> {
|
||||||
@@ -45,6 +48,12 @@ export function GetTransferList(): $CancellablePromise<($models.Transfer | null)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetTransferSyncMap(): $CancellablePromise<sync$0.Map | null> {
|
||||||
|
return $Call.ByID(2986557111).then(($result: any) => {
|
||||||
|
return $$createType4($result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function LoadHistory(): $CancellablePromise<void> {
|
export function LoadHistory(): $CancellablePromise<void> {
|
||||||
return $Call.ByID(2987999795);
|
return $Call.ByID(2987999795);
|
||||||
}
|
}
|
||||||
@@ -89,7 +98,13 @@ export function StoreTransferToList(transfer: $models.Transfer | null): $Cancell
|
|||||||
return $Call.ByID(3225941780, transfer);
|
return $Call.ByID(3225941780, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function StoreTransfersToList(transfers: ($models.Transfer | null)[]): $CancellablePromise<void> {
|
||||||
|
return $Call.ByID(658746199, transfers);
|
||||||
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = $models.Transfer.createFrom;
|
const $$createType0 = $models.Transfer.createFrom;
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
const $$createType2 = $Create.Array($$createType1);
|
const $$createType2 = $Create.Array($$createType1);
|
||||||
|
const $$createType3 = sync$0.Map.createFrom;
|
||||||
|
const $$createType4 = $Create.Nullable($$createType3);
|
||||||
|
|||||||
6
frontend/bindings/sync/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export {
|
||||||
|
Map
|
||||||
|
} from "./models.js";
|
||||||
52
frontend/bindings/sync/models.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map is like a Go map[any]any but is safe for concurrent use
|
||||||
|
* by multiple goroutines without additional locking or coordination.
|
||||||
|
* Loads, stores, and deletes run in amortized constant time.
|
||||||
|
*
|
||||||
|
* The Map type is specialized. Most code should use a plain Go map instead,
|
||||||
|
* with separate locking or coordination, for better type safety and to make it
|
||||||
|
* easier to maintain other invariants along with the map content.
|
||||||
|
*
|
||||||
|
* The Map type is optimized for two common use cases: (1) when the entry for a given
|
||||||
|
* key is only ever written once but read many times, as in caches that only grow,
|
||||||
|
* or (2) when multiple goroutines read, write, and overwrite entries for disjoint
|
||||||
|
* sets of keys. In these two cases, use of a Map may significantly reduce lock
|
||||||
|
* contention compared to a Go map paired with a separate [Mutex] or [RWMutex].
|
||||||
|
*
|
||||||
|
* The zero Map is empty and ready for use. A Map must not be copied after first use.
|
||||||
|
*
|
||||||
|
* In the terminology of [the Go memory model], Map arranges that a write operation
|
||||||
|
* “synchronizes before” any read operation that observes the effect of the write, where
|
||||||
|
* read and write operations are defined as follows.
|
||||||
|
* [Map.Load], [Map.LoadAndDelete], [Map.LoadOrStore], [Map.Swap], [Map.CompareAndSwap],
|
||||||
|
* and [Map.CompareAndDelete] are read operations;
|
||||||
|
* [Map.Delete], [Map.LoadAndDelete], [Map.Store], and [Map.Swap] are write operations;
|
||||||
|
* [Map.LoadOrStore] is a write operation when it returns loaded set to false;
|
||||||
|
* [Map.CompareAndSwap] is a write operation when it returns swapped set to true;
|
||||||
|
* and [Map.CompareAndDelete] is a write operation when it returns deleted set to true.
|
||||||
|
*
|
||||||
|
* [the Go memory model]: https://go.dev/ref/mem
|
||||||
|
*/
|
||||||
|
export class Map {
|
||||||
|
|
||||||
|
/** Creates a new Map instance. */
|
||||||
|
constructor($$source: Partial<Map> = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Map instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): Map {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new Map($$parsedSource as Partial<Map>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Mesh Drop</title>
|
<title>Mesh Drop</title>
|
||||||
<link rel="stylesheet" href="src/styles/style.css">
|
<link rel="stylesheet" href="src/styles/style.css">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="src/assets/icon.svg">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
71
frontend/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"@wailsio/runtime": "^3.0.0-alpha.79",
|
"@wailsio/runtime": "^3.0.0-alpha.79",
|
||||||
"vue": "^3.5.21",
|
"vue": "^3.5.21",
|
||||||
|
"vue-i18n": "^11.2.8",
|
||||||
"vuetify": "^3.10.1"
|
"vuetify": "^3.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -509,6 +510,50 @@
|
|||||||
"url": "https://github.com/sponsors/ayuhito"
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@intlify/core-base": {
|
||||||
|
"version": "11.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.8.tgz",
|
||||||
|
"integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/message-compiler": "11.2.8",
|
||||||
|
"@intlify/shared": "11.2.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/message-compiler": {
|
||||||
|
"version": "11.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
|
||||||
|
"integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "11.2.8",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/shared": {
|
||||||
|
"version": "11.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.8.tgz",
|
||||||
|
"integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -1350,6 +1395,12 @@
|
|||||||
"@vue/shared": "3.5.27"
|
"@vue/shared": "3.5.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/devtools-api": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vue/language-core": {
|
"node_modules/@vue/language-core": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz",
|
||||||
@@ -3165,6 +3216,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-i18n": {
|
||||||
|
"version": "11.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.8.tgz",
|
||||||
|
"integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "11.2.8",
|
||||||
|
"@intlify/shared": "11.2.8",
|
||||||
|
"@vue/devtools-api": "^6.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-tsc": {
|
"node_modules/vue-tsc": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@mdi/font": "7.4.47",
|
"@mdi/font": "7.4.47",
|
||||||
"@wailsio/runtime": "^3.0.0-alpha.79",
|
"@wailsio/runtime": "^3.0.0-alpha.79",
|
||||||
"vue": "^3.5.21",
|
"vue": "^3.5.21",
|
||||||
|
"vue-i18n": "^11.2.8",
|
||||||
"vuetify": "^3.10.1"
|
"vuetify": "^3.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
16
frontend/src/assets/icon.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0" y="0" width="512" height="512" rx="100" ry="100" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<g stroke="#0055ff" stroke-width="28" stroke-linecap="round" fill="none">
|
||||||
|
|
||||||
|
<circle cx="256" cy="256" r="28" fill="#0055ff" stroke="none"/>
|
||||||
|
|
||||||
|
<path d="M 190 200 A 70 70 0 0 0 190 312" />
|
||||||
|
<path d="M 140 160 A 130 130 0 0 0 140 352" />
|
||||||
|
<path d="M 90 120 A 190 190 0 0 0 90 392" />
|
||||||
|
|
||||||
|
<path d="M 322 200 A 70 70 0 0 1 322 312" />
|
||||||
|
<path d="M 372 160 A 130 130 0 0 1 372 352" />
|
||||||
|
<path d="M 422 120 A 190 190 0 0 1 422 392" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { onMounted, ref, computed } from "vue";
|
import { onMounted, ref, computed } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- 组件 ---
|
// --- 组件 ---
|
||||||
import PeerCard from "./PeerCard.vue";
|
import PeerCard from "./PeerCard.vue";
|
||||||
@@ -14,7 +15,10 @@ import { Transfer } from "../../bindings/mesh-drop/internal/transfer";
|
|||||||
// --- Service & 后端绑定 ---
|
// --- Service & 后端绑定 ---
|
||||||
import { Events } from "@wailsio/runtime";
|
import { Events } from "@wailsio/runtime";
|
||||||
import { GetPeers } from "../../bindings/mesh-drop/internal/discovery/service";
|
import { GetPeers } from "../../bindings/mesh-drop/internal/discovery/service";
|
||||||
import { GetTransferList } from "../../bindings/mesh-drop/internal/transfer/service";
|
import {
|
||||||
|
GetTransferList,
|
||||||
|
CleanFinishedTransferList,
|
||||||
|
} from "../../bindings/mesh-drop/internal/transfer/service";
|
||||||
|
|
||||||
// --- 状态 ---
|
// --- 状态 ---
|
||||||
const peers = ref<Peer[]>([]);
|
const peers = ref<Peer[]>([]);
|
||||||
@@ -22,6 +26,7 @@ const transferList = ref<Transfer[]>([]);
|
|||||||
const activeKey = ref("discover");
|
const activeKey = ref("discover");
|
||||||
const drawer = ref(true);
|
const drawer = ref(true);
|
||||||
const isMobile = ref(false);
|
const isMobile = ref(false);
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
// --- 计算属性 ---
|
// --- 计算属性 ---
|
||||||
const pendingCount = computed(() => {
|
const pendingCount = computed(() => {
|
||||||
@@ -32,18 +37,18 @@ const pendingCount = computed(() => {
|
|||||||
|
|
||||||
const menuItems = computed(() => [
|
const menuItems = computed(() => [
|
||||||
{
|
{
|
||||||
title: "Discover",
|
title: t("menu.discover"),
|
||||||
value: "discover",
|
value: "discover",
|
||||||
icon: "mdi-radar",
|
icon: "mdi-radar",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Transfers",
|
title: t("menu.transfers"),
|
||||||
value: "transfers",
|
value: "transfers",
|
||||||
icon: "mdi-inbox",
|
icon: "mdi-inbox",
|
||||||
badge: pendingCount.value > 0 ? pendingCount.value : null,
|
badge: pendingCount.value > 0 ? pendingCount.value : null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: t("menu.settings"),
|
||||||
value: "settings",
|
value: "settings",
|
||||||
icon: "mdi-cog",
|
icon: "mdi-cog",
|
||||||
},
|
},
|
||||||
@@ -53,10 +58,7 @@ const menuItems = computed(() => [
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
checkMobile();
|
checkMobile();
|
||||||
window.addEventListener("resize", checkMobile);
|
window.addEventListener("resize", checkMobile);
|
||||||
const list = await GetTransferList();
|
transferList.value = (await GetTransferList()) as Transfer[];
|
||||||
transferList.value = (
|
|
||||||
(list || []).filter((t) => t !== null) as Transfer[]
|
|
||||||
).sort((a, b) => b.create_time - a.create_time);
|
|
||||||
|
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
drawer.value = false;
|
drawer.value = false;
|
||||||
@@ -66,19 +68,14 @@ onMounted(async () => {
|
|||||||
// --- 后端集成 & 事件监听 ---
|
// --- 后端集成 & 事件监听 ---
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
peers.value = await GetPeers();
|
peers.value = await GetPeers();
|
||||||
peers.value = peers.value.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.On("peers:update", (event) => {
|
Events.On("peers:update", (event) => {
|
||||||
peers.value = event.data;
|
peers.value = event.data;
|
||||||
peers.value = peers.value.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.On("transfer:refreshList", async () => {
|
Events.On("transfer:refreshList", async () => {
|
||||||
const list = await GetTransferList();
|
transferList.value = (await GetTransferList()) as Transfer[];
|
||||||
transferList.value = (
|
|
||||||
(list || []).filter((t) => t !== null) as Transfer[]
|
|
||||||
).sort((a, b) => b.create_time - a.create_time);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 方法 ---
|
// --- 方法 ---
|
||||||
@@ -96,6 +93,10 @@ const handleMenuClick = (key: string) => {
|
|||||||
drawer.value = false;
|
drawer.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCleanFinished = async () => {
|
||||||
|
await CleanFinishedTransferList();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -169,13 +170,23 @@ const handleMenuClick = (key: string) => {
|
|||||||
class="mb-4 radar-icon"
|
class="mb-4 radar-icon"
|
||||||
style="opacity: 0.5"
|
style="opacity: 0.5"
|
||||||
></v-icon>
|
></v-icon>
|
||||||
<div class="text-grey">Scanning for peers...</div>
|
<div class="text-grey">{{ t("discover.scanning") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 传输视图 -->
|
<!-- 传输视图 -->
|
||||||
<div v-show="activeKey === 'transfers'">
|
<div v-show="activeKey === 'transfers'">
|
||||||
<div v-if="transferList.length > 0">
|
<div v-if="transferList.length > 0">
|
||||||
|
<div class="d-flex justify-end mb-2">
|
||||||
|
<v-btn
|
||||||
|
prepend-icon="mdi-delete-sweep"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
@click="handleCleanFinished"
|
||||||
|
>
|
||||||
|
{{ t("transfers.clearFinished") }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
<TransferItem
|
<TransferItem
|
||||||
v-for="transfer in transferList"
|
v-for="transfer in transferList"
|
||||||
:key="transfer.id"
|
:key="transfer.id"
|
||||||
@@ -187,7 +198,7 @@ const handleMenuClick = (key: string) => {
|
|||||||
class="empty-state d-flex flex-column justify-center align-center"
|
class="empty-state d-flex flex-column justify-center align-center"
|
||||||
>
|
>
|
||||||
<v-icon icon="mdi-inbox" size="100" class="mb-4 text-grey"></v-icon>
|
<v-icon icon="mdi-inbox" size="100" class="mb-4 text-grey"></v-icon>
|
||||||
<div class="text-grey">No transfers yet</div>
|
<div class="text-grey">{{ t("transfers.noTransfers") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch, onMounted } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- 组件 ---
|
// --- 组件 ---
|
||||||
import FileSendModal from "./modals/FileSendModal.vue";
|
import FileSendModal from "./modals/FileSendModal.vue";
|
||||||
@@ -13,12 +14,28 @@ import {
|
|||||||
SendText,
|
SendText,
|
||||||
} from "../../bindings/mesh-drop/internal/transfer/service";
|
} from "../../bindings/mesh-drop/internal/transfer/service";
|
||||||
import { Peer } from "../../bindings/mesh-drop/internal/discovery/models";
|
import { Peer } from "../../bindings/mesh-drop/internal/discovery/models";
|
||||||
|
import {
|
||||||
|
IsTrusted,
|
||||||
|
AddTrust,
|
||||||
|
RemoveTrust,
|
||||||
|
} from "../../bindings/mesh-drop/internal/config/config";
|
||||||
|
|
||||||
|
// --- 生命周期 ---
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
isTrusted.value = await IsTrusted(props.peer.id);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to check trusted peer status:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- 属性 & 事件 ---
|
// --- 属性 & 事件 ---
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "transferStarted"): void;
|
(e: "transferStarted"): void;
|
||||||
}>();
|
}>();
|
||||||
@@ -27,29 +44,30 @@ const emit = defineEmits<{
|
|||||||
const selectedIp = ref<string>("");
|
const selectedIp = ref<string>("");
|
||||||
const showFileModal = ref(false);
|
const showFileModal = ref(false);
|
||||||
const showTextModal = ref(false);
|
const showTextModal = ref(false);
|
||||||
|
const isTrusted = ref(false);
|
||||||
|
|
||||||
const sendOptions = [
|
const sendOptions = computed(() => [
|
||||||
{
|
{
|
||||||
title: "Send Files",
|
title: t("discover.sendFiles"),
|
||||||
value: "files",
|
value: "files",
|
||||||
icon: "mdi-file",
|
icon: "mdi-file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Send Folder",
|
title: t("discover.sendFolder"),
|
||||||
value: "folder",
|
value: "folder",
|
||||||
icon: "mdi-folder",
|
icon: "mdi-folder",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Send Text",
|
title: t("discover.sendText"),
|
||||||
value: "text",
|
value: "text",
|
||||||
icon: "mdi-format-font",
|
icon: "mdi-format-font",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Send Clipboard",
|
title: t("discover.sendClipboard"),
|
||||||
value: "clipboard",
|
value: "clipboard",
|
||||||
icon: "mdi-clipboard",
|
icon: "mdi-clipboard",
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
// --- 计算属性 ---
|
// --- 计算属性 ---
|
||||||
const ips = computed(() => {
|
const ips = computed(() => {
|
||||||
@@ -70,6 +88,10 @@ const osIcon = computed(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showMismatch = computed(() => {
|
||||||
|
return props.peer.trust_mismatch && isTrusted.value;
|
||||||
|
});
|
||||||
|
|
||||||
// --- 监听 ---
|
// --- 监听 ---
|
||||||
watch(
|
watch(
|
||||||
ips,
|
ips,
|
||||||
@@ -108,7 +130,7 @@ const handleAction = (key: string) => {
|
|||||||
const handleSendFolder = async () => {
|
const handleSendFolder = async () => {
|
||||||
if (!selectedIp.value) return;
|
if (!selectedIp.value) return;
|
||||||
const opts: Dialogs.OpenFileDialogOptions = {
|
const opts: Dialogs.OpenFileDialogOptions = {
|
||||||
Title: "Select folder to send",
|
Title: t("discover.selectFolder"),
|
||||||
CanChooseDirectories: true,
|
CanChooseDirectories: true,
|
||||||
CanChooseFiles: false,
|
CanChooseFiles: false,
|
||||||
AllowsMultipleSelection: false,
|
AllowsMultipleSelection: false,
|
||||||
@@ -118,7 +140,7 @@ const handleSendFolder = async () => {
|
|||||||
|
|
||||||
SendFolder(props.peer, selectedIp.value, folderPath as string).catch((e) => {
|
SendFolder(props.peer, selectedIp.value, folderPath as string).catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert("Failed to send folder: " + e);
|
alert(t("discover.sendFolderFailed", { error: e }));
|
||||||
});
|
});
|
||||||
emit("transferStarted");
|
emit("transferStarted");
|
||||||
};
|
};
|
||||||
@@ -127,19 +149,29 @@ const handleSendClipboard = async () => {
|
|||||||
if (!selectedIp.value) return;
|
if (!selectedIp.value) return;
|
||||||
const text = await Clipboard.Text();
|
const text = await Clipboard.Text();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
alert("Clipboard is empty");
|
alert(t("discover.clipboardEmpty"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SendText(props.peer, selectedIp.value, text).catch((e) => {
|
SendText(props.peer, selectedIp.value, text).catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert("Failed to send clipboard: " + e);
|
alert(t("discover.sendClipboardFailed", { error: e }));
|
||||||
});
|
});
|
||||||
emit("transferStarted");
|
emit("transferStarted");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTrust = () => {
|
||||||
|
AddTrust(props.peer.id, props.peer.pk);
|
||||||
|
isTrusted.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUntrust = () => {
|
||||||
|
RemoveTrust(props.peer.id);
|
||||||
|
isTrusted.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card hover link class="peer-card pa-2">
|
<v-card hover link class="peer-card pa-2" :ripple="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon :icon="osIcon" size="24" class="mr-2"></v-icon>
|
<v-icon :icon="osIcon" size="24" class="mr-2"></v-icon>
|
||||||
@@ -183,16 +215,31 @@ const handleSendClipboard = async () => {
|
|||||||
</v-menu>
|
</v-menu>
|
||||||
|
|
||||||
<!-- No Route -->
|
<!-- No Route -->
|
||||||
<v-chip v-else color="warning" size="small" label> No Route </v-chip>
|
<v-chip v-else color="warning" size="small" label>
|
||||||
|
{{ t("discover.noRoute") }}
|
||||||
|
</v-chip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<v-card-actions>
|
||||||
<v-menu>
|
<!-- Trust Mismatch Warning -->
|
||||||
|
<v-btn
|
||||||
|
v-if="showMismatch"
|
||||||
|
class="flex-grow-1"
|
||||||
|
color="warning"
|
||||||
|
variant="tonal"
|
||||||
|
prepend-icon="mdi-alert"
|
||||||
|
:ripple="false"
|
||||||
|
style="pointer-events: none; min-width: 0"
|
||||||
|
>
|
||||||
|
<span class="text-truncate">{{ t("discover.mismatch") }}</span>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-menu v-else>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
block
|
class="flex-grow-1"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
:disabled="ips.length === 0"
|
:disabled="ips.length === 0"
|
||||||
@@ -201,7 +248,7 @@ const handleSendClipboard = async () => {
|
|||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-send"></v-icon>
|
<v-icon icon="mdi-send"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
Send
|
{{ t("discover.send") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -218,7 +265,38 @@ const handleSendClipboard = async () => {
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</template>
|
|
||||||
|
<!-- Trust Mismatch Reset Override -->
|
||||||
|
<v-btn
|
||||||
|
v-if="showMismatch"
|
||||||
|
variant="tonal"
|
||||||
|
color="error"
|
||||||
|
@click="handleUntrust"
|
||||||
|
>
|
||||||
|
<v-icon icon="mdi-delete"></v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("discover.resetTrust")
|
||||||
|
}}</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-else-if="!isTrusted"
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
@click="handleTrust"
|
||||||
|
>
|
||||||
|
<v-icon icon="mdi-star-outline"></v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("discover.trustPeer")
|
||||||
|
}}</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn v-else variant="tonal" color="primary" @click="handleUntrust">
|
||||||
|
<v-icon icon="mdi-star"></v-icon>
|
||||||
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("discover.untrustPeer")
|
||||||
|
}}</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- Modals -->
|
<!-- Modals -->
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- Wails & 后端绑定 ---
|
// --- Wails & 后端绑定 ---
|
||||||
import { Dialogs } from "@wailsio/runtime";
|
import { Dialogs } from "@wailsio/runtime";
|
||||||
@@ -14,7 +15,12 @@ import {
|
|||||||
GetSaveHistory,
|
GetSaveHistory,
|
||||||
SetSaveHistory,
|
SetSaveHistory,
|
||||||
GetVersion,
|
GetVersion,
|
||||||
|
GetLanguage,
|
||||||
|
SetLanguage,
|
||||||
|
SetCloseToSystray,
|
||||||
|
GetCloseToSystray,
|
||||||
} from "../../bindings/mesh-drop/internal/config/config";
|
} from "../../bindings/mesh-drop/internal/config/config";
|
||||||
|
import { Language } from "bindings/mesh-drop/internal/config";
|
||||||
|
|
||||||
// --- 状态 ---
|
// --- 状态 ---
|
||||||
const savePath = ref("");
|
const savePath = ref("");
|
||||||
@@ -22,6 +28,14 @@ const hostName = ref("");
|
|||||||
const autoAccept = ref(false);
|
const autoAccept = ref(false);
|
||||||
const saveHistory = ref(false);
|
const saveHistory = ref(false);
|
||||||
const version = ref("");
|
const version = ref("");
|
||||||
|
const closeToSystray = ref(false);
|
||||||
|
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{ title: "English", value: "en" },
|
||||||
|
{ title: "简体中文", value: "zh-Hans" },
|
||||||
|
];
|
||||||
|
|
||||||
// ---生命周期 ---
|
// ---生命周期 ---
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -30,12 +44,17 @@ onMounted(async () => {
|
|||||||
autoAccept.value = await GetAutoAccept();
|
autoAccept.value = await GetAutoAccept();
|
||||||
saveHistory.value = await GetSaveHistory();
|
saveHistory.value = await GetSaveHistory();
|
||||||
version.value = await GetVersion();
|
version.value = await GetVersion();
|
||||||
|
let l = await GetLanguage();
|
||||||
|
if (l != "") {
|
||||||
|
locale.value = l;
|
||||||
|
}
|
||||||
|
closeToSystray.value = await GetCloseToSystray();
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 方法 ---
|
// --- 方法 ---
|
||||||
const changeSavePath = async () => {
|
const changeSavePath = async () => {
|
||||||
const opts: Dialogs.OpenFileDialogOptions = {
|
const opts: Dialogs.OpenFileDialogOptions = {
|
||||||
Title: "Select Save Path",
|
Title: t("settings.selectSavePath"),
|
||||||
CanChooseDirectories: true,
|
CanChooseDirectories: true,
|
||||||
CanChooseFiles: false,
|
CanChooseFiles: false,
|
||||||
AllowsMultipleSelection: false,
|
AllowsMultipleSelection: false,
|
||||||
@@ -46,11 +65,17 @@ const changeSavePath = async () => {
|
|||||||
savePath.value = path;
|
savePath.value = path;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听语言变化
|
||||||
|
watch(locale, async (newVal) => {
|
||||||
|
await SetLanguage(newVal as Language);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-list lines="one" bg-color="transparent">
|
<v-list lines="one" bg-color="transparent">
|
||||||
<v-list-item title="Save Path" :subtitle="savePath">
|
<!-- 保存路径 -->
|
||||||
|
<v-list-item :title="t('settings.savePath')" :subtitle="savePath">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-folder-download"></v-icon>
|
<v-icon icon="mdi-folder-download"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -61,11 +86,13 @@ const changeSavePath = async () => {
|
|||||||
@click="changeSavePath"
|
@click="changeSavePath"
|
||||||
prepend-icon="mdi-pencil"
|
prepend-icon="mdi-pencil"
|
||||||
>
|
>
|
||||||
Change
|
{{ t("settings.change") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item title="HostName">
|
|
||||||
|
<!-- 主机名 -->
|
||||||
|
<v-list-item :title="t('settings.hostName')">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-laptop"></v-icon>
|
<v-icon icon="mdi-laptop"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -79,7 +106,9 @@ const changeSavePath = async () => {
|
|||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item title="Save History">
|
|
||||||
|
<!-- 保存历史 -->
|
||||||
|
<v-list-item :title="t('settings.saveHistory')">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-history"></v-icon>
|
<v-icon icon="mdi-history"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -93,7 +122,9 @@ const changeSavePath = async () => {
|
|||||||
></v-switch>
|
></v-switch>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item title="Auto Accept">
|
|
||||||
|
<!-- 自动接受 -->
|
||||||
|
<v-list-item :title="t('settings.autoAccept')">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-content-save"></v-icon>
|
<v-icon icon="mdi-content-save"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -107,7 +138,42 @@ const changeSavePath = async () => {
|
|||||||
></v-switch>
|
></v-switch>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item title="Version">
|
|
||||||
|
<!-- 关闭窗口时最小化到托盘 -->
|
||||||
|
<v-list-item :title="t('settings.closeToSystray')">
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-tray"></v-icon>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<v-switch
|
||||||
|
v-model="closeToSystray"
|
||||||
|
color="primary"
|
||||||
|
inset
|
||||||
|
hide-details
|
||||||
|
@update:modelValue="SetCloseToSystray(closeToSystray)"
|
||||||
|
></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<!-- 语言 -->
|
||||||
|
<v-list-item :title="t('settings.language')">
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-translate"></v-icon>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<v-select
|
||||||
|
v-model="locale"
|
||||||
|
:items="languages"
|
||||||
|
variant="underlined"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
width="150"
|
||||||
|
></v-select>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<!-- 版本 -->
|
||||||
|
<v-list-item :title="t('settings.version')">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="mdi-information"></v-icon>
|
<v-icon icon="mdi-information"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- Wails & 后端绑定 ---
|
// --- Wails & 后端绑定 ---
|
||||||
import { Dialogs, Clipboard } from "@wailsio/runtime";
|
import { Dialogs, Clipboard } from "@wailsio/runtime";
|
||||||
@@ -16,6 +17,8 @@ const props = defineProps<{
|
|||||||
transfer: Transfer;
|
transfer: Transfer;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
// --- 状态 ---
|
// --- 状态 ---
|
||||||
const showContentDialog = ref(false);
|
const showContentDialog = ref(false);
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ const rejectTransfer = () => {
|
|||||||
|
|
||||||
const acceptToFolder = async () => {
|
const acceptToFolder = async () => {
|
||||||
const opts: Dialogs.OpenFileDialogOptions = {
|
const opts: Dialogs.OpenFileDialogOptions = {
|
||||||
Title: "Select Folder to save the file",
|
Title: t("transfers.selectSavePath"),
|
||||||
CanChooseDirectories: true,
|
CanChooseDirectories: true,
|
||||||
CanChooseFiles: false,
|
CanChooseFiles: false,
|
||||||
AllowsMultipleSelection: false,
|
AllowsMultipleSelection: false,
|
||||||
@@ -178,7 +181,9 @@ const handleCopy = async () => {
|
|||||||
></v-icon>
|
></v-icon>
|
||||||
{{
|
{{
|
||||||
props.transfer.file_name ||
|
props.transfer.file_name ||
|
||||||
(props.transfer.content_type === "text" ? "Text" : "Folder")
|
(props.transfer.content_type === "text"
|
||||||
|
? t("transfers.text")
|
||||||
|
: t("transfers.folder"))
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -187,9 +192,23 @@ const handleCopy = async () => {
|
|||||||
v-if="
|
v-if="
|
||||||
props.transfer.sender.name && props.transfer.type === 'receive'
|
props.transfer.sender.name && props.transfer.type === 'receive'
|
||||||
"
|
"
|
||||||
prepend-icon="mdi-account"
|
:color="
|
||||||
|
props.transfer.sender.trust_mismatch ? 'warning' : undefined
|
||||||
|
"
|
||||||
|
:prepend-icon="
|
||||||
|
props.transfer.sender.trust_mismatch
|
||||||
|
? 'mdi-alert'
|
||||||
|
: 'mdi-account'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ props.transfer.sender.name }}
|
{{ props.transfer.sender.name }}
|
||||||
|
<v-tooltip
|
||||||
|
v-if="props.transfer.sender.trust_mismatch"
|
||||||
|
activator="parent"
|
||||||
|
location="bottom"
|
||||||
|
>
|
||||||
|
{{ t("transfers.securityAlert") }}
|
||||||
|
</v-tooltip>
|
||||||
</v-chip>
|
</v-chip>
|
||||||
|
|
||||||
<v-chip
|
<v-chip
|
||||||
@@ -212,25 +231,25 @@ const handleCopy = async () => {
|
|||||||
v-if="props.transfer.status === 'completed'"
|
v-if="props.transfer.status === 'completed'"
|
||||||
class="text-success"
|
class="text-success"
|
||||||
>
|
>
|
||||||
- Completed
|
- {{ t("transfers.completed") }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="props.transfer.status === 'error'" class="text-error">
|
<span v-if="props.transfer.status === 'error'" class="text-error">
|
||||||
- {{ props.transfer.error_msg || "Error" }}
|
- {{ props.transfer.error_msg || t("common.error") }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="props.transfer.status === 'canceled'" class="text-info">
|
<span v-if="props.transfer.status === 'canceled'" class="text-info">
|
||||||
- Canceled
|
- {{ t("transfers.cancelled") }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="props.transfer.status === 'rejected'"
|
v-if="props.transfer.status === 'rejected'"
|
||||||
class="text-error"
|
class="text-error"
|
||||||
>
|
>
|
||||||
- Rejected
|
- {{ t("transfers.rejected") }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="props.transfer.status === 'pending'"
|
v-if="props.transfer.status === 'pending'"
|
||||||
class="text-warning"
|
class="text-warning"
|
||||||
>
|
>
|
||||||
- Waiting for accept
|
- {{ t("transfers.waitingForAccept") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -250,7 +269,9 @@ const handleCopy = async () => {
|
|||||||
<v-btn-group density="compact" variant="tonal" divided rounded="xl">
|
<v-btn-group density="compact" variant="tonal" divided rounded="xl">
|
||||||
<v-btn v-if="canAccept" color="success" @click="acceptTransfer">
|
<v-btn v-if="canAccept" color="success" @click="acceptTransfer">
|
||||||
<v-icon icon="mdi-content-save"></v-icon>
|
<v-icon icon="mdi-content-save"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Accept</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("common.accept")
|
||||||
|
}}</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -260,13 +281,15 @@ const handleCopy = async () => {
|
|||||||
>
|
>
|
||||||
<v-icon icon="mdi-folder-arrow-right"></v-icon>
|
<v-icon icon="mdi-folder-arrow-right"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">
|
<v-tooltip activator="parent" location="bottom">
|
||||||
Save to Folder
|
{{ t("transfers.saveToFolder") }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn v-if="canAccept" color="error" @click="rejectTransfer">
|
<v-btn v-if="canAccept" color="error" @click="rejectTransfer">
|
||||||
<v-icon icon="mdi-close"></v-icon>
|
<v-icon icon="mdi-close"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Reject</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("common.reject")
|
||||||
|
}}</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -276,13 +299,15 @@ const handleCopy = async () => {
|
|||||||
>
|
>
|
||||||
<v-icon icon="mdi-eye"></v-icon>
|
<v-icon icon="mdi-eye"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">
|
<v-tooltip activator="parent" location="bottom">
|
||||||
View Content
|
{{ t("transfers.viewContent") }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn v-if="canCopy" color="success" @click="handleCopy">
|
<v-btn v-if="canCopy" color="success" @click="handleCopy">
|
||||||
<v-icon icon="mdi-content-copy"></v-icon>
|
<v-icon icon="mdi-content-copy"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Copy</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("common.copy")
|
||||||
|
}}</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -296,7 +321,9 @@ const handleCopy = async () => {
|
|||||||
@click="handleDelete"
|
@click="handleDelete"
|
||||||
>
|
>
|
||||||
<v-icon icon="mdi-delete"></v-icon>
|
<v-icon icon="mdi-delete"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Delete</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("common.delete")
|
||||||
|
}}</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -305,7 +332,9 @@ const handleCopy = async () => {
|
|||||||
@click="CancelTransfer(props.transfer.id)"
|
@click="CancelTransfer(props.transfer.id)"
|
||||||
>
|
>
|
||||||
<v-icon icon="mdi-stop"></v-icon>
|
<v-icon icon="mdi-stop"></v-icon>
|
||||||
<v-tooltip activator="parent" location="bottom">Cancel</v-tooltip>
|
<v-tooltip activator="parent" location="bottom">{{
|
||||||
|
t("common.cancel")
|
||||||
|
}}</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-btn-group>
|
</v-btn-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,7 +343,7 @@ const handleCopy = async () => {
|
|||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-dialog v-model="showContentDialog" width="600">
|
<v-dialog v-model="showContentDialog" width="600">
|
||||||
<v-card title="Text Content">
|
<v-card :title="t('transfers.textContent')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-textarea
|
<v-textarea
|
||||||
:model-value="props.transfer.text"
|
:model-value="props.transfer.text"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- Wails & 后端绑定 ---
|
// --- Wails & 后端绑定 ---
|
||||||
import { Events, Dialogs } from "@wailsio/runtime";
|
import { Events, Dialogs } from "@wailsio/runtime";
|
||||||
@@ -20,6 +21,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
// --- 状态 ---
|
// --- 状态 ---
|
||||||
|
const { t } = useI18n();
|
||||||
const fileList = ref<{ name: string; path: string }[]>([]);
|
const fileList = ref<{ name: string; path: string }[]>([]);
|
||||||
|
|
||||||
// --- 计算属性 ---
|
// --- 计算属性 ---
|
||||||
@@ -51,7 +53,7 @@ watch(show, (newVal) => {
|
|||||||
// --- 方法 ---
|
// --- 方法 ---
|
||||||
const openFileDialog = async () => {
|
const openFileDialog = async () => {
|
||||||
const files = await Dialogs.OpenFile({
|
const files = await Dialogs.OpenFile({
|
||||||
Title: "Select files to send",
|
Title: t("modal.fileSend.selectTitle"),
|
||||||
AllowsMultipleSelection: true,
|
AllowsMultipleSelection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,14 +93,14 @@ const handleSendFiles = async () => {
|
|||||||
show.value = false;
|
show.value = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert("Failed to send files: " + e);
|
alert(t("modal.fileSend.failed", { error: e }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="show" width="600" persistent eager>
|
<v-dialog v-model="show" width="600" persistent eager>
|
||||||
<v-card title="Send Files">
|
<v-card :title="$t('modal.fileSend.title')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div
|
<div
|
||||||
v-if="fileList.length === 0"
|
v-if="fileList.length === 0"
|
||||||
@@ -113,7 +115,7 @@ const handleSendFiles = async () => {
|
|||||||
class="mb-2"
|
class="mb-2"
|
||||||
></v-icon>
|
></v-icon>
|
||||||
<div class="text-body-1 text-medium-emphasis">
|
<div class="text-body-1 text-medium-emphasis">
|
||||||
Click to select files
|
{{ $t("modal.fileSend.dragDrop") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -153,20 +155,23 @@ const handleSendFiles = async () => {
|
|||||||
@click="openFileDialog"
|
@click="openFileDialog"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
>
|
>
|
||||||
Add more files
|
{{ $t("modal.fileSend.addMore") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn variant="text" @click="show = false">Cancel</v-btn>
|
<v-btn variant="text" @click="show = false">{{
|
||||||
|
$t("common.cancel")
|
||||||
|
}}</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="handleSendFiles"
|
@click="handleSendFiles"
|
||||||
:disabled="fileList.length === 0"
|
:disabled="fileList.length === 0"
|
||||||
>
|
>
|
||||||
Send {{ fileList.length > 0 ? `(${fileList.length})` : "" }}
|
{{ $t("modal.fileSend.sendSrc") }}
|
||||||
|
{{ fileList.length > 0 ? `(${fileList.length})` : "" }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// --- Vue 核心 ---
|
// --- Vue 核心 ---
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref, watch, nextTick } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
// --- Wails & 后端绑定 ---
|
// --- Wails & 后端绑定 ---
|
||||||
import { SendText } from "../../../bindings/mesh-drop/internal/transfer/service";
|
import { SendText } from "../../../bindings/mesh-drop/internal/transfer/service";
|
||||||
@@ -19,7 +20,9 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
// --- 状态 ---
|
// --- 状态 ---
|
||||||
|
const { t } = useI18n();
|
||||||
const textContent = ref("");
|
const textContent = ref("");
|
||||||
|
const textareaRef = ref();
|
||||||
|
|
||||||
// --- 计算属性 ---
|
// --- 计算属性 ---
|
||||||
const show = computed({
|
const show = computed({
|
||||||
@@ -27,6 +30,14 @@ const show = computed({
|
|||||||
set: (value) => emit("update:modelValue", value),
|
set: (value) => emit("update:modelValue", value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- 监听 ---
|
||||||
|
watch(show, async (val) => {
|
||||||
|
if (val) {
|
||||||
|
await nextTick();
|
||||||
|
textareaRef.value?.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- 方法 ---
|
// --- 方法 ---
|
||||||
const executeSendText = async () => {
|
const executeSendText = async () => {
|
||||||
if (!props.selectedIp || !textContent.value) return;
|
if (!props.selectedIp || !textContent.value) return;
|
||||||
@@ -38,32 +49,35 @@ const executeSendText = async () => {
|
|||||||
textContent.value = "";
|
textContent.value = "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert("Failed to send text: " + e);
|
alert(t("modal.textSend.failed", { error: e }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="show" width="500" persistent eager>
|
<v-dialog v-model="show" width="500" persistent eager>
|
||||||
<v-card title="Send Text">
|
<v-card :title="$t('modal.textSend.title')">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-textarea
|
<v-textarea
|
||||||
|
ref="textareaRef"
|
||||||
v-model="textContent"
|
v-model="textContent"
|
||||||
label="Content"
|
:label="$t('modal.textSend.contentLabel')"
|
||||||
placeholder="Type something to send..."
|
:placeholder="$t('modal.textSend.placeholder')"
|
||||||
rows="4"
|
rows="4"
|
||||||
auto-grow
|
auto-grow
|
||||||
></v-textarea>
|
></v-textarea>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn variant="text" @click="show = false">Cancel</v-btn>
|
<v-btn variant="text" @click="show = false">{{
|
||||||
|
$t("common.cancel")
|
||||||
|
}}</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="executeSendText"
|
@click="executeSendText"
|
||||||
:disabled="!textContent"
|
:disabled="!textContent"
|
||||||
>
|
>
|
||||||
Send
|
{{ $t("modal.textSend.send") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
84
frontend/src/locales/en.json
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"accept": "Accept",
|
||||||
|
"reject": "Reject",
|
||||||
|
"copy": "Copy"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"discover": "Discover",
|
||||||
|
"transfers": "Transfers",
|
||||||
|
"settings": "Settings"
|
||||||
|
},
|
||||||
|
"discover": {
|
||||||
|
"scanning": "Scanning for peers...",
|
||||||
|
"noPeers": "No peers found",
|
||||||
|
"send": "Send",
|
||||||
|
"sendFiles": "Send Files",
|
||||||
|
"sendFolder": "Send Folder",
|
||||||
|
"sendText": "Send Text",
|
||||||
|
"sendClipboard": "Send Clipboard",
|
||||||
|
"selectFolder": "Select Folder",
|
||||||
|
"clipboardEmpty": "Clipboard is empty",
|
||||||
|
"noRoute": "No Route",
|
||||||
|
"mismatch": "Trust Mismatch",
|
||||||
|
"resetTrust": "Reset Trust",
|
||||||
|
"trustPeer": "Trust Peer",
|
||||||
|
"untrustPeer": "Untrust Peer",
|
||||||
|
"sendFolderFailed": "Failed to send folder: {error}",
|
||||||
|
"sendClipboardFailed": "Failed to send clipboard: {error}"
|
||||||
|
},
|
||||||
|
"transfers": {
|
||||||
|
"noTransfers": "No transfers yet",
|
||||||
|
"clearFinished": "Clear Finished",
|
||||||
|
"pending": "Pending",
|
||||||
|
"transferring": "Transferring",
|
||||||
|
"completed": "Completed",
|
||||||
|
"failed": "Failed",
|
||||||
|
"cancelled": "Cancelled",
|
||||||
|
"selectSavePath": "Select Save Path",
|
||||||
|
"text": "Text",
|
||||||
|
"folder": "Folder",
|
||||||
|
"securityAlert": "Security Alert",
|
||||||
|
"rejected": "Rejected",
|
||||||
|
"waitingForAccept": "Waiting for accept",
|
||||||
|
"saveToFolder": "Save to Folder",
|
||||||
|
"viewContent": "View Content",
|
||||||
|
"textContent": "Text Content"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"savePath": "Save Path",
|
||||||
|
"change": "Change",
|
||||||
|
"hostName": "Host Name",
|
||||||
|
"saveHistory": "Save History",
|
||||||
|
"autoAccept": "Auto Accept",
|
||||||
|
"version": "Version",
|
||||||
|
"language": "Language",
|
||||||
|
"selectSavePath": "Select Save Path",
|
||||||
|
"closeToSystray": "Close to Systray"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"fileSend": {
|
||||||
|
"title": "Send Files",
|
||||||
|
"selectTitle": "Select files to send",
|
||||||
|
"dragDrop": "Click to select files",
|
||||||
|
"addMore": "Add more files",
|
||||||
|
"sendSrc": "Send",
|
||||||
|
"failed": "Failed to send files: {error}"
|
||||||
|
},
|
||||||
|
"textSend": {
|
||||||
|
"title": "Send Text",
|
||||||
|
"contentLabel": "Content",
|
||||||
|
"placeholder": "Type something to send...",
|
||||||
|
"send": "Send",
|
||||||
|
"failed": "Failed to send text: {error}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
frontend/src/locales/zh-Hans.json
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"confirm": "确定",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"edit": "编辑",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"success": "成功",
|
||||||
|
"error": "错误",
|
||||||
|
"accept": "接收",
|
||||||
|
"reject": "拒绝",
|
||||||
|
"copy": "复制"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"discover": "发现",
|
||||||
|
"transfers": "传输",
|
||||||
|
"settings": "设置"
|
||||||
|
},
|
||||||
|
"discover": {
|
||||||
|
"scanning": "正在扫描设备...",
|
||||||
|
"noPeers": "未发现设备",
|
||||||
|
"send": "发送",
|
||||||
|
"sendFiles": "发送文件",
|
||||||
|
"sendFolder": "发送文件夹",
|
||||||
|
"sendText": "发送文本",
|
||||||
|
"sendClipboard": "发送剪贴板",
|
||||||
|
"selectFolder": "选择文件夹",
|
||||||
|
"clipboardEmpty": "剪贴板为空",
|
||||||
|
"noRoute": "不可达",
|
||||||
|
"mismatch": "信任不匹配",
|
||||||
|
"resetTrust": "重置信任",
|
||||||
|
"trustPeer": "信任设备",
|
||||||
|
"untrustPeer": "取消信任",
|
||||||
|
"sendFolderFailed": "发送文件夹失败: {error}",
|
||||||
|
"sendClipboardFailed": "发送剪贴板失败: {error}"
|
||||||
|
},
|
||||||
|
"transfers": {
|
||||||
|
"noTransfers": "暂无传输记录",
|
||||||
|
"clearFinished": "清除已完成",
|
||||||
|
"pending": "等待中",
|
||||||
|
"transferring": "传输中",
|
||||||
|
"completed": "已完成",
|
||||||
|
"failed": "失败",
|
||||||
|
"cancelled": "已取消",
|
||||||
|
"selectSavePath": "选择保存路径",
|
||||||
|
"text": "文本",
|
||||||
|
"folder": "文件夹",
|
||||||
|
"securityAlert": "安全警告",
|
||||||
|
"rejected": "已拒绝",
|
||||||
|
"waitingForAccept": "等待接收",
|
||||||
|
"saveToFolder": "保存到文件夹",
|
||||||
|
"viewContent": "查看内容",
|
||||||
|
"textContent": "文本内容"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"savePath": "保存路径",
|
||||||
|
"change": "更改",
|
||||||
|
"hostName": "设备名称",
|
||||||
|
"saveHistory": "保存历史记录",
|
||||||
|
"autoAccept": "自动接收",
|
||||||
|
"version": "版本",
|
||||||
|
"language": "语言",
|
||||||
|
"selectSavePath": "选择保存路径",
|
||||||
|
"closeToSystray": "关闭窗口时最小化到托盘"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"fileSend": {
|
||||||
|
"title": "发送文件",
|
||||||
|
"selectTitle": "选择要发送的文件",
|
||||||
|
"dragDrop": "点击选择文件",
|
||||||
|
"addMore": "添加更多文件",
|
||||||
|
"sendSrc": "发送",
|
||||||
|
"failed": "发送文件失败: {error}"
|
||||||
|
},
|
||||||
|
"textSend": {
|
||||||
|
"title": "发送文本",
|
||||||
|
"contentLabel": "内容",
|
||||||
|
"placeholder": "输入要发送的内容...",
|
||||||
|
"send": "发送",
|
||||||
|
"failed": "发送文本失败: {error}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
frontend/src/plugins/i18n.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import en from "../locales/en.json";
|
||||||
|
import zhHans from "../locales/zh-Hans.json";
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false, // use Composition API
|
||||||
|
locale: navigator.language.startsWith("zh") ? "zh-Hans" : "en",
|
||||||
|
fallbackLocale: "en",
|
||||||
|
messages: {
|
||||||
|
en,
|
||||||
|
"zh-Hans": zhHans,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@@ -5,11 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
import vuetify from './vuetify'
|
import vuetify from "./vuetify";
|
||||||
|
import i18n from "./i18n";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { App } from 'vue'
|
import type { App } from "vue";
|
||||||
|
|
||||||
export function registerPlugins(app: App) {
|
export function registerPlugins(app: App) {
|
||||||
app.use(vuetify)
|
app.use(vuetify);
|
||||||
|
app.use(i18n);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ body,
|
|||||||
/* 标准属性 */
|
/* 标准属性 */
|
||||||
cursor: default;
|
cursor: default;
|
||||||
/* 鼠标指针变为默认箭头,而不是文本输入的 I 形 */
|
/* 鼠标指针变为默认箭头,而不是文本输入的 I 形 */
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/plugins/index.ts","./src/plugins/vuetify.ts","./src/App.vue","./src/components/MainLayout.vue","./src/components/PeerCard.vue","./src/components/SettingsView.vue","./src/components/TransferItem.vue","./src/components/modals/FileSendModal.vue","./src/components/modals/TextSendModal.vue","./bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts","./bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts","./bindings/mesh-drop/index.ts","./bindings/mesh-drop/models.ts","./bindings/mesh-drop/internal/config/config.ts","./bindings/mesh-drop/internal/config/index.ts","./bindings/mesh-drop/internal/config/models.ts","./bindings/mesh-drop/internal/discovery/index.ts","./bindings/mesh-drop/internal/discovery/models.ts","./bindings/mesh-drop/internal/discovery/service.ts","./bindings/mesh-drop/internal/transfer/index.ts","./bindings/mesh-drop/internal/transfer/models.ts","./bindings/mesh-drop/internal/transfer/service.ts","./bindings/time/index.ts","./bindings/time/models.ts"],"version":"5.9.3"}
|
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/plugins/i18n.ts","./src/plugins/index.ts","./src/plugins/vuetify.ts","./src/App.vue","./src/components/MainLayout.vue","./src/components/PeerCard.vue","./src/components/SettingsView.vue","./src/components/TransferItem.vue","./src/components/modals/FileSendModal.vue","./src/components/modals/TextSendModal.vue","./bindings/github.com/wailsapp/wails/v3/internal/eventcreate.ts","./bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts","./bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts","./bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts","./bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/notificationservice.ts","./bindings/mesh-drop/index.ts","./bindings/mesh-drop/models.ts","./bindings/mesh-drop/internal/config/config.ts","./bindings/mesh-drop/internal/config/index.ts","./bindings/mesh-drop/internal/config/models.ts","./bindings/mesh-drop/internal/discovery/index.ts","./bindings/mesh-drop/internal/discovery/models.ts","./bindings/mesh-drop/internal/discovery/service.ts","./bindings/mesh-drop/internal/transfer/index.ts","./bindings/mesh-drop/internal/transfer/models.ts","./bindings/mesh-drop/internal/transfer/service.ts","./bindings/sync/index.ts","./bindings/sync/models.ts","./bindings/time/index.ts","./bindings/time/models.ts"],"version":"5.9.3"}
|
||||||
13
go.mod
@@ -5,12 +5,12 @@ go 1.25
|
|||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.68
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.67
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
github.com/adrg/xdg v0.5.3 // indirect
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
@@ -23,7 +23,6 @@ require (
|
|||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/ebitengine/purego v0.9.1 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
@@ -33,7 +32,6 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
@@ -57,21 +55,14 @@ require (
|
|||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
|
||||||
github.com/samber/lo v1.52.0 // indirect
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
|
||||||
golang.org/x/arch v0.20.0 // indirect
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/mod v0.32.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
|
|||||||
28
go.sum
@@ -1,5 +1,7 @@
|
|||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
@@ -34,10 +36,6 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
|
|||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
@@ -66,8 +64,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
@@ -138,8 +134,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
|
||||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
@@ -147,16 +141,6 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq
|
|||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
|
||||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
|
||||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
|
||||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -168,22 +152,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
||||||
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.67 h1:cUpNk00Hvu9DMBI6bpF4xxwwzf3yT1n9l7D1WUvMrQ8=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.68 h1:CBSP9rOISKiFv6hmqVj2HsU6f4bSMQmsmuSzPQMUxSE=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.67/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.68/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
|||||||
BIN
goreleaser/icon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
goreleaser/icon.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
15
goreleaser/info.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "0.1.0"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0000": {
|
||||||
|
"ProductVersion": "0.1.0",
|
||||||
|
"CompanyName": "Nite",
|
||||||
|
"FileDescription": "MeshDrop - A cross-platform file transfer application",
|
||||||
|
"LegalCopyright": "© 2026, Nite",
|
||||||
|
"ProductName": "MeshDrop",
|
||||||
|
"Comments": "A cross-platform file transfer application"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
goreleaser/mesh-drop.desktop
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=mesh-drop
|
||||||
|
Exec=mesh-drop
|
||||||
|
Icon=mesh-drop.png
|
||||||
|
Categories=Network;FileTransfer;
|
||||||
|
Terminal=false
|
||||||
|
Keywords=mesh,transfer,file,network,drop
|
||||||
|
Version=1.0
|
||||||
|
StartupNotify=false
|
||||||