Explore
Content discovery screen where users browse by categories, subcategories, facilitators, and tags. Supports two operational modes, generic tag-based browsing and a custom home-like feed.
The Explore module is the content discovery screen where users browse the full content catalog. Unlike the Home Screen, which shows a personalized feed of recommended content, Explore provides structured browsing through categories, subcategories, facilitators, and tags.
Explore has two operational modes controlled by feature flags:
| Mode | ViewModel | Data Source | Layout |
|---|---|---|---|
| Generic (default) | ExploreFeedViewModel | GenericExploreService (GraphQL) | Tag-filtered content grid |
| Custom | HomeViewModel (reused) | ExploreService (GraphQL, cached) | Home-like sectioned feed |
Like all core content modules, Explore's screens live in the Core package and its coordinator lives in the Client package. For the underlying patterns, see:
- Coordinator Pattern for the coordinator hierarchy and deep link routing
- Communication Patterns for Combine publishers and delegate patterns
- Design Token System for how screen config values flow from Figma exports to runtime theming
Activation
Explore is activated by including .explore in the customer app's tab selection:
func makeTabItems() -> [TabItem] {
[.showcase, .explore, .library, .journeys, .profile]
}The view type is determined automatically at coordinator creation time based on feature flags:
private func createExploreCoordinator() -> Coordinator {
var exploreType: ExploreViewType = .generic
if !config.flagManager.isActive(flag: .isLightVersion) &&
config.flagManager.isActive(flag: .exploreEnabled) {
exploreType = .custom
}
let exploreCoordinator = ExploreCoordinator(
config: config,
timerModuleInterface: timerModuleInterface,
type: exploreType)
exploreCoordinator.start()
addChildCoordinator(exploreCoordinator, flow: .explore)
return exploreCoordinator
}| Flag Combination | Result |
|---|---|
exploreEnabled active AND NOT isLightVersion | Custom mode (home-like feed) |
isLightVersion active | Generic mode (tag-based) |
exploreEnabled inactive | Generic mode (tag-based) |
Some customer apps use .exploreShowcase instead of .explore. This tab item uses ShowcaseCoordinator (the Home v2 coordinator) with isUsedForExplore: true, providing a Showcase-style layout for the Explore tab. This is a third alternative, separate from the generic/custom modes described here.
Architecture
Explore spans three packages in the Bricks dependency graph:
YoullNetwork
└── GenericExploreService / ExploreService ← GraphQL queries
│
Core │
├── ExploreFeedViewController ← UIViewController hosting SwiftUI
├── ExploreFeedViewModel (228 lines) ← Data + navigation delegates
├── ExploreFeedDataProvider (192 lines) ← Pagination + state machine
├── ContentItem ← Shared content model
├── Tag / ExploreTag ← Tag models
├── CategoryViewController + ViewModel + View ← Category details
├── SubcategoryViewController + ViewModel + View ← Subcategory details
└── Facilitator screens ← Facilitator details + content list
│
Client │
└── ExploreCoordinator (218 lines) ← Navigation + mode selectionExploreCoordinator
ExploreCoordinator is 218 lines, significantly simpler than HomeCoordinator's 1,146 lines. It manages the Explore tab's navigation and handles the dual view type logic.
The coordinator receives one optional module interface:
TimerModuleInterface: if injected, the Explore screen can show a timer button and navigate to timer screens
On start(), the coordinator branches based on ExploreViewType:
enum ExploreViewType {
case generic // Tag-based content grid
case custom // Home-like sectioned feed
}- Generic: calls
createExploreFeedController()which createsExploreFeedViewControllerwithExploreFeedViewModel - Custom: calls
createHomeController()which createsHomeViewControllerwithHomeViewModelconfigured asisUsedForExploreFeed: true
ExploreFeedViewController
ExploreFeedViewController (169 lines) is a UIViewController that hosts a SwiftUI view (ExploreFeedView). It uses ExploreBaseView as its root view, which provides the navigation bar, and embeds the SwiftUI content grid below.
ExploreFeedViewModel
ExploreFeedViewModel (228 lines) manages the generic Explore feed. It holds:
ExploreFeedDataProviderfor data loading and paginationTagsBarDataProviderfor tag selection statescreenConfig: ExploreFeedScreenConfigfor visual configuration
The ViewModel communicates user actions to the coordinator through the ExploreFeedDelegate protocol.
View Types
Generic Mode
The default mode shows a tag-filtered content grid.
Tags bar. A horizontal scrollable bar at the top displays available tags. When the user selects a tag, the content grid filters to show only items matching that tag. The .all tag shows all categories. Tags are loaded via ExploreTagsAPI.getExploreTags() and cached in Settings.exploreTags (UserDefaults) for offline display.
Content grid. Below the tags bar, a grid displays ContentItem objects. Each item can be a category, subcategory, or session. Items show a thumbnail, title, lock status (for subscription-gated content), and viewed percentage.
Pagination. The data provider uses offset-based pagination. When the user scrolls near the bottom, ExploreFeedDataProvider requests the next page. New items are deduplicated against existing items to prevent duplicates during rapid scrolling.
State machine. The data provider follows a state pattern:
| State | Meaning |
|---|---|
.idle | Ready, no load in progress |
.loading | Initial load in progress |
.reloading | Pull-to-refresh in progress |
.failure(Error) | Load failed, showing error state |
Custom Mode
When exploreEnabled is active and isLightVersion is not, Explore uses the same HomeViewModel that powers the Home Screen (v1). The coordinator creates a HomeViewController with isUsedForExploreFeed: true, which tells the ViewModel to fetch data via ExploreService instead of HomeFeedService.
The custom mode provides a home-like sectioned feed with featured content, tagged collections, and promotional sections. Data is fetched via FeedAPI.getExploreFeedSections() and cached in Core Data through DataApiCache.
For details on the feed section types, data flow, and configuration available in custom mode, see the Home Screen documentation.
Screens
Explore Feed
| Component | File | Lines | Purpose |
|---|---|---|---|
ExploreFeedViewController | Feed/ExploreFeedViewController.swift | 169 | UIViewController hosting SwiftUI view |
ExploreFeedViewModel | Feed/ExploreFeedViewModel.swift | 228 | Data management, tag selection, navigation delegates |
ExploreFeedDataProvider | Feed/ExploreFeedDataProvider.swift | 192 | Pagination, deduplication, state machine |
ExploreFeedAPI | Feed/ExploreFeedAPI.swift | 80 | API protocol definitions (ExploreContentAPI + ExploreTagsAPI) |
ExploreFeedView | Feed/View/ExploreFeedView.swift | SwiftUI content grid | |
ExploreBaseView | Feed/ExploreBaseView.swift | 31 | Base UIView with navigation bar |
TagsBarDataProvider | Feed/Tags Bar/TagsBarDataProvider.swift | Tag selection state management | |
ContentItem | Feed/Item/ContentItem.swift | Shared content model (category, subcategory, session) | |
ItemCellController | Feed/Item/ItemCellController.swift | Grid layout and cell configuration |
Category Details
When a user taps a category in the Explore feed, the coordinator pushes the category details screen. This screen shows a grid of subcategories within that category.
| Component | File | Lines | Purpose |
|---|---|---|---|
CategoryViewController | Category Details/CategoryViewController.swift | Category screen container | |
CategoryViewModel | Category Details/CategoryViewModel.swift | 60 | Category state management |
CategoryView | Category Details/CategoryView.swift | 150 | SwiftUI subcategory grid |
CategoryDetailsDataProvider | Category Details/CategoryDetailsDataProvider.swift | Loads subcategories | |
CategoryDetailsScreenConfig | Category Details/CategoryDetailsScreenConfig.swift | Visual configuration |
Subcategory Details
Tapping a subcategory shows its sessions. This screen also displays user reviews with star ratings.
| Component | File | Lines | Purpose |
|---|---|---|---|
SubcategoryViewController | Subcategory Details/SubcategoryViewController.swift | 160 | Subcategory screen container |
SubcategoryViewModel | Subcategory Details/SubcategoryViewModel.swift | 130 | Session loading and state |
SubcategoryView | Subcategory Details/SubcategoryView.swift | 220 | SwiftUI session grid with reviews |
SubcategoryDetailsDataProvider | Subcategory Details/SubcategoryDetailsDataProvider.swift | 120 | Loads sessions and reviews |
ReviewsWidgetConfig | Subcategory Details/ReviewsWidgetConfig.swift | Star rating widget configuration |
Facilitators
Facilitator screens show instructor/teacher profiles and their content. There are two screens: a details view and a content list.
| Component | File | Purpose |
|---|---|---|
FacilitatorDetailsViewController | Facilitators/FacilitatorDetails/ | Facilitator profile display |
FacilitatorDetailsViewModel | Facilitators/FacilitatorDetails/ | Profile data management |
FacilitatorDetailsView | Facilitators/FacilitatorDetails/ | SwiftUI profile view |
FacilitatorContentsListViewController | Facilitators/FacilitatorContentsList/ | List of facilitator's content |
FacilitatorContentsListViewModel | Facilitators/FacilitatorContentsList/ | Content list data |
FeedFacilitatorItem | Facilitators/Data/ | Model: id, fullName, profileImage, coverImage, content, order |
Data Flow
Generic Mode
User opens Explore tab
│
▼
ExploreFeedDataProvider.loadData()
│
├──▸ TagsBarDataProvider loads tags
│ ├── ExploreTagsAPI.getExploreTags() (GraphQL)
│ └── Cache tags in Settings.exploreTags
│
├──▸ If tag == .all:
│ └── ExploreContentAPI.getAllCategories() → [BNCategory]
│
├──▸ If tag == .tag(Tag):
│ └── ExploreContentAPI.getContent(with: tag, offset:) → [ContentItem]
│
└──▸ Emit state update → ExploreFeedView renders gridPagination adds more items when the user scrolls:
User scrolls near bottom
│
▼
ExploreFeedDataProvider.loadMore()
│
├── Increment offset
├── ExploreContentAPI.getContent(with: tag, offset:)
├── Deduplicate against existing items
└── Append to content listCustom Mode
User opens Explore tab (custom mode)
│
▼
HomeFeedDataProvider.loadData()
│
├──▸ ExploreService.getExploreFeedSections() (GraphQL)
│ └── FeedAPI.getExploreFeedSections()
│
├──▸ Cache in Core Data via DataApiCache
│
└──▸ Return sectioned feed (same structure as Home v1)Data Models
Tag:
@frozen public struct Tag: Codable, Hashable, Sendable {
public let id: String
public let name: String
public let imageUrl: URL?
public let sortBy: SliceSortBy?
public let sortDirection: SliceSortDirection?
}
public enum ExploreTag: Hashable, Equatable {
case all
case tag(Tag)
}ContentItem:
public struct ContentItem: Hashable, Sendable {
public enum ContentType: String, Sendable {
case category, subcategory, session
}
// Key properties: id, title, thumbnail, contentType,
// lock status, viewed percentage, content metadata
}ContentItem is the shared model used across all Explore screens (feed grid, category details, subcategory details).
ExploreFeedAPI protocols:
public typealias ExploreFeedAPI = ExploreContentAPI & ExploreTagsAPI
public protocol ExploreContentAPI {
var pageSize: Int { get }
func getAllCategories() -> AnyPublisher<[BNCategory], Error>
func getContent(with tag: Tag, offset: Int, sortInfo: ...) -> AnyPublisher<[ContentItem], Error>
func getSubcategories(forCategory categoryId: String, ...) -> AnyPublisher<[ContentItem], Error>
}
public protocol ExploreTagsAPI {
func getExploreTags() -> AnyPublisher<[Tag], Error>
}Configuration
ExploreScreenConfigFactoryType
Customer apps implement this protocol to configure the Explore screen's visual appearance:
protocol ExploreScreenConfigFactoryType {
func makeExploreFeedScreenConfig(bgUrl: URL?) -> ExploreFeedScreenConfig
}ExploreFeedScreenConfig
The ExploreFeedScreenConfig struct controls the visual appearance of the generic Explore feed:
| Area | What It Controls |
|---|---|
| Status bar | Light/dark style |
| Navigation bar | Title text, logo image, search button image |
| Tags bar | Background color, shadow, spacing, "All" pill toggle, tag cell theme |
| Content grid | Insets, element spacing, cell theme configs, content type themes |
| Background | Background image URL, tint color |
| Skeleton | Loading state placeholder appearance |
| Error state | Error screen appearance and retry button |
Other Screen Configs
Each detail screen has its own configuration struct:
| Config | Screen | Key Controls |
|---|---|---|
CategoryDetailsScreenConfig | Category details | Category grid layout and theming |
SubcategoryDetailsScreenConfig | Subcategory details | Session grid, header config |
SubcategoryDetailsHeaderConfig | Subcategory header | Header image, title styling |
FacilitatorDetailsScreenConfig | Facilitator profile | Profile layout, cover image |
FacilitatorContentsListScreenConfig | Facilitator content | Content list layout |
ReviewsWidgetConfig | Subcategory reviews | Star rating display |
Navigation and Integration
ExploreFeedDelegate
The ExploreFeedDelegate protocol bridges ExploreFeedViewModel (in Core) with ExploreCoordinator (in Client):
| Delegate Method | Navigation Target |
|---|---|
showDetails(forSession:autoPlayContentIDs:) | Content playback via ContentDetailsCoordinator.shared |
showDetails(forCategory:) | Category details screen (push) |
showDetails(forSubcategory:) | Subcategory details screen (push) |
showDetails(forSubcategoryId:) | Subcategory details by ID (push) |
Additional navigation from the Explore screen:
| Action | Target |
|---|---|
| Search button tap | SearchCoordinator.shared.createSearchController() |
| Timer button tap | timerModuleInterface?.showTimerSettingsController() |
| Tag selection | displayTaggedItemsFeed() (push tag feed screen) |
Deep Link Support
Explore supports deep linking for content-related destinations:
| Deep Link Case | Behavior |
|---|---|
explore(tagId:) | Opens Explore with a pre-selected tag via preselectTagInExploreScreen(tagId:) |
category(id:) | Pushes category details screen |
subcategory(id:) | Pushes subcategory details screen |
tag(id:, sortInfo:) | Pushes tag feed screen with sort parameters |
Cross-Module Integration
| Module | Integration Point |
|---|---|
| Player | Sessions open via ContentDetailsCoordinator.shared with auto-play queue |
| Search | Search button navigates to SearchCoordinator.shared |
| Timer | Timer button available if TimerModuleInterface is injected |
| Home Screen | Custom mode reuses HomeViewModel with isUsedForExploreFeed: true |
Explore does not directly integrate with Tuya, Biometrics, or Community modules.
For Agents
Reading Order
When working with the Explore module, read files in this order:
Client/Coordinators/Tabs/ExploreCoordinator.swift(218 lines) for the dual mode logic, navigation, and delegate handlingCore/Screens/Explore/Feed/ExploreFeedViewModel.swift(228 lines) for the ViewModel,ExploreFeedDelegateprotocol, and data provider usageCore/Screens/Explore/Feed/ExploreFeedDataProvider.swift(192 lines) for pagination, deduplication, and state machineCore/Screens/Explore/Feed/ExploreFeedAPI.swift(80 lines) for the API protocol definitionsCore/Screens/Explore/Feed/ExploreFeedScreenConfig.swift(195 lines) for the configuration structureClient/App/Screen Config/FactoryTypes.swift(search forExploreScreenConfigFactoryType) for the configuration protocol
Key Files
| Component | File Path |
|---|---|
| ExploreCoordinator | Bricks/modules/Client/Client/Coordinators/Tabs/ExploreCoordinator.swift |
| ExploreFeedViewController | Bricks/modules/Core/Core/Screens/Explore/Feed/ExploreFeedViewController.swift |
| ExploreFeedViewModel | Bricks/modules/Core/Core/Screens/Explore/Feed/ExploreFeedViewModel.swift |
| ExploreFeedDataProvider | Bricks/modules/Core/Core/Screens/Explore/Feed/ExploreFeedDataProvider.swift |
| ExploreFeedAPI | Bricks/modules/Core/Core/Screens/Explore/Feed/ExploreFeedAPI.swift |
| ExploreFeedScreenConfig | Bricks/modules/Core/Core/Screens/Explore/Feed/ExploreFeedScreenConfig.swift |
| ExploreFeedView (SwiftUI) | Bricks/modules/Core/Core/Screens/Explore/Feed/View/ExploreFeedView.swift |
| TagsBarDataProvider | Bricks/modules/Core/Core/Screens/Explore/Feed/Tags Bar/TagsBarDataProvider.swift |
| ContentItem | Bricks/modules/Core/Core/Screens/Explore/Feed/Item/ContentItem.swift |
| CategoryViewController | Bricks/modules/Core/Core/Screens/Explore/Category Details/CategoryViewController.swift |
| SubcategoryViewController | Bricks/modules/Core/Core/Screens/Explore/Subcategory Details/SubcategoryViewController.swift |
| FacilitatorDetailsViewController | Bricks/modules/Core/Core/Screens/Explore/Facilitators/FacilitatorDetails/FacilitatorDetailsViewController.swift |
| FacilitatorContentsList | Bricks/modules/Core/Core/Screens/Explore/Facilitators/FacilitatorContentsList/FacilitatorContentsListViewController.swift |
| ExploreScreenConfigFactoryType | Bricks/modules/Client/Client/App/Screen Config/FactoryTypes.swift |
| Tab setup | Bricks/modules/Client/Client/Coordinators/Main/MainCoordinators+TabSetup.swift |
Tips
- To understand the two modes: check
ExploreCoordinator.start()which branches onExploreViewType. Generic createsExploreFeedViewController, custom createsHomeViewControllerwithisUsedForExploreFeed: true. - Tags are cached in
Settings.exploreTags(UserDefaults) so they display immediately on next launch while fresh tags load from the server. ContentItemis shared across all Explore screens. It uses aContentTypeenum (.category,.subcategory,.session) to differentiate item types.- ExploreCoordinator is only 218 lines, much simpler than HomeCoordinator's 1,146 lines. Most navigation delegates to
ContentDetailsCoordinator.shared. - Custom mode is essentially Home v1 in a different tab. If you understand the Home feed, you understand custom Explore. The only difference is data source (
ExploreServicevsHomeFeedService). - To add a new content type to the grid: update
ContentItem.ContentType, add handling inItemCellController, and updateExploreFeedDelegateif navigation differs.
What's Next
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.
YoullNetwork
The dual-transport networking layer providing GraphQL and REST APIs, token management, caching, and data models for the entire Youll platform.