rename: Jarvis -> Jervis target, scheme, and folder throughout
Some checks are pending
CI / Build · Debug (push) Waiting to run
CI / Build · Release (push) Waiting to run

The bundle-ID and display-name renames weren't enough — the actual
Xcode target/product name was still "Jarvis", which drives CFBundleName,
Xcode's Organizer archive list, the .xcodeproj filename, and the scheme
name. Renamed all the way through:

- project.yml: top-level name, target key, PRODUCT_NAME, source/info paths
- Jarvis/ -> Jervis/ (source folder, git-tracked as renames, no content
  diffs on the moved files)
- .gitignore, CI workflows, README, CONTRIBUTING: Jarvis.xcodeproj /
  scheme Jarvis -> Jervis.xcodeproj / scheme Jervis

Verified: xcodebuild -scheme Jervis succeeds, produces Jervis.app,
CFBundleName/CFBundleExecutable/CFBundleDisplayName all read "Jervis".
CFBundleIdentifier intentionally stays com.kisani.jarvis (per prior
decision — bundle ID isn't user-facing anywhere including Organizer).

Swift type names (JarvisApp, JarvisWordmark, etc.) and the Gitea repo
name/URL are unchanged — pure internal source identifiers, not user or
developer-facing product identity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kutesir
2026-07-13 03:49:31 +03:00
parent 3a6f1bbdd8
commit bd632a1592
46 changed files with 20 additions and 19 deletions

View File

@@ -0,0 +1,321 @@
// ConnectivityView.swift
// Jarvis server address, a Remote Access card (Use <provider> when remote +
// host + live status), and an Auto-connect automation card. The app can't start
// the VPN tunnel (iOS), so when the remote is down we offer a one-tap jump.
import SwiftUI
struct ConnectivityView: View {
@EnvironmentObject var connectivity: ConnectivitySettings
@EnvironmentObject var manager: ConnectivityManager
@Environment(\.dismiss) private var dismiss
private var directReach: Reachability {
manager.status[connectivity.directHost.trimmingCharacters(in: .whitespaces)] ?? .unknown
}
private var remoteReach: Reachability {
manager.status[connectivity.remoteHost.trimmingCharacters(in: .whitespaces)] ?? .unknown
}
private var providerName: String { connectivity.provider.displayName }
var body: some View {
ZStack {
Palette.background.ignoresSafeArea()
ScrollView {
VStack(alignment: .leading, spacing: 26) {
header
serverSection
remoteAccessSection
automationSection
providerSection
actions
}
.padding(.horizontal, 20)
.padding(.vertical, 12)
}
}
.presentationDragIndicator(.visible)
.task { await recheck() }
}
// MARK: - Header
private var header: some View {
HStack {
Text("Connectivity")
.font(.system(size: 22, weight: .heavy))
.foregroundStyle(Palette.primaryText)
Spacer()
Button { dismiss() } label: {
Image(systemName: "xmark")
.font(.system(size: 15, weight: .bold))
.foregroundStyle(Color(hex: "888888"))
.frame(width: 44, height: 44)
}
}
.padding(.top, 8)
}
// MARK: - Server (LAN)
private var serverSection: some View {
VStack(alignment: .leading, spacing: 10) {
sectionLabel("SERVER")
Card {
cardRow(icon: "server.rack") {
fieldStack(title: "Direct (LAN) host",
text: $connectivity.directHost,
placeholder: "192.168.30.50:8080",
reach: directReach)
}
}
}
}
// MARK: - Remote access
private var remoteAccessSection: some View {
VStack(alignment: .leading, spacing: 10) {
sectionLabel("REMOTE ACCESS")
Card {
VStack(spacing: 0) {
cardRow(icon: "globe.badge.chevron.backward") {
Toggle(isOn: $connectivity.remoteEnabled) {
VStack(alignment: .leading, spacing: 3) {
Text("Use \(providerName) when remote")
.font(.system(size: 16, weight: .heavy))
.foregroundStyle(Palette.primaryText)
Text("Connects via VPN when not on home network")
.font(.system(size: 12))
.foregroundStyle(Color(hex: "888888"))
.fixedSize(horizontal: false, vertical: true)
}
}
.tint(Palette.orange)
}
if connectivity.remoteEnabled {
divider
cardRow(icon: "externaldrive.connected.to.line.below") {
fieldStack(title: "\(providerName) host",
text: $connectivity.remoteHost,
placeholder: connectivity.provider.remoteHint,
reach: remoteReach)
}
divider
statusRow
}
}
}
}
}
private var statusRow: some View {
HStack(spacing: 10) {
dot(for: connectivity.remoteEnabled ? remoteReach : .unknown)
Text(statusText)
.font(.system(size: 14, weight: .bold))
.foregroundStyle(statusColor)
Spacer()
if remoteReach != .reachable && connectivity.provider.canOpenApp {
Button { connectivity.provider.openApp() } label: {
Text("Open \(providerName)")
.font(.system(size: 13, weight: .bold))
.foregroundStyle(.black)
.padding(.horizontal, 14)
.frame(height: 36)
.background(Palette.orange)
.clipShape(Capsule())
}
}
}
.padding(.vertical, 12)
.padding(.horizontal, 14)
}
private var statusText: String {
switch remoteReach {
case .reachable: return "\(providerName) connected"
case .checking: return "Checking \(providerName)"
default: return "\(providerName) not active"
}
}
private var statusColor: Color {
switch remoteReach {
case .reachable: return Color(hex: "33C25E")
case .checking: return Palette.healthFailing
default: return Color(hex: "888888")
}
}
// MARK: - Automation
private var automationSection: some View {
VStack(alignment: .leading, spacing: 10) {
sectionLabel("AUTOMATION")
Card {
VStack(spacing: 0) {
cardRow(icon: "wifi") {
Toggle(isOn: $connectivity.autoConnect) {
VStack(alignment: .leading, spacing: 3) {
Text("Auto-connect on network change")
.font(.system(size: 16, weight: .heavy))
.foregroundStyle(Palette.primaryText)
Text("Prefers the LAN on a trusted network; switches to \(providerName) when away")
.font(.system(size: 12))
.foregroundStyle(Color(hex: "888888"))
.fixedSize(horizontal: false, vertical: true)
}
}
.tint(Palette.orange)
}
if connectivity.autoConnect {
divider
cardRow(icon: "timer") {
HStack {
Text("Wait before switching")
.font(.system(size: 15, weight: .bold))
.foregroundStyle(Palette.primaryText)
Spacer()
Stepper(value: $connectivity.switchDelaySeconds, in: 1...60, step: 1) {
Text("\(connectivity.switchDelaySeconds)s")
.font(.system(size: 14, weight: .bold, design: .monospaced))
.foregroundStyle(Palette.orange)
}
.labelsHidden()
.fixedSize()
Text("\(connectivity.switchDelaySeconds)s")
.font(.system(size: 14, weight: .bold, design: .monospaced))
.foregroundStyle(Palette.orange)
.frame(width: 36, alignment: .trailing)
}
}
}
}
}
Text("iOS can't start the \(providerName) tunnel for Jarvis — keep the \(providerName) app connected when away. Jarvis only picks the reachable address.")
.font(.system(size: 11))
.foregroundStyle(Palette.tertiaryText)
.lineSpacing(2)
}
}
// MARK: - Provider
private var providerSection: some View {
VStack(alignment: .leading, spacing: 10) {
sectionLabel("PROVIDER")
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(VPNProvider.allCases) { p in
let selected = connectivity.provider == p
Button { connectivity.provider = p } label: {
Text(p.displayName)
.font(.system(size: 13, weight: .bold))
.foregroundStyle(selected ? .black : Color(hex: "AAAAAA"))
.padding(.horizontal, 14)
.frame(height: 34)
.background(selected ? Palette.orange : Palette.surface)
.clipShape(Capsule())
}
}
}
}
}
}
// MARK: - Actions
private var actions: some View {
HStack(spacing: 12) {
Button { Task { await recheck() } } label: {
Text("Re-check")
.font(.system(size: 15, weight: .bold))
.foregroundStyle(Palette.primaryText)
.frame(maxWidth: .infinity).frame(height: 50)
.background(Palette.surface2)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
Button { Task { await manager.resolveAndActivate(); await recheck() } } label: {
Text("Apply")
.font(.system(size: 15, weight: .bold))
.foregroundStyle(.black)
.frame(maxWidth: .infinity).frame(height: 50)
.background(connectivity.isConfigured ? Palette.orange : Palette.surface2)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
.disabled(!connectivity.isConfigured)
}
}
// MARK: - Building blocks
private func sectionLabel(_ t: String) -> some View {
Text(t)
.font(.system(size: 11, weight: .bold, design: .monospaced))
.kerning(0.8)
.foregroundStyle(Palette.tertiaryText)
}
private func Card<Content: View>(@ViewBuilder _ content: () -> Content) -> some View {
content()
.background(Palette.surface)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
private func cardRow<Content: View>(icon: String, @ViewBuilder _ content: () -> Content) -> some View {
HStack(alignment: .center, spacing: 14) {
Image(systemName: icon)
.font(.system(size: 18, weight: .regular))
.foregroundStyle(Palette.secondaryText)
.frame(width: 26)
content()
}
.padding(.horizontal, 14)
.padding(.vertical, 14)
.frame(minHeight: 44)
}
private var divider: some View {
Rectangle().fill(Palette.surface2).frame(height: 1).padding(.leading, 54)
}
private func fieldStack(title: String, text: Binding<String>, placeholder: String, reach: Reachability) -> some View {
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.system(size: 12))
.foregroundStyle(Color(hex: "888888"))
HStack(spacing: 8) {
TextField("", text: text,
prompt: Text(placeholder).foregroundColor(Palette.mutedText))
.font(.system(size: 16, weight: .regular, design: .monospaced))
.foregroundStyle(Palette.orange)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.keyboardType(.URL)
if reach != .unknown { dot(for: reach) }
}
}
}
// MARK: - Reachability helpers
private func recheck() async {
await manager.check(connectivity.directHost)
if connectivity.remoteActiveInPolicy {
await manager.check(connectivity.remoteHost)
}
}
private func color(for r: Reachability) -> Color {
switch r {
case .reachable: return Color(hex: "33C25E")
case .unreachable: return Color(hex: "C25555")
case .checking: return Palette.healthFailing
case .unknown: return Palette.tertiaryText
}
}
private func dot(for r: Reachability) -> some View {
Circle().fill(color(for: r)).frame(width: 9, height: 9)
}
}