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