diff --git a/examples/movie_app/assets/jsons/screens/detail_screen.json b/examples/movie_app/assets/jsons/screens/detail_screen.json deleted file mode 100644 index 0dbd26cf7..000000000 --- a/examples/movie_app/assets/jsons/screens/detail_screen.json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "type": "scaffold", - "extendBodyBehindAppBar": true, - "appBar": { - "type": "appBar", - "backgroundColor": "transparent", - "leading": { - "type": "iconButton", - "icon": { - "type": "icon", - "icon": "chevron_left", - "color": "onSurface" - }, - "style": { - "backgroundColor": "#50050608", - "fixedSize": { - "width": 36, - "height": 36 - } - }, - "onPressed": { - "actionType": "navigate", - "navigationStyle": "pop" - } - } - }, - "body": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/{{movie_id}}?language=en-US", - "method": "get" - }, - "template": { - "type": "singleChildScrollView", - "child": { - "type": "column", - "children": [ - { - "type": "stack", - "children": [ - { - "type": "image", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 1000, - "height": 480, - "fit": "cover" - }, - { - "type": "positioned", - "bottom": 0, - "left": 0, - "right": 0, - "child": { - "type": "container", - "height": 240, - "decoration": { - "gradient": { - "colors": [ - "#00050608", - "#050608" - ], - "begin": "topCenter", - "end": "bottomCenter", - "stops": [ - 0.0, - 1.0 - ] - } - } - } - } - ] - }, - { - "type": "padding", - "padding": { - "left": 16, - "right": 16 - }, - "child": { - "type": "column", - "crossAxisAlignment": "start", - "children": [ - { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "expanded", - "child": { - "type": "text", - "data": "{{title}}", - "style": "headlineMedium", - "overflow": "ellipsis" - } - }, - { - "type": "container", - "height": 24, - "decoration": { - "borderRadius": 4, - "color": "primary" - }, - "child": { - "type": "row", - "children": [ - { - "type": "sizedBox", - "width": 6 - }, - { - "type": "icon", - "icon": "star_rounded", - "color": "onPrimary", - "size": 14 - }, - { - "type": "sizedBox", - "width": 2 - }, - { - "type": "text", - "data": "{{vote_average}}", - "style": { - "color": "onPrimary", - "fontSize": 14 - } - }, - { - "type": "sizedBox", - "width": 6 - } - ] - } - } - ] - }, - { - "type": "divider" - }, - { - "type": "text", - "data": "{{release_date}} · {{runtime}} mins", - "style": "bodySmall", - "textAlign": "left" - }, - { - "type": "divider" - }, - { - "type": "row", - "children": [ - { - "type": "expanded", - "child": { - "type": "filledButton", - "child": { - "type": "row", - "mainAxisAlignment": "center", - "children": [ - { - "type": "icon", - "icon": "play_circle_filled", - "size": 24 - }, - { - "type": "sizedBox", - "width": 6 - }, - { - "type": "text", - "data": "Watch Trailer" - } - ] - }, - "onPressed": {} - } - }, - { - "type": "sizedBox", - "width": 16 - }, - { - "type": "outlinedButton", - "child": { - "type": "row", - "mainAxisAlignment": "center", - "children": [ - { - "type": "icon", - "icon": "favorite_outline", - "size": 24 - }, - { - "type": "sizedBox", - "width": 6 - }, - { - "type": "text", - "data": "Add to Watchlist" - } - ] - }, - "onPressed": {} - } - ] - }, - { - "type": "sizedBox", - "height": 24 - }, - { - "type": "column", - "crossAxisAlignment": "start", - "children": [ - { - "type": "text", - "data": "About", - "style": "bodyMedium" - }, - { - "type": "sizedBox", - "height": 4 - }, - { - "type": "container", - "width": 24, - "height": 2, - "color": "primary" - } - ] - }, - { - "type": "sizedBox", - "height": 20 - }, - { - "type": "text", - "data": "{{overview}}", - "style": "bodyMedium" - }, - { - "type": "sizedBox", - "height": 24 - }, - { - "type": "column", - "crossAxisAlignment": "start", - "children": [ - { - "type": "text", - "data": "Cast", - "style": { - "fontSize": 16, - "fontWeight": "w600", - "height": 1.3, - "color": "onSurfaceVariant" - } - }, - { - "type": "sizedBox", - "height": 10 - }, - { - "type": "sizedBox", - "height": 146, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/{{movie_id}}/credits?language=en-US", - "method": "get" - }, - "targetPath": "cast", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 16 - }, - "itemTemplate": { - "type": "sizedBox", - "width": 80, - "child": { - "type": "column", - "crossAxisAlignment": "start", - "children": [ - { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{profile_path}}", - "fit": "cover", - "width": 80, - "height": 96 - } - }, - { - "type": "sizedBox", - "height": 8 - }, - { - "type": "text", - "data": "{{name}}", - "style": "titleSmall", - "overflow": "ellipsis" - }, - { - "type": "text", - "data": "{{character}}", - "style": "bodySmall", - "overflow": "ellipsis" - } - ] - } - } - } - } - } - ] - }, - { - "type": "sizedBox", - "height": 24 - }, - { - "type": "column", - "crossAxisAlignment": "start", - "children": [ - { - "type": "text", - "data": "Similar Movies", - "style": { - "fontSize": 16, - "fontWeight": "w600", - "height": 1.3, - "color": "onSurfaceVariant" - } - }, - { - "type": "sizedBox", - "height": 10 - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/{{movie_id}}/similar?language=en-US&page=1", - "method": "get" - }, - "targetPath": "results", - "resultTarget": "data", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{data.id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{data.poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "sizedBox", - "height": 80 - } - ] - } - } - ] - } - } - } -} \ No newline at end of file diff --git a/examples/movie_app/assets/jsons/screens/home_screen.json b/examples/movie_app/assets/jsons/screens/home_screen.json deleted file mode 100644 index 1a6144fe0..000000000 --- a/examples/movie_app/assets/jsons/screens/home_screen.json +++ /dev/null @@ -1,453 +0,0 @@ -{ - "type": "defaultBottomNavigationController", - "length": 3, - "child": { - "type": "scaffold", - "extendBodyBehindAppBar": true, - "body": { - "type": "bottomNavigationView", - "children": [ - { - "type": "listView", - "padding": 0, - "children": [ - { - "type": "movieCarousel", - "request": { - "url": "https://api.themoviedb.org/3/trending/movie/day?language=en-US", - "method": "get" - } - }, - { - "type": "column", - "children": [ - { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 24, - "bottom": 10 - }, - "child": { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "text", - "data": "Now Playing", - "style": "labelLarge" - } - ] - } - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=1", - "method": "get" - }, - "targetPath": "results", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "padding": { - "left": 16 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "column", - "children": [ - { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 24, - "bottom": 10 - }, - "child": { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "text", - "data": "Popular Movies", - "style": "labelLarge" - } - ] - } - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/popular?language=en-US&page=1", - "method": "get" - }, - "targetPath": "results", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "padding": { - "left": 16 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "column", - "children": [ - { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 24, - "bottom": 10 - }, - "child": { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "text", - "data": "Trending Movies", - "style": "labelLarge" - } - ] - } - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/trending/movie/day?language=en-US", - "method": "get" - }, - "targetPath": "results", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "padding": { - "left": 20 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "column", - "children": [ - { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 24, - "bottom": 10 - }, - "child": { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "text", - "data": "Top Rated", - "style": "labelLarge" - } - ] - } - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/top_rated?language=en-US&page=1", - "method": "get" - }, - "targetPath": "results", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "padding": { - "left": 16 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "column", - "children": [ - { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 24, - "bottom": 10 - }, - "child": { - "type": "row", - "mainAxisAlignment": "spaceBetween", - "children": [ - { - "type": "text", - "data": "Upcoming Movies", - "style": "labelLarge" - } - ] - } - }, - { - "type": "sizedBox", - "height": 164, - "child": { - "type": "dynamicView", - "request": { - "url": "https://api.themoviedb.org/3/movie/upcoming?language=en-US&page=1", - "method": "get" - }, - "targetPath": "results", - "template": { - "type": "listView", - "scrollDirection": "horizontal", - "shrinkWrap": true, - "separator": { - "type": "sizedBox", - "width": 8 - }, - "padding": { - "left": 16 - }, - "itemTemplate": { - "type": "gestureDetector", - "onTap": { - "actionType": "setValue", - "values": [ - { - "key": "movie_id", - "value": "{{id}}" - } - ], - "action": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/detail_screen.json" - } - }, - "child": { - "type": "clipRRect", - "borderRadius": 6, - "child": { - "type": "image", - "imageType": "network", - "src": "https://media.themoviedb.org/t/p/w440_and_h660_face/{{poster_path}}", - "width": 108, - "height": 164 - } - } - } - } - } - } - ] - }, - { - "type": "sizedBox", - "height": 80 - } - ] - }, - { - "type": "center", - "child": { - "type": "text", - "data": "Search" - } - }, - { - "type": "center", - "child": { - "type": "text", - "data": "Profile" - } - } - ] - }, - "bottomNavigationBar": { - "type": "bottomNavigationBar", - "items": [ - { - "type": "navigationBarItem", - "label": "Home", - "icon": { - "type": "icon", - "icon": "home_outlined" - } - }, - { - "type": "navigationBarItem", - "label": "Search", - "icon": { - "type": "icon", - "icon": "search_outlined" - } - }, - { - "type": "navigationBarItem", - "label": "Profile", - "icon": { - "type": "icon", - "icon": "person_outlined" - } - } - ] - } - } -} \ No newline at end of file diff --git a/examples/movie_app/assets/jsons/screens/onboarding_screen.json b/examples/movie_app/assets/jsons/screens/onboarding_screen.json deleted file mode 100644 index b956160e8..000000000 --- a/examples/movie_app/assets/jsons/screens/onboarding_screen.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "type": "scaffold", - "body": { - "type": "stack", - "children": [ - { - "type": "image", - "imageType": "asset", - "src": "assets/images/image.png", - "width": 10000, - "height": 10000, - "fit": "cover" - }, - { - "type": "positioned", - "left": 0, - "right": 0, - "bottom": 0, - "child": { - "type": "container", - "width": 1000, - "height": 500, - "decoration": { - "gradient": { - "gradientType": "linear", - "colors": [ - "#00050608", - "#050608", - "#050608" - ], - "begin": "topCenter", - "end": "bottomCenter", - "stops": [ - 0.0, - 0.8, - 1.0 - ] - } - }, - "child": { - "type": "padding", - "padding": { - "left": 16, - "right": 16, - "top": 48, - "bottom": 48 - }, - "child": { - "type": "column", - "mainAxisAlignment": "end", - "crossAxisAlignment": "start", - "children": [ - { - "type": "text", - "data": "Movie ", - "style": "displayMedium", - "children": [ - { - "text": "\nDatabase", - "style": { - "color": "primary" - } - } - ] - }, - { - "type": "sizedBox", - "height": 24 - }, - { - "type": "text", - "data": "Watch & enjoy a variety of award winning TV shows, movies, anime, and a lot more", - "style": "bodyMedium" - }, - { - "type": "sizedBox", - "height": 64 - }, - { - "type": "sizedBox", - "height": 48, - "width": 1000, - "child": { - "type": "filledButton", - "child": { - "type": "text", - "data": "Get Started" - }, - "onPressed": { - "actionType": "navigate", - "assetPath": "assets/jsons/screens/home_screen.json" - } - } - } - ] - } - } - } - } - ] - } -} \ No newline at end of file diff --git a/examples/movie_app/assets/jsons/theme/light_theme.json b/examples/movie_app/assets/jsons/theme/light_theme.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/movie_app/lib/constants/app_api.dart b/examples/movie_app/lib/constants/app_api.dart new file mode 100644 index 000000000..a491b44e8 --- /dev/null +++ b/examples/movie_app/lib/constants/app_api.dart @@ -0,0 +1,64 @@ +/// Application API endpoints and configuration +/// +/// This file contains all API-related constants including base URLs, +/// endpoints, and API configuration. +class AppApi { + AppApi._(); // Private constructor to prevent instantiation + + // ============================================================================ + // Base Configuration + // ============================================================================ + + static const String baseUrl = 'https://api.themoviedb.org/3'; + static const String imageBaseUrl = + 'https://media.themoviedb.org/t/p/w440_and_h660_face'; + static const String language = 'en-US'; + + // ============================================================================ + // Movie Endpoints + // ============================================================================ + + /// Trending movies endpoint for the current day + static String getTrendingMoviesUrl([int page = 1]) => + '$baseUrl/trending/movie/day?language=$language&page=$page'; + + /// Now playing movies endpoint + static String getNowPlayingMoviesUrl([int page = 1]) => + '$baseUrl/movie/now_playing?language=$language&page=$page'; + + /// Popular movies endpoint + static String getPopularMoviesUrl([int page = 1]) => + '$baseUrl/movie/popular?language=$language&page=$page'; + + /// Top rated movies endpoint + static String getTopRatedMoviesUrl([int page = 1]) => + '$baseUrl/movie/top_rated?language=$language&page=$page'; + + /// Upcoming movies endpoint + static String getUpcomingMoviesUrl([int page = 1]) => + '$baseUrl/movie/upcoming?language=$language&page=$page'; + + /// Movie details endpoint + static String getMovieDetailsUrl(int movieId) => + '$baseUrl/movie/$movieId?language=$language'; + + /// Movie credits endpoint + static String getMovieCreditsUrl(int movieId) => + '$baseUrl/movie/$movieId/credits?language=$language'; + + /// Similar movies endpoint + static String getSimilarMoviesUrl(int movieId, [int page = 1]) => + '$baseUrl/movie/$movieId/similar?language=$language&page=$page'; + + // ============================================================================ + // Image URLs + // ============================================================================ + + /// Get full poster image URL from poster path + static String getPosterImageUrl(String posterPath) => + '$imageBaseUrl/$posterPath'; + + /// Get full profile image URL from profile path + static String getProfileImageUrl(String profilePath) => + '$imageBaseUrl/$profilePath'; +} diff --git a/examples/movie_app/lib/constants/app_assets.dart b/examples/movie_app/lib/constants/app_assets.dart new file mode 100644 index 000000000..8d018e42d --- /dev/null +++ b/examples/movie_app/lib/constants/app_assets.dart @@ -0,0 +1,22 @@ +/// Application asset paths +/// +/// This file contains all asset paths used throughout the application. +class AppAssets { + AppAssets._(); // Private constructor to prevent instantiation + + // ============================================================================ + // Images + // ============================================================================ + + static const String onboardingImage = 'assets/images/image.png'; + + // ============================================================================ + // JSON Screens + // ============================================================================ + + static const String onboardingScreenJson = + 'assets/jsons/screens/onboarding_screen.json'; + static const String homeScreenJson = 'assets/jsons/screens/home_screen.json'; + static const String detailScreenJson = + 'assets/jsons/screens/detail_screen.json'; +} diff --git a/examples/movie_app/lib/constants/app_constants.dart b/examples/movie_app/lib/constants/app_constants.dart new file mode 100644 index 000000000..7b0c277f5 --- /dev/null +++ b/examples/movie_app/lib/constants/app_constants.dart @@ -0,0 +1,9 @@ +library; + +/// Applicationwide constants +/// +/// This file exports all constants used throughout the application +/// for convenient access via a single import. +export 'app_api.dart'; +export 'app_assets.dart'; +export 'app_strings.dart'; diff --git a/examples/movie_app/lib/constants/app_strings.dart b/examples/movie_app/lib/constants/app_strings.dart new file mode 100644 index 000000000..4f4f49336 --- /dev/null +++ b/examples/movie_app/lib/constants/app_strings.dart @@ -0,0 +1,49 @@ +/// Application-wide string constants +/// +/// This file contains all user-facing strings and text content used throughout +/// the application. Grouped by feature/screen for easy maintenance. +class AppStrings { + AppStrings._(); // Private constructor to prevent instantiation + + // ============================================================================ + // Onboarding Screen + // ============================================================================ + + static const String onboardingTitle = 'Movie '; + static const String onboardingTitleAccent = '\nDatabase'; + static const String onboardingDescription = + 'Watch & enjoy a variety of award winning TV shows, movies, anime, and a lot more'; + static const String onboardingGetStartedButton = 'Get Started'; + + // ============================================================================ + // Home Screen + // ============================================================================ + + static const String nowPlaying = 'Now Playing'; + static const String popularMovies = 'Popular Movies'; + static const String trendingMovies = 'Trending Movies'; + static const String topRated = 'Top Rated'; + static const String upcomingMovies = 'Upcoming Movies'; + + // Bottom Navigation + static const String bottomNavHome = 'Home'; + static const String bottomNavSearch = 'Search'; + static const String bottomNavProfile = 'Profile'; + + // ============================================================================ + // Detail Screen + // ============================================================================ + + static const String watchTrailer = 'Watch Trailer'; + static const String addToWatchlist = 'Add to Watchlist'; + static const String about = 'About'; + static const String cast = 'Cast'; + static const String similarMovies = 'Similar Movies'; + + // ============================================================================ + // Common + // ============================================================================ + + static const String search = 'Search'; + static const String profile = 'Profile'; +} diff --git a/examples/movie_app/lib/default_stac_options.dart b/examples/movie_app/lib/default_stac_options.dart new file mode 100644 index 000000000..84eb0d686 --- /dev/null +++ b/examples/movie_app/lib/default_stac_options.dart @@ -0,0 +1,25 @@ +// This file is automatically generated by stac init. + +import 'package:stac_core/core/stac_options.dart'; + +/// Default [StacOptions] for use with your stac project. +/// +/// Use this to initialize stac **before** calling [runApp]. +/// +/// Example: +/// ```dart +/// import 'package:flutter/material.dart'; +/// import 'package:stac/stac.dart'; +/// import 'default_stac_options.dart'; +/// +/// void main() { +/// Stac.initialize(options: defaultStacOptions); +/// +/// runApp(...); +/// } +/// ``` +StacOptions get defaultStacOptions => StacOptions( + name: 'movie_app', + description: '', + projectId: 'pha1PAyoVRqREK5M2k3E', +); diff --git a/examples/movie_app/lib/main.dart b/examples/movie_app/lib/main.dart index da663068e..3ec8688a5 100644 --- a/examples/movie_app/lib/main.dart +++ b/examples/movie_app/lib/main.dart @@ -1,5 +1,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:movie_app/default_stac_options.dart'; +import 'package:movie_app/themes/app_theme.dart'; import 'package:movie_app/widgets/movie_carousel/movie_carousel_parser.dart'; import 'package:stac/stac.dart'; @@ -17,7 +19,11 @@ void main() async { ), ); - await Stac.initialize(dio: dio, parsers: [MovieCarouselParser()]); + await Stac.initialize( + options: defaultStacOptions, + dio: dio, + parsers: [MovieCarouselParser()], + ); runApp(const MyApp()); } @@ -29,83 +35,10 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return StacApp( title: 'Flutter Demo', - theme: StacTheme.fromJson(darkThemeJson), + theme: darkTheme, homeBuilder: (_) { - return Stac.fromAssets('assets/jsons/screens/onboarding_screen.json'); + return Stac(routeName: 'onboarding_screen'); }, ); } } - -final Map lightThemeJson = { - "brightness": "light", - "colorScheme": { - "brightness": "light", - "primary": "#14865F", - "onPrimary": "#FFFFFF", - "secondary": "#14865F", - "onSecondary": "#FFFFFF", - "background": "#FFFFFF", - "onBackground": "#010810", - "surface": "#FFFFFF", - "onSurface": "#010810", - "surfaceVariant": "#F6F7F8", - "onSurfaceVariant": "#65010810", - "error": "#FD1717", - "onError": "#FFFFFF", - "outline": "#080110810", - "onOutline": "#120110810", - }, -}; - -final Map darkThemeJson = { - "brightness": "dark", - "colorScheme": { - "brightness": "dark", - "primary": "#95E183", - "onPrimary": "#050608", - "secondary": "#95E183", - "onSecondary": "#FFFFFF", - "background": "#050608", - "onBackground": "#FFFFFF", - "surface": "#050608", - "onSurface": "#FFFFFF", - "surfaceVariant": "#101214", - "onSurfaceVariant": "#65FFFFFF", - "error": "#FF6565", - "onError": "#050608", - "outline": "#08FFFFFF", - "onOutline": "#12FFFFFF", - }, - "textTheme": { - "displayLarge": {"fontSize": 48, "fontWeight": "w700", "height": 1.1}, - "displayMedium": {"fontSize": 40, "fontWeight": "w700", "height": 1.1}, - "displaySmall": {"fontSize": 34, "fontWeight": "w700", "height": 1.1}, - "headlineLarge": {"fontSize": 30, "fontWeight": "w700", "height": 1.3}, - "headlineMedium": {"fontSize": 26, "fontWeight": "w700", "height": 1.3}, - "headlineSmall": {"fontSize": 23, "fontWeight": "w700", "height": 1.3}, - "titleLarge": {"fontSize": 20, "fontWeight": "w500", "height": 1.3}, - "titleMedium": {"fontSize": 18, "fontWeight": "w500", "height": 1.3}, - "titleSmall": {"fontSize": 16, "fontWeight": "w500", "height": 1.3}, - "labelLarge": {"fontSize": 16, "fontWeight": "w700", "height": 1.3}, - "labelMedium": {"fontSize": 14, "fontWeight": "w600", "height": 1.3}, - "labelSmall": {"fontSize": 12, "fontWeight": "w500", "height": 1.3}, - "bodyLarge": {"fontSize": 18, "fontWeight": "w400", "height": 1.5}, - "bodyMedium": {"fontSize": 16, "fontWeight": "w400", "height": 1.5}, - "bodySmall": {"fontSize": 14, "fontWeight": "w400", "height": 1.5}, - }, - "filledButtonTheme": { - "minimumSize": {"width": 120, "height": 40}, - "textStyle": {"fontSize": 16, "fontWeight": "w500", "height": 1.3}, - "padding": {"left": 10, "right": 10, "top": 8, "bottom": 8}, - "shape": {"type": "roundedRectangleBorder", "borderRadius": 8}, - }, - "outlinedButtonTheme": { - "minimumSize": {"width": 120, "height": 40}, - "textStyle": {"fontSize": 16, "fontWeight": "w500", "height": 1.3}, - "padding": {"left": 10, "right": 10, "top": 8, "bottom": 8}, - "side": {"color": "#95E183", "width": 1.0}, - "shape": {"type": "roundedRectangleBorder", "borderRadius": 8}, - }, - "dividerTheme": {"color": "#24FFFFFF", "thickness": 1}, -}; diff --git a/examples/movie_app/lib/themes/app_theme.dart b/examples/movie_app/lib/themes/app_theme.dart new file mode 100644 index 000000000..84e694bca --- /dev/null +++ b/examples/movie_app/lib/themes/app_theme.dart @@ -0,0 +1,120 @@ +import 'package:stac_core/stac_core.dart'; // Hide StacTheme from stac_core to use the one from stac + +/// Dark theme for the Movie App. +StacTheme get darkTheme { + return StacTheme( + brightness: StacBrightness.dark, + colorScheme: StacColorScheme( + brightness: StacBrightness.dark, + primary: '#95E183', + onPrimary: '#050608', + secondary: '#95E183', + onSecondary: '#FFFFFF', + surface: '#050608', + onSurface: '#FFFFFF', + onSurfaceVariant: '#65FFFFFF', + error: '#FF6565', + onError: '#050608', + outline: '#08FFFFFF', + ), + textTheme: StacTextTheme( + displayLarge: StacCustomTextStyle( + fontSize: 48, + fontWeight: StacFontWeight.w700, + height: 1.1, + ), + displayMedium: StacCustomTextStyle( + fontSize: 40, + fontWeight: StacFontWeight.w700, + height: 1.1, + ), + displaySmall: StacCustomTextStyle( + fontSize: 34, + fontWeight: StacFontWeight.w700, + height: 1.1, + ), + headlineLarge: StacCustomTextStyle( + fontSize: 30, + fontWeight: StacFontWeight.w700, + height: 1.3, + ), + headlineMedium: StacCustomTextStyle( + fontSize: 26, + fontWeight: StacFontWeight.w700, + height: 1.3, + ), + headlineSmall: StacCustomTextStyle( + fontSize: 23, + fontWeight: StacFontWeight.w700, + height: 1.3, + ), + titleLarge: StacCustomTextStyle( + fontSize: 20, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + titleMedium: StacCustomTextStyle( + fontSize: 18, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + titleSmall: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + labelLarge: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w700, + height: 1.3, + ), + labelMedium: StacCustomTextStyle( + fontSize: 14, + fontWeight: StacFontWeight.w600, + height: 1.3, + ), + labelSmall: StacCustomTextStyle( + fontSize: 12, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + bodyLarge: StacCustomTextStyle( + fontSize: 18, + fontWeight: StacFontWeight.w400, + height: 1.5, + ), + bodyMedium: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w400, + height: 1.5, + ), + bodySmall: StacCustomTextStyle( + fontSize: 14, + fontWeight: StacFontWeight.w400, + height: 1.5, + ), + ), + filledButtonTheme: StacButtonStyle( + minimumSize: StacSize(120, 40), + textStyle: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + padding: StacEdgeInsets.only(left: 10, right: 10, top: 8, bottom: 8), + shape: StacRoundedRectangleBorder(borderRadius: StacBorderRadius.all(8)), + ), + outlinedButtonTheme: StacButtonStyle( + minimumSize: StacSize(120, 40), + textStyle: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w500, + height: 1.3, + ), + padding: StacEdgeInsets.only(left: 10, right: 10, top: 8, bottom: 8), + side: StacBorderSide(color: '#95E183', width: 1.0), + shape: StacRoundedRectangleBorder(borderRadius: StacBorderRadius.all(8)), + ), + dividerTheme: StacDividerThemeData(color: '#24FFFFFF', thickness: 1), + ); +} diff --git a/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart index a021bab4f..0271fc7d9 100644 --- a/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart +++ b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart @@ -1,15 +1,54 @@ +import 'package:json_annotation/json_annotation.dart'; import 'package:stac_core/actions/network_request/stac_network_request.dart'; +import 'package:stac_core/core/stac_widget.dart'; -class MovieCarousel { - MovieCarousel({required this.request}); +part 'movie_carousel.g.dart'; +/// A Stac model representing a movie carousel widget. +/// +/// Displays a carousel of trending movies fetched from a network request. +/// +/// {@tool snippet} +/// Dart Example: +/// ```dart +/// StacMovieCarousel( +/// request: StacNetworkRequest( +/// url: 'https://api.themoviedb.org/3/trending/movie/day', +/// method: Method.get, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// JSON Example: +/// ```json +/// { +/// "type": "movieCarousel", +/// "request": { +/// "actionType": "networkRequest", +/// "url": "https://api.themoviedb.org/3/trending/movie/day", +/// "method": "get" +/// } +/// } +/// ``` +/// {@end-tool} +@JsonSerializable() +class StacMovieCarousel extends StacWidget { + const StacMovieCarousel({required this.request}); + + /// The network request to fetch movie data for the carousel. final StacNetworkRequest request; - factory MovieCarousel.fromJson(Map json) { - return MovieCarousel(request: StacNetworkRequest.fromJson(json['request'])); - } + /// Widget type identifier. + @override + String get type => 'movieCarousel'; + + /// Creates a [StacMovieCarousel] from a JSON map. + factory StacMovieCarousel.fromJson(Map json) => + _$StacMovieCarouselFromJson(json); - Map toJson() { - return {'request': request.toJson()}; - } + /// Converts this [StacMovieCarousel] instance to a JSON map. + @override + Map toJson() => _$StacMovieCarouselToJson(this); } diff --git a/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.g.dart b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.g.dart new file mode 100644 index 000000000..b35d52b8b --- /dev/null +++ b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'movie_carousel.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StacMovieCarousel _$StacMovieCarouselFromJson(Map json) => + StacMovieCarousel( + request: StacNetworkRequest.fromJson( + json['request'] as Map, + ), + ); + +Map _$StacMovieCarouselToJson(StacMovieCarousel instance) => + {'request': instance.request, 'type': instance.type}; diff --git a/examples/movie_app/lib/widgets/movie_carousel/movie_carousel_parser.dart b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel_parser.dart index 65b240d4b..4f24a42f4 100644 --- a/examples/movie_app/lib/widgets/movie_carousel/movie_carousel_parser.dart +++ b/examples/movie_app/lib/widgets/movie_carousel/movie_carousel_parser.dart @@ -3,18 +3,18 @@ import 'package:movie_app/widgets/movie_carousel/movie_carousel.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:stac/stac.dart'; -class MovieCarouselParser extends StacParser { +class MovieCarouselParser extends StacParser { const MovieCarouselParser(); @override String get type => 'movieCarousel'; @override - MovieCarousel getModel(Map json) => - MovieCarousel.fromJson(json); + StacMovieCarousel getModel(Map json) => + StacMovieCarousel.fromJson(json); @override - Widget parse(BuildContext context, MovieCarousel model) { + Widget parse(BuildContext context, StacMovieCarousel model) { return FutureBuilder( future: StacNetworkService.request(context, model.request), builder: (context, snapshot) { diff --git a/examples/movie_app/pubspec.lock b/examples/movie_app/pubspec.lock index 3b765f6f6..20ddc4db6 100644 --- a/examples/movie_app/pubspec.lock +++ b/examples/movie_app/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" args: dependency: transitive description: @@ -25,6 +41,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: a9461b8e586bf018dd4afd2e13b49b08c6a844a4b226c8d1d10f3a723cdd78c3 + url: "https://pub.dev" + source: hosted + version: "2.10.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + url: "https://pub.dev" + source: hosted + version: "8.12.0" cached_network_image: dependency: transitive description: @@ -57,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -65,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.dev" + source: hosted + version: "4.11.0" collection: dependency: transitive description: @@ -73,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" crypto: dependency: transitive description: @@ -89,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + url: "https://pub.dev" + source: hosted + version: "3.1.2" dio: dependency: "direct main" description: @@ -171,6 +267,22 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" http: dependency: transitive description: @@ -179,6 +291,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -187,14 +307,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - json_annotation: + io: dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: "direct main" description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" + url: "https://pub.dev" + source: hosted + version: "6.11.1" leak_tracker: dependency: transitive description: @@ -235,6 +371,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -259,6 +403,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" octo_image: dependency: transitive description: @@ -267,6 +419,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -355,6 +515,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" rxdart: dependency: transitive description: @@ -363,6 +547,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -376,6 +576,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + url: "https://pub.dev" + source: hosted + version: "1.3.8" source_span: dependency: transitive description: @@ -476,6 +692,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -564,6 +788,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + url: "https://pub.dev" + source: hosted + version: "1.1.4" web: dependency: transitive description: @@ -572,6 +804,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" xdg_directories: dependency: transitive description: @@ -588,6 +836,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.27.0" diff --git a/examples/movie_app/pubspec.yaml b/examples/movie_app/pubspec.yaml index cb8a7eb67..4d65983b7 100644 --- a/examples/movie_app/pubspec.yaml +++ b/examples/movie_app/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ^3.7.2 + sdk: ^3.8.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -34,10 +34,11 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - stac: ^1.0.0-dev.6 - stac_core: + stac: ^1.0.1 + stac_core: ^1.0.0 dio: ^5.8.0+1 smooth_page_indicator: ^1.2.1 + json_annotation: ^4.9.0 dev_dependencies: flutter_test: @@ -49,6 +50,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + build_runner: ^2.4.0 + json_serializable: ^6.8.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -61,7 +64,6 @@ flutter: # the material Icons class. uses-material-design: true assets: - - assets/jsons/screens/ - assets/images/ # To add assets to your application, add an assets section, like this: # assets: diff --git a/examples/movie_app/stac/detail_screen.dart b/examples/movie_app/stac/detail_screen.dart new file mode 100644 index 000000000..b3557d0d3 --- /dev/null +++ b/examples/movie_app/stac/detail_screen.dart @@ -0,0 +1,287 @@ +import 'package:movie_app/constants/app_constants.dart'; +import 'package:stac_core/stac_core.dart'; + +@StacScreen(screenName: 'detail_screen') +StacWidget detailScreen() { + return StacScaffold( + extendBodyBehindAppBar: true, + appBar: StacAppBar( + backgroundColor: 'transparent', + leading: StacIconButton( + icon: StacIcon(icon: 'chevron_left', color: 'onSurface'), + style: StacButtonStyle( + backgroundColor: '#50050608', + fixedSize: StacSize(36, 36), + ), + onPressed: StacAction( + jsonData: {'actionType': 'navigate', 'navigationStyle': 'pop'}, + ), + ), + ), + body: StacDynamicView( + request: StacNetworkRequest( + url: '${AppApi.baseUrl}/movie/{{movie_id}}?language=${AppApi.language}', + method: Method.get, + ), + template: StacSingleChildScrollView( + child: StacColumn( + children: [ + StacStack( + children: [ + StacImage( + src: '${AppApi.imageBaseUrl}/{{poster_path}}', + width: double.maxFinite, + height: 480, + fit: StacBoxFit.cover, + ), + StacPositioned( + bottom: 0, + left: 0, + right: 0, + child: StacContainer( + height: 240, + decoration: StacBoxDecoration( + gradient: StacGradient.linear( + colors: ['#00050608', '#050608'], + begin: StacAlignment.topCenter, + end: StacAlignment.bottomCenter, + stops: [0.0, 1.0], + ), + ), + ), + ), + ], + ), + StacPadding( + padding: StacEdgeInsets.only(left: 16, right: 16), + child: StacColumn( + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacExpanded( + child: StacText( + data: '{{title}}', + style: StacThemeData.textTheme.headlineMedium, + overflow: StacTextOverflow.ellipsis, + ), + ), + StacContainer( + height: 24, + decoration: StacBoxDecoration( + borderRadius: StacBorderRadius.all(4), + color: 'primary', + ), + child: StacRow( + children: [ + StacSizedBox(width: 6), + StacIcon( + icon: 'star_rounded', + color: 'onPrimary', + size: 14, + ), + StacSizedBox(width: 2), + StacText( + data: '{{vote_average}}', + style: StacCustomTextStyle( + color: 'onPrimary', + fontSize: 14, + ), + ), + StacSizedBox(width: 6), + ], + ), + ), + ], + ), + StacDivider(), + StacText( + data: '{{release_date}} · {{runtime}} mins', + style: StacThemeData.textTheme.bodySmall, + textAlign: StacTextAlign.left, + ), + StacDivider(), + StacRow( + children: [ + StacExpanded( + child: StacFilledButton( + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.center, + children: [ + StacIcon(icon: 'play_circle_filled', size: 24), + StacSizedBox(width: 6), + StacText(data: AppStrings.watchTrailer), + ], + ), + onPressed: null, + ), + ), + StacSizedBox(width: 16), + StacOutlinedButton( + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.center, + children: [ + StacIcon(icon: 'favorite_outline', size: 24), + StacSizedBox(width: 6), + StacText(data: AppStrings.addToWatchlist), + ], + ), + onPressed: null, + ), + ], + ), + StacSizedBox(height: 24), + StacColumn( + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacText( + data: AppStrings.about, + style: StacThemeData.textTheme.bodyMedium, + ), + StacSizedBox(height: 4), + StacContainer(width: 24, height: 2, color: 'primary'), + ], + ), + StacSizedBox(height: 20), + StacText( + data: '{{overview}}', + style: StacThemeData.textTheme.bodyMedium, + ), + StacSizedBox(height: 24), + StacColumn( + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacText( + data: AppStrings.cast, + style: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w600, + height: 1.3, + color: 'onSurfaceVariant', + ), + ), + StacSizedBox(height: 10), + StacSizedBox( + height: 146, + child: StacDynamicView( + request: StacNetworkRequest( + url: + '${AppApi.baseUrl}/movie/{{movie_id}}/credits?language=${AppApi.language}', + method: Method.get, + ), + targetPath: 'cast', + template: _buildCastListViewTemplate(), + ), + ), + ], + ), + StacSizedBox(height: 24), + StacColumn( + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacText( + data: AppStrings.similarMovies, + style: StacCustomTextStyle( + fontSize: 16, + fontWeight: StacFontWeight.w600, + height: 1.3, + color: 'onSurfaceVariant', + ), + ), + StacSizedBox(height: 10), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: + '${AppApi.baseUrl}/movie/{{movie_id}}/similar?language=${AppApi.language}&page=1', + method: Method.get, + ), + targetPath: 'results', + resultTarget: 'data', + template: _buildSimilarMoviesListViewTemplate(), + ), + ), + ], + ), + StacSizedBox(height: 80), + ], + ), + ), + ], + ), + ), + ), + ); +} + +/// Helper function to build a ListView template with itemTemplate for cast list. +StacWidget _buildCastListViewTemplate() { + final templateJson = { + 'type': 'listView', + 'scrollDirection': 'horizontal', + 'shrinkWrap': true, + 'separator': StacSizedBox(width: 16).toJson(), + 'itemTemplate': StacSizedBox( + width: 80, + child: StacColumn( + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacClipRRect( + borderRadius: StacBorderRadius.all(6), + child: StacImage( + src: '${AppApi.imageBaseUrl}/{{profile_path}}', + fit: StacBoxFit.cover, + width: 80, + height: 96, + ), + ), + StacSizedBox(height: 8), + StacText( + data: '{{name}}', + style: StacThemeData.textTheme.titleSmall, + overflow: StacTextOverflow.ellipsis, + ), + StacText( + data: '{{character}}', + style: StacThemeData.textTheme.bodySmall, + overflow: StacTextOverflow.ellipsis, + ), + ], + ), + ).toJson(), + }; + + return StacWidget(jsonData: templateJson); +} + +/// Helper function to build a ListView template with itemTemplate for similar movies. +StacWidget _buildSimilarMoviesListViewTemplate() { + final templateJson = { + 'type': 'listView', + 'scrollDirection': 'horizontal', + 'shrinkWrap': true, + 'separator': StacSizedBox(width: 8).toJson(), + 'itemTemplate': StacGestureDetector( + onTap: StacAction.fromJson({ + 'actionType': 'setValue', + 'values': [ + {'key': 'movie_id', 'value': '{{data.id}}'}, + ], + 'action': {'actionType': 'navigate', 'routeName': 'detail_screen'}, + }), + child: StacClipRRect( + borderRadius: StacBorderRadius.all(6), + child: StacImage( + imageType: StacImageType.network, + src: '${AppApi.imageBaseUrl}/{{data.poster_path}}', + width: 108, + height: 164, + ), + ), + ).toJson(), + }; + + return StacWidget(jsonData: templateJson); +} diff --git a/examples/movie_app/stac/home_screen.dart b/examples/movie_app/stac/home_screen.dart new file mode 100644 index 000000000..ca329de7a --- /dev/null +++ b/examples/movie_app/stac/home_screen.dart @@ -0,0 +1,242 @@ +import 'package:movie_app/constants/app_constants.dart'; +import 'package:movie_app/widgets/movie_carousel/movie_carousel.dart'; +import 'package:stac_core/stac_core.dart'; + +@StacScreen(screenName: 'home_screen') +StacWidget homeScreen() { + return StacDefaultBottomNavigationController( + length: 3, + child: StacScaffold( + extendBodyBehindAppBar: true, + body: StacBottomNavigationView( + children: [ + StacListView( + padding: StacEdgeInsets.all(0), + children: [ + StacMovieCarousel( + request: StacNetworkRequest( + url: AppApi.getTrendingMoviesUrl(), + method: Method.get, + ), + ), + StacColumn( + children: [ + StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 10, + ), + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacText( + data: AppStrings.nowPlaying, + style: StacThemeData.textTheme.labelLarge, + ), + ], + ), + ), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: AppApi.getNowPlayingMoviesUrl(), + method: Method.get, + ), + targetPath: 'results', + template: _buildMovieListViewTemplate(), + ), + ), + ], + ), + StacColumn( + children: [ + StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 10, + ), + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacText( + data: AppStrings.popularMovies, + style: StacThemeData.textTheme.labelLarge, + ), + ], + ), + ), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: AppApi.getPopularMoviesUrl(), + method: Method.get, + ), + targetPath: 'results', + template: _buildMovieListViewTemplate(), + ), + ), + ], + ), + StacColumn( + children: [ + StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 10, + ), + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacText( + data: AppStrings.trendingMovies, + style: StacThemeData.textTheme.labelLarge, + ), + ], + ), + ), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: AppApi.getTrendingMoviesUrl(), + method: Method.get, + ), + targetPath: 'results', + template: _buildMovieListViewTemplate(), + ), + ), + ], + ), + StacColumn( + children: [ + StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 10, + ), + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacText( + data: AppStrings.topRated, + style: StacThemeData.textTheme.labelLarge, + ), + ], + ), + ), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: AppApi.getTopRatedMoviesUrl(), + method: Method.get, + ), + targetPath: 'results', + template: _buildMovieListViewTemplate(), + ), + ), + ], + ), + StacColumn( + children: [ + StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 24, + bottom: 10, + ), + child: StacRow( + mainAxisAlignment: StacMainAxisAlignment.spaceBetween, + children: [ + StacText( + data: AppStrings.upcomingMovies, + style: StacThemeData.textTheme.labelLarge, + ), + ], + ), + ), + StacSizedBox( + height: 164, + child: StacDynamicView( + request: StacNetworkRequest( + url: AppApi.getUpcomingMoviesUrl(), + method: Method.get, + ), + targetPath: 'results', + template: _buildMovieListViewTemplate(), + ), + ), + ], + ), + StacSizedBox(height: 80), + ], + ), + StacCenter(child: StacText(data: AppStrings.search)), + StacCenter(child: StacText(data: AppStrings.profile)), + ], + ), + bottomNavigationBar: StacBottomNavigationBar( + items: [ + StacBottomNavigationBarItem( + label: AppStrings.bottomNavHome, + icon: StacIcon(icon: 'home_outlined'), + ), + StacBottomNavigationBarItem( + label: AppStrings.bottomNavSearch, + icon: StacIcon(icon: 'search_outlined'), + ), + StacBottomNavigationBarItem( + label: AppStrings.bottomNavProfile, + icon: StacIcon(icon: 'person_outlined'), + ), + ], + ), + ), + ); +} + +/// Helper function to build a ListView template with itemTemplate for movie lists. +/// Note: itemTemplate is a parser-specific feature handled by the dynamicView parser. +/// We construct the template as JSON to include itemTemplate. +StacWidget _buildMovieListViewTemplate() { + // Create template JSON with itemTemplate (parser-specific feature) + final templateJson = { + 'type': 'listView', + 'scrollDirection': 'horizontal', + 'shrinkWrap': true, + 'separator': StacSizedBox(width: 8).toJson(), + 'padding': StacEdgeInsets.only(left: 16).toJson(), + 'itemTemplate': StacGestureDetector( + onTap: StacAction.fromJson({ + 'actionType': 'setValue', + 'values': [ + {'key': 'movie_id', 'value': '{{id}}'}, + ], + 'action': {'actionType': 'navigate', 'routeName': 'detail_screen'}, + }), + child: StacClipRRect( + borderRadius: StacBorderRadius.all(6), + child: StacImage( + imageType: StacImageType.network, + src: '${AppApi.imageBaseUrl}/{{poster_path}}', + width: 108, + height: 164, + ), + ), + ).toJson(), + }; + + // Create a StacWidget with the JSON data + return StacWidget(jsonData: templateJson); +} diff --git a/examples/movie_app/stac/onboarding_screen.dart b/examples/movie_app/stac/onboarding_screen.dart new file mode 100644 index 000000000..ada942fc5 --- /dev/null +++ b/examples/movie_app/stac/onboarding_screen.dart @@ -0,0 +1,76 @@ +import 'package:movie_app/constants/app_constants.dart'; +import 'package:stac_core/stac_core.dart'; + +@StacScreen(screenName: 'onboarding_screen') +StacWidget onboardingScreen() { + return StacScaffold( + body: StacStack( + children: [ + StacImage( + imageType: StacImageType.asset, + src: AppAssets.onboardingImage, + width: double.maxFinite, + height: double.maxFinite, + fit: StacBoxFit.cover, + ), + StacPositioned( + left: 0, + right: 0, + bottom: 0, + child: StacContainer( + width: double.maxFinite, + height: 500, + decoration: StacBoxDecoration( + gradient: StacGradient.linear( + colors: ['#00050608', '#050608', '#050608'], + begin: StacAlignment.topCenter, + end: StacAlignment.bottomCenter, + stops: [0.0, 0.8, 1.0], + ), + ), + child: StacPadding( + padding: StacEdgeInsets.only( + left: 16, + right: 16, + top: 48, + bottom: 48, + ), + child: StacColumn( + mainAxisAlignment: StacMainAxisAlignment.end, + crossAxisAlignment: StacCrossAxisAlignment.start, + children: [ + StacText( + data: AppStrings.onboardingTitle, + style: StacThemeData.textTheme.displayMedium, + children: [ + StacTextSpan( + text: AppStrings.onboardingTitleAccent, + style: StacTextStyle(color: StacColors.primary), + ), + ], + ), + StacSizedBox(height: 24), + StacText( + data: AppStrings.onboardingDescription, + style: StacThemeData.textTheme.bodyMedium, + ), + StacSizedBox(height: 64), + StacSizedBox( + height: 48, + width: double.maxFinite, + child: StacFilledButton( + child: StacText( + data: AppStrings.onboardingGetStartedButton, + ), + onPressed: StacNavigateAction(routeName: 'home_screen'), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); +}