Youll
Modules

Home Screen

The primary content feed users see after login. Three implementations exist, from the original UIKit-based Home to the newest SwiftUI Slices with REST backend. The most complex content module in the Youll platform.

The Home Screen module is the primary content feed that users see after login. It displays a dynamic, vertically scrolling feed of content sections personalized to each user based on their cohort membership, subscription status, and feature flags.

The platform provides three home screen implementations, each fully functional and actively used across different customer apps:

VersionNameUI FrameworkBackendTab ItemStatus
v1HomeUIKitHasura / GraphQL.homeDeprecated
v2ShowcaseSwiftUIHasura / GraphQL.showcase / .exploreShowcaseActive
v3SlicesSwiftUIREST.slicesActive

New customer apps should use Showcase (v2) or Slices (v3). Home v1 remains functional for existing customers but is deprecated and should not be used for new implementations. Which active version a customer app uses depends on the tab item returned by makeTabItems().

Like all core content modules, the home screen implementations live in the Core package (screens) and the Client package (coordinators). For the underlying patterns, see:

Activation

The home screen is activated by including the appropriate tab item in the customer app's tab selection:

// v1: Home (UIKit + GraphQL) — DEPRECATED
func makeTabItems() -> [TabItem] {
    [.home, .explore, .library, .journeys, .profile]
}

// v2: Showcase (SwiftUI + GraphQL)
func makeTabItems() -> [TabItem] {
    [.showcase, .explore, .library, .journeys, .profile]
}

// v3: Slices (SwiftUI + REST)
func makeTabItems() -> [TabItem] {
    [.slices, .explore, .library, .journeys, .profile]
}

No feature flags are required to enable the home screen itself. Individual sections within the feed are controlled by their own feature flags (see Feature Flags below).

Version Overview

Home (v1) — Deprecated

Home v1 is deprecated. New customer apps should use Showcase (v2) or Slices (v3) instead.

The original home screen implementation, built with UIKit. Uses a UICollectionView with compositional layout and diffable data source. The backend is powered by Hasura/GraphQL, with the GetHomeScreenSectionsQuery returning ordered feed sections ("slices"). Supports 27 distinct section types.

Home v1 remains functional for existing customer apps that have not yet migrated, but no new implementations should use it.

Showcase (v2)

A SwiftUI rewrite of the home screen, built to replace Home v1. Also uses the Hasura/GraphQL backend. Showcase was designed with a more device-centric layout, making it suitable for hardware-focused apps (e.g., apps using Tuya IoT devices), though it works for any content-on-demand app.

Showcase uses ShowcaseCoordinator in Client and its screens live under Core/Screens/Showcase/. It also supports the .exploreShowcase tab variant (differentiated by an isUsedForExplore flag).

Slices (v3)

The newest home screen implementation, also built with SwiftUI. The key difference from Showcase is the backend: Slices uses REST APIs instead of Hasura/GraphQL. This represents the platform's direction toward REST-based data fetching for the home feed.

The sections below document the Home (v1) implementation in detail for reference and for existing customers still using it. Showcase (v2) and Slices (v3) share many of the same concepts (feed sections, personalization, configuration) but with different UI frameworks and data transport layers.


Home (v1) Detail — Deprecated

Architecture

Home (v1) spans three packages in the Bricks dependency graph:

YoullNetwork
  └── FeedAPI.getHomeFeedSections()     ← GraphQL query

Core   │
  ├── HomeFeedService                    ← API + caching layer
  ├── HomeFeedDataProvider               ← State machine + snapshot generation
  ├── HomeViewModel                      ← UI state + delegate callbacks
  ├── HomeViewController / HomeView      ← UICollectionView-based feed
  ├── FeedSection (27 cases)             ← Section type definitions
  ├── HomeFeedDataSource                 ← Diffable data source
  ├── HomeFeedLayoutProvider             ← Compositional layout
  ├── FeedCollectionItemProvider         ← Cell/header registration + dequeue
  └── 11 cell types                      ← One per visual section style

Client │
  └── HomeCoordinator                    ← Navigation + integration (~1,146 lines)

HomeCoordinator

HomeCoordinator inherits from BaseCoordinator and conforms to the HomeNavigable protocol (shared with ShowcaseCoordinator for deep link routing). It is created by MainCoordinator.createHomeCoordinator() during tab setup:

private func createHomeCoordinator() -> Coordinator {
    let homeCoordinator = HomeCoordinator(
        config: config,
        delegate: self,
        timerModuleInterface: timerModuleInterface,
        tuyaModuleInterface: tuyaModuleInterface)
    homeCoordinator.childCoordinatorDelegate = self
    homeCoordinator.start()
    addChildCoordinator(homeCoordinator, flow: .home)
    return homeCoordinator
}

HomeCoordinator receives two optional module interfaces:

  • TimerModuleInterface: if injected, the Home feed can display a timer widget and navigate to timer screens
  • TuyaModuleInterface: if injected, the Home feed can display IoT device widgets and navigate to device control screens

The coordinator creates these services on init:

  • HomeFeedService for API calls and caching
  • NavigationContentService for navigation bar content
  • EventsContentService for events data
  • JourneyService for journey data
  • LinearJourneyManager for linear journey state tracking

HomeViewController

HomeViewController is a UICollectionView-based screen with two header modes:

Header TypeBehavior
.floatingHeader content scrolls with the feed, floating above the background
.fixedWithLogoHeader is pinned to the top with a logo, search button, and optional live users count

The header type is configured by the customer app via HomeScreenConfigFactoryType.headerType.

The view controller uses:

  • HomeFeedDataSource (wraps UICollectionViewDiffableDataSource) for diff-based updates
  • HomeFeedLayoutProvider for compositional layout per section
  • FeedCollectionItemProvider for cell registration and dequeuing

HomeViewModel

HomeViewModel owns the data pipeline and exposes state to the view controller. It holds:

  • HomeFeedDataProvider for loading and caching feed data
  • HomeFeedLayoutProvider for section layout computation
  • FeedCollectionItemProvider for cell/header configuration
  • screenConfig: HomeScreenConfig for all visual configuration

The ViewModel communicates user actions back to the coordinator through the HomeFeedDelegate protocol, which has 25+ methods covering every possible interaction in the feed.

Feed Sections

The FeedSection enum defines 27 section types that can appear in the Home (v1) feed. The server returns ordered feed sections via GraphQL, and HomeFeedService maps them to FeedSection cases. Feature flags filter out sections for disabled modules before the data reaches the UI.

Content Sections

SectionPurposeVisual Style
.featuredFeatured content cards, typically editorial picksHorizontal scroll, large cards
.suggestedRecommended content (often quiz-driven)Horizontal scroll, standard cards
.tagContent grouped by a tagHorizontal scroll, standard cards
.borderedContent grouped by tag, bordered card styleHorizontal scroll, bordered cards
.bannerSingle full-width content bannerFull-width card
.historyRecently played contentHorizontal scroll
.favoritesUser's favorited contentHorizontal scroll
.dailyQuoteInspirational daily quoteFull-width quote card

Journey Sections

SectionPurpose
.journeyTimeline journey with day-by-day progress
.linearJourneyLinear journey with sequential sessions
.currentJourneyActive journey showing current session and progress
.linearJourneysAll available linear journeys (discovery)

Module Integration Sections

SectionPurposeRequires
.eventsUpcoming events listeventsEnabled flag
.todayMealsPlanToday's meal planmealsEnabled flag
.todayWorkoutToday's highlighted workoutactivityEnabled flag
.workoutsWorkout collectionactivityEnabled flag
.timerQuick-access timer widgetTimerModuleInterface injected
.tuyaHardwareIoT device status and controlsTuyaModuleInterface injected
.timerSessionsTimer session historyTimerModuleInterface injected

UI & Layout Sections

SectionPurpose
.navigationTop navigation bar with logo, welcome message, live users count, search button
.greetingsPersonalized greeting with the user's first name in gradient text
.titlingText titles and subtitles for visual hierarchy
.spacerVertical spacing between sections
.shareShare section with URL and CTA

Commercial Sections

SectionPurpose
.promoOfferPromotional offer with countdown timer and CTA
.lifetimeAccessProductLifetime subscription offer
.facilitatorFacilitator/instructor profiles

Section Ordering

The feed order is server-driven. The GraphQL query GetHomeScreenSectionsQuery returns slices in a specific order, and HomeFeedService preserves that order. The only client-side modifications are:

  1. Prefix sections added before server content: .navigation and optionally .greetings
  2. Promo offers inserted after the navigation section (fetched separately)
  3. Feature flag filtering: sections for disabled modules are removed (e.g., .events removed when eventsEnabled is false)

Section Dismissal

Users can dismiss certain sections from their feed. When a user dismisses a section, HomeFeedService calls the dismissHomeSlice GraphQL mutation with the slice ID. The section is removed from the local snapshot immediately and will not reappear on the next server fetch.

Data Flow

Query and Caching

User opens Home tab


HomeFeedDataProvider.loadData()

    ├──▸ Check Core Data cache
    │    └── If cached: emit .loaded(cache: true) with cached sections

    ├──▸ HomeFeedService.getFeedData()
    │    ├── Get user's cohort IDs from profile
    │    ├── Call FeedAPI.getHomeFeedSections() (GraphQL)
    │    ├── Fetch timer sessions (REST, if enabled)
    │    ├── Store result in Core Data cache
    │    └── Return HomeScreenFeed

    └──▸ Emit .loaded(cache: false) with fresh sections

The data provider follows a state machine pattern:

StateMeaning
.loadingInitial load in progress
.loaded(cache: true)Displaying cached data while fresh data loads
.loaded(cache: false)Displaying fresh server data
.failure(Error)Load failed, showing error state
.reloadingPull-to-refresh in progress

Personalization

Content personalization happens at multiple levels:

Cohort-based filtering. The GraphQL query includes the user's cohort IDs (from their profile). The backend returns only slices that match the user's cohort membership. This is the primary personalization mechanism.

Subscription status filtering. Each slice has a visibility scope: all_users, subscribed_only, or unsubscribed_only. The query sends the user's subscription status, and the backend filters accordingly.

Suggested content. The .suggested section can be driven by quiz results. When a user completes a quiz, the backend uses their answers to populate a personalized content recommendation section with a specific quizConfigID.

Promo offers. Promotional offers are fetched separately and filtered client-side against Settings.offerIdsToHide (offers the user has previously dismissed).

Refresh Behavior

TriggerBehavior
Pull-to-refreshFull reload from server
Network restorationFull reload (observed via NetworkReachability.reachablePublisher)
viewWillAppearReload if more than 5 minutes since last load
Registration completedReload (via Config.registrationCompletionPublisher)
Subscription completedReload (via Config.subscriptionCompletionPublisher)
Player closedReload (via ContentDetailsCoordinator.reloadHomeContentPublisher)
Content liked/unlikedUpdate favorites (via ContentDetailsCoordinator.didLikeContentPublisher)

Greetings and Welcome

Greetings Section

When Settings.displayHomeScreenGreetings is true, HomeFeedService prepends a .greetings section to the feed. It extracts the user's first name from their profile (defaulting to "Stranger" if empty) and renders it in HomeFeedGreetingCell with a gradient-styled username label.

The greeting configuration (text, colors, gradient) is part of HomeScreenConfig.content.

Welcome Message

A one-shot welcome popup displayed on first launch after onboarding:

  1. Condition: Settings.displayedWelcomeMessage == false AND Settings.onboardingType == .regular
  2. Configuration: ScreenConfig.generic.makeWelcomeMessageViewConfig()
  3. Flow: HomeCoordinator.displayWelcomeMessageIfNeeded() creates a WelcomeMessageViewController with a WelcomeMessageViewModel
  4. After display: Sets Settings.displayedWelcomeMessage = true so it never shows again
  5. Optional follow-up: A welcome session can be configured via makeWelcomeSessionViewConfig()

Configuration

HomeScreenConfigFactoryType

Customer apps implement this protocol to configure the Home screen's visual appearance:

protocol HomeScreenConfigFactoryType {
    var headerType: HeaderType { get }
    func makeHomeScreenConfig(navBarTitle: String, bgUrl: URL?) -> HomeScreenConfig
    func makeTagFeedScreenConfig(title: String, bgUrl: URL?) -> TagFeedScreenConfig
    func makeEventsListScreenConfig(showTopSearchButton: Bool) -> EventsListScreenConfig?
    func makeDailyQuoteModalConfig() -> DailyQuoteModalViewConfig?
    func makeTimerSessionsHistoryConfig() -> TimerSessionsHistoryViewConfig?
}
MethodPurposeRequired
headerType.floating or .fixedWithLogoYes
makeHomeScreenConfigFull Home screen visual configYes
makeTagFeedScreenConfigTag feed drill-down screen configYes
makeEventsListScreenConfigEvents list screen (returns nil if events not used)Optional
makeDailyQuoteModalConfigDaily quote modal (returns nil if not used)Optional
makeTimerSessionsHistoryConfigTimer sessions history (returns nil if not used)Optional

HomeScreenConfig

The HomeScreenConfig struct controls every visual aspect of the Home (v1) screen:

AreaWhat It Controls
Status barLight/dark style, background handling
BackgroundBackground image URL, placeholder, background color
Navigation barLogo image, welcome message text, search button visibility, live users display config
ContentPull-to-refresh icon and label, greeting label with gradient, header text and subtitle
Cell themesPer-section-type theme configs for colors, fonts, sizes, corner radii (featured, suggested, tag, facilitator, events, workouts, timer, promo, bordered, banner, history, meals, journey, and more)
Promo cellCountdown timer styling, CTA button config
SkeletonLoading state placeholder appearance
Error stateError screen appearance and retry button

Cell themes are the most granular part of configuration. Each section type has its own theme config object, allowing customer apps to give each section a distinct visual identity while reusing the same underlying cell implementations.

HomeFeedDelegate

The HomeFeedDelegate protocol is the bridge between HomeViewModel (in Core) and HomeCoordinator (in Client). When a user taps anything in the feed, the ViewModel calls the appropriate delegate method, and the coordinator handles navigation.

Content navigation:

Delegate MethodNavigation Target
showTaggedItemDetails(for:autoPlayItemIDs:)Content playback via ContentDetailsCoordinator.shared
showAllContentForTag(id:itemType:title:sortBy:sortDirection:)Tag feed drill-down screen
showAllSuggestedItems(quizConfigID:)Suggested content list
didSelectFacilitator(item:)Facilitator profile via ContentDetailsCoordinator.shared
didSelectDailyQuote(quote:)Daily quote modal
shareAction(url:)System share sheet

Cross-module navigation:

Delegate MethodTarget ModuleRoute
didSelectLinearJourney(_:)JourneysJourney details screen
didSelectTimelineJourney(_:)JourneysJourney details screen
didSelectCurrentJourneySession(_:allSessions:)JourneysContent playback
showAllJourneys()JourneysJourneys list
didSelectEvent(item:)EventsEvent details screen
showAllEvents()EventsEvents list screen
didSelectMeal(item:)MealsMeal details screen
showMeals()MealsMeals list screen
didSelectWorkout(workoutModel:isTodaysWorkout:)ActivityWorkout details
showAllWorkouts(for:sectionTitle:)ActivityWorkouts list
showTimer(tuyaController:)TimerTimer screen (via TimerModuleInterface)
showTimerSessionsHistory(selectedSession:)TimerTimer sessions history
didSelectTuyaDevice(item:)TuyaDevice control screen (via TuyaModuleInterface)
didSelectPairTuyaDevice()TuyaDevice pairing flow (via TuyaModuleInterface)
searchAction()SearchSearch screen (via SearchCoordinator.shared)
presentQuiz(skipIntroScreen:)QuizQuiz flow (via QuizCoordinator.shared)
showAllFavorites()LibraryFavorites screen

Commercial navigation:

Delegate MethodNavigation Target
didSelectPromoOffer(_:)Promo offer details or subscription flow
didSelectLifetimeAccessProduct(id:)Lifetime access purchase flow

Combine Subscriptions

HomeCoordinator subscribes to several cross-cutting publishers set up in its start() method:

PublisherSourcePurpose
didClosePlayerPublisherContentDetailsCoordinator.sharedRefresh feed when player closes
reloadHomeContentPublisherContentDetailsCoordinator.sharedFull reload triggered externally
didLikeContentPublisherContentDetailsCoordinator.sharedUpdate favorites section when content is liked/unliked
quizDismissPublisherQuizCoordinator.sharedHandle quiz completion, refresh suggested content
registrationCompletionPublisherConfigRefresh feed after user registers
subscriptionCompletionPublisherConfigRefresh feed after subscription purchase

Cross-Sibling Communication

MainCoordinator wires up cross-sibling delegates after creating tab coordinators:

if let homeCoordinator = childCoordinators[.home] as? HomeCoordinator,
   let journeyCoordinator = childCoordinators[.journeys] as? JourneysCoordinator {
    journeyCoordinator.journeyCompletionDelegate = homeCoordinator
}

HomeCoordinator conforms to JourneyCompletionDelegate, receiving callbacks when the user views journey content in the Journeys tab. This allows the Home feed to refresh journey progress sections.

HomeCoordinator conforms to the HomeNavigable protocol, enabling deep link routing from MainCoordinator:

protocol HomeNavigable {
    func navigateToCategory(id: String?)
    func navigateToSubcategory(id: String?)
    func navigateToTag(id: String?, sortInfo: (sortBy: SliceSortBy, sortDirection: SliceSortDirection)?)
    func navigateToContent(id: String?)
    func navigateToEvent(id: String?)
}

When a deep link arrives for content, categories, or events, MainCoordinator switches to the Home tab and calls the appropriate HomeNavigable method.

Additional Flows

HomeCoordinator manages several secondary flows triggered from the Home screen:

FlowTriggerDescription
Push notification promptFirst visit after conditions metdisplayNotificationAlertControllerIfNeeded() presents the push notification permission dialog
Quiz triggerOn visit if quiz is configureddisplayQuizIfNeeded() checks flags and presents quiz flow
What's New popupAfter app updateWhatsNewControllerManager shows a popup when content has changed (hash comparison)
Reminders popupAfter player close (if reminders flag active)Presents reminder configuration

Feature Flags

These feature flags control which sections appear in the Home (v1) feed:

FlagEffect on Home
journeysEnabledJourney sections (.journey, .linearJourney, .currentJourney, .linearJourneys) visible
eventsEnabled.events section visible (filtered in HomeFeedService)
mealsEnabled.todayMealsPlan section visible
activityEnabled.todayWorkout and .workouts sections visible
quizEnabledQuiz CTA visible, .suggested sections with quiz config active, quiz trigger on visit
remindersReminders popup after player close

Flags are checked by HomeFeedService when processing the server response. Sections for disabled modules are removed before the data reaches the UI layer.

For Agents

Reading Order (Home v1)

When working with the Home (v1) module, read files in this order:

  1. Client/Coordinators/Tabs/HomeCoordinator.swift (~1,146 lines) for the full navigation flow and integration points
  2. Core/Screens/Home/Feed/HomeViewModel.swift for the ViewModel, HomeFeedDelegate protocol, and data provider usage
  3. Core/Screens/Home/DataProvider/FeedSection.swift for all 27 section types
  4. Core/Screens/Home/DataProvider/HomeFeedDataProvider.swift for the state machine and data loading
  5. Core/Data/API Service/HomeFeedService.swift for the API layer, caching, and feature flag filtering
  6. Core/Screens/Home/Feed/HomeScreenConfig.swift for the configuration structure
  7. Core/Screens/Home/Feed/HomeViewController.swift for the view controller and collection view setup
  8. Client/App/Screen Config/FactoryTypes.swift (search for HomeScreenConfigFactoryType) for the configuration protocol

Key Files (Home v1)

ComponentFile Path
HomeCoordinatorBricks/modules/Client/Client/Coordinators/Tabs/HomeCoordinator.swift
HomeViewControllerBricks/modules/Core/Core/Screens/Home/Feed/HomeViewController.swift
HomeViewBricks/modules/Core/Core/Screens/Home/Feed/HomeView.swift
HomeViewModelBricks/modules/Core/Core/Screens/Home/Feed/HomeViewModel.swift
HomeScreenConfigBricks/modules/Core/Core/Screens/Home/Feed/HomeScreenConfig.swift
HomeFeedServiceBricks/modules/Core/Core/Data/API Service/HomeFeedService.swift
HomeFeedDataProviderBricks/modules/Core/Core/Screens/Home/DataProvider/HomeFeedDataProvider.swift
FeedSection enumBricks/modules/Core/Core/Screens/Home/DataProvider/FeedSection.swift
FeedItemBricks/modules/Core/Core/Screens/Home/DataProvider/FeedItem.swift
FeedSectionHeaderBricks/modules/Core/Core/Screens/Home/DataProvider/FeedSectionHeader.swift
HomeFeedDataSourceBricks/modules/Core/Core/Screens/Home/DataSource.swift
HomeFeedLayoutProviderBricks/modules/Core/Core/Screens/Home/Feed/HomeFeedLayoutProvider.swift
FeedCollectionItemProviderBricks/modules/Core/Core/Screens/Home/Feed/FeedCollectionItemProvider.swift
FeedAPI (GraphQL)Bricks/modules/YoullNetwork/Network/Sources/Public/Data Task/FeedAPI.swift
HomeScreenConfigFactoryTypeBricks/modules/Client/Client/App/Screen Config/FactoryTypes.swift
Tab setupBricks/modules/Client/Client/Coordinators/Main/MainCoordinators+TabSetup.swift
Home cellsBricks/modules/Core/Core/Screens/Home/Cells/ (11 cell files)
Tag feed screenBricks/modules/Core/Core/Screens/Home/TagFeed/TagContentViewController.swift
Feature flagsBricks/modules/Core/Core/Content/Flagging/Flag.swift

Cell Types (Home v1)

CellPurpose
HomeNavigationBarCellTop nav bar with logo, welcome message, live users, search
HomeFeedGreetingCellPersonalized greeting with gradient username
HomeFeaturedCellFeatured content cards
HomeJourneyCellTimeline journey display
HomeLinearJourneyCellLinear journey progress
HomeTimerCellTimer quick-access widget
HomeTuyaHardwareCellIoT device controls
HomeShareCellShare section
HomeTitleCellSection titles
HomeFeedCTACellCall-to-action buttons
HomeSpaceCellVertical spacing

Tips (Home v1)

  • To add a new feed section type: Add a case to FeedSection, add a cell in Core/Screens/Home/Cells/, register it in FeedCollectionItemProvider, add layout in HomeFeedLayoutProvider, and add a theme config to HomeScreenConfig.
  • To modify the greeting: Look at HomeFeedService.prefixSections() for the data, HomeFeedGreetingCell for the UI, and HomeScreenConfig.content for the theme.
  • To add a new navigation action from the feed: Add a method to HomeFeedDelegate (in HomeViewModel.swift), call it from HomeViewModel.didSelectItem(at:), and implement it in HomeCoordinator.
  • To change feed refresh behavior: Look at HomeFeedDataProvider for the state machine and HomeViewModel.reloadIfNeededAfterViewWillAppear() for the 5-minute throttle logic.
  • To trace a content tap: The ViewModel calls HomeFeedDelegate.showTaggedItemDetails(for:autoPlayItemIDs:), HomeCoordinator calls ContentDetailsCoordinator.shared.didSelectSession(...), which presents the player.
  • HomeCoordinator is ~1,146 lines. It handles navigation for all 27 section types. When debugging a specific interaction, search for the corresponding HomeFeedDelegate method name in the coordinator.

Key Files (Showcase v2)

ComponentFile Path
ShowcaseCoordinatorBricks/modules/Client/Client/Coordinators/Tabs/ShowcaseCoordinator.swift
Showcase screensBricks/modules/Core/Core/Screens/Showcase/

What's Next

On this page