Wonderous
How the pieces fit together
For someone long enough in the Flutter community, it is hard to miss the Wonderous App, a beautiful App built by the talented folks at GSkinner. This is a great example of a content-rich app showcasing wonders around the world with slick animations.
The Vyuh Framework was designed to work well with Content of all kinds, especially where the CMS-hosted Content becomes the centerpiece of your experience. The Wonderous App fits perfectly with this idea, and we decided to rebuild it using the Vyuh Framework.
Framework Features at Play
A variety of features from the framework are demonstrated:
- Custom content types -- The Wonder schema and its sections
- Custom documents with configurable section-based rendering
- Navigation between pages and templated routing
- Custom actions for user interactions
- API integrations with Unsplash for photos
- Google Maps integration for location display
- YouTube player for wonder videos
Distilling Wonderous
How the pieces fit together
The app centers around the 8 Wonders of the world. Each Wonder is a compiled set of information such as: facts and history, the construction process, location details, photos, and a timeline of events. The app experience is a master-detail scenario with the landing screen showing all the wonders and allowing navigation into each wonder's detail screens organized by bottom tabs.
How the pieces fit together
A Schema-first Approach
The approach starts with defining the Wonder as structured content. After identifying all content aspects of a wonder, a schema was created that describes the Wonder details including facts, history, construction information, location, photos, and timeline events.
How the pieces fit together
This schema translates to a CMS schema with Sanity.io. Each wonder is a separate document of type wonderous.wonder, created inside the CMS Studio with all the details gathered from the original Wonderous app.
export const wonder: SchemaTypeDefinition = defineType({
name: 'wonderous.wonder',
type: 'document',
title: 'Wonder',
fields: [
defineField({ name: 'identifier', title: 'Identifier', type: 'string' }),
defineField({ name: 'hexColor', title: 'Hex Color', type: 'string' }),
defineField({ name: 'title', title: 'Title', type: 'string' }),
defineField({ name: 'subtitle', title: 'Subtitle', type: 'string' }),
// ... additional fields for video, dates, photos, etc.
],
});How the pieces fit together
Navigation and Structure
How the pieces fit together
The journey starts with a list of Wonders and navigates into each wonder's details. Routes are defined on the CMS and linked to application routes using GoRouter.
How the pieces fit together
All wonders share the same visual structure -- they act as template routes where only the wonder-specific content changes. The Vyuh Framework supports this through CMSRoute (a subclass of GoRoute):
Future<List<RouteBase>> routes() async {
return [
CMSRoute(path: '/wonderous'),
CMSRoute(
path: '/wonderous/wonder/:wonder([^/]+)',
routes: [
StatefulShellRoute.indexedStack(
branches: [
StatefulShellBranch(routes: [
CMSRoute(path: 'details', cmsPathResolver: wonderPathResolver),
]),
StatefulShellBranch(routes: [
CMSRoute(path: 'events', cmsPathResolver: wonderPathResolver),
]),
StatefulShellBranch(routes: [
CMSRoute(path: 'photos', cmsPathResolver: wonderPathResolver),
]),
],
),
],
),
];
}The wonderPathResolver maps dynamic paths like /wonderous/wonder/taj-mahal/details to the CMS path /wonderous/wonder/details for content rendering.
Linking the wonder cards on the CMS is a matter of locating the card, editing its navigation action, and linking to the wonder details:
How the pieces fit together
How the pieces fit together
How the pieces fit together
Rendering Wonder Sections
How the pieces fit together
Each wonder is divided into sections (Hero, Facts & History, Construction, Location Info) as separate Content Blocks. This allows changing their order or adding more content between sections from the CMS.
The WonderSection content item and its layout are defined as a ContentBuilder within the FeatureDescriptor:
final feature = FeatureDescriptor(
name: 'wonderous',
title: 'Wonderous',
description: 'The Wonderous app as a Vyuh Feature',
icon: Icons.castle_outlined,
init: () async {
// Initialize API keys for Unsplash
},
routes: routes,
extensions: [
ContentExtensionDescriptor(
contentBuilders: [
ContentBuilder<WonderSection>(
content: WonderSection.typeDescriptor,
defaultLayout: WonderSectionLayout(),
defaultLayoutDescriptor: WonderSectionLayout.typeDescriptor,
),
],
)
],
);The WonderSectionLayout renders each section type appropriately, and a shared Future<Wonder> ensures no duplicate network calls are made when fetching wonder data.
Managing Wonders
How the pieces fit together
With the CMS-driven approach, adding new wonders is a matter of gathering the visual assets, collecting the facts, creating a wonder document, and publishing. Updates to existing wonders are live to customers in minutes rather than requiring a new app release.
Disclaimer
All assets used in building this version of Wonderous are the property of GSkinner. They are used purely for a technical exploration to demonstrate the abilities of the Vyuh Framework.
Read the Code
See the code for the example on GitHub: Wonderous