diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore index 5b1e3843..fd09e19b 100644 --- a/dataconnect/.gitignore +++ b/dataconnect/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/dataconnect/firebase.json b/dataconnect/firebase.json index 00106af8..958f35d4 100644 --- a/dataconnect/firebase.json +++ b/dataconnect/firebase.json @@ -1,43 +1,4 @@ { - "flutter": { - "platforms": { - "android": { - "default": { - "projectId": "quickstart-flutter-cc624", - "appId": "1:731568610039:android:2fda8b27330be9447251f2", - "fileOutput": "android/app/google-services.json" - } - }, - "ios": { - "default": { - "projectId": "quickstart-flutter-cc624", - "appId": "1:731568610039:ios:e78b43f1d75a77d87251f2", - "uploadDebugSymbols": false, - "fileOutput": "ios/Runner/GoogleService-Info.plist" - } - }, - "dart": { - "lib/firebase_options.dart": { - "projectId": "quickstart-flutter-cc624", - "configurations": { - "android": "1:731568610039:android:2fda8b27330be9447251f2", - "ios": "1:731568610039:ios:e78b43f1d75a77d87251f2", - "macos": "1:731568610039:ios:e78b43f1d75a77d87251f2", - "web": "1:731568610039:web:2896847d0f5655fb7251f2", - "windows": "1:731568610039:web:3c1e404f955f0b467251f2" - } - } - }, - "macos": { - "default": { - "projectId": "quickstart-flutter-cc624", - "appId": "1:731568610039:ios:e78b43f1d75a77d87251f2", - "uploadDebugSymbols": false, - "fileOutput": "macos/Runner/GoogleService-Info.plist" - } - } - } - }, "dataconnect": { "source": "dataconnect" }, "emulators": { "dataconnect": { "port": 9399 }, "auth": { "port": 9400 } } } diff --git a/dataconnect/lib/actor_detail.dart b/dataconnect/lib/actor_detail.dart index d93a6536..a25a0556 100644 --- a/dataconnect/lib/actor_detail.dart +++ b/dataconnect/lib/actor_detail.dart @@ -1,92 +1,78 @@ -import 'package:dataconnect/movie_state.dart'; -import 'package:dataconnect/movies_connector/movies.dart'; -import 'package:dataconnect/widgets/list_movies.dart'; import 'package:flutter/material.dart'; -class ActorDetail extends StatefulWidget { - const ActorDetail({super.key, required this.actorId}); +import 'movie_state.dart'; +import 'widgets/list_movies.dart'; - final String actorId; - - @override - State createState() => _ActorDetailState(); -} - -class _ActorDetailState extends State { - bool loading = true; - GetActorByIdActor? actor; - @override - void initState() { - super.initState(); - - MovieState.getActorById(widget.actorId).then((value) { - setState(() { - loading = false; - actor = value.data.actor; - }); - }); - } +class ActorDetail extends StatelessWidget { + const ActorDetail({ + super.key, + required this.actorId, + }); - _buildActorInfo() { - return [ - Align( - alignment: Alignment.centerLeft, - child: Container( - child: Text( - actor!.name, - style: const TextStyle(fontSize: 30), - ), - )), - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: AspectRatio( - aspectRatio: 9 / 16, // 9:16 aspect ratio for the image - child: Image.network( - actor!.imageUrl, - fit: BoxFit.cover, - ), - ), - ), - ]), - ]; - } - - Widget _buildMainRoles() { - return ListMovies( - movies: MovieState.convertMainActorDetail(actor!.mainActors), - title: "Main Roles"); - } - - Widget _buildSupportingRoles() { - return ListMovies( - movies: - MovieState.convertSupportingActorDetail(actor!.supportingActors), - title: "Supporting Roles"); - } + final String actorId; @override Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: actor == null - ? const Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [CircularProgressIndicator()], - ) - : Padding( - padding: const EdgeInsets.all(30), - child: SingleChildScrollView( - child: Column( + return FutureBuilder( + future: MovieState.getActorById(actorId), + builder: (context, snapshot) { + final actor = snapshot.data?.data.actor; + final loading = snapshot.connectionState == ConnectionState.waiting; + return Scaffold( + body: SafeArea( + child: () { + if (actor == null || loading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Padding( + padding: const EdgeInsets.all(30), + child: SingleChildScrollView( + child: Column( children: [ - ..._buildActorInfo(), - _buildMainRoles(), - _buildSupportingRoles() + Align( + alignment: Alignment.centerLeft, + child: Text( + actor.name, + style: const TextStyle(fontSize: 30), + )), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: AspectRatio( + aspectRatio: + 9 / 16, // 9:16 aspect ratio for the image + child: Image.network( + actor.imageUrl, + fit: BoxFit.cover, + ), + ), + ), + ], + ), + ListMovies( + movies: MovieState.convertMainActorDetail( + actor.mainActors, + ), + title: "Main Roles", + ), + ListMovies( + movies: MovieState.convertSupportingActorDetail( + actor.supportingActors, + ), + title: "Supporting Roles", + ), ], - )))), + ), + ), + ); + }(), + ), + ); + }, ); } } diff --git a/dataconnect/lib/destination.dart b/dataconnect/lib/destination.dart index 54b60147..25820bbe 100644 --- a/dataconnect/lib/destination.dart +++ b/dataconnect/lib/destination.dart @@ -1,21 +1,30 @@ import 'package:flutter/material.dart'; -class Destination { - const Destination({required this.label, required this.icon}); - final String label; - final IconData icon; -} - -class Route { - Route({required this.path, required this.label, required this.iconData}); +class Route extends NavigationDestination { + Route({ + super.key, + required this.path, + required super.label, + required this.iconData, + }) : super(icon: Icon(iconData)); final String path; - final String label; final IconData iconData; } -var homePath = Route(path: '/home', label: 'Home', iconData: Icons.home); -var searchPath = - Route(path: '/search', label: 'Search', iconData: Icons.search); -var profilePath = - Route(path: '/profile', label: 'Profile', iconData: Icons.person); -var paths = [homePath, searchPath, profilePath]; +final homePath = Route( + path: '/home', + label: 'Home', + iconData: Icons.home, +); +final searchPath = Route( + path: '/search', + label: 'Search', + iconData: Icons.search, +); +final profilePath = Route( + path: '/profile', + label: 'Profile', + iconData: Icons.person, +); + +final paths = [homePath, searchPath, profilePath]; diff --git a/dataconnect/lib/login.dart b/dataconnect/lib/login.dart index 97f6b3ba..1bfa1078 100644 --- a/dataconnect/lib/login.dart +++ b/dataconnect/lib/login.dart @@ -1,4 +1,3 @@ -import 'package:dataconnect/movies_connector/movies.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -12,77 +11,26 @@ class Login extends StatefulWidget { class _LoginState extends State { final _formKey = GlobalKey(); - String _username = ''; - String _password = ''; - Widget _buildForm() { - return Form( - key: _formKey, - child: Column( - children: [ - TextFormField( - decoration: const InputDecoration( - hintText: "Username", border: OutlineInputBorder()), - onChanged: (value) { - setState(() { - _username = value; - }); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter some text'; - } - return null; - }, - ), - const SizedBox(height: 30), - TextFormField( - obscureText: true, - autocorrect: false, - decoration: const InputDecoration( - hintText: "Password", border: OutlineInputBorder()), - onChanged: (value) { - setState(() { - _password = value; - }); - }, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter a password'; - } + final _username = TextEditingController(); + final _password = TextEditingController(); - return null; - }, - ), - ElevatedButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - logIn(); - } - }, - child: const Text('Sign In')), - Text('Don\'t have an account?'), - ElevatedButton( - onPressed: () { - context.go('/signup'); - }, - child: const Text('Sign Up')), - ], - )); - } - - logIn() async { - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar(content: Text('Signing In'))); + Future logIn(BuildContext context) async { + final messenger = ScaffoldMessenger.of(context); + final navigator = GoRouter.of(context); + messenger.showSnackBar(const SnackBar(content: Text('Signing In'))); try { - await FirebaseAuth.instance - .signInWithEmailAndPassword(email: _username, password: _password); + await FirebaseAuth.instance.signInWithEmailAndPassword( + email: _username.text, + password: _password.text, + ); if (mounted) { - context.go('/home'); + navigator.go('/home'); } } catch (_) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('There was an error when creating a user.'))); + messenger.showSnackBar(const SnackBar( + content: Text('There was an error when creating a user.'), + )); } } } @@ -91,13 +39,62 @@ class _LoginState extends State { Widget build(BuildContext context) { return Scaffold( body: SafeArea( - child: Padding( - padding: EdgeInsets.all(30), + child: Padding( + padding: const EdgeInsets.all(30), + child: Center( + child: Form( + key: _formKey, child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [_buildForm()], - ))), + children: [ + TextFormField( + decoration: const InputDecoration( + hintText: "Username", + border: OutlineInputBorder(), + ), + controller: _username, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + ), + const SizedBox(height: 30), + TextFormField( + obscureText: true, + autocorrect: false, + decoration: const InputDecoration( + hintText: "Password", + border: OutlineInputBorder(), + ), + controller: _password, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a password'; + } + return null; + }, + ), + ElevatedButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + logIn(context); + } + }, + child: const Text('Sign In')), + const Text("Don't have an account?"), + ElevatedButton( + onPressed: () { + context.go('/signup'); + }, + child: const Text('Sign Up'), + ), + ], + ), + ), + ), + ), + ), ); } } diff --git a/dataconnect/lib/main.dart b/dataconnect/lib/main.dart index 716bbe05..ac6d1720 100644 --- a/dataconnect/lib/main.dart +++ b/dataconnect/lib/main.dart @@ -1,18 +1,20 @@ -import 'package:dataconnect/models/movie.dart'; -import 'package:dataconnect/movie_state.dart'; -import 'package:dataconnect/router.dart'; -import 'package:dataconnect/widgets/list_movies.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'movies_connector/movies.dart'; +import 'models/movie.dart'; +import 'movie_state.dart'; +import 'router.dart'; +import 'util/auth.dart'; +import 'widgets/list_movies.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + await Auth.instance.init(); MoviesConnector.instance.dataConnect .useDataConnectEmulator('localhost', 9399); FirebaseAuth.instance.useAuthEmulator('localhost', 9400); @@ -69,21 +71,25 @@ class _MyHomePageState extends State { child: Column( children: [ ListMovies( - title: 'Top 10 Movies', - movies: _topMovies - .map( - (e) => - Movie(id: e.id, title: e.title, imageUrl: e.imageUrl), - ) - .toList()), + title: 'Top 10 Movies', + movies: _topMovies + .map((e) => Movie( + id: e.id, + title: e.title, + imageUrl: e.imageUrl, + )) + .toList(), + ), ListMovies( - title: 'Latest Movies', - movies: _latestMovies - .map( - (e) => - Movie(id: e.id, title: e.title, imageUrl: e.imageUrl), - ) - .toList()), + title: 'Latest Movies', + movies: _latestMovies + .map((e) => Movie( + id: e.id, + title: e.title, + imageUrl: e.imageUrl, + )) + .toList(), + ), ], ), )), diff --git a/dataconnect/lib/movie_detail.dart b/dataconnect/lib/movie_detail.dart index 097aac1f..5f9ccef2 100644 --- a/dataconnect/lib/movie_detail.dart +++ b/dataconnect/lib/movie_detail.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:dataconnect/movie_state.dart'; import 'package:dataconnect/movies_connector/movies.dart'; import 'package:dataconnect/widgets/login_guard.dart'; @@ -20,284 +22,305 @@ class _MovieDetailState extends State { GetMovieByIdData? data; bool _favorited = false; - final TextEditingController _reviewTextController = TextEditingController(); + final _reviewTextController = TextEditingController(); + StreamSubscription? _movieByIdSub, _movieInfoSub; + @override void initState() { super.initState(); + init(widget.id); + } - MovieState.subscribeToMovieById(widget.id).listen((value) { - if (mounted) { - setState(() { - loading = false; - data = value.data; - }); - } - }); + @override + void dispose() { + _movieByIdSub?.cancel(); + _movieInfoSub?.cancel(); + super.dispose(); + } - MovieState.subscribeToGetMovieInfo(widget.id).listen((value) { - if (mounted) { - setState(() { - _favorited = value.data.favorite_movie != null; - }); - } - }); + @override + void didUpdateWidget(covariant MovieDetail oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.id != widget.id) { + init(widget.id); + } } - void _refreshData() { - MovieState.refreshMovieDetail(widget.id); + Future init(String id) async { + _movieByIdSub?.cancel(); + _movieInfoSub?.cancel(); + _movieByIdSub = MovieState.subscribeToMovieById(widget.id).listen( + (value) { + if (mounted) { + setState(() { + loading = false; + data = value.data; + }); + } + }, + ); + _movieInfoSub = MovieState.subscribeToGetMovieInfo(widget.id).listen( + (value) { + if (mounted) { + setState(() { + _favorited = value.data.favorite_movie != null; + }); + } + }, + ); } void _toggleFavorite() { MovieState.toggleFavorite(widget.id, _favorited).then((_) { - setState(() { - _favorited = !_favorited; - }); + if (mounted) { + setState(() { + _favorited = !_favorited; + }); + } }); } - String _getFavoriteLabelText() { - return _favorited ? 'Remove From Favorites' : 'Add To Favorites'; - } - - List _buildMainDescription() { - var movie = data!.movie!; - return [ - Align( - alignment: Alignment.centerLeft, - child: Container( - child: Text( - movie.title, - style: const TextStyle(fontSize: 30), - ), - )), - Row( - children: [ - Text(movie.releaseYear.toString()), - const SizedBox(width: 10), - Row( - children: [const Icon(Icons.star), Text(movie.rating.toString())], - ) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: AspectRatio( - aspectRatio: 9 / 16, // 9:16 aspect ratio for the image - child: Image.network( - movie.imageUrl, - fit: BoxFit.cover, - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 0, 0), - child: Column(children: [ - Row( - children: movie.tags!.map((tag) { - return Chip( - label: Text( - tag, - overflow: TextOverflow.ellipsis, - )); - }).toList(), - ), - Text(movie.description!) - ]), - )) - ]), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LoginGuard( - widgetToGuard: OutlinedButton.icon( - onPressed: () { - _toggleFavorite(); - }, - icon: Icon(_favorited ? Icons.favorite : Icons.favorite_border), - label: Text(_getFavoriteLabelText()), - )) - ], - ) - ]; - } - - void _visitActorDetail(String id) { - context.push("/actors/$id"); - } - - Widget _buildMainActorsList() { - return Container( - height: 125, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Main Actors", - style: TextStyle(fontSize: 30), - ), - Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - GetMovieByIdMovieMainActors actor = - data!.movie!.mainActors[index]; - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - radius: 30, - child: - ClipOval(child: Image.network(actor.imageUrl))), - Text( - actor.name, - overflow: TextOverflow.ellipsis, - ) - ]); - }, - itemCount: data!.movie!.mainActors.length, - )) - ], - )); - } - - Widget _buildSupportingActorsList() { - return Container( - height: 125, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Supporting Actors", - style: TextStyle(fontSize: 30), - ), - Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - GetMovieByIdMovieSupportingActors actor = - data!.movie!.supportingActors[index]; - return InkWell( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - radius: 30, - child: ClipOval( - child: Image.network(actor.imageUrl))), - Text( - actor.name, - overflow: TextOverflow.ellipsis, - ) - ]), - onTap: () { - _visitActorDetail(actor.id); - }, - ); - }, - itemCount: data!.movie!.mainActors.length, - ), - ) - ], - )); + void _refreshData() { + MovieState.refreshMovieDetail(widget.id); } - List _buildRatings() { - return [ - Text("Rating: $_ratingValue"), - Slider( - value: _ratingValue, - max: 10, - divisions: 20, - label: _ratingValue.toString(), - onChanged: (double value) { - setState(() { - _ratingValue = value; - }); - }, - ), - LoginGuard( - widgetToGuard: TextField( - decoration: const InputDecoration( - hintText: "Write your review", - border: OutlineInputBorder(), - ), - controller: _reviewTextController, - ), - message: "writing a review"), - LoginGuard( - widgetToGuard: OutlinedButton.icon( - onPressed: () { - MoviesConnector.instance - .addReview( - movieId: widget.id, - rating: _ratingValue.toInt(), - reviewText: _reviewTextController.text) - .execute() - .then((_) { - _refreshData(); - _reviewTextController.clear(); - MovieState.triggerUpdateFavorite(); - }); - }, - label: const Text('Submit Review'), - ), - ), - ...data!.movie!.reviews.map((review) { - return Card( - child: Padding( - padding: const EdgeInsets.all(20.0), + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: () { + final movie = data?.movie; + if (movie == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return Padding( + padding: const EdgeInsets.all(30), + child: SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(review.user.username), + Align( + alignment: Alignment.centerLeft, + child: Text( + movie.title, + style: const TextStyle(fontSize: 30), + )), Row( children: [ - Text(DateFormat.yMMMd().format(review.reviewDate)), - const SizedBox( - width: 10, - ), - Text("Rating ${review.rating}") + Text(movie.releaseYear.toString()), + const SizedBox(width: 10), + Row( + children: [ + const Icon(Icons.star), + Text(movie.rating.toString()) + ], + ) ], ), - Text(review.reviewText!) - ], - )), - ); - }) - ]; -/**/ - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: data == null - ? const Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [CircularProgressIndicator()], - ) - : Padding( - padding: const EdgeInsets.all(30), - child: SingleChildScrollView( - child: Column( + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: AspectRatio( + aspectRatio: + 9 / 16, // 9:16 aspect ratio for the image + child: Image.network( + movie.imageUrl, + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(15, 0, 0, 0), + child: Column(children: [ + Row( + children: movie.tags!.map((tag) { + return Chip( + label: Text( + tag, + overflow: TextOverflow.ellipsis, + )); + }).toList(), + ), + Text(movie.description!) + ]), + )) + ]), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ..._buildMainDescription(), - _buildMainActorsList(), - _buildSupportingActorsList(), - ..._buildRatings() + LoginGuard( + builder: (context) => OutlinedButton.icon( + onPressed: () { + _toggleFavorite(); + }, + icon: Icon(_favorited + ? Icons.favorite + : Icons.favorite_border), + label: Text( + _favorited + ? 'Remove From Favorites' + : 'Add To Favorites', + ), + ), + ) ], - )))), + ), + SizedBox( + height: 125, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Main Actors", + style: TextStyle(fontSize: 30), + ), + Expanded( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + GetMovieByIdMovieMainActors actor = + data!.movie!.mainActors[index]; + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 30, + child: ClipOval( + child: + Image.network(actor.imageUrl))), + Text( + actor.name, + overflow: TextOverflow.ellipsis, + ) + ]); + }, + itemCount: data!.movie!.mainActors.length, + )) + ], + ), + ), + SizedBox( + height: 125, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Supporting Actors", + style: TextStyle(fontSize: 30), + ), + Expanded( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + GetMovieByIdMovieSupportingActors actor = + data!.movie!.supportingActors[index]; + return InkWell( + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 30, + child: ClipOval( + child: Image.network( + actor.imageUrl))), + Text( + actor.name, + overflow: TextOverflow.ellipsis, + ) + ]), + onTap: () { + context.push("/actors/${actor.id}"); + }, + ); + }, + itemCount: data!.movie!.mainActors.length, + ), + ) + ], + ), + ), + Text("Rating: $_ratingValue"), + Slider( + value: _ratingValue, + max: 10, + divisions: 20, + label: _ratingValue.toString(), + onChanged: (double value) { + setState(() { + _ratingValue = value; + }); + }, + ), + LoginGuard( + builder: (context) => TextField( + decoration: const InputDecoration( + hintText: "Write your review", + border: OutlineInputBorder(), + ), + controller: _reviewTextController, + ), + message: "writing a review", + ), + LoginGuard( + builder: (context) => OutlinedButton.icon( + onPressed: () { + MoviesConnector.instance + .addReview( + movieId: widget.id, + rating: _ratingValue.toInt(), + reviewText: _reviewTextController.text) + .execute() + .then( + (_) { + _refreshData(); + _reviewTextController.clear(); + MovieState.triggerUpdateFavorite(); + }, + ); + }, + label: const Text('Submit Review'), + ), + ), + ...data!.movie!.reviews.map((review) { + return Card( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(review.user.username), + Row( + children: [ + Text(DateFormat.yMMMd() + .format(review.reviewDate)), + const SizedBox( + width: 10, + ), + Text("Rating ${review.rating}") + ], + ), + Text(review.reviewText!) + ], + )), + ); + }), + ], + ), + ), + ); + }(), + ), ); } } diff --git a/dataconnect/lib/movie_state.dart b/dataconnect/lib/movie_state.dart index 3be9cfb6..e12c806b 100644 --- a/dataconnect/lib/movie_state.dart +++ b/dataconnect/lib/movie_state.dart @@ -1,6 +1,6 @@ -import 'package:dataconnect/movies_connector/movies.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart'; +import 'movies_connector/movies.dart'; import 'models/movie.dart'; class MovieState { diff --git a/dataconnect/lib/navigation_shell.dart b/dataconnect/lib/navigation_shell.dart index f73b02e8..6f0964e7 100644 --- a/dataconnect/lib/navigation_shell.dart +++ b/dataconnect/lib/navigation_shell.dart @@ -1,9 +1,14 @@ -import 'package:dataconnect/destination.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'destination.dart'; + class NavigationShell extends StatelessWidget { - const NavigationShell({required this.navigationShell, super.key}); + const NavigationShell({ + super.key, + required this.navigationShell, + }); + final StatefulNavigationShell navigationShell; void _goBranch(int index) { @@ -24,10 +29,7 @@ class NavigationShell extends StatelessWidget { bottomNavigationBar: NavigationBar( selectedIndex: navigationShell.currentIndex, onDestinationSelected: _goBranch, - destinations: paths - .map((destination) => NavigationDestination( - icon: Icon(destination.iconData), label: destination.label)) - .toList(), + destinations: paths, ), ); } diff --git a/dataconnect/lib/profile.dart b/dataconnect/lib/profile.dart index 1942d136..d33c6ae7 100644 --- a/dataconnect/lib/profile.dart +++ b/dataconnect/lib/profile.dart @@ -1,102 +1,57 @@ -import 'dart:async'; - -import 'package:dataconnect/movie_state.dart'; -import 'package:dataconnect/models/movie.dart'; -import 'package:dataconnect/movies_connector/movies.dart'; -import 'package:dataconnect/util/auth.dart'; -import 'package:dataconnect/widgets/list_movies.dart'; -import 'package:dataconnect/widgets/list_reviews.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:flutter/material.dart'; - import 'package:go_router/go_router.dart'; -class Profile extends StatefulWidget { - const Profile({super.key}); - - @override - State createState() => _ProfileState(); -} - -class _ProfileState extends State - with WidgetsBindingObserver, RouteAware { - User? _currentUser; - List _favoriteMovies = []; - List _reviews = []; - String? _displayName; - final RouteObserver routeObserver = RouteObserver(); - StreamSubscription>? _listener; - - @override - void initState() { - super.initState(); - Auth.getCurrentUser().then((user) { - if (mounted) { - setState(() { - _currentUser = user; - }); - } - }); - _listener = MovieState.subscribeToCurrentUser().listen((res) { - if (mounted) { - setState(() { - _displayName = res.data.user!.name; - _favoriteMovies = - res.data.user!.favoriteMovies.map((e) => e.movie).toList(); - _reviews = res.data.user!.reviews; - }); - } - }); - } - - @override - void dispose() { - super.dispose(); - _listener?.cancel(); - } - - _refresh() { - MoviesConnector.instance.getCurrentUser().execute(); - } +import 'movie_state.dart'; +import 'models/movie.dart'; +import 'movies_connector/movies.dart'; +import 'util/auth.dart'; +import 'widgets/list_movies.dart'; +import 'widgets/list_reviews.dart'; +import 'widgets/login_guard.dart'; - Widget _buildUserInfo() { - return Container( - child: Column( - children: [ - Text('Welcome back ${_displayName ?? ''}!'), - TextButton( - onPressed: () async { - FirebaseAuth.instance.signOut(); - context.go('/login'); - }, - child: Text('Sign out')) - ], - ), - ); - } +class Profile extends StatelessWidget { + const Profile({super.key}); @override Widget build(BuildContext context) { - return _currentUser == null - ? const Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [CircularProgressIndicator()], - ) - : RefreshIndicator( - child: SingleChildScrollView( + return LoginGuard(builder: (context) { + return StreamBuilder( + stream: MovieState.subscribeToCurrentUser(), + builder: (context, snapshot) { + final res = snapshot.data; + if (res == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + final displayName = res.data.user!.name; + final favoriteMovies = + res.data.user!.favoriteMovies.map((e) => e.movie).toList(); + final reviews = res.data.user!.reviews; + return RefreshIndicator( + child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Container( - padding: EdgeInsets.all(30), + padding: const EdgeInsets.all(30), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildUserInfo(), + Column( + children: [ + Text('Welcome back $displayName !'), + TextButton( + onPressed: () async { + FirebaseAuth.instance.signOut(); + context.go('/login'); + }, + child: const Text('Sign out')) + ], + ), ListMovies( title: "Favorite Movies", - movies: _favoriteMovies + movies: favoriteMovies .map( (e) => Movie( id: e.id, @@ -105,21 +60,25 @@ class _ProfileState extends State ) .toList()), ListReviews( - reviews: _reviews + reviews: reviews .map( (e) => Review( reviewText: e.reviewText!, rating: e.rating!, - username: _currentUser!.email!, + username: Auth.instance.currentUser!.email!, date: e.reviewDate), ) .toList(), title: "Reviews"), ], ), - )), - onRefresh: () async { - await _refresh(); - }); + ), + ), + onRefresh: () async { + MoviesConnector.instance.getCurrentUser().execute(); + }, + ); + }); + }); } } diff --git a/dataconnect/lib/router.dart b/dataconnect/lib/router.dart index 00615dd7..4aeb25b1 100644 --- a/dataconnect/lib/router.dart +++ b/dataconnect/lib/router.dart @@ -1,9 +1,9 @@ -import 'package:dataconnect/destination.dart'; -import 'package:dataconnect/login.dart'; -import 'package:dataconnect/profile.dart'; -import 'package:dataconnect/search.dart'; import 'package:go_router/go_router.dart'; +import 'login.dart'; +import 'destination.dart'; +import 'profile.dart'; +import 'search.dart'; import 'actor_detail.dart'; import 'main.dart'; import 'movie_detail.dart'; @@ -11,56 +11,67 @@ import 'navigation_shell.dart'; import 'sign_up.dart'; import 'util/auth.dart'; -var router = GoRouter(initialLocation: homePath.path, routes: [ - StatefulShellRoute.indexedStack( +var router = GoRouter( + refreshListenable: Auth.instance, + initialLocation: homePath.path, + routes: [ + StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return NavigationShell(navigationShell: navigationShell); }, branches: [ - StatefulShellBranch(routes: [ - GoRoute( - path: homePath.path, - builder: (context, state) => const MyHomePage(), - ), - GoRoute( - path: '/movies/:movieId', - builder: (context, state) => - MovieDetail(id: state.pathParameters['movieId']!)), - GoRoute( - path: "/actors", - redirect: (context, state) => - '/actors/${state.pathParameters['actorId']}', - routes: [ - GoRoute( - path: ":actorId", - builder: (context, state) => - ActorDetail(actorId: state.pathParameters['actorId']!)) - ]) - ]), - StatefulShellBranch(routes: [ - GoRoute( - path: "/search", - builder: (context, state) => const Search(), - ), - ]), - StatefulShellBranch(routes: [ - GoRoute( - path: "/profile", + StatefulShellBranch( + routes: [ + GoRoute( + path: homePath.path, + builder: (context, state) => const MyHomePage(), + ), + GoRoute( + path: '/movies/:movieId', + builder: (context, state) => + MovieDetail(id: state.pathParameters['movieId']!)), + GoRoute( + path: "/actors", + redirect: (context, state) => + '/actors/${state.pathParameters['actorId']}', + routes: [ + GoRoute( + path: ":actorId", + builder: (context, state) => ActorDetail( + actorId: state.pathParameters['actorId']!)) + ]) + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: "/search", + builder: (context, state) => const Search(), + ), + ], + ), + StatefulShellBranch( + routes: [ + GoRoute( + path: "/profile", + redirect: (context, state) async { + return Auth.instance.isLoggedIn ? null : '/login'; + }, + builder: (context, state) => const Profile()), + GoRoute( + path: "/login", + builder: (context, state) => const Login(), redirect: (context, state) async { - return (await Auth.isLoggedIn()) ? null : '/login'; + return Auth.instance.isLoggedIn ? '/profile' : null; }, - builder: (context, state) => const Profile()), - GoRoute( - path: "/login", - builder: (context, state) => const Login(), - redirect: (context, state) async { - return (await Auth.isLoggedIn()) ? '/profile' : null; - }, - ), - GoRoute( - path: "/signup", - builder: (context, state) => const SignUp(), - ) - ]) - ]) -]); + ), + GoRoute( + path: "/signup", + builder: (context, state) => const SignUp(), + ) + ], + ) + ], + ) + ], +); diff --git a/dataconnect/lib/search.dart b/dataconnect/lib/search.dart index bc2d18c1..23932e26 100644 --- a/dataconnect/lib/search.dart +++ b/dataconnect/lib/search.dart @@ -1,10 +1,11 @@ -import 'package:dataconnect/models/movie.dart'; -import 'package:dataconnect/movies_connector/movies.dart'; -import 'package:dataconnect/widgets/list_actors.dart'; -import 'package:dataconnect/widgets/list_movies.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'models/movie.dart'; +import 'movies_connector/movies.dart'; +import 'widgets/list_actors.dart'; +import 'widgets/list_movies.dart'; + class Search extends StatefulWidget { const Search({super.key}); diff --git a/dataconnect/lib/util/auth.dart b/dataconnect/lib/util/auth.dart index 8c59b99c..2113b806 100644 --- a/dataconnect/lib/util/auth.dart +++ b/dataconnect/lib/util/auth.dart @@ -1,20 +1,44 @@ +import 'dart:async'; + import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; -class Auth { - static Future isLoggedIn() async { - User? user = await FirebaseAuth.instance.authStateChanges().first; - if (user == null) { - return false; - } +typedef AuthState = ({User? user, String? token}); + +class Auth extends ValueNotifier { + bool get isLoggedIn => value.user != null && value.token != null; + User? get currentUser => value.user; + String? get token => value.token; + + StreamSubscription? _cleanup; + + Future init() async { + final user = FirebaseAuth.instance.currentUser; + final token = await user?.token; + value = (user: user, token: token); + _cleanup = FirebaseAuth.instance.authStateChanges().listen((user) async { + final token = await user?.token; + value = (user: user, token: token); + }); + } + + @override + void dispose() { + super.dispose(); + _cleanup?.cancel(); + } + + static Auth instance = Auth((user: null, token: null)); + + Auth(super.value); +} + +extension on User { + Future get token async { try { - String? idToken = await user.getIdToken(); - return idToken != null; + return await getIdToken(); } catch (_) { - return false; + return null; } } - - static getCurrentUser() { - return FirebaseAuth.instance.authStateChanges().first; - } } diff --git a/dataconnect/lib/widgets/list_actors.dart b/dataconnect/lib/widgets/list_actors.dart index 47ea5dbd..51251934 100644 --- a/dataconnect/lib/widgets/list_actors.dart +++ b/dataconnect/lib/widgets/list_actors.dart @@ -1,64 +1,63 @@ import 'package:flutter/material.dart'; class Actor { - Actor({required this.imageUrl, required this.name, required this.id}); + Actor({ + required this.imageUrl, + required this.name, + required this.id, + }); String imageUrl; String name; String id; } -class ListActors extends StatefulWidget { - const ListActors({super.key, required this.actors, required this.title}); +class ListActors extends StatelessWidget { + const ListActors({ + super.key, + required this.actors, + required this.title, + }); final List actors; final String title; - @override - State createState() => _ListActorsState(); -} - -class _ListActorsState extends State { - _buildActor(Actor actor) { - return SizedBox( - width: 100, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircleAvatar( - radius: 30, - child: ClipOval(child: Image.network(actor.imageUrl))), - Text( - actor.name, - overflow: TextOverflow.ellipsis, - ) - ])); - } - - _buildActorsList() { - return SizedBox( - height: 125, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.title, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - return _buildActor(widget.actors[index]); - }, - itemCount: widget.actors.length, - )) - ], - )); - } - @override Widget build(BuildContext context) { - return _buildActorsList(); + return SizedBox( + height: 125, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + Expanded( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + final actor = actors[index]; + return SizedBox( + width: 100, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + radius: 30, + child: ClipOval(child: Image.network(actor.imageUrl))), + Text( + actor.name, + overflow: TextOverflow.ellipsis, + ) + ], + ), + ); + }, + itemCount: actors.length, + )) + ], + ), + ); } } diff --git a/dataconnect/lib/widgets/list_movies.dart b/dataconnect/lib/widgets/list_movies.dart index 1936e30e..bfa97042 100644 --- a/dataconnect/lib/widgets/list_movies.dart +++ b/dataconnect/lib/widgets/list_movies.dart @@ -1,19 +1,20 @@ -import 'package:dataconnect/models/movie.dart'; -import 'package:dataconnect/movies_connector/movies.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -class ListMovies extends StatefulWidget { - const ListMovies({super.key, required this.movies, required this.title}); +import '/models/movie.dart'; + +class ListMovies extends StatelessWidget { + const ListMovies({ + super.key, + required this.movies, + required this.title, + }); final List movies; final String title; - @override - State createState() => _ListMoviesState(); -} -class _ListMoviesState extends State { - Widget _buildMovieList(String title, List movies) { + @override + Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -39,54 +40,45 @@ class _ListMoviesState extends State { scrollDirection: Axis.horizontal, itemCount: movies.length, itemBuilder: (context, index) { - return _buildMovieItem(movies[index]); + final movie = movies[index]; + return Container( + width: 150, // Adjust the width as needed + padding: const EdgeInsets.all(4.0), + child: Card( + child: InkWell( + onTap: () { + context.push("/movies/${movie.id}"); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: + 9 / 16, // 9:16 aspect ratio for the image + child: Image.network( + movie.imageUrl, + fit: BoxFit.cover, + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + movie.title, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ), + ); }, ), ), ], ); } - - void _visitDetail(String id) { - context.push("/movies/$id"); - } - - Widget _buildMovieItem(Movie movie) { - return Container( - width: 150, // Adjust the width as needed - padding: const EdgeInsets.all(4.0), - child: Card( - child: InkWell( - onTap: () { - _visitDetail(movie.id); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: 9 / 16, // 9:16 aspect ratio for the image - child: Image.network( - movie.imageUrl, - fit: BoxFit.cover, - ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - movie.title, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - ], - )), - ), - ); - } - - @override - Widget build(BuildContext context) { - return _buildMovieList(widget.title, widget.movies); - } } diff --git a/dataconnect/lib/widgets/list_reviews.dart b/dataconnect/lib/widgets/list_reviews.dart index 41470418..8188cbf9 100644 --- a/dataconnect/lib/widgets/list_reviews.dart +++ b/dataconnect/lib/widgets/list_reviews.dart @@ -2,66 +2,64 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class Review { - Review( - {required this.reviewText, - required this.rating, - required this.username, - required this.date}); + Review({ + required this.reviewText, + required this.rating, + required this.username, + required this.date, + }); String reviewText; int rating; String username; DateTime date; } -class ListReviews extends StatefulWidget { - const ListReviews({super.key, required this.reviews, required this.title}); +class ListReviews extends StatelessWidget { + const ListReviews({ + super.key, + required this.reviews, + required this.title, + }); final List reviews; final String title; - @override - State createState() => _ListReviewsState(); -} - -class _ListReviewsState extends State { - Widget _buildRatingList() { - List reviews = widget.reviews; - return SizedBox( - height: 125, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - var rating = reviews[index]; - return Card( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(rating.username), - Row( - children: [ - Text(DateFormat.yMMMd().format(rating.date)), - const SizedBox( - width: 10, - ), - Text("Rating ${rating.rating}") - ], - ), - Text(rating.reviewText!) - ], - )), - ); - }, - itemCount: reviews.length, - )); - - // return Expanded(child: Text('abc')); - } @override Widget build(BuildContext context) { return Column( - children: [Text(widget.title), _buildRatingList()], + children: [ + Text(title), + SizedBox( + height: 125, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + var rating = reviews[index]; + return Card( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(rating.username), + Row( + children: [ + Text(DateFormat.yMMMd().format(rating.date)), + const SizedBox( + width: 10, + ), + Text("Rating ${rating.rating}") + ], + ), + Text(rating.reviewText) + ], + )), + ); + }, + itemCount: reviews.length, + ), + ), + ], ); } } diff --git a/dataconnect/lib/widgets/login_guard.dart b/dataconnect/lib/widgets/login_guard.dart index 3072c4c4..1e3f9d0d 100644 --- a/dataconnect/lib/widgets/login_guard.dart +++ b/dataconnect/lib/widgets/login_guard.dart @@ -1,38 +1,33 @@ import 'package:dataconnect/util/auth.dart'; import 'package:flutter/material.dart'; -class LoginGuard extends StatefulWidget { - const LoginGuard({super.key, required this.widgetToGuard, this.message}); - - final Widget widgetToGuard; +class LoginGuard extends StatelessWidget { + const LoginGuard({ + super.key, + required this.builder, + this.message, + }); + final WidgetBuilder builder; final String? message; - @override - State createState() => _LoginGuardState(); -} - -class _LoginGuardState extends State { - bool isLoggedIn = false; - @override - void initState() { - super.initState(); - Auth.isLoggedIn().then((value) { - setState(() { - isLoggedIn = value; - }); - }); - } - @override Widget build(BuildContext context) { - if (isLoggedIn) { - return widget.widgetToGuard; - } - if (widget.message == null) { - return const SizedBox(); - } - return Text( - 'Please visit the Profile page to log in before ${widget.message}'); + return ValueListenableBuilder( + valueListenable: Auth.instance, + builder: (context, state, _) { + final isLoggedIn = Auth.instance.isLoggedIn; + if (!isLoggedIn) { + if (message == null) { + return const SizedBox(); + } + return Text( + 'Please visit the profile page' + 'to log in before $message', + ); + } + return builder(context); + }, + ); } } diff --git a/dataconnect/macos/Podfile.lock b/dataconnect/macos/Podfile.lock new file mode 100644 index 00000000..7cb336e7 --- /dev/null +++ b/dataconnect/macos/Podfile.lock @@ -0,0 +1,131 @@ +PODS: + - AppCheckCore (11.2.0): + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - Firebase/AppCheck (11.4.2): + - Firebase/CoreOnly + - FirebaseAppCheck (~> 11.4.0) + - Firebase/Auth (11.4.2): + - Firebase/CoreOnly + - FirebaseAuth (~> 11.4.0) + - Firebase/CoreOnly (11.4.2): + - FirebaseCore (= 11.4.2) + - firebase_app_check (0.3.1-4): + - Firebase/AppCheck (~> 11.4.0) + - Firebase/CoreOnly (~> 11.4.0) + - firebase_core + - FlutterMacOS + - firebase_auth (5.3.3): + - Firebase/Auth (~> 11.4.0) + - Firebase/CoreOnly (~> 11.4.0) + - firebase_core + - FlutterMacOS + - firebase_core (3.8.0): + - Firebase/CoreOnly (~> 11.4.0) + - FlutterMacOS + - FirebaseAppCheck (11.4.0): + - AppCheckCore (~> 11.0) + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseCore (~> 11.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - FirebaseAppCheckInterop (11.5.0) + - FirebaseAuth (11.4.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.4) + - FirebaseCoreExtension (~> 11.4) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GTMSessionFetcher/Core (< 5.0, >= 3.4) + - RecaptchaInterop (~> 100.0) + - FirebaseAuthInterop (11.5.0) + - FirebaseCore (11.4.2): + - FirebaseCoreInternal (< 12.0, >= 11.4.2) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreExtension (11.4.1): + - FirebaseCore (~> 11.0) + - FirebaseCoreInternal (11.5.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FlutterMacOS (1.0.0) + - GoogleUtilities/AppDelegateSwizzler (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.0.2): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.0.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.0.2)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/Reachability (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GTMSessionFetcher/Core (4.1.0) + - PromisesObjC (2.4.0) + +DEPENDENCIES: + - firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`) + - firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`) + - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + +SPEC REPOS: + trunk: + - AppCheckCore + - Firebase + - FirebaseAppCheck + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - GoogleUtilities + - GTMSessionFetcher + - PromisesObjC + +EXTERNAL SOURCES: + firebase_app_check: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos + firebase_auth: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos + firebase_core: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f + Firebase: 7fd5466678d964be78fbf536d8a3385da19c4828 + firebase_app_check: 27a62bd66d30049207742fcb7fc996bdb82ba622 + firebase_auth: 85ed6c80b24da60af13391f9cdc0e566440b1963 + firebase_core: d95c4a2225d7b6ed46bc31fb2a6f421fc7c8285b + FirebaseAppCheck: 933cbda29279ed316b82360bca77602ac1af1ff2 + FirebaseAppCheckInterop: d265d9f4484e7ec1c591086408840fdd383d1213 + FirebaseAuth: c359af98bd703cbf4293eec107a40de08ede6ce6 + FirebaseAuthInterop: 1219bee9b23e6ebe84c256a0d95adab53d11c331 + FirebaseCore: 6b32c57269bd999aab34354c3923d92a6e5f3f84 + FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e + FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + GTMSessionFetcher: 923b710231ad3d6f3f0495ac1ced35421e07d9a6 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 + +COCOAPODS: 1.16.2 diff --git a/dataconnect/macos/Runner.xcodeproj/project.pbxproj b/dataconnect/macos/Runner.xcodeproj/project.pbxproj index e6892799..1cfc1f84 100644 --- a/dataconnect/macos/Runner.xcodeproj/project.pbxproj +++ b/dataconnect/macos/Runner.xcodeproj/project.pbxproj @@ -21,12 +21,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 0B54BE36095256A990923F60 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2C7FA3F3771C2A29C4152FF /* Pods_RunnerTests.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 682E69E29E5999549E31008B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCBDA75210EC7C9219DEEA17 /* Pods_Runner.framework */; }; E81C99BCE641EAAC42D0435C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F9721B425DB992F996064CCA /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ @@ -61,6 +63,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04AEC284C642086067839821 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 0D7D17F289FAFC2610AFF3B2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; @@ -77,9 +81,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5074E90362781627D86FABAA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8B4A2D9CD56B4E2E89139CB8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AA887168DC22BA276076B2A8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + B2ADFEFAAEAA3DEBDB5C916D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + B2C7FA3F3771C2A29C4152FF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F9721B425DB992F996064CCA /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; + FCBDA75210EC7C9219DEEA17 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,6 +97,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0B54BE36095256A990923F60 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,6 +105,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 682E69E29E5999549E31008B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -128,6 +140,7 @@ 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, F9721B425DB992F996064CCA /* GoogleService-Info.plist */, + CB6772AFF3560852DF3C4E63 /* Pods */, ); sourceTree = ""; }; @@ -175,9 +188,24 @@ path = Runner; sourceTree = ""; }; + CB6772AFF3560852DF3C4E63 /* Pods */ = { + isa = PBXGroup; + children = ( + 04AEC284C642086067839821 /* Pods-Runner.debug.xcconfig */, + 8B4A2D9CD56B4E2E89139CB8 /* Pods-Runner.release.xcconfig */, + 5074E90362781627D86FABAA /* Pods-Runner.profile.xcconfig */, + 0D7D17F289FAFC2610AFF3B2 /* Pods-RunnerTests.debug.xcconfig */, + B2ADFEFAAEAA3DEBDB5C916D /* Pods-RunnerTests.release.xcconfig */, + AA887168DC22BA276076B2A8 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + FCBDA75210EC7C9219DEEA17 /* Pods_Runner.framework */, + B2C7FA3F3771C2A29C4152FF /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -189,6 +217,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + DE64E3F8A561EACF28AA5DB4 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -207,11 +236,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 03EF311D5499B576A847F58B /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + D515E6E5EBB879A7D1A062B1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -295,6 +326,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 03EF311D5499B576A847F58B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -333,6 +386,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + D515E6E5EBB879A7D1A062B1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + DE64E3F8A561EACF28AA5DB4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -384,6 +476,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0D7D17F289FAFC2610AFF3B2 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -398,6 +491,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = B2ADFEFAAEAA3DEBDB5C916D /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -412,6 +506,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = AA887168DC22BA276076B2A8 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -465,7 +560,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -487,6 +582,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -547,7 +643,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -597,7 +693,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -619,6 +715,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -639,6 +736,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/dataconnect/macos/Runner.xcworkspace/contents.xcworkspacedata b/dataconnect/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/dataconnect/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/dataconnect/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/dataconnect/macos/Runner/AppDelegate.swift b/dataconnect/macos/Runner/AppDelegate.swift index 8e02df28..b3c17614 100644 --- a/dataconnect/macos/Runner/AppDelegate.swift +++ b/dataconnect/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } }