Add macOS app installer workflow
This commit is contained in:
parent
1f7e696f1c
commit
53fa92d222
12
FchatiApp/FchatiApp.entitlements
Normal file
12
FchatiApp/FchatiApp.entitlements
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?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.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
26
FchatiApp/Info.plist
Normal file
26
FchatiApp/Info.plist
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>FchatiApp</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>de.diyaa.fchati</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Fchati</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>14.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
18
FchatiApp/Package.swift
Normal file
18
FchatiApp/Package.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// swift-tools-version: 5.9
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "FchatiApp",
|
||||||
|
platforms: [.macOS(.v14)],
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "FchatiApp",
|
||||||
|
path: "Sources/FchatiApp"
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "FchatiAppTests",
|
||||||
|
dependencies: ["FchatiApp"],
|
||||||
|
path: "Tests/FchatiAppTests"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
95
FchatiApp/Sources/FchatiApp/FchatiApp.swift
Normal file
95
FchatiApp/Sources/FchatiApp/FchatiApp.swift
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct FchatiApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
MenuBarExtra("Fchati", systemImage: "message") {
|
||||||
|
RootView()
|
||||||
|
}
|
||||||
|
.menuBarExtraStyle(.window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppTab: String, CaseIterable, Identifiable {
|
||||||
|
case chat = "Chat"
|
||||||
|
case pairing = "Pairing"
|
||||||
|
case settings = "Settings"
|
||||||
|
|
||||||
|
var id: Self { self }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: String {
|
||||||
|
switch self {
|
||||||
|
case .chat:
|
||||||
|
"message"
|
||||||
|
case .pairing:
|
||||||
|
"link"
|
||||||
|
case .settings:
|
||||||
|
"gearshape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RootView: View {
|
||||||
|
@State private var selectedTab: AppTab = .chat
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Picker("Section", selection: $selectedTab) {
|
||||||
|
ForEach(AppTab.allCases) { tab in
|
||||||
|
Label(tab.title, systemImage: tab.icon)
|
||||||
|
.tag(tab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
.labelsHidden()
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Group {
|
||||||
|
switch selectedTab {
|
||||||
|
case .chat:
|
||||||
|
PlaceholderView(
|
||||||
|
icon: "message",
|
||||||
|
title: "No messages yet",
|
||||||
|
description: "Pair with someone to start chatting."
|
||||||
|
)
|
||||||
|
case .pairing:
|
||||||
|
PlaceholderView(
|
||||||
|
icon: "link",
|
||||||
|
title: "Pair a device",
|
||||||
|
description: "Create or join a pairing session."
|
||||||
|
)
|
||||||
|
case .settings:
|
||||||
|
PlaceholderView(
|
||||||
|
icon: "gearshape",
|
||||||
|
title: "Settings",
|
||||||
|
description: "Your preferences will appear here."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
.frame(width: 360)
|
||||||
|
.frame(minHeight: 480)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PlaceholderView: View {
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
let description: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ContentUnavailableView(
|
||||||
|
title,
|
||||||
|
systemImage: icon,
|
||||||
|
description: Text(description)
|
||||||
|
)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
11
FchatiApp/Tests/FchatiAppTests/AppTabTests.swift
Normal file
11
FchatiApp/Tests/FchatiAppTests/AppTabTests.swift
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import FchatiApp
|
||||||
|
|
||||||
|
final class AppTabTests: XCTestCase {
|
||||||
|
func testTabsMatchTheAppShell() {
|
||||||
|
XCTAssertEqual(AppTab.allCases, [.chat, .pairing, .settings])
|
||||||
|
XCTAssertEqual(AppTab.chat.icon, "message")
|
||||||
|
XCTAssertEqual(AppTab.pairing.icon, "link")
|
||||||
|
XCTAssertEqual(AppTab.settings.icon, "gearshape")
|
||||||
|
}
|
||||||
|
}
|
||||||
50
IMPLEMENTATION_STATUS.md
Normal file
50
IMPLEMENTATION_STATUS.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# حالة التنفيذ
|
||||||
|
|
||||||
|
## آخر تحديث
|
||||||
|
|
||||||
|
تم إنشاء الهيكل الأولي لتطبيق ماك، مع سكربت محلي لاختباره وبنائه وتثبيته وفتحه.
|
||||||
|
|
||||||
|
## ما تم إنجازه
|
||||||
|
|
||||||
|
- إنشاء حزمة سويفت لتطبيق ماك يدعم نظام ماك 14 فما فوق.
|
||||||
|
- إنشاء تطبيق شريط القوائم مع واجهة بعرض 360 نقطة وارتفاع أدنى 480 نقطة.
|
||||||
|
- إضافة تبويبات للدردشة والاقتران والإعدادات، مع حالات بداية مؤقتة لكل تبويب.
|
||||||
|
- إضافة صلاحيات الشبكة وصندوق الحماية للتطبيق.
|
||||||
|
- إضافة اختبار يتحقق من وجود التبويبات الثلاثة وأيقوناتها.
|
||||||
|
- إضافة ملف معلومات لتجميع الملف التنفيذي داخل حزمة تطبيق قابلة للتشغيل.
|
||||||
|
- إضافة سكربت للتنظيف والاختبار والبناء والتثبيت والفتح.
|
||||||
|
|
||||||
|
## السكربت المحلي
|
||||||
|
|
||||||
|
المسار:
|
||||||
|
|
||||||
|
`scripts/run-macos-app.sh`
|
||||||
|
|
||||||
|
ينفذ السكربت الخطوات التالية بالترتيب:
|
||||||
|
|
||||||
|
1. يحذف نواتج البناء الحالية الخاصة بتطبيق ماك فقط.
|
||||||
|
2. يشغّل الاختبارات.
|
||||||
|
3. يبني نسخة إصدار من التطبيق.
|
||||||
|
4. ينشئ حزمة تطبيق ماك مؤقتة ويوقعها محليًا.
|
||||||
|
5. يحذف النسخة الموجودة من التطبيق في مجلد التطبيقات، إن وجدت.
|
||||||
|
6. ينقل النسخة الجديدة إلى مجلد التطبيقات.
|
||||||
|
7. يفتح التطبيق الجديد.
|
||||||
|
|
||||||
|
مسار التثبيت الناتج:
|
||||||
|
|
||||||
|
`/Applications/Fchati.app`
|
||||||
|
|
||||||
|
## التحقق المنفذ
|
||||||
|
|
||||||
|
- نجح اختبار الحزمة، مع اختبار واحد ناجح ودون إخفاقات.
|
||||||
|
- تم تنفيذ سكربت التثبيت كاملًا بنجاح، بما في ذلك البناء والتثبيت والفتح.
|
||||||
|
- تمت إضافة اختبار للحفاظ على عقد واجهة البداية.
|
||||||
|
- لم يتم تعديل ملف المهام الموجود مسبقًا.
|
||||||
|
|
||||||
|
## الحالة الحالية
|
||||||
|
|
||||||
|
واجهة البداية وحزمة التطبيق جاهزتان للتشغيل المحلي.
|
||||||
|
|
||||||
|
لا تزال ميزات التخزين الآمن والاقتران والاتصال بالخادم والدردشة الفعلية غير منفذة.
|
||||||
|
|
||||||
|
المهمة التالية المقترحة هي إضافة التخزين الآمن للهوية والرموز، ثم ربط شاشة الاقتران بالخادم.
|
||||||
60
scripts/run-macos-app.sh
Executable file
60
scripts/run-macos-app.sh
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPOSITORY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PROJECT_DIR="$REPOSITORY_DIR/FchatiApp"
|
||||||
|
BUILD_DIR="$PROJECT_DIR/.build"
|
||||||
|
PRODUCT_NAME="FchatiApp"
|
||||||
|
INSTALL_PATH="/Applications/Fchati.app"
|
||||||
|
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/fchati-install.XXXXXX")"
|
||||||
|
STAGING_APP="$STAGING_DIR/Fchati.app"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$STAGING_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
if [[ ! -f "$PROJECT_DIR/Package.swift" ]]; then
|
||||||
|
echo "FchatiApp package not found: $PROJECT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -e "$INSTALL_PATH" && ! -d "$INSTALL_PATH" ]]; then
|
||||||
|
echo "Expected an application bundle at $INSTALL_PATH, but found a non-directory." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing the current Swift build artifacts..."
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
|
||||||
|
echo "Running tests..."
|
||||||
|
swift test --package-path "$PROJECT_DIR"
|
||||||
|
|
||||||
|
echo "Building the release executable..."
|
||||||
|
swift build --configuration release --package-path "$PROJECT_DIR"
|
||||||
|
|
||||||
|
EXECUTABLE_PATH="$PROJECT_DIR/.build/release/$PRODUCT_NAME"
|
||||||
|
if [[ ! -x "$EXECUTABLE_PATH" ]]; then
|
||||||
|
echo "Release executable not found: $EXECUTABLE_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating the application bundle..."
|
||||||
|
mkdir -p "$STAGING_APP/Contents/MacOS"
|
||||||
|
cp "$PROJECT_DIR/Info.plist" "$STAGING_APP/Contents/Info.plist"
|
||||||
|
cp "$EXECUTABLE_PATH" "$STAGING_APP/Contents/MacOS/$PRODUCT_NAME"
|
||||||
|
codesign --force --sign - --timestamp=none "$STAGING_APP"
|
||||||
|
|
||||||
|
if [[ -d "$INSTALL_PATH" ]]; then
|
||||||
|
echo "Removing the installed application..."
|
||||||
|
rm -rf "$INSTALL_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing the application..."
|
||||||
|
mv "$STAGING_APP" "$INSTALL_PATH"
|
||||||
|
|
||||||
|
echo "Opening Fchati..."
|
||||||
|
open "$INSTALL_PATH"
|
||||||
|
|
||||||
|
echo "Fchati was installed and opened successfully."
|
||||||
Loading…
Reference in New Issue
Block a user