/

aidly

(engineering)

engineering

aidly.xcodeproj

aidly.xcodeproj

iOS 13.3 +

iOS 13.3 +

21K LOC

21K LOC

5 months in development

5 months in development

242 Swift Files

Architecture

MVVM

Aidly follows a strict Model-View-ViewModel (MVVM) architecture to clearly separate business logic, data handling, and UI presentation. Dependencies are injected at init‑time.

ViewController (chat example)

ChatViewController manages user interface interactions and updates, responding directly to changes in the associated ChatModel. By delegating all business logic to the ViewModel, the controller remains focused solely on UI responsibilities, achieving clarity and simplifying UI maintenance.

// MARK: ChatViewController
final class ChatViewController: UIViewController {
    private let viewModel: ChatViewModel

    init(viewModel: ChatViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) not supported") }

    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }

    private func bind() {
        viewModel.onMessagesUpdated = { [weak self] in
            DispatchQueue.main.async { self?.tableView.reloadData() }
        }

        clearButton.addAction(.init { [weak self] _ in
            Task { await self?.viewModel.clear() }
        }, for: .touchUpInside)
    }
}

ViewModel (chat example)

ViewModel (chat example)

ViewModel (chat example)

The ChatModel encapsulates all core business logic, conversation state management, and data persistence. By isolating these functions from the UI layer, it ensures testability and keeps the presentation layer (ChatViewController) free from complex logic, enabling straightforward updates and enhancements.

// MARK: ChatViewModel
final class ChatViewModel {
    var onMessagesUpdated: (() -> Void)?

    private(set) var messages: [Message] = [] {
        didSet { onMessagesUpdated?() }
    }

    private let aiService: AIService
    private let conversationStore: ConversationStore

    init(aiService: AIService, conversationStore: ConversationStore) {
        self.aiService = aiService
        self.conversationStore = conversationStore
        self.messages = conversationStore.load()
    }

    func send(_ text: String) async {
        messages.append(Message(text, role: .user))
        let reply = await aiService.chat(with: messages)
        messages.append(reply)
        conversationStore.save(messages)
    }

    func clear() async { messages.removeAll() }
}

Dependency Injection & Protocol-Oriented Services

Service Protocols (example)

Protocols define explicit blueprints for services such as speech handling, token management, and membership handling, enabling easy mocking for tests, clear dependency boundaries, and promoting modularity across the app.

protocol AIService { func chat(with history: [Message]) async -> Message }
protocol CredentialStore { func credential(for type: CredentialType) -> String }

struct OpenAIApiService: AIService {
    private let httpClient: HTTPClient
    private let credentials: CredentialStore
    ...
}

// Registration
ServiceContainer.shared.register(AIService.self) {
    OpenAIApiService(httpClient: URLSession.shared,
                     credentials: KeychainStore())
}

Dependency Injection via Shared Services (example)

Implemented shared singleton services like AuthenticationHandler, UserManager, and ConversationHandler, injected through a centralized configuration. This approach ensures consistent access to app-wide dependencies, proper initialization order, and streamlined cross-service communication.

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UIApplication.shared.updateStatusBarStyle()
        
        // Configure services in dependency order
        AuthenticationHandler.shared.configure {
            MembershipManager.shared.initialize()
            UserManager.shared.configure()
        }
        return true
    }
}

AI Integration & Function-Calling Architecture

Implemented a robust, protocol-oriented AI integration utilizing OpenAI's advanced function-calling capabilities. This architecture allows structured AI-driven interactions, including dynamic itinerary creation, recipe generation, and personalized workout routines, ensuring consistency, extensibility, and optimal user experience.

Highlights

Protocol-Oriented Design

Defined clear interfaces (AIFunction protocol) and concrete implementations (AIItineraryFunction, AIRecipeFunction, AIWorkoutFunction) for structured AI response handling.

protocol AIFunction {
    var name: String { get set }
    var type: AIFunctionType { get }
}

struct AIItineraryFunction: AIFunction {
    var name: String
    var location: String
    var stayDuration: String?
    var type: AIFunctionType = .getItinerary
}

...

Function Type System

Employed an enum-based function type system (AIFunctionType) with automatic JSON schema generation to facilitate structured communication with OpenAI’s function-calling API.

enum AIFunctionType: String, Codable {
    case getItinerary, getRecipe, getWorkout
    var description: String { ... }
}

struct GetItineraryParams: Codable {
    let location: String
    let stayDuration: Int
}

struct AIRequest<P: Codable>: Codable {
    let function: AIFunctionType
    let parameters: P
}

let body = try JSONEncoder().encode(
    AIRequest(function: .getItinerary,
              parameters: GetItineraryParams(location: "Tokyo", stayDuration: 5))
)

UX Design and Theming

Aidly supports 10 different color schemes, each made with distinct color variations and subtle characteristics. This is done by implementing an extensible theming system supporting dynamic persona-based styling, multiple color schemes (e.g., Brand, Warm, Cold, Hacker), and seamless light/dark mode transitions. Centralized definitions ensure consistency, personalization, and instant UI updates, enhancing both accessibility and user experience.

Centralized Color Scheme Management

Defined unified color and gradient palettes in Color.swift, Gradient.swift, and Palette.swift, enabling cohesive UI across the entire app.

public enum Color {
    public enum Light {
        public enum Background {
            public static func primary() -> UIColor { return UIColor(245, 240, 248) }
            public static func secondary() -> UIColor { return UIColor(235, 229, 239) }
            public static func tertiary() -> UIColor { return UIColor(251, 251, 251) }
            public static func selected() -> UIColor { return UIColor(198, 198, 204) }
        }
        
        public enum Text {
            public static func primary() -> UIColor { return UIColor(4, 9, 38) }
            public static func secondary() -> UIColor { return .white }
            public static func tertiary() -> UIColor { return UIColor(133, 133, 133) }
            public static func placeholder() -> UIColor { return .lightGray }
            
            public static func receiver() -> UIColor { ... }
            public static func sender() -> UIColor { ... }
        }

        ...
    }
    public enum Dark {
        ...
    }
}

Switching color schemes becomes easy with Palette.swift through the use of ColorScheme.

class Palette {
    // MARK: - Properties
    static let shared = Palette(colorScheme: .warm)
    
    var colorScheme: ColorScheme
    
    // MARK: - Initialization
    init(colorScheme: ColorScheme) {
        self.colorScheme = colorScheme
    }
    
    // MARK: - Updates
    func updateColorScheme(_ colorScheme: ColorScheme) {
        self.colorScheme = colorScheme
        Appearance.colorScheme = colorScheme
        UIView.appearance(whenContainedInInstancesOf: [UIToolbar.self]).tintColor = UIColor.primaryAccent
    }
}

Layers

Presentation

Home UI

HomeViewController

Chat UI

ChatViewController

Settings UI

SettingsViewController

Training UI

TrainingViewController

Paywall UI

PaywallViewController

Navigation

NavigationViewController

Business Logic

AI Service

AIService

...

Persona Management

PersonaHandler

...

Membership

MembershipManager

...

Conversation

ConversationHandler

...

Speech

SpeechHandler

...

Training

TrainingHelper

...

Integrations

OpenAI

Implemented via REST API calls to OpenAI's completion endpoint (api.openai.com/v1/chat/completions), secured by Bearer token authentication. Supports structured responses through function calling (e.g., recipes, workouts, itineraries), tracks usage cost per request.

RevenueCat

Integrated RevenueCat during app initialization, managing subscriptions (monthly/annual). Supports automatic entitlement checks, seamless purchase restoration, and real-time updates on membership status through Purchases.shared.purchase() and associated callbacks.

Flagsmith

Implemented via REST API calls to Flagsmith (api.flagsmith.com/api/v1/flags/) authenticated by environment keys. Enables feature gating based on user tiers (free vs. premium), caches feature flags locally, and automatically refreshes flags upon app activation.

Yelp

Integrated Yelp Fusion API (v3) via REST calls secured by Bearer token authentication for robust business search. Implemented within BusinessHelper, providing location-based recommendations (restaurants, venues) with structured JSON parsing and precise URL query parameters for efficient term and location filtering.

All screenshots, assets, and content on this page are property of Fernando Cervantes and are not authorized for any usage or distribution.