Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f194fb9cca | |||
| 36a4f93127 | |||
| aae5adef38 | |||
| 51754f737c | |||
| 85ccd835ab | |||
| 8e302bfdde | |||
| b609331b8e | |||
| 59845174e7 | |||
| 0bdb6a626d | |||
| 7f0256cc33 | |||
| 0d52800569 | |||
| a1dd9e656d | |||
| 46d68e5448 | |||
| 2c820ca0ea |
@@ -1,10 +1,16 @@
|
||||
name: ci-build-upload-binaries
|
||||
name: build-upload-binaries
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'mixnode/**'
|
||||
inputs:
|
||||
add_tokio_unstable:
|
||||
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
@@ -16,49 +22,40 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "--cfg tokio_unstable"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt update && sudo apt install libudev-dev
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
|
||||
- name: Sets env vars for tokio if set in manual dispatch inputs
|
||||
run: |
|
||||
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build mixnode binary
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --features cpucycles --package nym-mixnode
|
||||
args: --workspace --release
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: nym-binaries-artifacts
|
||||
path: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
retention-days: 30
|
||||
|
||||
@@ -25,14 +25,14 @@ These steps are for an operator who is setting up a [Gateway](gateway-setup.md)
|
||||
|
||||
1. New flags will need to be added to the `init` and `run` command. The `--host` option should be replaced with these flags:
|
||||
|
||||
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
|
||||
- `--public-ips`: A comma separated list of IP’s that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
|
||||
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
|
||||
- `--public-ips`: A comma separated list of IP’s that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
|
||||
|
||||
```sh
|
||||
--listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)"
|
||||
```
|
||||
|
||||
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
|
||||
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
|
||||
|
||||
2. Make sure to enable all necessary [ports](maintenance.md#configure-your-firewall) on the Gateway:
|
||||
|
||||
@@ -42,7 +42,7 @@ sudo ufw allow 1789,1790,8000,9000,9001,22/tcp, 9001/tcp
|
||||
|
||||
The Gateway will then be accessible on something like: *http://85.159.211.99:8080/api/v1/swagger/index.html*
|
||||
|
||||
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
|
||||
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
|
||||
|
||||
Please modify `[host.public_ips]` section of your config file stored as `~/.nym/gateways/<ID>/config/config.toml`.
|
||||
|
||||
@@ -92,7 +92,7 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo
|
||||
apt update
|
||||
apt install caddy
|
||||
|
||||
systemctl enable caddy.service
|
||||
systemctl enable caddy.service
|
||||
|
||||
cd /etc/caddy
|
||||
|
||||
@@ -102,7 +102,7 @@ if [ -f Caddyfile ]; then
|
||||
rm -f Caddyfile
|
||||
fi
|
||||
|
||||
cat <<EOF >> Caddyfile
|
||||
cat <<EOF >> Caddyfile
|
||||
${host}:${port_value} {
|
||||
@websockets {
|
||||
header Connection *Upgrade*
|
||||
@@ -191,9 +191,9 @@ nohup ./<NODE> run --id <YOUR_ID> # where `<YOUR_ID>` is the id you set during t
|
||||
|
||||
#### tmux
|
||||
|
||||
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
|
||||
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
|
||||
|
||||
Use the following command to get `tmux`.
|
||||
Use the following command to get `tmux`.
|
||||
|
||||
Platform|Install Command
|
||||
---|---
|
||||
@@ -204,15 +204,15 @@ RHEL or CentOS|`yum install tmux`
|
||||
macOS (using Homebrew|`brew install tmux`
|
||||
macOS (using MacPorts)|`port install tmux`
|
||||
openSUSE|`zypper install tmux`
|
||||
|
||||
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
|
||||
|
||||
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
|
||||
|
||||
**Running tmux**
|
||||
|
||||
Now you have installed tmux on your VPS, let's run a Mix Node on tmux, which allows you to detach your terminal and let your `<NODE>` run on its own on the VPS.
|
||||
|
||||
* Pause your `<NODE>`
|
||||
* Start tmux with the command
|
||||
* Start tmux with the command
|
||||
```sh
|
||||
tmux
|
||||
```
|
||||
@@ -230,7 +230,7 @@ tmux attach-session
|
||||
|
||||
#### systemd
|
||||
|
||||
To automate with `systemd` use this init service file and follow the steps below.
|
||||
To automate with `systemd` use this init service file and follow the steps below.
|
||||
|
||||
##### For Mix Node
|
||||
|
||||
@@ -327,18 +327,18 @@ Change the `<PATH>` in `ExecStart` to point at your `<NODE>` binary (`nym-mixnod
|
||||
|
||||
Example: If you have built nym in the `$HOME` directory on your server, your username is `jetpanther`, and node `<ID>` is `puma`, then the `ExecStart` line (command) in the script located in `/etc/systemd/system/nym-mixnode.service` for Nym Mixnode might look like this:
|
||||
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
|
||||
|
||||
Basically, you want the full `/<PATH>/<TO>/nym-mixnode run --id <WHATEVER-YOUR-NODE-ID-IS>`. If you are unsure about your `/<PATH>/<TO>/<NODE>`, then `cd` to your directory where you run your `<NODE>` from and run `pwd` command which returns the full path for you.
|
||||
|
||||
Once done, save the script and follow these steps:
|
||||
|
||||
```sh
|
||||
systemctl daemon-reload
|
||||
systemctl daemon-reload
|
||||
# to pickup the new unit file
|
||||
```
|
||||
|
||||
Enable the newly created service:
|
||||
Enable the newly created service:
|
||||
|
||||
```sh
|
||||
# for Mix Node
|
||||
@@ -350,7 +350,7 @@ systemctl enable nym-gateway.service
|
||||
# for Network Requester
|
||||
systemctl enable nym-network-requester.service
|
||||
|
||||
# for Nymvisor
|
||||
# for Nymvisor
|
||||
systemctl enable nymvisor.service
|
||||
```
|
||||
|
||||
@@ -367,7 +367,7 @@ service nym-gateway start
|
||||
service nym-network-requester.service
|
||||
|
||||
# for Nymvisor
|
||||
service nymvisor.service start
|
||||
service nymvisor.service start
|
||||
```
|
||||
|
||||
This will cause your `<NODE>` to start at system boot time. If you restart your machine, your `<NODE>` will come back up automatically.
|
||||
@@ -427,7 +427,7 @@ systemctl start nymd # to actually start the service
|
||||
journalctl -f -u nymd # to monitor system logs showing the service start
|
||||
```
|
||||
|
||||
##### For Nym API
|
||||
##### For Nym API
|
||||
|
||||
Below is a `systemd` unit file to place at `/etc/systemd/system/nym-api.service` to automate your API instance:
|
||||
|
||||
@@ -535,7 +535,7 @@ In case you chose tmux option for Mix Node automation, see your `ulimit` list by
|
||||
ulimit -a
|
||||
|
||||
# watch for the output line -n
|
||||
-n: file descriptors 1024
|
||||
-n: file descriptors 1024
|
||||
```
|
||||
|
||||
You can change it either by running a command:
|
||||
@@ -582,14 +582,14 @@ scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/mixnodes/<YOUR_ID> <TA
|
||||
```
|
||||
* Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc.
|
||||
* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks.
|
||||
* Re-run the node from the new location.
|
||||
* Re-run the node from the new location.
|
||||
|
||||
|
||||
## Virtual IPs and hosting via Google & AWS
|
||||
|
||||
For true internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a `<NODE>` there, please read the following.
|
||||
|
||||
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
|
||||
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
|
||||
|
||||
You can run `ifconfig` command. For example, on a Google machine, you may see the following output:
|
||||
|
||||
@@ -601,7 +601,7 @@ ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1460
|
||||
|
||||
The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`.
|
||||
|
||||
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
|
||||
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
|
||||
|
||||
On self-hosted machine it's a bit more tricky. In that case as an operator you must be sure that your ISP allows for public IPv4 and IPv6 and then it may be a bit of playing around to find the right configuration. One way may be to bind your binary with the `--host` flag to local address `127.0.0.1` and run `echo "$(curl -4 https://ifconfig.me)"` to get a public address which you use to bond your Mix Node to `nym-api` via Nym wallet.
|
||||
|
||||
@@ -650,7 +650,7 @@ Query Response:
|
||||
|
||||
### Validator: Installing and configuring nginx for HTTPS
|
||||
#### Setup
|
||||
[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
|
||||
[Nginx](https://www.nginx.com/resources/glossary/nginx) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
|
||||
|
||||
Install `nginx` and allow the 'Nginx Full' rule in your firewall:
|
||||
|
||||
@@ -678,15 +678,16 @@ Which should return:
|
||||
└─2380 nginx: worker process
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
#### Full Node Configuration
|
||||
|
||||
Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`:
|
||||
Proxying various full node services through port 80 can then be done by creating a file with the following at `/etc/nginx/sites-enabled/nyxd-webrequests.conf`:
|
||||
|
||||
```sh
|
||||
### To expose RPC server
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name "domain_name";
|
||||
server_name "<rpc.nyx.yourdomain.tld>";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:26657;
|
||||
@@ -695,20 +696,58 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
### To expose Cosmos API server
|
||||
server {
|
||||
server_name "<api.nyx.yourdomain.tld>";
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:1317;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade websocket;
|
||||
proxy_set_header Connection Upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
### To expose GRPC endpoint
|
||||
server {
|
||||
server_name "<grpc.nyx.yourdomain.tld>";
|
||||
location / {
|
||||
grpc_pass 127.0.0.1:9090;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### nym-api Configuration
|
||||
|
||||
```sh
|
||||
### To expose nym-api webserver
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name "<nym-api.nyx.yourdomain.tld>";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Followed by:
|
||||
|
||||
```sh
|
||||
sudo apt install certbot nginx python3
|
||||
certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect
|
||||
certbot --nginx -m <you@yourdomain.com> --agree-tos
|
||||
```
|
||||
|
||||
```admonish caution title=""
|
||||
If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx`
|
||||
```
|
||||
|
||||
These commands will get you an https encrypted nginx proxy in front of the API.
|
||||
These commands will get you an https encrypted nginx proxy in front of the various endpoints.
|
||||
|
||||
### Configuring Prometheus metrics (optional)
|
||||
|
||||
@@ -795,6 +834,3 @@ All validator-specific port configuration can be found in `$HOME/.nymd/config/co
|
||||
| 1317 | REST API server endpoint |
|
||||
| 26656 | Listen for incoming peer connections |
|
||||
| 26660 | Listen for Prometheus connections |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
# Nym API Setup
|
||||
|
||||
[//]: # (> The Nym API binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
|
||||
[//]: # ()
|
||||
[//]: # (> The nym-api binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
|
||||
|
||||
> The `nym-api` binary should be coming out in the next release - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
|
||||
> The `nym-api` binary will be released in the immediate future - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
|
||||
>
|
||||
> You can build the API with `cargo build --release --bin nym-api`.
|
||||
|
||||
> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
## What is the Nym API?
|
||||
The Nym API is a binary that will be operated by the Nyx Blockchain Validator set. This binary can be run in several different modes, and has two main bits of functionality:
|
||||
* network monitoring (calculating the routing score of Mixnet nodes)
|
||||
The Nym API is a binary that will be operated by the Nyx validator set. This binary can be run in several different modes, and has two main bits of functionality:
|
||||
* network monitoring (calculating the routing score of Mixnet nodes)
|
||||
* generation and validation of [zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a), our implementation of the Coconut Selective Disclosure Credential Scheme.
|
||||
|
||||
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
|
||||
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
|
||||
|
||||
The process of enabling these different aspects of the system will take time. Operators of the Nym API binary for the moment will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside their Validator.
|
||||
The process of enabling these different aspects of the system will take time. At the moment, Nym API operators will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside a full node.
|
||||
|
||||
### Rewards
|
||||
Operators of Nym API instances will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
|
||||
```admonish warning
|
||||
It is highly recommended to run `nym-api` alongside a full node since you will be exposing HTTP port(s) to the Internet. We also observed degradation in p2p and block signing operations when `nym-api` was run alongside a signing validator.
|
||||
```
|
||||
|
||||
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237600NYM per month) according to the % of credentials they have signed.
|
||||
### Rewards
|
||||
Operators of Nym API will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
|
||||
|
||||
### (Coming Soon) Machine Specs
|
||||
We are working on load testing currently in order to get good specs for a Validator + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
|
||||
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237,600 NYM per month), proportional to the percentage of credentials they have signed.
|
||||
|
||||
### (Coming Soon) Machine Specs
|
||||
We are working on load testing currently in order to get good specs for a full node + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
|
||||
|
||||
### (Coming Soon) Credential Generation
|
||||
Validators that take part in the DKG ceremony (more details on this soon) will become part of the quorum generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
|
||||
Validators that take part in the DKG ceremony will become part of the 'quorum' generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
|
||||
|
||||
## Current version
|
||||
The DKG ceremony will be used to create a subset of existing validators - referred to as the quorum. As outlined above, they will be the ones taking part in the generation and verification of zk-Nym credentials. The size of the 'minimum viable quorum' is 10 - we are aiming for a larger number than this for the initial quorum in order to have some redundancy in the case of a Validator dropping or going offline.
|
||||
|
||||
We will be releasing more detailed step-by-step documentation for involved validators nearer to the ceremony itself, but at a high level it will involve:
|
||||
* the deployment and initialisation of [`group`](https://github.com/nymtech/nym/tree/develop/contracts/multisig/cw4-group) and [`multisig`](https://github.com/nymtech/nym/tree/develop/contracts/multisig) contracts by Nym. Validators that are members of the `group` contract are the only ones that will be able to take part in the ceremony.
|
||||
* the deployment and initialisation of an instance of the [DKG contract](https://github.com/nymtech/nym/tree/develop/contracts/coconut-dkg) by Nym.
|
||||
* Validators will update their `nym-api` configs with the address of the deployed contracts. They will also stop running their API instance in caching only mode, instead switching over run with the `--enabled-credentials-mode`.
|
||||
* From the perspective of operators, this is all they have to do. Under the hood, each `nym-api` instance will then take part in several rounds of key submission, verification, and derivation. This will continue until quorum is acheived. More information on this will be released closer to the time of the ceremony.
|
||||
|
||||
**We will be communicating individually with members of the existing Validator set who have expressed interest in joining the quorum concerning the timing and specifics of the ceremony**.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-api --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
@@ -54,31 +69,31 @@ You can also check the various arguments required for individual commands with:
|
||||
./nym-api <COMMAND> --help
|
||||
```
|
||||
|
||||
### Initialising your Nym API Instance
|
||||
Initialise your API instance with:
|
||||
### Initialising your Nym API Instance
|
||||
Initialise your API instance with:
|
||||
|
||||
```
|
||||
./nym-api init
|
||||
```
|
||||
|
||||
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
|
||||
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
|
||||
|
||||
### Running your Nym API Instance
|
||||
The API binary currently defaults to running in caching mode. You can run your API with:
|
||||
### Running your Nym API Instance
|
||||
The API binary currently defaults to running in caching mode. You can run your API with:
|
||||
|
||||
```
|
||||
./nym-api run
|
||||
```
|
||||
|
||||
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
|
||||
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
|
||||
|
||||
```
|
||||
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
|
||||
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
|
||||
```
|
||||
|
||||
> You can also change the value of `local_validator` in the config file found by default in `$HOME/.nym/nym-api/<ID>/config/config.toml`.
|
||||
|
||||
This process is quite noisy, but informative:
|
||||
This process is quite noisy, but informative:
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
@@ -160,5 +175,8 @@ Starting nym api...
|
||||
```
|
||||
~~~
|
||||
|
||||
## Automation
|
||||
## Automation
|
||||
You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md) for an example `service` file.
|
||||
|
||||
## Exposing web endpoint using HTTPS
|
||||
It is recommended to expose the webserver over HTTPS by using a webserver like Nginx. An example configuration for configuring Nginx is listed [on the maintenance page](maintenance.md#nym-api-configuration)
|
||||
|
||||
@@ -216,7 +216,7 @@ You can use the following command to download them for the correct network:
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json
|
||||
|
||||
# Sandbox testnet
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis'
|
||||
curl https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json
|
||||
```
|
||||
|
||||
### `config.toml` configuration
|
||||
@@ -254,7 +254,7 @@ And if you wish to add a human-readable moniker to your node:
|
||||
Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit.
|
||||
|
||||
### `app.toml` configuration
|
||||
In the file `$HOME/nyxd/config/app.toml`, set the following values:
|
||||
In the file `$HOME/.nyxd/config/app.toml`, set the following values:
|
||||
|
||||
```
|
||||
# Mainnet
|
||||
@@ -264,7 +264,6 @@ minimum-gas-prices = "0.025unym,0.025unyx"
|
||||
```
|
||||
# Sandbox Testnet
|
||||
minimum-gas-prices = "0.025unym,0.025unyx"
|
||||
enable = true` in the `[api]` section to get the API server running
|
||||
```
|
||||
|
||||
### Setting up your validator's admin user
|
||||
@@ -304,6 +303,31 @@ File at /path/to/genesis.json is a valid genesis file
|
||||
|
||||
> If this test did not pass, check that you have replaced the contents of `/<PATH-TO>/.nyxd/config/genesis.json` with that of the correct genesis file.
|
||||
|
||||
### Setting up nyxd as full node (non-signing)
|
||||
|
||||
```admonish danger title=""
|
||||
Skip this section if you're planning to run a validator node to join network consensus. To ensure security & maximum availability of validators, do not expose additional services to the Internet
|
||||
```
|
||||
|
||||
Unlike signing validators, full nodes do not propose / sign blocks. A full node is typically used for indexing blocks produced on the chain and for exposing web interfaces such as RPC, API and gRPC endpoints required for external applications/services to interact with the blockchain.
|
||||
|
||||
By default, API server is disabled and RPC/gRPC servers listen to the loopback address only. In a production setup, it is recommended to use a webserver such as Nginx or caddy to proxy requests to the endpoints as required.
|
||||
|
||||
To enable Cosmos REST API, you can enable it in `$HOME/.nyxd/config/app.toml` like :
|
||||
|
||||
```
|
||||
[api]
|
||||
|
||||
# Enable defines if the API server should be enabled. Toggle this to `true`
|
||||
enable = true
|
||||
|
||||
# Swagger defines if swagger documentation should automatically be registered.
|
||||
# You can also expose swagger documentation by toggling the below configuration to true
|
||||
swagger = true
|
||||
```
|
||||
|
||||
For more information on enabling access to various endpoints via Nginx, refer to the [example configuration here](./maintenance.md#setup)
|
||||
|
||||
### Open firewall ports
|
||||
|
||||
Before starting the validator, we will need to open the firewall ports:
|
||||
@@ -319,6 +343,9 @@ sudo ufw enable
|
||||
# 22 : Default SSH port
|
||||
sudo ufw allow 26656,26660,22
|
||||
|
||||
## !! FOR FULL NODES ONLY !! - exposing Nginx for serving web requests
|
||||
sudo ufw allow 80,443
|
||||
|
||||
# to check everything worked
|
||||
sudo ufw status
|
||||
```
|
||||
@@ -358,10 +385,8 @@ lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd
|
||||
You can then restart `nyxd` - it should start syncing from a block > 2000000.
|
||||
|
||||
### Joining Consensus
|
||||
```admonish caution title=""
|
||||
When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens.
|
||||
|
||||
Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time.
|
||||
```admonish info title=""
|
||||
You can skip this section if you are planning to run a full-node. This step will make your node a signing validator which joins network consensus
|
||||
```
|
||||
|
||||
Once your validator has synced and you have received tokens, you can join consensus and produce blocks.
|
||||
@@ -473,11 +498,15 @@ To upgrade your validator, follow the steps on the [maintenance page](./maintena
|
||||
|
||||
#### Common reasons for your validator being jailed
|
||||
|
||||
The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
|
||||
Your validator will be jailed if your node:
|
||||
- misses _`x`_ amount of blocks in _`y`_ interval, where _`x`_ and _`y`_ are parameters set by chain governance
|
||||
- performs double signing (two conflicting signatures on the same block using the same key)
|
||||
|
||||
Running the command `df -H` will return the size of the various partitions of your VPS.
|
||||
Double signing is a serious infraction. If a node double signs, all the delegators to the node (including self-delegation) will be slashed by 5%. Additionally, the node will be permanently jailed and removed from consensus (called _tombstoning_)
|
||||
|
||||
If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process.
|
||||
One of the most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
|
||||
|
||||
Running the command `df -H` will return the size of the various partitions of your VPS. If the partition with blockchain data is almost full, try pruning the blockchain data or expanding the storage size.
|
||||
|
||||
### Day 2 operations with your validator
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ serde_json = { workspace = true }
|
||||
sysinfo = "0.27.7"
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
console-subscriber = { version = "0.2", optional = true }
|
||||
toml = "0.5.8"
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
cfg-if = "1.0.0"
|
||||
@@ -84,9 +83,6 @@ cpucycles = [
|
||||
"nym-bin-common/tracing",
|
||||
]
|
||||
|
||||
# build with RUSTFLAGS="--cfg tokio_unstable"
|
||||
tokio-console = ["console-subscriber", "tokio/tracing"]
|
||||
|
||||
[package.metadata.deb]
|
||||
name = "nym-mixnode"
|
||||
maintainer-scripts = "debian"
|
||||
|
||||
+1
-7
@@ -4,7 +4,7 @@
|
||||
use ::nym_config::defaults::setup_env;
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info};
|
||||
use log::info;
|
||||
use nym_bin_common::bin_info;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
@@ -53,12 +53,6 @@ fn test_function() {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "tokio-console")] {
|
||||
console_subscriber::init();
|
||||
}
|
||||
}
|
||||
|
||||
let args = Cli::parse();
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm
|
||||
NymVPM.xcworkspace/
|
||||
@@ -0,0 +1,149 @@
|
||||
# List of rules with definitions
|
||||
# https://github.com/realm/SwiftLint/blob/master/Rules.md
|
||||
|
||||
disabled_rules:
|
||||
- todo
|
||||
|
||||
opt_in_rules:
|
||||
- anyobject_protocol
|
||||
- attributes
|
||||
- balanced_xctest_lifecycle
|
||||
- capture_variable
|
||||
- closure_body_length
|
||||
- closure_end_indentation
|
||||
- closure_parameter_position
|
||||
- closure_spacing
|
||||
- collection_alignment
|
||||
- colon
|
||||
- conditional_returns_on_newline
|
||||
- contains_over_filter_count
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- discouraged_none_name
|
||||
- discouraged_object_literal
|
||||
- empty_collection_literal
|
||||
- empty_count
|
||||
- empty_string
|
||||
- empty_xctest_method
|
||||
- enum_case_associated_values_count
|
||||
- expiring_todo
|
||||
- explicit_init
|
||||
- fallthrough
|
||||
- fatal_error_message
|
||||
- file_header
|
||||
- file_name
|
||||
- file_name_no_space
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- force_cast
|
||||
- force_try
|
||||
- force_unwrapping
|
||||
- function_default_parameter_at_end
|
||||
- identical_operands
|
||||
- implicit_return
|
||||
- indentation_width
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- legacy_random
|
||||
- line_length
|
||||
- literal_expression_end_indentation
|
||||
- lower_acl_than_parent
|
||||
- multiline_arguments
|
||||
- multiline_arguments_brackets
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
- multiline_parameters_brackets
|
||||
- operator_usage_whitespace
|
||||
- overridden_super_call
|
||||
- pattern_matching_keywords
|
||||
- prefer_self_type_over_type_of_self
|
||||
- prefer_zero_over_explicit_init
|
||||
- private_action
|
||||
- private_outlet
|
||||
- prohibited_interface_builder
|
||||
- prohibited_super_call
|
||||
- redundant_nil_coalescing
|
||||
- redundant_type_annotation
|
||||
- sorted_first_last
|
||||
- switch_case_on_newline
|
||||
- test_case_accessibility
|
||||
- toggle_bool
|
||||
- trailing_closure
|
||||
- unavailable_function
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- weak_delegate
|
||||
- xct_specific_matcher
|
||||
- yoda_condition
|
||||
|
||||
attributes:
|
||||
always_on_same_line:
|
||||
- "@IBSegueAction"
|
||||
- "@IBAction"
|
||||
- "@NSManaged"
|
||||
- "@objc"
|
||||
- "@MainActor"
|
||||
- "@discardableResult"
|
||||
|
||||
conditional_returns_on_newline:
|
||||
if_only: true
|
||||
|
||||
cyclomatic_complexity:
|
||||
ignores_case_statements: true
|
||||
|
||||
enum_case_associated_values_count:
|
||||
warning: 4
|
||||
error: 6
|
||||
|
||||
function_parameter_count:
|
||||
warning: 6
|
||||
error: 8
|
||||
|
||||
identifier_name:
|
||||
min_length: 2
|
||||
max_length: 60
|
||||
|
||||
indentation_width:
|
||||
indentation_width: 4
|
||||
|
||||
legacy_hashing: error
|
||||
|
||||
line_length:
|
||||
ignores_urls: true
|
||||
ignores_comments: true
|
||||
ignores_function_declarations: false
|
||||
ignores_interpolated_strings: true
|
||||
warning: 120
|
||||
error: 240
|
||||
|
||||
multiline_parameters:
|
||||
allowsSingleLine: false
|
||||
|
||||
nesting:
|
||||
type_level: 3
|
||||
|
||||
private_outlet:
|
||||
allow_private_set: true
|
||||
|
||||
trailing_closure:
|
||||
only_single_muted_parameter: true
|
||||
|
||||
trailing_whitespace:
|
||||
ignores_comments: true
|
||||
|
||||
type_body_length:
|
||||
warning: 400
|
||||
error: 500
|
||||
|
||||
type_name:
|
||||
min_length: 2
|
||||
max_length: 60
|
||||
|
||||
warning_threshold: 15
|
||||
|
||||
reporter: "xcode"
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,36 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Home",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Home",
|
||||
targets: ["Home"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../UIComponents"),
|
||||
.package(path: "../Settings")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Home",
|
||||
dependencies: [
|
||||
"UIComponents",
|
||||
"Settings"
|
||||
],
|
||||
path: "Sources"
|
||||
),
|
||||
.testTarget(
|
||||
name: "HomeTests",
|
||||
dependencies: ["Home"]
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
import SwiftUI
|
||||
import Settings
|
||||
|
||||
struct HomeFlowCoordinator<Content: View>: View {
|
||||
@ObservedObject var state: HomeFlowState
|
||||
let content: () -> Content
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $state.path) {
|
||||
ZStack {
|
||||
content()
|
||||
}
|
||||
.navigationDestination(for: HomeLink.self, destination: linkDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension HomeFlowCoordinator {
|
||||
@ViewBuilder private func linkDestination(link: HomeLink) -> some View {
|
||||
switch link {
|
||||
case .firstHop(text: _):
|
||||
HopListView(viewModel: HopListViewModel(path: $state.path, type: .first))
|
||||
case .lastHop:
|
||||
HopListView(viewModel: HopListViewModel(path: $state.path, type: .last))
|
||||
case .settings:
|
||||
SettingsView(viewModel: SettingsViewModel(path: $state.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
public class HomeFlowState: ObservableObject {
|
||||
@Published var path = NavigationPath()
|
||||
@Published var presentedItem: HomeLink?
|
||||
@Published var coverItem: HomeLink?
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
enum HomeLink: Hashable, Identifiable {
|
||||
case firstHop(text: String?)
|
||||
case lastHop
|
||||
case settings
|
||||
|
||||
var id: String {
|
||||
String(describing: self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct HomeView: View {
|
||||
@ObservedObject private var viewModel: HomeViewModel
|
||||
|
||||
public init(viewModel: HomeViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HomeFlowCoordinator(state: viewModel, content: content)
|
||||
}
|
||||
}
|
||||
|
||||
private extension HomeView {
|
||||
@ViewBuilder
|
||||
func content() -> some View {
|
||||
VStack {
|
||||
navbar()
|
||||
Spacer()
|
||||
statusAreaSection()
|
||||
Spacer()
|
||||
networkSection()
|
||||
connectionSection()
|
||||
connectButton()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background {
|
||||
NymColor.background
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: "NymVPN".localizedString,
|
||||
rightButton: CustomNavBarButton(type: .settings, action: { viewModel.navigateToSettings() })
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 50)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func statusAreaSection() -> some View {
|
||||
StatusButton(config: .disconnected)
|
||||
Spacer()
|
||||
.frame(height: 8)
|
||||
|
||||
StatusInfoView()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func networkSection() -> some View {
|
||||
HStack {
|
||||
Text("selectNetwork".localizedString)
|
||||
.textStyle(.Title.Medium.primary)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
NetworkButton(viewModel: NetworkButtonViewModel(type: .mixnet, selectedNetwork: $viewModel.selectedNetwork))
|
||||
.padding(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
|
||||
.onTapGesture {
|
||||
viewModel.selectedNetwork = .mixnet
|
||||
}
|
||||
|
||||
NetworkButton(viewModel: NetworkButtonViewModel(type: .wireguard, selectedNetwork: $viewModel.selectedNetwork))
|
||||
.padding(.horizontal, 16)
|
||||
.onTapGesture {
|
||||
viewModel.selectedNetwork = .wireguard
|
||||
}
|
||||
Spacer()
|
||||
.frame(height: 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func connectionSection() -> some View {
|
||||
HStack {
|
||||
Text("connectTo".localizedString)
|
||||
.foregroundStyle(NymColor.sysOnSurfaceWhite)
|
||||
.textStyle(.Title.Medium.primary)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
VStack {
|
||||
HopButton(hopType: .first, country: Country(name: "Germany", code: "de"))
|
||||
.onTapGesture {
|
||||
viewModel.navigateToFirstHopSelection()
|
||||
}
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
HopButton(hopType: .last, country: Country(name: "Switzerland", code: "ch"))
|
||||
.onTapGesture {
|
||||
viewModel.navigateToLastHopSelection()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
Spacer()
|
||||
.frame(height: 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func connectButton() -> some View {
|
||||
ConnectButton()
|
||||
.padding(.horizontal, 16)
|
||||
.onTapGesture {
|
||||
viewModel.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import SwiftUI
|
||||
import UIComponents
|
||||
import Tunnels
|
||||
|
||||
public class HomeViewModel: HomeFlowState {
|
||||
private let tunnelsManager: TunnelsManager
|
||||
|
||||
@Published var selectedNetwork: NetworkButtonViewModel.ButtonType
|
||||
|
||||
public init(
|
||||
selectedNetwork: NetworkButtonViewModel.ButtonType,
|
||||
tunnelsManager: TunnelsManager = TunnelsManager.shared
|
||||
) {
|
||||
self.selectedNetwork = selectedNetwork
|
||||
self.tunnelsManager = tunnelsManager
|
||||
|
||||
tunnelsManager.loadConfigurations()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Navigation -
|
||||
|
||||
public extension HomeViewModel {
|
||||
func navigateToSettings() {
|
||||
path.append(HomeLink.settings)
|
||||
}
|
||||
|
||||
func navigateToFirstHopSelection() {
|
||||
path.append(HomeLink.firstHop(text: ""))
|
||||
}
|
||||
|
||||
func navigateToLastHopSelection() {
|
||||
path.append(HomeLink.lastHop)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tunnel testing -
|
||||
|
||||
public extension HomeViewModel {
|
||||
func connect() {
|
||||
if let tunnel = tunnelsManager.currentTunnel, tunnel.tunnel.connection.status == .connected {
|
||||
tunnelsManager.disconnect()
|
||||
} else {
|
||||
tunnelsManager.test()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct HopListView: View {
|
||||
private let viewModel: HopListViewModel
|
||||
|
||||
public init(viewModel: HopListViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
navbar()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
countryButton()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
countryButton2()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
searchView()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
availableCountryList()
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background {
|
||||
NymColor.background
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension HopListView {
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: viewModel.type.selectHopLocalizedTitle,
|
||||
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateHome() })
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func countryButton() -> some View {
|
||||
CountryCellButton(
|
||||
viewModel: CountryCellButtonViewModel(
|
||||
type: .fastest(
|
||||
country: Country(name: "Germany", code: "de")
|
||||
),
|
||||
isSelected: false
|
||||
)
|
||||
)
|
||||
.padding(.horizontal, 15)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func countryButton2() -> some View {
|
||||
CountryCellButton(
|
||||
viewModel: CountryCellButtonViewModel(
|
||||
type: .country(
|
||||
country: Country(name: "Germany", code: "de")
|
||||
),
|
||||
isSelected: true
|
||||
)
|
||||
)
|
||||
.padding(.horizontal, 15)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func searchView() -> some View {
|
||||
SearchView(viewModel: SearchViewModel())
|
||||
.padding(.horizontal, 40)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func availableCountryList() -> some View {
|
||||
ScrollView {
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
switzerland()
|
||||
germany()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func germany() -> some View {
|
||||
CountryCellButton(
|
||||
viewModel: CountryCellButtonViewModel(
|
||||
type: .country(
|
||||
country: Country(name: "Germany", code: "de")
|
||||
),
|
||||
isSelected: false
|
||||
)
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func switzerland() -> some View {
|
||||
CountryCellButton(
|
||||
viewModel: CountryCellButtonViewModel(
|
||||
type: .country(
|
||||
country: Country(name: "Switzerland", code: "ch")
|
||||
),
|
||||
isSelected: false
|
||||
)
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct HopListViewModel {
|
||||
public let type: HopType
|
||||
|
||||
@Binding var path: NavigationPath
|
||||
|
||||
public init(path: Binding<NavigationPath>, type: HopType) {
|
||||
self.type = type
|
||||
_path = path
|
||||
}
|
||||
}
|
||||
|
||||
extension HopListViewModel {
|
||||
func navigateHome() {
|
||||
path = .init()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
public enum HopType {
|
||||
case first
|
||||
case last
|
||||
|
||||
var selectHopLocalizedTitle: String {
|
||||
switch self {
|
||||
case .first:
|
||||
"firstHopSelection".localizedString
|
||||
case .last:
|
||||
"lastHopSelection".localizedString
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import XCTest
|
||||
@testable import Home
|
||||
|
||||
final class HomeTests: XCTestCase {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.net.nymtech.vpn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,45 @@
|
||||
import NetworkExtension
|
||||
import OSLog
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
NSLog("🏁 Starting tunnel with options: \(options ?? [:])")
|
||||
os_log("🏁 Starting tunnel with options: \(options ?? [:])")
|
||||
|
||||
fetchData()
|
||||
completionHandler(nil)
|
||||
}
|
||||
|
||||
func fetchData() {
|
||||
// Do not use the NSTimer here that will not run in background
|
||||
let popTime = DispatchTime.now() + DispatchTimeInterval.seconds(Int(1))
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: popTime) { [weak self] in
|
||||
// Fetch your data from server and generate local notification by using UserNotifications framework
|
||||
self?.doSomeStuff()
|
||||
self?.fetchData()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func doSomeStuff() {
|
||||
NSLog("🔥 ROKAS TIMER TIC TAK")
|
||||
os_log("🔥 ROKAS TIMER TIC TAK")
|
||||
}
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
NSLog("🛑 Rokas stopping tunnel")
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||
|
||||
if let handler = completionHandler {
|
||||
handler(messageData)
|
||||
}
|
||||
}
|
||||
|
||||
override func sleep(completionHandler: @escaping () -> Void) {
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
override func wake() {}
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D9264E262B3DA062004594A8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D9264E252B3DA062004594A8 /* Assets.xcassets */; };
|
||||
D954C0B92B51C1CA00C6BAAC /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */; };
|
||||
D954C0BC2B51C1CA00C6BAAC /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */; };
|
||||
D954C0C12B51C1CA00C6BAAC /* NymMixnetTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D96740232B56A67A001F6891 /* Tunnels in Frameworks */ = {isa = PBXBuildFile; productRef = D96740222B56A67A001F6891 /* Tunnels */; };
|
||||
D968BDC92B6AEA9D003F42E5 /* AppSettings in Frameworks */ = {isa = PBXBuildFile; productRef = D968BDC82B6AEA9D003F42E5 /* AppSettings */; };
|
||||
D968BDCB2B6BC98E003F42E5 /* AppVersionProvider in Frameworks */ = {isa = PBXBuildFile; productRef = D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */; };
|
||||
D99A147A2B357E9900F2728B /* NymVPNApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99A14792B357E9900F2728B /* NymVPNApp.swift */; };
|
||||
D99A148A2B35A73F00F2728B /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = D99A14892B35A73F00F2728B /* Home */; };
|
||||
D99A148C2B35A74200F2728B /* Theme in Frameworks */ = {isa = PBXBuildFile; productRef = D99A148B2B35A74200F2728B /* Theme */; };
|
||||
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */ = {isa = PBXBuildFile; productRef = D9CD835C2B6C32D4008DA864 /* Modifiers */; };
|
||||
D9F699E02B4C1862005576A9 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = D9F699DF2B4C1862005576A9 /* Settings */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
D954C0BF2B51C1CA00C6BAAC /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D99A146E2B357E9900F2728B /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D954C0B72B51C1CA00C6BAAC;
|
||||
remoteInfo = NymMixnetTunnel;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
D954C0B32B51C17200C6BAAC /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
D954C0C12B51C1CA00C6BAAC /* NymMixnetTunnel.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D9264E252B3DA062004594A8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NymMixnetTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||
D954C0BD2B51C1CA00C6BAAC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D954C0BE2B51C1CA00C6BAAC /* NymMixnetTunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NymMixnetTunnel.entitlements; sourceTree = "<group>"; };
|
||||
D99A14762B357E9900F2728B /* NymVPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NymVPN.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D99A14792B357E9900F2728B /* NymVPNApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NymVPNApp.swift; sourceTree = "<group>"; };
|
||||
D9F699E32B4C47DE005576A9 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D9F699E62B4C89F1005576A9 /* NymVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NymVPN.entitlements; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
D954C0B52B51C1CA00C6BAAC /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D954C0B92B51C1CA00C6BAAC /* NetworkExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D99A14732B357E9900F2728B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D968BDCB2B6BC98E003F42E5 /* AppVersionProvider in Frameworks */,
|
||||
D96740232B56A67A001F6891 /* Tunnels in Frameworks */,
|
||||
D9F699E02B4C1862005576A9 /* Settings in Frameworks */,
|
||||
D968BDC92B6AEA9D003F42E5 /* AppSettings in Frameworks */,
|
||||
D99A148C2B35A74200F2728B /* Theme in Frameworks */,
|
||||
D99A148A2B35A73F00F2728B /* Home in Frameworks */,
|
||||
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
D954C0BA2B51C1CA00C6BAAC /* NymMixnetTunnel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */,
|
||||
D954C0BD2B51C1CA00C6BAAC /* Info.plist */,
|
||||
D954C0BE2B51C1CA00C6BAAC /* NymMixnetTunnel.entitlements */,
|
||||
);
|
||||
path = NymMixnetTunnel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D967402C2B56CA1A001F6891 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D9F699E32B4C47DE005576A9 /* Bridging-Header.h */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D99A146D2B357E9900F2728B = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D99A14782B357E9900F2728B /* NymVPN */,
|
||||
D954C0BA2B51C1CA00C6BAAC /* NymMixnetTunnel */,
|
||||
D99A14772B357E9900F2728B /* Products */,
|
||||
D99A14882B35A73F00F2728B /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D99A14772B357E9900F2728B /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D99A14762B357E9900F2728B /* NymVPN.app */,
|
||||
D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D99A14782B357E9900F2728B /* NymVPN */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D9F699E62B4C89F1005576A9 /* NymVPN.entitlements */,
|
||||
D99A14792B357E9900F2728B /* NymVPNApp.swift */,
|
||||
D9264E252B3DA062004594A8 /* Assets.xcassets */,
|
||||
D967402C2B56CA1A001F6891 /* Resources */,
|
||||
);
|
||||
path = NymVPN;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D99A14882B35A73F00F2728B /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D954C0C22B51C1CA00C6BAAC /* Build configuration list for PBXNativeTarget "NymMixnetTunnel" */;
|
||||
buildPhases = (
|
||||
D954C0B42B51C1CA00C6BAAC /* Sources */,
|
||||
D954C0B52B51C1CA00C6BAAC /* Frameworks */,
|
||||
D954C0B62B51C1CA00C6BAAC /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = NymMixnetTunnel;
|
||||
productName = NymMixnetTunnel;
|
||||
productReference = D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
D99A14752B357E9900F2728B /* NymVPN */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D99A14842B357E9A00F2728B /* Build configuration list for PBXNativeTarget "NymVPN" */;
|
||||
buildPhases = (
|
||||
D99A14722B357E9900F2728B /* Sources */,
|
||||
D99A14732B357E9900F2728B /* Frameworks */,
|
||||
D99A14742B357E9900F2728B /* Resources */,
|
||||
D99A14872B359C8800F2728B /* Run Swiftlint */,
|
||||
D954C0B32B51C17200C6BAAC /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
D954C0C02B51C1CA00C6BAAC /* PBXTargetDependency */,
|
||||
);
|
||||
name = NymVPN;
|
||||
packageProductDependencies = (
|
||||
D99A14892B35A73F00F2728B /* Home */,
|
||||
D99A148B2B35A74200F2728B /* Theme */,
|
||||
D9F699DF2B4C1862005576A9 /* Settings */,
|
||||
D96740222B56A67A001F6891 /* Tunnels */,
|
||||
D968BDC82B6AEA9D003F42E5 /* AppSettings */,
|
||||
D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */,
|
||||
D9CD835C2B6C32D4008DA864 /* Modifiers */,
|
||||
);
|
||||
productName = NymVPN;
|
||||
productReference = D99A14762B357E9900F2728B /* NymVPN.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
D99A146E2B357E9900F2728B /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1520;
|
||||
LastUpgradeCheck = 1510;
|
||||
TargetAttributes = {
|
||||
D954C0B72B51C1CA00C6BAAC = {
|
||||
CreatedOnToolsVersion = 15.2;
|
||||
};
|
||||
D99A14752B357E9900F2728B = {
|
||||
CreatedOnToolsVersion = 15.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D99A14712B357E9900F2728B /* Build configuration list for PBXProject "NymVPN" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = D99A146D2B357E9900F2728B;
|
||||
productRefGroup = D99A14772B357E9900F2728B /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
D99A14752B357E9900F2728B /* NymVPN */,
|
||||
D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D954C0B62B51C1CA00C6BAAC /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D99A14742B357E9900F2728B /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D9264E262B3DA062004594A8 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
D99A14872B359C8800F2728B /* Run Swiftlint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Swiftlint";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
D954C0B42B51C1CA00C6BAAC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D954C0BC2B51C1CA00C6BAAC /* PacketTunnelProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D99A14722B357E9900F2728B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D99A147A2B357E9900F2728B /* NymVPNApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
D954C0C02B51C1CA00C6BAAC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */;
|
||||
targetProxy = D954C0BF2B51C1CA00C6BAAC /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
D954C0C32B51C1CA00C6BAAC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = VW5DZLFHM5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = NymMixnetTunnel/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NymMixnetTunnel;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "net.nymtech.vpn.network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D954C0C42B51C1CA00C6BAAC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = VW5DZLFHM5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = NymMixnetTunnel/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NymMixnetTunnel;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "net.nymtech.vpn.network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D99A14822B357E9A00F2728B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D99A14832B357E9A00F2728B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D99A14852B357E9A00F2728B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = VW5DZLFHM5;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NymVPN;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.nymtech.vpn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/NymVPN/Resources/Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D99A14862B357E9A00F2728B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = VW5DZLFHM5;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NymVPN;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.nymtech.vpn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/NymVPN/Resources/Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
D954C0C22B51C1CA00C6BAAC /* Build configuration list for PBXNativeTarget "NymMixnetTunnel" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D954C0C32B51C1CA00C6BAAC /* Debug */,
|
||||
D954C0C42B51C1CA00C6BAAC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D99A14712B357E9900F2728B /* Build configuration list for PBXProject "NymVPN" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D99A14822B357E9A00F2728B /* Debug */,
|
||||
D99A14832B357E9A00F2728B /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D99A14842B357E9A00F2728B /* Build configuration list for PBXNativeTarget "NymVPN" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D99A14852B357E9A00F2728B /* Debug */,
|
||||
D99A14862B357E9A00F2728B /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D96740222B56A67A001F6891 /* Tunnels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Tunnels;
|
||||
};
|
||||
D968BDC82B6AEA9D003F42E5 /* AppSettings */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppSettings;
|
||||
};
|
||||
D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = AppVersionProvider;
|
||||
};
|
||||
D99A14892B35A73F00F2728B /* Home */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Home;
|
||||
};
|
||||
D99A148B2B35A74200F2728B /* Theme */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Theme;
|
||||
};
|
||||
D9CD835C2B6C32D4008DA864 /* Modifiers */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Modifiers;
|
||||
};
|
||||
D9F699DF2B4C1862005576A9 /* Settings */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Settings;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = D99A146E2B357E9900F2728B /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:NymVPN.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Home">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Services">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Settings">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Theme">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:UIComponents">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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/>
|
||||
</plist>
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ch.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ch" viewBox="0 0 512 512">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="red" d="M0 0h512v512H0z"/>
|
||||
<g fill="#fff">
|
||||
<path d="M96 208h320v96H96z"/>
|
||||
<path d="M208 96h96v320h-96z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 288 B |
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "de.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 512 512">
|
||||
<path fill="#ffce00" d="M0 341.3h512V512H0z"/>
|
||||
<path fill="#000001" d="M0 0h512v170.7H0z"/>
|
||||
<path fill="red" d="M0 170.7h512v170.6H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,29 @@
|
||||
import SwiftUI
|
||||
import Home
|
||||
import Theme
|
||||
import AppSettings
|
||||
import Tunnels
|
||||
|
||||
@main
|
||||
struct NymVPNApp: App {
|
||||
|
||||
init() {
|
||||
setup()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
NavigationStack {
|
||||
HomeView(viewModel: HomeViewModel(selectedNetwork: .mixnet))
|
||||
}
|
||||
.environmentObject(AppSettings.shared)
|
||||
.environmentObject(TunnelsManager.shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension NymVPNApp {
|
||||
func setup() {
|
||||
ThemeConfiguration.setup()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef Bridging_Header_h
|
||||
#define Bridging_Header_h
|
||||
|
||||
//#import "libwg.h"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
@@ -0,0 +1,54 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Services",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "AppSettings",
|
||||
targets: ["AppSettings"]
|
||||
),
|
||||
.library(
|
||||
name: "AppVersionProvider",
|
||||
targets: ["AppVersionProvider"]
|
||||
),
|
||||
.library(
|
||||
name: "Modifiers",
|
||||
targets: ["Modifiers"]
|
||||
),
|
||||
.library(
|
||||
name: "Tunnels",
|
||||
targets: ["Tunnels"]
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "AppSettings",
|
||||
dependencies: [],
|
||||
path: "Sources/Services/AppSettings"
|
||||
),
|
||||
.target(
|
||||
name: "AppVersionProvider",
|
||||
dependencies: [],
|
||||
path: "Sources/Services/AppVersionProvider"
|
||||
),
|
||||
.target(
|
||||
name: "Modifiers",
|
||||
dependencies: [
|
||||
"AppSettings"
|
||||
],
|
||||
path: "Sources/Services/Modifiers"
|
||||
),
|
||||
.target(
|
||||
name: "Tunnels",
|
||||
dependencies: [],
|
||||
path: "Sources/Services/Tunnels"
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
public final class AppSettings: ObservableObject {
|
||||
public static let shared = AppSettings()
|
||||
|
||||
@AppStorage("currentAppearance") public var currentTheme: AppSetting.Appearance = .automatic
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct AppSetting {
|
||||
public enum Appearance: Int, CaseIterable {
|
||||
case automatic
|
||||
case light
|
||||
case dark
|
||||
|
||||
public var colorScheme: ColorScheme? {
|
||||
switch self {
|
||||
case .light:
|
||||
return .light
|
||||
case .dark:
|
||||
return .dark
|
||||
case .automatic:
|
||||
return ColorScheme(.unspecified)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public enum AppVersionProvider {
|
||||
public static func appVersion(in bundle: Bundle = .main) -> String {
|
||||
guard let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
else {
|
||||
fatalError("Missing CFBundleShortVersionString")
|
||||
}
|
||||
return version
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
|
||||
public struct AppearanceUpdate: ViewModifier {
|
||||
@EnvironmentObject private var appSettings: AppSettings
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.preferredColorScheme(appSettings.currentTheme.colorScheme)
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func appearanceUpdate() -> some View {
|
||||
modifier(AppearanceUpdate())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import NetworkExtension
|
||||
|
||||
public final class Tunnel: ObservableObject {
|
||||
public var tunnel: NETunnelProviderManager
|
||||
@Published public var isEnabled: Bool
|
||||
|
||||
private var observers = [AnyObject]()
|
||||
|
||||
public init(tunnel: NETunnelProviderManager) {
|
||||
self.tunnel = tunnel
|
||||
isEnabled = tunnel.isEnabled
|
||||
|
||||
let configurationChangeNotification = NotificationCenter.default.addObserver(
|
||||
forName: .NEVPNConfigurationChange,
|
||||
object: tunnel,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
print("Tunnel isEnabled: \(tunnel.isEnabled)")
|
||||
self?.isEnabled = tunnel.isEnabled
|
||||
}
|
||||
observers.append(configurationChangeNotification)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import NetworkExtension
|
||||
import OSLog
|
||||
|
||||
public final class TunnelsManager: ObservableObject {
|
||||
public static let shared = TunnelsManager()
|
||||
|
||||
public var currentTunnel: Tunnel?
|
||||
|
||||
private var observers = [AnyObject]()
|
||||
|
||||
private init() {
|
||||
setup()
|
||||
}
|
||||
|
||||
public func loadConfigurations() {
|
||||
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
|
||||
print("1. Loading VPN Configurations")
|
||||
if let error {
|
||||
print("Error: \(String(describing: error))")
|
||||
}
|
||||
|
||||
managers?.forEach {
|
||||
print("Found VPN Configuration")
|
||||
print("\($0)")
|
||||
self?.currentTunnel = Tunnel(tunnel: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func test() {
|
||||
loadConfigurations()
|
||||
os_log("2 Starting test")
|
||||
if currentTunnel == nil {
|
||||
let manager = createTestManager()
|
||||
manager.saveToPreferences { error in
|
||||
if error == nil {
|
||||
print("3 Added config Successfully")
|
||||
} else {
|
||||
print("3 Failure to add config")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("3 Current tunnel already exists")
|
||||
currentTunnel?.tunnel.isEnabled = true
|
||||
}
|
||||
|
||||
print("4 Connecting")
|
||||
connect()
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
do {
|
||||
let options = [
|
||||
NEVPNConnectionStartOptionUsername: "john",
|
||||
NEVPNConnectionStartOptionPassword: "password"
|
||||
] as [String: NSObject]
|
||||
|
||||
try currentTunnel?.tunnel.connection.startVPNTunnel(options: options)
|
||||
} catch {
|
||||
print("FAILED to connect: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
print("5 Disconnecting")
|
||||
// currentTunnel?.tunnel.connection.stopVPNTunnel()
|
||||
|
||||
NEVPNManager.shared().loadFromPreferences { [weak self] error in
|
||||
if let error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
self?.currentTunnel?.tunnel.connection.stopVPNTunnel()
|
||||
NEVPNManager.shared().connection.stopVPNTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension TunnelsManager {
|
||||
func createTestManager() -> NETunnelProviderManager {
|
||||
let manager = NETunnelProviderManager()
|
||||
manager.localizedDescription = "NymVPN Mixnet"
|
||||
|
||||
let tunnelConfiguration = NETunnelProviderProtocol()
|
||||
tunnelConfiguration.providerBundleIdentifier = "net.nymtech.vpn.network-extension"
|
||||
tunnelConfiguration.serverAddress = "127.0.0.1:4009"
|
||||
tunnelConfiguration.providerConfiguration = [:]
|
||||
|
||||
manager.protocolConfiguration = tunnelConfiguration
|
||||
manager.isEnabled = true
|
||||
return manager
|
||||
}
|
||||
}
|
||||
|
||||
private extension TunnelsManager {
|
||||
func setup() {
|
||||
registerNotifications()
|
||||
}
|
||||
|
||||
func registerNotifications() {
|
||||
let statusDidChangeNotification = NotificationCenter.default.addObserver(
|
||||
forName: .NEVPNStatusDidChange,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { status in
|
||||
print("VPN Status: \(status)")
|
||||
}
|
||||
observers.append(statusDidChangeNotification)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import XCTest
|
||||
@testable import Services
|
||||
|
||||
final class TunnelsTests: XCTestCase {}
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
@@ -0,0 +1,33 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Settings",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Settings",
|
||||
targets: ["Settings"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../UIComponents")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Settings",
|
||||
dependencies: [
|
||||
"UIComponents"
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "SettingsTests",
|
||||
dependencies: ["Settings"]
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
|
||||
struct SettingsFlowCoordinator<Content: View>: View {
|
||||
@ObservedObject var state: SettingsFlowState
|
||||
let content: () -> Content
|
||||
|
||||
var body: some View {
|
||||
content()
|
||||
.navigationDestination(for: SettingsLink.self, destination: linkDestination)
|
||||
}
|
||||
|
||||
@ViewBuilder private func linkDestination(link: SettingsLink) -> some View {
|
||||
switch link {
|
||||
case .theme:
|
||||
AppearanceView(viewModel: AppearanceViewModel(path: $state.path, appSettings: AppSettings.shared))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import SwiftUI
|
||||
|
||||
public class SettingsFlowState: ObservableObject {
|
||||
@Published var presentedItem: SettingsLink?
|
||||
|
||||
@Binding var path: NavigationPath
|
||||
|
||||
public init(path: Binding<NavigationPath>) {
|
||||
_path = path
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
enum SettingsLink: Hashable, Identifiable {
|
||||
case theme
|
||||
|
||||
var id: String {
|
||||
String(describing: self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import UIComponents
|
||||
import Theme
|
||||
|
||||
public struct SettingsView: View {
|
||||
@StateObject private var viewModel: SettingsViewModel
|
||||
|
||||
public init(viewModel: SettingsViewModel) {
|
||||
_viewModel = StateObject(wrappedValue: viewModel)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
SettingsFlowCoordinator(state: viewModel, content: content)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsView {
|
||||
@ViewBuilder
|
||||
func content() -> some View {
|
||||
VStack {
|
||||
navbar()
|
||||
settingsList()
|
||||
Spacer()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(edges: [.bottom])
|
||||
.background {
|
||||
NymColor.background
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: viewModel.settingsTitle,
|
||||
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateHome() })
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func settingsList() -> some View {
|
||||
SettingsList(
|
||||
viewModel:
|
||||
SettingsListViewModel(
|
||||
sections: viewModel.sections,
|
||||
appVersion: viewModel.appVersion()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import Foundation
|
||||
import AppVersionProvider
|
||||
import UIComponents
|
||||
|
||||
public class SettingsViewModel: SettingsFlowState {
|
||||
let settingsTitle = "settings".localizedString
|
||||
|
||||
var sections: [SettingsSection] {
|
||||
[
|
||||
connectionSection(),
|
||||
themeSection(),
|
||||
logsSection(),
|
||||
feedbackSection(),
|
||||
legalSection()
|
||||
]
|
||||
}
|
||||
|
||||
func navigateHome() {
|
||||
path = .init()
|
||||
}
|
||||
|
||||
func appVersion() -> String {
|
||||
AppVersionProvider.appVersion()
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewModel {
|
||||
func navigateToTheme() {
|
||||
path.append(SettingsLink.theme)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewModel {
|
||||
func connectionSection() -> SettingsSection {
|
||||
.connection(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "autoConnectTitle".localizedString,
|
||||
subtitle: "autoConnectSubtitle".localizedString,
|
||||
imageName: "autoConnect",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .toggle,
|
||||
title: "entryLocationTitle".localizedString,
|
||||
subtitle: "entryLocationSubtitle".localizedString,
|
||||
imageName: "entryHop",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func themeSection() -> SettingsSection {
|
||||
.theme(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "displayTheme".localizedString,
|
||||
imageName: "displayTheme",
|
||||
action: { [weak self] in
|
||||
self?.navigateToTheme()
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func logsSection() -> SettingsSection {
|
||||
.logs(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "logs".localizedString,
|
||||
imageName: "logs",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func feedbackSection() -> SettingsSection {
|
||||
.feedback(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "feedback".localizedString,
|
||||
imageName: "feedback",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "support".localizedString,
|
||||
imageName: "support",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func legalSection() -> SettingsSection {
|
||||
.legal(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "legal".localizedString,
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct AppearanceView: View {
|
||||
private let viewModel: AppearanceViewModel
|
||||
|
||||
public init(viewModel: AppearanceViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
navbar()
|
||||
themeOptions()
|
||||
Spacer()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(edges: [.bottom])
|
||||
.background {
|
||||
NymColor.background
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppearanceView {
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: viewModel.title,
|
||||
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateBack() })
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func themeOptions() -> some View {
|
||||
ForEach(viewModel.themes, id: \.self) { theme in
|
||||
SettingButton(
|
||||
viewModel:
|
||||
SettingButtonViewModel(
|
||||
title: viewModel.themeTitle(for: theme),
|
||||
subtitle: viewModel.themeSubtitle(for: theme),
|
||||
isSelected: viewModel.isSelected(for: theme)
|
||||
)
|
||||
)
|
||||
.onTapGesture {
|
||||
viewModel.setCurrentTheme(with: theme)
|
||||
}
|
||||
.padding(EdgeInsets(top: 24, leading: 16, bottom: 0, trailing: 16))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Theme
|
||||
|
||||
public struct AppearanceViewModel {
|
||||
private let appSettings: AppSettings
|
||||
|
||||
let title = "displayTheme".localizedString
|
||||
|
||||
@Binding var path: NavigationPath
|
||||
|
||||
var themes: [AppSetting.Appearance] {
|
||||
AppSetting.Appearance.allCases
|
||||
}
|
||||
|
||||
var currentTheme: AppSetting.Appearance {
|
||||
appSettings.currentTheme
|
||||
}
|
||||
|
||||
public init(path: Binding<NavigationPath>, appSettings: AppSettings) {
|
||||
_path = path
|
||||
self.appSettings = appSettings
|
||||
}
|
||||
|
||||
func setCurrentTheme(with theme: AppSetting.Appearance) {
|
||||
appSettings.currentTheme = theme
|
||||
}
|
||||
}
|
||||
|
||||
extension AppearanceViewModel {
|
||||
func themeTitle(for theme: AppSetting.Appearance) -> String {
|
||||
switch theme {
|
||||
case .light:
|
||||
return "lightThemeTitle".localizedString
|
||||
case .dark:
|
||||
return "darkThemeTitle".localizedString
|
||||
case .automatic:
|
||||
return "automaticThemeTitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
func themeSubtitle(for theme: AppSetting.Appearance) -> String? {
|
||||
switch theme {
|
||||
case .light, .dark:
|
||||
return nil
|
||||
case .automatic:
|
||||
return "automaticThemeSubtitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
func isSelected(for theme: AppSetting.Appearance) -> Bool {
|
||||
appSettings.currentTheme == theme
|
||||
}
|
||||
}
|
||||
|
||||
extension AppearanceViewModel {
|
||||
func navigateBack() {
|
||||
path.removeLast()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import XCTest
|
||||
@testable import Settings
|
||||
|
||||
final class SettingsTests: XCTestCase {}
|
||||
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,35 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Theme",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.iOS(.v16)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Theme",
|
||||
targets: ["Theme"]
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Theme",
|
||||
resources: [
|
||||
.copy("Resources/Fonts/Lato-Bold.ttf"),
|
||||
.copy("Resources/Fonts/Lato-Regular.ttf"),
|
||||
.copy("Resources/Fonts/Lato-SemiBold.ttf"),
|
||||
.copy("Resources/Fonts/Lato-Medium.ttf"),
|
||||
.process("Resources/Colors.xcassets"),
|
||||
.process("Resources/Localizable.xcstrings")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "ThemeTests",
|
||||
dependencies: ["Theme"]
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct NymColor {
|
||||
// MARK: - Navigation bar -
|
||||
public static let navigationBarBackground = Color(.navigationBarBackground)
|
||||
public static let navigationBarSettingsGear = Color(.navigationBarSettingsGear)
|
||||
|
||||
// MARK: - Status button -
|
||||
public static let statusButtonBackground = Color(.statusButtonBackground)
|
||||
public static let sysOnSurfaceWhite = Color(.sysOnSurfaceWhite)
|
||||
public static let statusInfoText = Color(.statusInfoText)
|
||||
public static let statusTimer = Color(.statusTimer)
|
||||
|
||||
public static let countrySelectionSelectedBackground = Color(.countrySelectionSelectedBackground)
|
||||
|
||||
// MARK: - Network button -
|
||||
public static let networkButtonCircle = Color(.networkButtonCircle)
|
||||
|
||||
// MARK: - Connect -
|
||||
public static let connectTitle = Color(.connectTitle)
|
||||
|
||||
// MARK: - Background
|
||||
public static let background = Color(.background)
|
||||
|
||||
// MARK: - Sys -
|
||||
public static let sysOnSecondary = Color(.sysOnSecondary)
|
||||
public static let sysOnSurface = Color(.sysOnSurface)
|
||||
public static let sysOutline = Color(.sysOutline)
|
||||
|
||||
// MARK: - Key -
|
||||
public static let confirm = Color(.confirm)
|
||||
public static let statusGreen = Color(.statusGreen)
|
||||
|
||||
// MARK: - Primary -
|
||||
public static let primaryOrange = Color(.primaryOrange)
|
||||
|
||||
// MARK: - Settings -
|
||||
public static let settingsSeparator = Color(.settingsSeparator)
|
||||
public static let settingsVersion = Color(.settingsVersion)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension Bundle {
|
||||
func localizedString(forKey key: String) -> String {
|
||||
localizedString(forKey: key, value: nil, table: nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
init(_ name: String) {
|
||||
guard let namedColor = UIColor(named: name, in: Bundle.module, compatibleWith: nil)
|
||||
else {
|
||||
fatalError("Could not load color from Theme module")
|
||||
}
|
||||
self.init(namedColor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public extension String {
|
||||
var localizedString: String {
|
||||
Bundle.module.localizedString(forKey: self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import SwiftUI
|
||||
|
||||
public enum NymFont {
|
||||
case lato(size: CGFloat, weight: LatoWeight)
|
||||
|
||||
public var font: Font {
|
||||
switch self {
|
||||
case let .lato(size, weight):
|
||||
Font.custom("Lato-\(weight.rawValue)", size: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Weights -
|
||||
|
||||
extension NymFont {
|
||||
public enum LatoWeight: String, CaseIterable {
|
||||
case regular = "Regular"
|
||||
case bold = "Bold"
|
||||
case semibold = "SemiBold"
|
||||
case medium = "Medium"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Register fonts -
|
||||
|
||||
extension NymFont {
|
||||
public static func register() {
|
||||
for latoWeight in NymFont.LatoWeight.allCases {
|
||||
let fontName = "Lato-\(latoWeight.rawValue)"
|
||||
guard let fontURL = Bundle.module.url(forResource: fontName, withExtension: "ttf") else { continue }
|
||||
CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct NymTextStyle {
|
||||
let nymFont: NymFont
|
||||
let lineSpacing: CGFloat
|
||||
let kerning: CGFloat
|
||||
|
||||
init(nymFont: NymFont, lineSpacing: CGFloat = 0, kerning: CGFloat = 0) {
|
||||
self.nymFont = nymFont
|
||||
self.lineSpacing = lineSpacing
|
||||
self.kerning = kerning
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Styles -
|
||||
extension NymTextStyle {
|
||||
// MARK: - Title -
|
||||
public struct Title {
|
||||
public struct Large {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 22, weight: .regular))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Medium {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Label -
|
||||
public struct Label {
|
||||
public struct Huge {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 18, weight: .bold))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Large {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 14, weight: .bold), kerning: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Small {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 11, weight: .medium), kerning: 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Body -
|
||||
public struct Body {
|
||||
public struct Large {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Medium {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 14, weight: .regular), kerning: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Small {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 12, weight: .regular), kerning: 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct NymTextStyleModifier: ViewModifier {
|
||||
public let textStyle: NymTextStyle
|
||||
|
||||
public init(textStyle: NymTextStyle) {
|
||||
self.textStyle = textStyle
|
||||
}
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.font(textStyle.nymFont.font)
|
||||
.kerning(textStyle.kerning)
|
||||
.lineSpacing(textStyle.lineSpacing)
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func textStyle(_ textStyle: NymTextStyle) -> some View {
|
||||
modifier(NymTextStyleModifier(textStyle: textStyle))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "97",
|
||||
"green" : "199",
|
||||
"red" : "43"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.100",
|
||||
"blue" : "93",
|
||||
"green" : "196",
|
||||
"red" : "71"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.080",
|
||||
"blue" : "0x71",
|
||||
"green" : "0x5B",
|
||||
"red" : "0x61"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.160",
|
||||
"blue" : "0x33",
|
||||
"green" : "0x30",
|
||||
"red" : "0x30"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "113",
|
||||
"green" : "101",
|
||||
"red" : "105"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "153",
|
||||
"green" : "143",
|
||||
"red" : "147"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "28",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "250",
|
||||
"red" : "254"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.639",
|
||||
"green" : "0.639",
|
||||
"red" : "0.639"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.349",
|
||||
"green" : "0.329",
|
||||
"red" : "0.337"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "27",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "229",
|
||||
"green" : "225",
|
||||
"red" : "230"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "27",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "126",
|
||||
"green" : "116",
|
||||
"red" : "121"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "153",
|
||||
"green" : "143",
|
||||
"red" : "147"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.965",
|
||||
"green" : "0.957",
|
||||
"red" : "0.949"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "27",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "27",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.120",
|
||||
"blue" : "0x4F",
|
||||
"green" : "0x45",
|
||||
"red" : "0x49"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.160",
|
||||
"blue" : "0xD0",
|
||||
"green" : "0xC4",
|
||||
"red" : "0xCA"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.192",
|
||||
"green" : "0.157",
|
||||
"red" : "0.165"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.122",
|
||||
"green" : "0.106",
|
||||
"red" : "0.110"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.812",
|
||||
"green" : "0.769",
|
||||
"red" : "0.788"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "126",
|
||||
"green" : "116",
|
||||
"red" : "121"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "208",
|
||||
"green" : "196",
|
||||
"red" : "202"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "78",
|
||||
"green" : "110",
|
||||
"red" : "251"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "229",
|
||||
"green" : "225",
|
||||
"red" : "230"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "79",
|
||||
"green" : "69",
|
||||
"red" : "73"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "126",
|
||||
"green" : "116",
|
||||
"red" : "121"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "113",
|
||||
"green" : "91",
|
||||
"red" : "98"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,391 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"2hopWireGuardSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Best for browsing, streaming, sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"2hopWireGuardTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "2-hop WireGuard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5hopMixnetSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Best for payments, emails, messages"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5hopMixnetTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "5-hop mixnet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoConnectSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Auto connect at app startup"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoConnectTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Auto-connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"automaticThemeSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Use device theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"automaticThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatic"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connect" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connecting" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connecting..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connectTo" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connect to"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"darkThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dark theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"disconnected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Disconnected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"disconnecting" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Disconnecting..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayTheme" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Display theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entryLocationSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Manually select your entry hop"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entryLocationTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Entry location selector"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fastest" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fastest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"feedback" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Feedback"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"firstHop" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "First hop"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"firstHopSelection" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "First hop selection"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastHop" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Last hop"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastHopSelection" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Last hop selection"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"legal" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Legal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lightThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Light theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logs" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Logs"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"NymVPN" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "NymVPN"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"search" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchCountry" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Search country"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectNetwork" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select network"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"support" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Support"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
public struct ThemeConfiguration {
|
||||
public static func setup() {
|
||||
NymFont.register()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user