diff --git a/android/app/build.gradle b/android/app/build.gradle index 3a5deeb..93fd370 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,6 +24,7 @@ if (flutterVersionName == null) { apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion flutter.compileSdkVersion @@ -44,13 +45,14 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.tinder" + applicationId "com.tinder" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { @@ -68,4 +70,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation platform('com.google.firebase:firebase-bom:30.1.0') } diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..55686d0 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,39 @@ +{ + "project_info": { + "project_number": "12384404765", + "project_id": "tinder-af728", + "storage_bucket": "tinder-af728.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:12384404765:android:21128f6e7c33e5eeeecdf4", + "android_client_info": { + "package_name": "com.tinder" + } + }, + "oauth_client": [ + { + "client_id": "12384404765-44t2opis1um3imshe6u8n09hnb30tpgg.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCFQ90dEVIgvXYC9PowiA5vxfuR703G_dY" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "12384404765-44t2opis1um3imshe6u8n09hnb30tpgg.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 01baeec..1e89661 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ { + final DatabaseService _databaseService; + final UserService _userService; + final List> _idsAndImages = []; + + AppCubit(this._databaseService, this._userService) : super(AppInitial()); + + void setInitial() { + emit(AppInitial()); + } + + // Инициализация юзера во время авторизации + Future initUserAuth( + {required String id, + required String login, + required String password}) async { + Map data = await _databaseService.getUserData(id); + String name = data['name']; + List likes = []; + if (data['likes'] != null) { + for (var element in data['likes']) { + likes.add(element.toString()); + } + } + List dislikes = []; + if (data['dislikes'] != null) { + for (var element in data['dislikes']) { + dislikes.add(element.toString()); + } + } + String image = data['image']; + _userService.user = AppUser( + id: id, + login: login, + password: password, + name: name, + likes: likes, + dislikes: dislikes, + image: image); + } + + // Инициализация юзера во время регистрации + Future initUserRegister( + {required String id, + required String name, + required String login, + required String password}) async { + int usersCount = await _databaseService.getUsersCount(); + _userService.user = AppUser( + id: id, + login: login, + password: password, + name: name, + likes: [], + dislikes: [], + image: 'assets/images/${usersCount + 1}.jpg'); + _databaseService.setUserData(_userService.user!); + } + + Future addLike(String id) async { + _userService.user!.likes.add(id); + await _databaseService.setUserData(_userService.user!); + } + + Future addDislike(String id) async { + _userService.user!.dislikes.add(id); + await _databaseService.setUserData(_userService.user!); + } + + // Инициализация списка всех юзеров + Future initUsers() async { + _userService.users ??= await _databaseService.getUsers(); + } + + // Инициализация списка айди и изображений юзеров + Future initIdsAndImages() async { + await initUsers(); + final Map usersImages = {}; + for (Map map in _userService.users!) { + usersImages[map['id']!] = map['image']!; + } + Iterable usersIds = usersImages.keys; + for (String id in usersIds) { + if (!_userService.user!.likes.contains(id) && + !_userService.user!.dislikes.contains(id) && + _userService.user!.id != id) { + _idsAndImages.add(Tuple2(id, usersImages[id]!)); + } + } + if (_idsAndImages.isEmpty) { + emit(AppFinish()); + } else { + emit(AppCards()); + } + } + + List> get idsAndImages { + return _idsAndImages; + } + + void clear() { + _idsAndImages.clear(); + } +} diff --git a/lib/cubit/app_state.dart b/lib/cubit/app_state.dart new file mode 100644 index 0000000..687f0e9 --- /dev/null +++ b/lib/cubit/app_state.dart @@ -0,0 +1,10 @@ +part of 'app_cubit.dart'; + +@immutable +abstract class AppState {} + +class AppInitial extends AppState {} + +class AppCards extends AppState {} + +class AppFinish extends AppState {} diff --git a/lib/cubit/auth_cubit.dart b/lib/cubit/auth_cubit.dart index 505e4ce..f36a909 100644 --- a/lib/cubit/auth_cubit.dart +++ b/lib/cubit/auth_cubit.dart @@ -1,27 +1,116 @@ import 'package:bloc/bloc.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; +import 'package:tinder/services/storage.dart'; part 'auth_state.dart'; class AuthCubit extends Cubit { - int counter = 0; + final FirebaseAuth _auth; + final Storage _storage; - AuthCubit() : super(AuthInitial()); + AuthCubit(this._auth, this._storage) : super(AuthInitialState()); - void login() async { - if (counter == 0) { - emit(AuthInProgress()); - await Future.delayed(const Duration(seconds: 1)); - emit(AuthError()); - counter++; + // Future initStorage() async { + // _storage = Storage(await SharedPreferences.getInstance()); + // } + + Future?> automaticAuth() async { + String? login = _storage.getEmail(); + String? password = _storage.getPassword(); + if (login != null && password != null) { + Map? data = await logIn(login, password); + if (data == null) { + emit(AuthPage()); + } + return data; } else { + emit(AuthPage()); + return null; + } + } + + Future?> logIn(String login, String password) async { + try { emit(AuthInProgress()); - await Future.delayed(const Duration(seconds: 2)); - emit(AuthSuccess()); + UserCredential result = await _auth.signInWithEmailAndPassword( + email: login, password: password); + User? user = result.user; + if (user != null) { + await _storage.setData(login, password); + emit(AuthSuccess()); + return {'id': user.uid, 'login': login, 'password': password}; + } else { + emit(AuthError()); + return null; + } + } catch (error) { + print(error); + emit(AuthError()); + return null; } } - void registration() { + bool isEmptyFieldsAuth(String login, String password) { + if (login.isEmpty || password.isEmpty) { + emit(AuthEmptyFields()); + return true; + } + return false; + } + + // bool isEmptyFieldsRegister(String name, String login, String password) { + // if (name.isEmpty || login.isEmpty || password.isEmpty) { + // emit(RegistrationEmptyFields()); + // return true; + // } + // return false; + // } + + void setRegistration() { emit(AuthRegistration()); } + + // Future?> register( + // String name, String login, String password) async { + // try { + // UserCredential result = await _auth.createUserWithEmailAndPassword( + // email: login, password: password); + // User? user = result.user; + // if (user != null) { + // await _storage.setData(login, password); + // emit(RegistrationSuccess()); + // return { + // 'id': user.uid, + // 'name': name, + // 'email': login, + // 'password': password + // }; + // } else { + // emit(RegistrationError()); + // return null; + // } + // } on FirebaseAuthException catch (error) { + // print(error); + // if (error.code == 'email-already-in-use') { + // emit(RegErrorEmailInUse()); // ошибка: почта уже используется + // } else if (error.code == 'invalid-email') { + // emit(RegErrorInvalidEmail()); // ошибка: неккоректная почта + // } else if (error.code == 'operation-not-allowed') { + // emit(RegErrorDisabledAccount()); // ошибка: аккаунт отключен + // } else { + // emit(RegErrorShortPassword()); // ошибка: короткий пароль + // } + // return null; + // } catch (error) { + // print(error); + // emit(RegistrationError()); + // return null; + // } + // } + + Future logOut() async { + await _storage.clear(); + await _auth.signOut(); + } } diff --git a/lib/cubit/auth_state.dart b/lib/cubit/auth_state.dart index ab73f52..79a1e55 100644 --- a/lib/cubit/auth_state.dart +++ b/lib/cubit/auth_state.dart @@ -3,12 +3,34 @@ part of 'auth_cubit.dart'; @immutable abstract class AuthState {} -class AuthInitial extends AuthState {} +class AuthInitialState extends AuthState {} + +class AuthPage extends AuthState {} class AuthError extends AuthState {} +class AuthEmptyFields extends AuthState {} + class AuthSuccess extends AuthState {} class AuthInProgress extends AuthState {} -class AuthRegistration extends AuthState{} +class AuthRegistration extends AuthState {} + +// class RegistrationInitial extends AuthState {} + +// class RegistrationError extends AuthState {} + +// class RegErrorEmailInUse extends AuthState {} + +// class RegErrorInvalidEmail extends AuthState {} + +// class RegErrorDisabledAccount extends AuthState {} + +// class RegErrorShortPassword extends AuthState {} + +// class RegistrationEmptyFields extends AuthState {} + +// class RegistrationSuccess extends AuthState {} + +// class RegistrationInProgress extends AuthState {} diff --git a/lib/cubit/rating_cubit.dart b/lib/cubit/rating_cubit.dart new file mode 100644 index 0000000..d56975e --- /dev/null +++ b/lib/cubit/rating_cubit.dart @@ -0,0 +1,38 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:tinder/services/user_service.dart'; +import 'package:tuple/tuple.dart'; + +part 'rating_state.dart'; + +class RatingCubit extends Cubit { + final UserService _userService; + + RatingCubit(this._userService) : super(RatingInitial()); + + List> getLikedUsers() { + List> likedUsers = []; + List likedId = _userService.user!.likes; + for (String id in likedId) { + for (Map map in _userService.users!) { + if (map['id'] == id) { + likedUsers.add(Tuple2(map['image']!, map['name']!)); + } + } + } + return likedUsers; + } + + List> getDislikedUsers() { + List> dislikedUsers = []; + List dislikedId = _userService.user!.dislikes; + for (String id in dislikedId) { + for (Map map in _userService.users!) { + if (map['id'] == id) { + dislikedUsers.add(Tuple2(map['image']!, map['name']!)); + } + } + } + return dislikedUsers; + } +} diff --git a/lib/cubit/rating_state.dart b/lib/cubit/rating_state.dart new file mode 100644 index 0000000..ab4b07f --- /dev/null +++ b/lib/cubit/rating_state.dart @@ -0,0 +1,6 @@ +part of 'rating_cubit.dart'; + +@immutable +abstract class RatingState {} + +class RatingInitial extends RatingState {} diff --git a/lib/cubit/registration_cubit.dart b/lib/cubit/registration_cubit.dart new file mode 100644 index 0000000..621eb0f --- /dev/null +++ b/lib/cubit/registration_cubit.dart @@ -0,0 +1,59 @@ +import 'package:bloc/bloc.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:tinder/services/storage.dart'; + +part 'registration_state.dart'; + +class RegistrationCubit extends Cubit { + final FirebaseAuth _auth; + final Storage _storage; + + RegistrationCubit(this._auth, this._storage) : super(RegistrationInitial()); + + bool isEmptyFieldsRegister(String name, String login, String password) { + if (name.isEmpty || login.isEmpty || password.isEmpty) { + emit(RegistrationEmptyFields()); + return true; + } + return false; + } + + Future?> register( + String name, String login, String password) async { + try { + UserCredential result = await _auth.createUserWithEmailAndPassword( + email: login, password: password); + User? user = result.user; + if (user != null) { + await _storage.setData(login, password); + emit(RegistrationSuccess()); + return { + 'id': user.uid, + 'name': name, + 'email': login, + 'password': password + }; + } else { + emit(RegistrationError()); + return null; + } + } on FirebaseAuthException catch (error) { + print(error); + if (error.code == 'email-already-in-use') { + emit(RegErrorEmailInUse()); // ошибка: почта уже используется + } else if (error.code == 'invalid-email') { + emit(RegErrorInvalidEmail()); // ошибка: неккоректная почта + } else if (error.code == 'operation-not-allowed') { + emit(RegErrorDisabledAccount()); // ошибка: аккаунт отключен + } else { + emit(RegErrorShortPassword()); // ошибка: короткий пароль + } + return null; + } catch (error) { + print(error); + emit(RegistrationError()); + return null; + } + } +} diff --git a/lib/cubit/registration_state.dart b/lib/cubit/registration_state.dart new file mode 100644 index 0000000..3d26ec3 --- /dev/null +++ b/lib/cubit/registration_state.dart @@ -0,0 +1,22 @@ +part of 'registration_cubit.dart'; + +@immutable +abstract class RegistrationState {} + +class RegistrationInitial extends RegistrationState {} + +class RegistrationError extends RegistrationState {} + +class RegErrorEmailInUse extends RegistrationState {} + +class RegErrorInvalidEmail extends RegistrationState {} + +class RegErrorDisabledAccount extends RegistrationState {} + +class RegErrorShortPassword extends RegistrationState {} + +class RegistrationEmptyFields extends RegistrationState {} + +class RegistrationSuccess extends RegistrationState {} + +class RegistrationInProgress extends RegistrationState {} diff --git a/lib/main.dart b/lib/main.dart index 4146c17..c034b39 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,24 +1,60 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tinder/cubit/auth_cubit.dart'; +import 'package:tinder/cubit/app_cubit.dart'; +import 'package:tinder/cubit/rating_cubit.dart'; +import 'package:tinder/cubit/registration_cubit.dart'; import 'package:tinder/router.dart'; import 'package:tinder/routes.dart'; +import 'package:tinder/services/database_service.dart'; +import 'package:tinder/services/storage.dart'; +import 'package:tinder/services/user_service.dart'; -void main() { - runApp(const MyApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + Storage storage = Storage(await SharedPreferences.getInstance()); + runApp(MyApp(storage: storage)); } class MyApp extends StatelessWidget { - const MyApp({super.key}); + final DatabaseService _databaseService = DatabaseService(); + final UserService _userService = UserService(); + final FirebaseAuth _auth = FirebaseAuth.instance; + final Storage storage; + + MyApp({super.key, required this.storage}); @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => AuthCubit(), - child: const MaterialApp( - title: 'Tinder', - onGenerateRoute: MyRouter.generateRoute, - initialRoute: Routes.auth, + return Provider( + create: (BuildContext context) => _userService, + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => AuthCubit(_auth, storage), + ), + BlocProvider( + create: (BuildContext context) => RegistrationCubit(_auth, storage), + ), + BlocProvider( + create: (BuildContext context) => + AppCubit(_databaseService, _userService), + ), + BlocProvider( + create: (BuildContext context) => + RatingCubit(_userService), + ), + ], + child: const MaterialApp( + title: 'Tinder', + onGenerateRoute: MyRouter.generateRoute, + initialRoute: Routes.initial, + ), ), ); } diff --git a/lib/models/app_user.dart b/lib/models/app_user.dart new file mode 100644 index 0000000..c5b225f --- /dev/null +++ b/lib/models/app_user.dart @@ -0,0 +1,30 @@ +class AppUser { + final String id; + final String name; + final String login; + final String password; + final List likes; + final List dislikes; + final String? image; + + AppUser( + {required this.id, + required this.name, + required this.login, + required this.password, + required this.likes, + required this.dislikes, + required this.image}); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'login': login, + 'password': password, + 'likes': likes, + 'dislikes': dislikes, + 'image': image, + }; + } +} diff --git a/lib/router.dart b/lib/router.dart index db7d951..673236f 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,33 +1,45 @@ import 'package:flutter/material.dart'; import 'package:tinder/routes.dart'; +import 'package:tinder/screens/app.dart'; import 'package:tinder/screens/auth.dart'; import 'package:tinder/screens/cards.dart'; import 'package:tinder/screens/finish.dart'; +import 'package:tinder/screens/auth_initial.dart'; +import 'package:tinder/screens/rating.dart'; import 'package:tinder/screens/registration.dart'; class MyRouter { static Route generateRoute(RouteSettings settings) { switch (settings.name) { - case Routes.auth: - return MaterialPageRoute( - builder: (BuildContext context) => const Auth()); - case Routes.registration: - return MaterialPageRoute( - builder: (BuildContext context) => const Registration()); - case Routes.cards: - return MaterialPageRoute( - builder: (BuildContext context) => const Cards()); - case Routes.finish: - return MaterialPageRoute( - builder: (BuildContext context) => const Finish()); - default: - return MaterialPageRoute( - builder: (BuildContext context) => const Scaffold( - body: Center( - child: Text('Navigation Error'), - ), - ), - ); - } + case Routes.initial: + return MaterialPageRoute( + builder: (BuildContext context) => const AuthInitial()); + case Routes.auth: + return MaterialPageRoute( + builder: (BuildContext context) => const Auth()); + case Routes.registration: + return MaterialPageRoute( + builder: (BuildContext context) => const Registration()); + case Routes.cards: + return MaterialPageRoute( + builder: (BuildContext context) => const Cards()); + case Routes.finish: + return MaterialPageRoute( + builder: (BuildContext context) => const Finish()); + case Routes.app: + return MaterialPageRoute( + builder: (BuildContext context) => const App()); + case Routes.rating: + return MaterialPageRoute( + builder: (BuildContext context) => const Rating()); + default: + return MaterialPageRoute( + builder: (BuildContext context) => const Scaffold( + body: Center( + child: Text('Navigation Error'), + ), + ), + ); + } } -} \ No newline at end of file +} diff --git a/lib/routes.dart b/lib/routes.dart index c54b7a9..e2d3ae4 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -3,4 +3,7 @@ abstract class Routes { static const cards = '/cards'; static const finish = '/finish'; static const registration = '/registration'; -} \ No newline at end of file + static const app = '/app'; + static const initial = '/initial'; + static const rating = '/rating'; +} diff --git a/lib/screens/app.dart b/lib/screens/app.dart new file mode 100644 index 0000000..7ae0e14 --- /dev/null +++ b/lib/screens/app.dart @@ -0,0 +1,70 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tinder/cubit/app_cubit.dart'; +import 'package:tinder/screens/cards.dart'; +import 'package:tinder/screens/finish.dart'; + +// Cтраница, которая откроет либо карточки с другими пользователями, +// либо страницу финиша, если карточек не осталось +class App extends StatefulWidget { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + late final Timer _timer; + + late AppCubit _appCubit; + + Future initData() async { + await context.read().initIdsAndImages(); + } + + @override + void initState() { + _timer = Timer(const Duration(milliseconds: 600), initData); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocBuilder( + builder: (context, state) { + if (state is AppCards) { + return const Cards(); + } else if (state is AppFinish) { + return const Finish(); + } else { + return Center( + child: SizedBox( + height: 50, + width: 50, + child: CircularProgressIndicator( + color: Colors.deepPurple[400], + ), + ), + ); + } + }, + ), + ); + } + + @override + void didChangeDependencies() { + _appCubit = context.read(); + super.didChangeDependencies(); + } + + @override + void dispose() { + _appCubit.setInitial(); + _timer.cancel(); + super.dispose(); + } +} diff --git a/lib/screens/auth.dart b/lib/screens/auth.dart index 98d2a51..a6b2ee9 100644 --- a/lib/screens/auth.dart +++ b/lib/screens/auth.dart @@ -1,111 +1,165 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tinder/cubit/auth_cubit.dart'; +import 'package:tinder/cubit/app_cubit.dart'; +import 'package:tinder/routes.dart'; -class Auth extends StatelessWidget { +class Auth extends StatefulWidget { const Auth({super.key}); + @override + State createState() => _AuthState(); +} + +class _AuthState extends State { + late String _login; + late String _password; + + final TextEditingController loginController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + Future _onLoginPressed() async { + _login = loginController.text.trim(); + _password = passwordController.text.trim(); + + // Проверка полей на пустоту, если пустые - выдается ошибка + if (!context.read().isEmptyFieldsAuth(_login, _password)) { + Map? data = + await context.read().logIn(_login, _password); + if (data != null) { + initUser(data); + } + } + } + + void initUser(Map data) { + context.read().initUserAuth( + id: data['id']!, login: data['login']!, password: data['password']!); + } + @override Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Padding( - padding: const EdgeInsets.all(30), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox( - height: 18, - ), - TextField( - decoration: InputDecoration( - labelText: 'Логин', - labelStyle: const TextStyle(color: Colors.grey, fontSize: 18), - floatingLabelStyle: - const TextStyle(color: Colors.grey, fontSize: 20), - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(50), - borderSide: const BorderSide( - width: 0, - style: BorderStyle.none, + return WillPopScope( + onWillPop: () async { + return false; + }, + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: const Text('Авторизация'), + backgroundColor: Colors.deepPurple[400], + centerTitle: true, + ), + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(30), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: loginController, + decoration: InputDecoration( + labelText: 'Логин', + labelStyle: + const TextStyle(color: Colors.grey, fontSize: 17), + floatingLabelStyle: + const TextStyle(color: Colors.grey, fontSize: 20), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(50), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), ), ), - ), - ), - const SizedBox( - height: 20, - ), - TextField( - obscureText: true, - decoration: InputDecoration( - labelText: 'Пароль', - labelStyle: const TextStyle(color: Colors.grey, fontSize: 18), - floatingLabelStyle: - const TextStyle(color: Colors.grey, fontSize: 20), - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(50), - borderSide: const BorderSide( - width: 0, - style: BorderStyle.none, + const SizedBox( + height: 20, + ), + TextField( + controller: passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Пароль', + labelStyle: + const TextStyle(color: Colors.grey, fontSize: 17), + floatingLabelStyle: + const TextStyle(color: Colors.grey, fontSize: 20), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(50), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), ), ), - ), - ), - const SizedBox( - height: 20, - ), - BlocConsumer(listener: (context, state) { - if (state is AuthSuccess) { - Navigator.of(context).pushReplacementNamed('/cards'); - } else if (state is AuthRegistration) { - Navigator.of(context).pushNamed('/registration'); - } - }, builder: (context, state) { - if (state is AuthError) { - return Text( - 'Неправильный логин или пароль', - style: TextStyle(fontSize: 17, color: Colors.red[600]), - ); - } else if (state is AuthInProgress) { - return CircularProgressIndicator( - color: Colors.deepPurple[400]); - } else { - return const Text(''); - } - }), - const SizedBox( - height: 20, - ), - ElevatedButton( - onPressed: () { - context.read().login(); - }, - style: ElevatedButton.styleFrom( - textStyle: const TextStyle(fontSize: 20), - primary: Colors.deepPurple[400], - padding: - const EdgeInsets.symmetric(horizontal: 40, vertical: 15), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), + const SizedBox( + height: 20, ), - ), - child: const Text('Войти'), - ), - const SizedBox( - height: 10, - ), - TextButton( - onPressed: () { - context.read().registration(); - }, - child: Text( - 'Нет аккаунта', - style: TextStyle(color: Colors.grey[500], fontSize: 18), - ), + BlocConsumer( + listener: (context, state) { + if (state is AuthSuccess) { + Navigator.of(context).pushReplacementNamed(Routes.app); + } else if (state is AuthRegistration) { + Navigator.of(context).pushNamed(Routes.registration); + } + }, + builder: (context, state) { + if (state is AuthError) { + return Text( + 'Неправильный логин или пароль', + style: + TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is AuthEmptyFields) { + return Text( + 'Поля должны быть заполнены', + style: + TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is AuthInProgress) { + return CircularProgressIndicator( + color: Colors.deepPurple[400]); + } else { + return const Text(''); + } + }, + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: _onLoginPressed, + style: ElevatedButton.styleFrom( + textStyle: const TextStyle(fontSize: 20), + primary: Colors.deepPurple[400], + padding: const EdgeInsets.symmetric( + horizontal: 40, vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + ), + child: const Text('Войти'), + ), + const SizedBox( + height: 10, + ), + TextButton( + onPressed: () { + context.read().setRegistration(); + }, + child: Text( + 'Нет аккаунта', + style: TextStyle(color: Colors.grey[500], fontSize: 18), + ), + ), + ], ), - ], + ), ), ), ), diff --git a/lib/screens/auth_initial.dart b/lib/screens/auth_initial.dart new file mode 100644 index 0000000..6d6457b --- /dev/null +++ b/lib/screens/auth_initial.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tinder/cubit/app_cubit.dart'; +import 'package:tinder/cubit/auth_cubit.dart'; +import 'package:tinder/screens/app.dart'; +import 'package:tinder/screens/auth.dart'; + +// Начальная страница приложения, которая откроет либо страницу авторизации, +// либо само приложение, если пользователь уже авторизован +class AuthInitial extends StatefulWidget { + const AuthInitial({super.key}); + + @override + State createState() => _AuthInitialState(); +} + +class _AuthInitialState extends State { + late final Map _data; + late final Timer _timer; + + Future auth() async { + // Попытка автоматического входа в приложение + Map? data = await context.read().automaticAuth(); + if (data != null) { + _data = data; + await initUser(); + } + } + + Future initUser() async { + await context.read().initUserAuth( + id: _data['id']!, + login: _data['login']!, + password: _data['password']!, + ); + } + + @override + void initState() { + _timer = Timer(const Duration(milliseconds: 1), auth); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocBuilder( + builder: (context, state) { + if (state is AuthSuccess) { + return const App(); + } else if (state is AuthPage) { + return const Auth(); + } else { + return Center( + child: SizedBox( + height: 50, + width: 50, + child: CircularProgressIndicator( + color: Colors.deepPurple[400], + ), + ), + ); + } + }, + buildWhen: (previous, current) => + current is AuthInitial || + current is AuthSuccess || + current is AuthPage, + ), + ); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } +} diff --git a/lib/screens/cards.dart b/lib/screens/cards.dart index 88c9053..b1f2021 100644 --- a/lib/screens/cards.dart +++ b/lib/screens/cards.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import 'package:swipe_cards/swipe_cards.dart'; +import 'package:tinder/cubit/auth_cubit.dart'; +import 'package:tinder/cubit/app_cubit.dart'; import 'package:tinder/routes.dart'; +import 'package:tuple/tuple.dart'; class Cards extends StatefulWidget { const Cards({Key? key}) : super(key: key); @@ -10,26 +15,27 @@ class Cards extends StatefulWidget { } class _CardsState extends State { - final List _images = [ - 'images/1.jpg', - 'images/2.jpg', - 'images/3.jpg', - 'images/4.jpg', - 'images/5.jpg', - ]; + final List> _idsAndImages = []; final List _swipeItems = []; late final MatchEngine _matchEngine; + void initIdsAndImages() { + _idsAndImages.addAll(context.read().idsAndImages); + } + @override void initState() { - for (int i = 0; i < _images.length; i++) { + initIdsAndImages(); + for (int i = 0; i < _idsAndImages.length; i++) { _swipeItems.add( SwipeItem( - content: buildCard(_images[i]), + content: buildCard(_idsAndImages[i].item2), likeAction: () { + context.read().addLike(_idsAndImages[i].item1); snackBar('Liked'); }, nopeAction: () { + context.read().addDislike(_idsAndImages[i].item1); snackBar('Disliked'); }, ), @@ -42,6 +48,20 @@ class _CardsState extends State { @override Widget build(BuildContext context) { return Scaffold( + floatingActionButton: TextButton( + child: Text( + 'Выйти', + style: TextStyle( + fontSize: 20, + color: Colors.grey[500], + ), + ), + onPressed: () { + context.read().clear(); + context.read().logOut(); + Navigator.of(context).pushReplacementNamed(Routes.auth); + }, + ), body: Center( child: Padding( padding: const EdgeInsets.all(30), @@ -71,7 +91,9 @@ class _CardsState extends State { height: 450, decoration: BoxDecoration( color: Colors.red[50], - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: const BorderRadius.all( + Radius.circular(25), + ), border: Border.all( color: Colors.red, ), @@ -82,7 +104,9 @@ class _CardsState extends State { Container( height: 320, decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(25)), + borderRadius: const BorderRadius.all( + Radius.circular(25), + ), image: DecorationImage( image: Image.asset(image).image, fit: BoxFit.cover, @@ -97,19 +121,31 @@ class _CardsState extends State { children: [ GestureDetector( onTap: () { - _matchEngine.currentItem!.like(); + _matchEngine.currentItem!.nope(); }, child: Container( height: 70, width: 70, - decoration: const BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.all(Radius.circular(40)), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(0, 5), + ), + ], + borderRadius: const BorderRadius.all( + Radius.circular(40), + ), ), + child: const Icon(Icons.heart_broken, + size: 50, color: Colors.red), ), ), const SizedBox( - width: 100, + width: 120, ), GestureDetector( onTap: () { @@ -118,10 +154,22 @@ class _CardsState extends State { child: Container( height: 70, width: 70, - decoration: const BoxDecoration( - color: Colors.green, - borderRadius: BorderRadius.all(Radius.circular(40)), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(0, 5), + ), + ], + borderRadius: const BorderRadius.all( + Radius.circular(40), + ), ), + child: + const Icon(Icons.favorite, size: 50, color: Colors.green), ), ), ], @@ -140,4 +188,10 @@ class _CardsState extends State { ), ); } + + @override + void dispose() { + _idsAndImages.clear(); + super.dispose(); + } } diff --git a/lib/screens/finish.dart b/lib/screens/finish.dart index 3a41a76..52cff48 100644 --- a/lib/screens/finish.dart +++ b/lib/screens/finish.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tinder/cubit/auth_cubit.dart'; +import 'package:tinder/cubit/app_cubit.dart'; import 'package:tinder/routes.dart'; class Finish extends StatelessWidget { @@ -6,6 +9,7 @@ class Finish extends StatelessWidget { @override Widget build(BuildContext context) { + //context.read().getLikedAndDisliked(); return Scaffold( floatingActionButton: TextButton( child: Text( @@ -16,6 +20,8 @@ class Finish extends StatelessWidget { ), ), onPressed: () { + context.read().clear(); + context.read().logOut(); Navigator.of(context).pushReplacementNamed(Routes.auth); }, ), @@ -24,7 +30,7 @@ class Finish extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Вы достигли конца списка', + 'Пока что больше никого нет...', style: TextStyle(fontSize: 20, color: Colors.grey[500]), ), const SizedBox( @@ -32,7 +38,7 @@ class Finish extends StatelessWidget { ), ElevatedButton( onPressed: () { - Navigator.of(context).pushReplacementNamed(Routes.cards); + Navigator.of(context).pushNamed(Routes.rating); }, style: ElevatedButton.styleFrom( textStyle: const TextStyle(fontSize: 18), @@ -43,7 +49,7 @@ class Finish extends StatelessWidget { borderRadius: BorderRadius.circular(50), ), ), - child: const Text('Начать сначала'), + child: const Text('Посмотреть оценки'), ), const SizedBox( height: 15, diff --git a/lib/screens/rating.dart b/lib/screens/rating.dart new file mode 100644 index 0000000..5dc433d --- /dev/null +++ b/lib/screens/rating.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tinder/cubit/rating_cubit.dart'; +import 'package:tuple/tuple.dart'; + +class Rating extends StatefulWidget { + const Rating({Key? key}) : super(key: key); + + @override + State createState() => _RatingState(); +} + +class _RatingState extends State { + final List _likedUsersWidget = []; + final List _dislikedUsersWidget = []; + + void getUsers() { + List> likedUsers = + context.read().getLikedUsers(); + List> dislikedUsers = + context.read().getDislikedUsers(); + + for (Tuple2 tuple in likedUsers) { + _likedUsersWidget.add(_listItem(tuple.item1, tuple.item2)); + } + for (Tuple2 tuple in dislikedUsers) { + _dislikedUsersWidget.add(_listItem(tuple.item1, tuple.item2)); + } + } + + @override + void initState() { + getUsers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.deepPurple[400], + bottom: const TabBar( + indicatorColor: Colors.white, + tabs: [ + Tab( + icon: Icon(Icons.favorite), + ), + Tab( + icon: Icon(Icons.heart_broken), + ), + ], + ), + title: const Text('Оценки'), + ), + body: TabBarView( + children: [ + ListView(children: _likedUsersWidget), + ListView(children: _dislikedUsersWidget) + ], + ), + ), + ); + } + + Widget _listItem(String image, String name) { + return Container( + margin: const EdgeInsets.all(5), + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: const Offset(0, 5), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 70, + width: 70, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(40), + ), + image: DecorationImage( + image: Image.asset(image).image, + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(width: 25), + Container( + height: 70, + alignment: Alignment.centerRight, + child: Text( + name, + style: const TextStyle(fontSize: 18), + ), + ), + ], + ), + ); + } + + @override + void dispose() { + _likedUsersWidget.clear(); + _dislikedUsersWidget.clear(); + super.dispose(); + } +} diff --git a/lib/screens/registration.dart b/lib/screens/registration.dart index 7f76a43..ba09314 100644 --- a/lib/screens/registration.dart +++ b/lib/screens/registration.dart @@ -1,20 +1,194 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tinder/cubit/app_cubit.dart'; +import 'package:tinder/cubit/registration_cubit.dart'; +import 'package:tinder/routes.dart'; -// По ТЗ нужно сделать заглушку -class Registration extends StatelessWidget { +class Registration extends StatefulWidget { const Registration({Key? key}) : super(key: key); + @override + State createState() => _RegistrationState(); +} + +class _RegistrationState extends State { + late String _name; + late String _login; + late String _password; + + final TextEditingController nameController = TextEditingController(); + final TextEditingController loginController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + Future _onRegisterPressed() async { + _name = nameController.text.trim(); + _login = loginController.text.trim(); + _password = passwordController.text.trim(); + + // Проверка полей на пустоту + if (!context + .read() + .isEmptyFieldsRegister(_name, _login, _password)) { + Map? data = await context + .read() + .register(_name, _login, _password); + if (data != null) { + initUser(data); + } + } + } + + void initUser(Map data) { + context.read().initUserRegister( + id: data['id']!, + name: data['name']!, + login: data['email']!, + password: data['password']!); + } + @override Widget build(BuildContext context) { return Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.startTop, - floatingActionButton: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Назад', - style: TextStyle(fontSize: 18, color: Colors.grey[500]), + appBar: AppBar( + title: const Text('Регистрация'), + backgroundColor: Colors.deepPurple[400], + centerTitle: true, + ), + body: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(30), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: nameController, + decoration: InputDecoration( + labelText: 'Имя', + labelStyle: + const TextStyle(color: Colors.grey, fontSize: 17), + floatingLabelStyle: + const TextStyle(color: Colors.grey, fontSize: 20), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(50), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + TextField( + controller: loginController, + decoration: InputDecoration( + labelText: 'Адрес электронной почты', + labelStyle: + const TextStyle(color: Colors.grey, fontSize: 17), + floatingLabelStyle: + const TextStyle(color: Colors.grey, fontSize: 20), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(50), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + TextField( + controller: passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Пароль', + labelStyle: + const TextStyle(color: Colors.grey, fontSize: 17), + floatingLabelStyle: + const TextStyle(color: Colors.grey, fontSize: 20), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(50), + borderSide: const BorderSide( + width: 0, + style: BorderStyle.none, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + BlocConsumer( + listener: (context, state) { + if (state is RegistrationSuccess) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.app, (_) => false); + } + }, builder: (context, state) { + if (state is RegistrationEmptyFields) { + return Text( + 'Поля должны быть заполнены', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is RegistrationError) { + return Text( + 'Ошибка регистрации', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is RegErrorEmailInUse) { + return Text( + 'Адрес электронной почты уже используется', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + textAlign: TextAlign.center, + ); + } else if (state is RegErrorInvalidEmail) { + return Text( + 'Неверный адрес электронной почты', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is RegErrorDisabledAccount) { + return Text( + 'Учётная запись отключена', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is RegErrorShortPassword) { + return Text( + 'Пароль должен быть не менее 6 символов', + style: TextStyle(fontSize: 17, color: Colors.red[600]), + ); + } else if (state is RegistrationInProgress) { + return CircularProgressIndicator( + color: Colors.deepPurple[400]); + } else { + return const Text(''); + } + }), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: _onRegisterPressed, + style: ElevatedButton.styleFrom( + textStyle: const TextStyle(fontSize: 20), + primary: Colors.deepPurple[400], + padding: const EdgeInsets.symmetric( + horizontal: 40, vertical: 15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + ), + child: const Text('Ок'), + ), + ], + ), + ), ), ), ); diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart new file mode 100644 index 0000000..262e162 --- /dev/null +++ b/lib/services/database_service.dart @@ -0,0 +1,52 @@ +import 'package:firebase_database/firebase_database.dart'; +import 'package:tinder/models/app_user.dart'; + +class DatabaseService { + final DatabaseReference databaseReference = FirebaseDatabase.instance.ref(); + + Future getUsersCount() async { + DataSnapshot dataSnapshot = await databaseReference.get(); + return dataSnapshot.children.length; + } + + // Получить айди, имя и изображение всех пользователей + Future>> getUsers() async { + DataSnapshot dataSnapshot = await databaseReference.get(); + Iterable dataSnapshotIter = dataSnapshot.children; + List> users = []; + + // Цикл по всем пользователям + for (int i = 0; i < dataSnapshotIter.length; i++) { + Iterable userSnapshotIter = + dataSnapshotIter.elementAt(i).children; + Map user = {}; + + // Цикл по данным одного пользователя + for (int j = 0; j < userSnapshotIter.length; j++) { + String? key = userSnapshotIter.elementAt(j).key; + Object? value = userSnapshotIter.elementAt(j).value; + if (key == 'name' || key == 'id' || key == 'image') { + user[key!] = value.toString(); + } + } + users.add(user); + } + return users; + } + + Future setUserData(AppUser user) async { + await databaseReference.child(user.id).set(user.toMap()); + } + + // Получить данные текущего пользователя + Future> getUserData(String id) async { + DataSnapshot dataSnapshot = await databaseReference.child(id).get(); + Iterable dataSnapshotIter = dataSnapshot.children; + Map data = {}; + for (int i = 0; i < dataSnapshotIter.length; i++) { + data[dataSnapshotIter.elementAt(i).key!] = + dataSnapshotIter.elementAt(i).value; + } + return data; + } +} diff --git a/lib/services/storage.dart b/lib/services/storage.dart new file mode 100644 index 0000000..c7cb718 --- /dev/null +++ b/lib/services/storage.dart @@ -0,0 +1,25 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tinder/services/storage_keys.dart'; + +class Storage { + final SharedPreferences _storage; + + Storage(this._storage); + + Future setData(String email, String password) async { + await _storage.setString(StorageKeys.email, email); + await _storage.setString(StorageKeys.password, password); + } + + String? getEmail() { + return _storage.getString(StorageKeys.email); + } + + String? getPassword() { + return _storage.getString(StorageKeys.password); + } + + Future clear() async { + await _storage.clear(); + } +} diff --git a/lib/services/storage_keys.dart b/lib/services/storage_keys.dart new file mode 100644 index 0000000..b2e06aa --- /dev/null +++ b/lib/services/storage_keys.dart @@ -0,0 +1,4 @@ +abstract class StorageKeys { + static const email = 'email'; + static const password = 'password'; +} \ No newline at end of file diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart new file mode 100644 index 0000000..6696751 --- /dev/null +++ b/lib/services/user_service.dart @@ -0,0 +1,6 @@ +import 'package:tinder/models/app_user.dart'; + +class UserService { + AppUser? user; + List>? users; +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e5b0871 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,18 @@ import FlutterMacOS import Foundation +import cloud_firestore +import firebase_auth +import firebase_core +import firebase_database +import path_provider_macos +import shared_preferences_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index c2bb60e..ea975a8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.18" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.8" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.17" collection: dependency: transitive description: @@ -57,6 +78,83 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.20" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.2.8" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.17" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "1.18.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.1" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.5" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.16" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+8" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+15" flutter: dependency: "direct main" description: flutter @@ -81,6 +179,32 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: @@ -123,13 +247,146 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" - provider: + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.16" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.10" + path_provider_linux: dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" source: hosted version: "6.0.3" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.12" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" sky_engine: dependency: transitive description: flutter @@ -184,6 +441,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" + tuple: + dependency: "direct main" + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: @@ -191,6 +462,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" sdks: dart: ">=2.18.0-170.0.dev <3.0.0" - flutter: ">=1.17.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index e81b664..396cf2a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,14 @@ dependencies: bloc: ^8.0.3 flutter_bloc: ^8.0.1 swipe_cards: ^1.0.0 + firebase_auth: ^3.3.20 + cloud_firestore: ^3.1.18 + firebase_core: ^1.18.0 + shared_preferences: ^2.0.15 + firebase_database: ^9.0.16 + path_provider: ^2.0.11 + tuple: ^2.0.0 + provider: ^6.0.3 dev_dependencies: flutter_test: @@ -62,7 +70,8 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - images/ + - assets/images/ + - assets/first_users.txt # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart index fe5723f..5f88ef0 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:tinder/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);