0% found this document useful (0 votes)
13 views35 pages

Movie Data Model and Provider in Flutter

Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views35 pages

Movie Data Model and Provider in Flutter

Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

import '../utils/constants.

dart';

class Movie {
final int id;
final String title;
final String posterPath;
final String backdropPath;
final String overview;
final double voteAverage;
final String releaseDate;
final List<int> genreIds;

Movie({
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
});

factory [Link](Map<String, dynamic> json) {


return Movie(
id: json['id'] ?? 0,
title: json['title'] ?? 'Unknown Title',
posterPath: json['poster_path'] ?? '',
backdropPath: json['backdrop_path'] ?? '',
overview: json['overview'] ?? 'No overview available',
voteAverage: (json['vote_average'] != null)
? (json['vote_average'] as num).toDouble()
: 0.0,
releaseDate: json['release_date'] ?? '',
genreIds: json['genre_ids'] != null
? List<int>.from(json['genre_ids'])
: [],
);
}

// Get effective poster path - either actual path or placeholder


String getEffectivePosterPath() {
if ([Link]) {
return [Link];
}
return '${[Link]}${ApiConstants.w500ImageSize}$posterPath';
}
// Get effective backdrop path - either actual path or placeholder
String getEffectiveBackdropPath() {
if ([Link]) {
return [Link];
}
return '${[Link]}${[Link]}$backdropPath';
}
}

class MovieDetail extends Movie {


final int runtime;
final List<Genre> genres;
final String status;
final String? tagline;
final List<Video> videos;

MovieDetail({
required int id,
required String title,
required String posterPath,
required String backdropPath,
required String overview,
required double voteAverage,
required String releaseDate,
required List<int> genreIds,
required [Link],
required [Link],
required [Link],
[Link],
[Link] = const [],
}) : super(
id: id,
title: title,
posterPath: posterPath,
backdropPath: backdropPath,
overview: overview,
voteAverage: voteAverage,
releaseDate: releaseDate,
genreIds: genreIds,
);

factory [Link](Map<String, dynamic> json) {


// Extract videos if available
List<Video> videosList = [];
if (json['videos'] != null && json['videos']['results'] != null) {
final videoResults = json['videos']['results'] as List;
videosList = videoResults
.map((videoJson) => [Link](videoJson))
.toList();
}

return MovieDetail(
id: json['id'] ?? 0,
title: json['title'] ?? 'Unknown Title',
posterPath: json['poster_path'] ?? '',
backdropPath: json['backdrop_path'] ?? '',
overview: json['overview'] ?? 'No overview available',
voteAverage: (json['vote_average'] != null)
? (json['vote_average'] as num).toDouble()
: 0.0,
releaseDate: json['release_date'] ?? '',
genreIds: [], // Not available in detail endpoint
runtime: json['runtime'] ?? 0,
genres: json['genres'] != null
? (json['genres'] as List).map((genre) => [Link](genre)).toList()
: [],
status: json['status'] ?? 'Unknown',
tagline: json['tagline'],
videos: videosList,
);
}

// Get the official trailer if available


Video? getTrailer() {
// First try to find the official trailer
for (final video in videos) {
if ([Link]() == 'trailer' &&
[Link] &&
[Link]() == 'youtube') {
return video;
}
}

// If no official trailer, return any trailer


for (final video in videos) {
if ([Link]() == 'trailer' &&
[Link]() == 'youtube') {
return video;
}
}

// If no trailer at all, return any video


if ([Link]) {
return [Link];
}
return null;
}
}

class Genre {
final int id;
final String name;

Genre({
required [Link],
required [Link],
});

factory [Link](Map<String, dynamic> json) {


return Genre(
id: json['id'] ?? 0,
name: json['name'] ?? 'Unknown',
);
}
}

class Video {
final String id;
final String key;
final String name;
final String site;
final String type;
final bool official;

Video({
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
required [Link],
});

factory [Link](Map<String, dynamic> json) {


return Video(
id: json['id'] ?? '',
key: json['key'] ?? '',
name: json['name'] ?? '',
site: json['site'] ?? '',
type: json['type'] ?? '',
official: json['official'] ?? false,
);
}
String getYoutubeUrl() {
if ([Link]() == 'youtube') {
return '[Link]
}
return '';
}

String getYoutubeThumbnail() {
if ([Link]() == 'youtube') {
return '[Link]
}
return '';
}
}

import 'package:flutter/[Link]';

import '../models/[Link]';
import '../services/api_service.dart';

class MovieProvider extends ChangeNotifier {


final ApiService _apiService = ApiService();

List<Movie> _popularMovies = [];


List<Movie> _topRatedMovies = [];
List<Movie> _upcomingMovies = [];
List<Movie> _nowPlayingMovies = [];
List<Movie> _searchResults = [];

MovieDetail? _selectedMovie;

bool _isLoading = false;


String _error = '';

// Getters
List<Movie> get popularMovies => _popularMovies;
List<Movie> get topRatedMovies => _topRatedMovies;
List<Movie> get upcomingMovies => _upcomingMovies;
List<Movie> get nowPlayingMovies => _nowPlayingMovies;
List<Movie> get searchResults => _searchResults;
MovieDetail? get selectedMovie => _selectedMovie;
bool get isLoading => _isLoading;
String get error => _error;

// Initialize all data


Future<void> initialize() async {
try {
_isLoading = true;
notifyListeners();

await fetchPopularMovies();
await fetchTopRatedMovies();
await fetchUpcomingMovies();
await fetchNowPlayingMovies();

} catch (e) {
_error = [Link]();
} finally {
_isLoading = false;
notifyListeners();
}
}

// Fetch popular movies


Future<void> fetchPopularMovies() async {
try {
_popularMovies = await _apiService.getPopularMovies();
_error = '';
} catch (e) {
_error = [Link]();
}
}

// Fetch top rated movies


Future<void> fetchTopRatedMovies() async {
try {
_topRatedMovies = await _apiService.getTopRatedMovies();
_error = '';
} catch (e) {
_error = [Link]();
}
}

// Fetch upcoming movies


Future<void> fetchUpcomingMovies() async {
try {
_upcomingMovies = await _apiService.getUpcomingMovies();
_error = '';
} catch (e) {
_error = [Link]();
}
}

// Fetch now playing movies


Future<void> fetchNowPlayingMovies() async {
try {
_nowPlayingMovies = await _apiService.getNowPlayingMovies();
_error = '';
} catch (e) {
_error = [Link]();
}
}

// Fetch movie details


Future<void> fetchMovieDetails(int movieId) async {
try {
_isLoading = true;
_selectedMovie = null;
notifyListeners();

_selectedMovie = await _apiService.getMovieDetails(movieId);


_error = '';
} catch (e) {
_error = [Link]();
} finally {
_isLoading = false;
notifyListeners();
}
}

// Search movies
Future<void> searchMovies(String query) async {
try {
_isLoading = true;
notifyListeners();

_searchResults = await _apiService.searchMovies(query);


_error = '';
} catch (e) {
_error = [Link]();
} finally {
_isLoading = false;
notifyListeners();
}
}

// Clear search results


void clearSearchResults() {
_searchResults = [];
notifyListeners();
}
}

import 'package:flutter/[Link]';
import 'package:provider/[Link]';
import '../models/[Link]';
import '../providers/movie_provider.dart';
import '../utils/[Link]';
import '../widgets/movie_card.dart';

class AllMoviesScreen extends StatelessWidget {


final String category;
final String title;

const AllMoviesScreen({
Key? key,
required [Link],
required [Link],
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: [Link],
appBar: AppBar(
title: Text(title),
backgroundColor: [Link],
foregroundColor: [Link],
),
body: Consumer<MovieProvider>(
builder: (context, movieProvider, child) {
List<Movie> movies = [];

// Select the right movie list based on category


if (category == [Link]) {
movies = [Link];
} else if (category == [Link]) {
movies = [Link];
} else if (category == [Link]) {
movies = [Link];
} else if (category == [Link]) {
movies = [Link];
}

if ([Link]) {
return const Center(
child: CircularProgressIndicator(
color: [Link],
),
);
}
return [Link](
padding: const [Link](16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: [Link],
itemBuilder: (context, index) {
return MovieCard(
movie: movies[index],
height: [Link],
);
},
);
},
),
);
}
}

import 'package:flutter/[Link]';
import 'package:provider/[Link]';
import 'package:intl/[Link]';

import '../models/[Link]';
import '../providers/movie_provider.dart';
import '../utils/[Link]';
import '../widgets/movie_slider.dart';
import 'search_screen.dart';
import 'all_movies_screen.dart';

class HomeScreen extends StatefulWidget {


const HomeScreen({[Link]});

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {


bool _isInit = false;
int _currentIndex = 0;

@override
void didChangeDependencies() {
if (!_isInit) {
[Link]<MovieProvider>(context, listen: false).initialize();
_isInit = true;
}
[Link]();
}

Widget _buildMoviesTab(MovieProvider movieProvider) {


final isLoading = [Link];
final hasError = [Link];

if (isLoading && !hasError && [Link]) {


return const Center(
child: CircularProgressIndicator(
color: [Link],
),
);
}

if (hasError && [Link]) {


return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Text(
'Error loading movies',
style: [Link](context).[Link],
),
const SizedBox(height: 8),
Text(
[Link],
style: [Link](context).[Link],
textAlign: [Link],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
[Link]();
},
style: [Link](
backgroundColor: [Link],
),
child: const Text('Retry'),
),
],
),
);
}

return CustomScrollView(
slivers: [
// Featured Movie Section
if ([Link])
SliverToBoxAdapter(
child: FeaturedMovieSection(
movie: [Link],
),
),

// Popular Movies Section


SliverPadding(
padding: const [Link](horizontal: 16.0, vertical: 8.0),
sliver: SliverToBoxAdapter(
child: Row(
mainAxisAlignment: [Link],
children: [
Text(
[Link],
style: [Link](context).[Link],
),
TextButton(
onPressed: () {
[Link](
context,
MaterialPageRoute(
builder: (context) => AllMoviesScreen(
category: [Link],
title: 'Popular Movies',
),
),
);
},
child: Row(
children: [
Text(
'see all',
style: TextStyle(
color: [Link](0.7),
fontSize: 14,
),
),
Icon(
Icons.chevron_right,
color: [Link](0.7),
size: 20,
),
],
),
),
],
),
),
),

SliverPadding(
padding: const [Link](bottom: 24.0),
sliver: SliverToBoxAdapter(
child: MovieSlider(
movies: [Link],
isFullWidth: false,
),
),
),

// Top Rated Movies Section


SliverPadding(
padding: const [Link](horizontal: 16.0, vertical: 8.0),
sliver: SliverToBoxAdapter(
child: Row(
mainAxisAlignment: [Link],
children: [
Text(
[Link],
style: [Link](context).[Link],
),
TextButton(
onPressed: () {
[Link](
context,
MaterialPageRoute(
builder: (context) => AllMoviesScreen(
category: [Link],
title: 'Top Rated Movies',
),
),
);
},
child: Row(
children: [
Text(
'see all',
style: TextStyle(
color: [Link](0.7),
fontSize: 14,
),
),
Icon(
Icons.chevron_right,
color: [Link](0.7),
size: 20,
),
],
),
),
],
),
),
),

SliverPadding(
padding: const [Link](bottom: 80.0),
sliver: SliverToBoxAdapter(
child: MovieSlider(
movies: [Link],
isFullWidth: false,
),
),
),
],
);
}

Widget _buildShowsTab() {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Icon(
[Link],
size: 80,
color: [Link](0.7),
),
const SizedBox(height: 16),
Text(
'TV Shows Coming Soon',
style: [Link](context).[Link],
),
const SizedBox(height: 8),
Text(
'This feature is currently under development',
style: [Link](context).[Link],
textAlign: [Link],
),
],
),
);
}

Widget _buildWatchlistTab() {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Icon(
[Link],
size: 80,
color: [Link](0.7),
),
const SizedBox(height: 16),
Text(
'Your Watchlist',
style: [Link](context).[Link],
),
const SizedBox(height: 8),
Text(
'Movies you save will appear here',
style: [Link](context).[Link],
textAlign: [Link],
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() {
_currentIndex = 0;
});
},
style: [Link](
backgroundColor: [Link],
foregroundColor: [Link],
padding: const [Link](horizontal: 24, vertical: 12),
),
child: const Text('Browse Movies'),
),
],
),
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: [Link],
body: Consumer<MovieProvider>(
builder: (context, movieProvider, child) {
if (_currentIndex == 0) {
return _buildMoviesTab(movieProvider);
} else if (_currentIndex == 1) {
return _buildShowsTab();
} else if (_currentIndex == 3) {
return _buildWatchlistTab();
} else {
// Search tab
return const SearchScreen();
}
},
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: [Link],
selectedItemColor: [Link],
unselectedItemColor: [Link],
type: [Link],
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Movies',
),
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Shows',
),
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Watchlist',
),
],
),
);
}
}

class FeaturedMovieSection extends StatelessWidget {


final Movie movie;

const FeaturedMovieSection({
[Link],
required [Link],
});

@override
Widget build(BuildContext context) {
String formattedDate = '';
try {
final date = [Link]([Link]);
formattedDate = [Link]().format(date);
} catch (_) {
formattedDate = 'Coming Soon';
}

return Stack(
children: [
// Background image
Container(
height: 450,
width: [Link],
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'${[Link]}${[Link]}$
{[Link]}',
),
fit: [Link],
),
),
foregroundDecoration: BoxDecoration(
gradient: LinearGradient(
begin: [Link],
end: [Link],
colors: [
[Link],
[Link](0.7),
[Link],
],
stops: const [0.5, 0.8, 1.0],
),
),
),

// Movie info
Positioned(
bottom: 20,
left: 16,
right: 16,
child: Column(
crossAxisAlignment: [Link],
children: [
Text(
[Link],
style: const TextStyle(
fontSize: 28,
fontWeight: [Link],
color: [Link],
),
),
Text(
formattedDate,
style: TextStyle(
fontSize: 16,
color: [Link](0.7),
),
),
const SizedBox(height: 16),

// Dots indicators
Row(
children: [
for (int i = 0; i < 5; i++)
Container(
width: i == 0 ? 24 : 8,
height: 8,
margin: const [Link](right: 4),
decoration: BoxDecoration(
color: i == 0
? [Link]
: [Link](0.3),
borderRadius: [Link](4),
),
),
],
),
],
),
),
],
);
}
}
class MovieSliderSection extends StatelessWidget {
final String title;
final List<Movie> movies;
final bool isLoading;
final bool isFullWidth;

const MovieSliderSection({
[Link],
required [Link],
required [Link],
[Link] = false,
[Link] = false,
});

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: [Link],
children: [
Text(
title,
style: [Link](context).[Link],
),
const SizedBox(height: 16),
if (isLoading && [Link])
const SizedBox(
height: 200,
child: Center(
child: CircularProgressIndicator(),
),
)
else
MovieSlider(
movies: movies,
isFullWidth: isFullWidth,
),
],
);
}
}

import 'package:flutter/[Link]';
import 'package:provider/[Link]';

import '../providers/movie_provider.dart';
import '../widgets/movie_card.dart';

class SearchScreen extends StatefulWidget {


const SearchScreen({[Link]});
@override
State<SearchScreen> createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {


final TextEditingController _searchController = TextEditingController();
bool _isSearching = false;

@override
void initState() {
[Link]();
// Clear any previous search results
[Link]((_) {
[Link]<MovieProvider>(context, listen: false).clearSearchResults();
});
}

@override
void dispose() {
_searchController.dispose();
[Link]();
}

void _performSearch(String query) {


if ([Link]) {
setState(() {
_isSearching = true;
});
[Link]<MovieProvider>(context, listen: false)
.searchMovies(query)
.then((_) {
setState(() {
_isSearching = false;
});
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Search for movies...',
border: [Link],
suffixIcon: IconButton(
icon: const Icon([Link]),
onPressed: () {
_searchController.clear();
[Link]<MovieProvider>(context, listen: false).clearSearchResults();
},
),
),
onSubmitted: _performSearch,
),
),
body: Consumer<MovieProvider>(
builder: (context, movieProvider, child) {
final searchResults = [Link];
final isLoading = [Link];
final hasError = [Link];

if (_isSearching || isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}

if (hasError) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
Text(
'Error searching movies',
style: [Link](context).[Link],
),
const SizedBox(height: 8),
Text(
[Link],
style: [Link](context).[Link],
textAlign: [Link],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _performSearch(_searchController.text),
child: const Text('Retry'),
),
],
),
);
}
if (_searchController.[Link]) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
const Icon(
[Link],
size: 80,
color: Colors.white30,
),
const SizedBox(height: 16),
Text(
'Search for movies',
style: [Link](context).[Link]?.copyWith(
color: Colors.white70,
),
),
],
),
);
}

if ([Link]) {
return Center(
child: Column(
mainAxisAlignment: [Link],
children: [
const Icon(
Icons.movie_filter,
size: 80,
color: Colors.white30,
),
const SizedBox(height: 16),
Text(
'No movies found',
style: [Link](context).[Link]?.copyWith(
color: Colors.white70,
),
),
const SizedBox(height: 8),
Text(
'Try different keywords',
style: [Link](context).[Link]?.copyWith(
color: Colors.white70,
),
),
],
),
);
}

return [Link](
padding: const [Link](16.0),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: [Link],
itemBuilder: (context, index) {
return MovieCard(
movie: searchResults[index],
height: 250,
);
},
);
},
),
);
}
}

import 'dart:convert';
import 'package:http/[Link]' as http;
import 'package:flutter/[Link]';

import '../models/[Link]';
import '../utils/[Link]';

class ApiService {
final String apiKey = [Link];
final String accessToken = [Link];
final String baseUrl = [Link];

Future<List<Movie>> getMovies(String endpoint) async {


try {
debugPrint('Fetching movies from endpoint: $endpoint');

// First, try with Bearer token (preferred auth method)


final response = await [Link](
[Link]('$baseUrl/$endpoint?language=en-US&page=1'),
headers: {
'Authorization': 'Bearer $accessToken',
'accept': 'application/json',
},
);

// If token fails, fall back to API key


if ([Link] != 200) {
debugPrint('Bearer token authentication failed, trying with API key');
final apiKeyResponse = await [Link](
[Link]('$baseUrl/$endpoint?api_key=$apiKey&language=en-US&page=1'),
);

if ([Link] == 200) {
final decodedData = [Link]([Link]);
debugPrint('API key auth successful. Movies received.');

final List<dynamic> results = decodedData['results'] ?? [];


debugPrint('Number of movies received: ${[Link]}');

if ([Link]) {
debugPrint('Results empty, returning dummy data for testing');
return _getDummyMovies();
}

return [Link]((movie) => [Link](movie)).toList();


} else {
debugPrint('API Error with key auth: ${[Link]}');
return _getDummyMovies();
}
}

debugPrint('Bearer token auth successful. Response status code: $


{[Link]}');
final decodedData = [Link]([Link]);
debugPrint('Decoded data received successfully.');

final List<dynamic> results = decodedData['results'] ?? [];


debugPrint('Number of movies received: ${[Link]}');

if ([Link]) {
debugPrint('Results empty, returning dummy data for testing');
return _getDummyMovies();
}

return [Link]((movie) => [Link](movie)).toList();


} catch (e) {
debugPrint('Exception occurred: $e');
return _getDummyMovies();
}
}
// Generate dummy movies for testing purposes
List<Movie> _getDummyMovies() {
final List<Movie> dummyMovies = [];

// Add 10 dummy movies


for (int i = 1; i <= 10; i++) {
[Link](
Movie(
id: i,
title: 'Movie $i',
posterPath: '',
backdropPath: '',
overview: 'This is a sample movie description for testing purposes when the API is not
available.',
voteAverage: 7.5,
releaseDate: '2023-01-01',
genreIds: [28, 12, 14],
),
);
}

return dummyMovies;
}

Future<List<Movie>> getPopularMovies() async {


return getMovies('movie/popular');
}

Future<List<Movie>> getTopRatedMovies() async {


return getMovies('movie/top_rated');
}

Future<List<Movie>> getUpcomingMovies() async {


return getMovies('movie/upcoming');
}

Future<List<Movie>> getNowPlayingMovies() async {


return getMovies('movie/now_playing');
}

Future<MovieDetail> getMovieDetails(int movieId) async {


try {
debugPrint('Fetching movie details for ID: $movieId');

// First try with Bearer token (preferred auth method)


final response = await [Link](
[Link]('$baseUrl/movie/$movieId?language=en-US&append_to_response=videos'),
headers: {
'Authorization': 'Bearer $accessToken',
'accept': 'application/json',
},
);

// If token fails, fall back to API key


if ([Link] != 200) {
debugPrint('Bearer token authentication failed for movie details, trying with API key');
final apiKeyResponse = await [Link](
[Link]('$baseUrl/movie/$movieId?api_key=$apiKey&language=en-
US&append_to_response=videos'),
);

if ([Link] == 200) {
debugPrint('Movie details received successfully with API key');
final decodedData = [Link]([Link]);
debugPrint('Videos included: ${decodedData['videos'] != null}');
if (decodedData['videos'] != null) {
final videoCount = decodedData['videos']['results']?.length ?? 0;
debugPrint('Number of videos received: $videoCount');
}
return [Link](decodedData);
} else {
debugPrint('Error in movie details with API key: ${[Link]}');
return _getDummyMovieDetail(movieId);
}
}

debugPrint('Movie details received successfully with Bearer token');


final decodedData = [Link]([Link]);
debugPrint('Videos included: ${decodedData['videos'] != null}');
if (decodedData['videos'] != null) {
final videoCount = decodedData['videos']['results']?.length ?? 0;
debugPrint('Number of videos received: $videoCount');
}
return [Link](decodedData);
} catch (e) {
debugPrint('Exception in movie details: $e');
return _getDummyMovieDetail(movieId);
}
}

// Generate dummy movie details for testing


MovieDetail _getDummyMovieDetail(int movieId) {
return MovieDetail(
id: movieId,
title: 'Sample Movie',
posterPath: '',
backdropPath: '',
overview: 'This is a detailed description of the sample movie for testing purposes when
the API is unavailable.',
voteAverage: 8.0,
releaseDate: '2023-02-15',
genreIds: [28, 12, 878],
runtime: 120,
genres: [
Genre(id: 28, name: 'Action'),
Genre(id: 12, name: 'Adventure'),
Genre(id: 878, name: 'Science Fiction'),
],
status: 'Released',
tagline: 'A sample movie tagline',
videos: [
Video(
id: 'dummy-1',
key: 'dQw4w9WgXcQ', // Rick Roll video for testing
name: 'Sample Trailer',
site: 'YouTube',
type: 'Trailer',
official: true,
),
],
);
}

Future<List<Movie>> searchMovies(String query) async {


try {
if ([Link]) return [];

debugPrint('Searching for movies with query: $query');

// First try with Bearer token


final response = await [Link](
[Link]('$baseUrl/search/movie?language=en-
US&query=$query&page=1&include_adult=false'),
headers: {
'Authorization': 'Bearer $accessToken',
'accept': 'application/json',
},
);

// If token fails, fall back to API key


if ([Link] != 200) {
debugPrint('Bearer token authentication failed for search, trying with API key');
final apiKeyResponse = await [Link](
[Link]('$baseUrl/search/movie?api_key=$apiKey&language=en-
US&query=$query&page=1&include_adult=false'),
);

if ([Link] == 200) {
final decodedData = [Link]([Link]);
final List<dynamic> results = decodedData['results'] ?? [];
debugPrint('Search results count with API key: ${[Link]}');

if ([Link]) {
return _getDummySearchResults(query);
}

return [Link]((movie) => [Link](movie)).toList();


} else {
debugPrint('Search API error with API key: ${[Link]}');
return _getDummySearchResults(query);
}
}

debugPrint('Search response code with Bearer token: ${[Link]}');


final decodedData = [Link]([Link]);
final List<dynamic> results = decodedData['results'] ?? [];
debugPrint('Search results count with Bearer token: ${[Link]}');

if ([Link]) {
return _getDummySearchResults(query);
}

return [Link]((movie) => [Link](movie)).toList();


} catch (e) {
debugPrint('Search exception: $e');
return _getDummySearchResults(query);
}
}

// Generate dummy search results


List<Movie> _getDummySearchResults(String query) {
final List<Movie> dummyResults = [];

// Create 5 dummy search results


for (int i = 1; i <= 5; i++) {
[Link](
Movie(
id: 1000 + i,
title: '$query Result $i',
posterPath: '',
backdropPath: '',
overview: 'This is a sample search result for "$query".',
voteAverage: 6.0 + i * 0.5,
releaseDate: '2023-03-$i',
genreIds: [28, 35],
),
);
}

return dummyResults;
}
}

import 'package:flutter/[Link]';
import 'package:flutter_dotenv/flutter_dotenv.dart';

class ApiConstants {
// API credentials
static final String apiKey = [Link]['TMDB_API_KEY'] ??
'2276c8584be8ca4ae8a299da484c645a';
static final String accessToken = [Link]['TMDB_ACCESS_TOKEN'] ??
'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMjc2Yzg1ODRiZThjYTRhZThhMjk5ZGE0ODRjNjQ1YSIsInN1
YiI6IjY1NzYzMWI4YTYzZWM2MDEzM2YzNWU1YSIsIm5iZiI6MTcwMjIyMzI4OCwiZXhwIjoxNzA0O
DE1Mjg4LCJzY29wZSI6ImFwaV9yZWFkIiwianRpIjoiMGMxNGRiNWUzMzMzOTc3ZGQ1NDE5ZG
M3MTYzMWI5ZjUifQ.RWJ3DuPHG-nZGnlZ3H1lJbC-JRUjKxTXIE1f_JDOv24';

// API endpoints
static const String baseUrl = '[Link]
static const String imageBaseUrl = '[Link]
static const String originalImageSize = 'original';
static const String w500ImageSize = 'w500';
static const String w300ImageSize = 'w300';

// Placeholder images for when real images aren't available


static const String placeholderPosterUrl =
'[Link]
static const String placeholderBackdropUrl =
'[Link]
}

class AppColors {
static const Color scaffoldBackground = Color(0xFF000000);
static const Color cardColor = Color(0xFF1A1E25);
static const Color primaryColor = Color(0xFF121212);
static const Color secondaryColor = Color(0xFFFF0000);
static const Color ratingColor = Color(0xFFFFCB45);
}

class MovieCategories {
static const String popular = 'Popular';
static const String topRated = 'Top Rated';
static const String upcoming = 'Upcoming';
static const String nowPlaying = 'Now Playing';
}

import 'package:flutter/[Link]';
import 'package:cached_network_image/cached_network_image.dart';

import '../models/[Link]';
import '../screens/movie_detail_screen.dart';
import '../utils/[Link]';

class MovieCard extends StatelessWidget {


final Movie movie;
final double height;
final bool isFullWidth;

const MovieCard({
[Link],
required [Link],
[Link] = 200,
[Link] = false,
});

@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
[Link](
context,
MaterialPageRoute(
builder: (context) => MovieDetailScreen(
movieId: [Link],
posterPath: [Link],
title: [Link],
),
),
);
},
child: Column(
crossAxisAlignment: [Link],
children: [
// Poster image with rating
Stack(
children: [
// Movie poster
ClipRRect(
borderRadius: [Link](12),
child: [Link]
? CachedNetworkImage(
imageUrl: '${[Link]}${ApiConstants.w500ImageSize}$
{[Link]}',
fit: [Link],
height: 180,
width: 120,
placeholder: (context, url) => Container(
color: [Link].shade800,
height: 180,
width: 120,
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
color: [Link],
),
),
),
errorWidget: (context, url, error) => Container(
color: [Link].shade800,
height: 180,
width: 120,
child: const Column(
mainAxisAlignment: [Link],
children: [
Icon(Icons.image_not_supported, color: Colors.white70),
SizedBox(height: 8),
Text(
'No image',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
),
)
: Container(
color: [Link].shade800,
height: 180,
width: 120,
child: const Column(
mainAxisAlignment: [Link],
children: [
Icon(Icons.image_not_supported, color: Colors.white70),
SizedBox(height: 8),
Text(
'No image',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
),
),

// Rating badge
Positioned(
bottom: 8,
left: 8,
child: Container(
padding: const [Link](horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: [Link](0.7),
borderRadius: [Link](4),
),
child: Row(
children: [
const Icon(
[Link],
color: [Link],
size: 14,
),
const SizedBox(width: 2),
Text(
([Link] / 2).toStringAsFixed(1),
style: const TextStyle(
color: [Link],
fontWeight: [Link],
fontSize: 12,
),
),
const Text(
'/10',
style: TextStyle(
color: Colors.white70,
fontSize: 10,
),
),
],
),
),
),
],
),

// Movie title
const SizedBox(height: 6),
SizedBox(
width: 120,
child: Text(
[Link],
style: const TextStyle(
color: [Link],
fontWeight: FontWeight.w500,
fontSize: 14,
),
maxLines: 1,
overflow: [Link],
),
),
],
),
);
}
}

import 'package:flutter/[Link]';

import '../models/[Link]';
import 'movie_card.dart';

class MovieSlider extends StatelessWidget {


final List<Movie> movies;
final bool isFullWidth;

const MovieSlider({
[Link],
required [Link],
[Link] = false,
});

@override
Widget build(BuildContext context) {
if ([Link]) {
return SizedBox(
height: 200,
child: Center(
child: Text(
'No movies available',
style: [Link](context).[Link],
),
),
);
}

if (isFullWidth) {
return SizedBox(
height: 220,
child: [Link](
controller: PageController(viewportFraction: 0.9),
itemCount: [Link] > 5 ? 5 : [Link],
itemBuilder: (context, index) {
return Padding(
padding: const [Link](horizontal: 8.0),
child: MovieCard(
movie: movies[index],
height: 220,
isFullWidth: true,
),
);
},
),
);
}

return SizedBox(
height: 220,
child: [Link](
scrollDirection: [Link],
padding: const [Link](horizontal: 16.0),
itemCount: [Link],
itemBuilder: (context, index) {
return Padding(
padding: const [Link](right: 12.0),
child: MovieCard(
movie: movies[index],
height: 180,
),
);
},
),
);
}
}

import 'package:flutter/[Link]';
import 'package:provider/[Link]';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter/[Link]';

import 'providers/movie_provider.dart';
import 'screens/home_screen.dart';
import 'utils/[Link]';

Future<void> main() async {


[Link]();

// Set preferred orientations


await [Link]([
[Link],
[Link],
]);

// Load environment variables


try {
await [Link](fileName: ".env");
} catch (e) {
debugPrint('Error loading .env file: $e');
// Continue without .env since we have fallback values
}

runApp(const MyApp());
}

class MyApp extends StatelessWidget {


const MyApp({[Link]});

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MovieProvider(),
child: MaterialApp(
title: 'Movie App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: [Link](
seedColor: [Link],
brightness: [Link],
),
scaffoldBackgroundColor: [Link],
textTheme: const TextTheme(
titleLarge: TextStyle(
color: [Link],
fontWeight: [Link],
fontSize: 22,
),
titleMedium: TextStyle(
color: [Link],
fontWeight: FontWeight.w600,
fontSize: 18,
),
bodyMedium: TextStyle(
color: Colors.white70,
fontSize: 16,
),
bodySmall: TextStyle(
color: Colors.white54,
fontSize: 14,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: [Link],
selectedItemColor: [Link],
unselectedItemColor: Colors.white54,
type: [Link],
showSelectedLabels: true,
showUnselectedLabels: true,
selectedLabelStyle: TextStyle(fontSize: 12),
unselectedLabelStyle: TextStyle(fontSize: 12),
),
useMaterial3: true,
),
home: const HomeScreen(),
),
);
}
}

You might also like