diff --git a/.gitignore b/.gitignore index 29a3a50..7a06ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,13 @@ .history .svn/ migrate_working_dir/ +assets/.env + +google-services.json +firebase.json +GoogleService-Info.plist +firebase_options.dart + # IntelliJ related *.iml @@ -16,6 +23,10 @@ migrate_working_dir/ *.iws .idea/ +# Flutter related files +*.flutter-plugins +*.flutter-plugins-dependencies + # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. diff --git a/.idea/mapsee_front.iml b/.idea/mapsee_front.iml index afddb80..bc94149 100644 --- a/.idea/mapsee_front.iml +++ b/.idea/mapsee_front.iml @@ -6,10 +6,14 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3827c7d..3ca57c5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/android/app/build.gradle b/android/app/build.gradle index 57e0ac7..bf7b08f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,8 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" diff --git a/android/settings.gradle b/android/settings.gradle index b9e43bd..9759a22 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,6 +19,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.1.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "1.8.22" apply false } diff --git a/assets/images/mapsee_logo.png b/assets/images/mapsee_logo.png new file mode 100644 index 0000000..9d3acc7 Binary files /dev/null and b/assets/images/mapsee_logo.png differ diff --git a/lib/auth/auth_gate.dart b/lib/auth/auth_gate.dart new file mode 100644 index 0000000..ae690e8 --- /dev/null +++ b/lib/auth/auth_gate.dart @@ -0,0 +1,26 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:mapsee/auth/login_or_register.dart'; +import 'package:mapsee/pages/home_page.dart'; + +class AuthGate extends StatelessWidget { + const AuthGate({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + // user is logged in + if (snapshot.hasData) { + return const HomePage(); + } + //user is not logged in + else { + return const LoginOrRegister(); + } + }), + ); + } +} diff --git a/lib/auth/auth_service.dart b/lib/auth/auth_service.dart new file mode 100644 index 0000000..59e836e --- /dev/null +++ b/lib/auth/auth_service.dart @@ -0,0 +1,34 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthService { + // instance of auth + final FirebaseAuth _auth = FirebaseAuth.instance; + + // sign in + Future signInWithEmailAndPassword( + String email, String password) async { + try { + UserCredential userCredential = await _auth.signInWithEmailAndPassword( + email: email, password: password); + return userCredential; + } on FirebaseException catch (e) { + throw Exception(e.code); + } + } + // sign up + Future signUpWithEmailPassword(String email, String password) async{ + try{ + UserCredential userCredential = await _auth.createUserWithEmailAndPassword(email: email, password: password); + return userCredential; + }on FirebaseException catch(e){ + throw Exception(e.code); + } + } + + // sign out + Future signOut()async{ + return await _auth.signOut(); +} + + //errors +} diff --git a/lib/auth/login_or_register.dart b/lib/auth/login_or_register.dart new file mode 100644 index 0000000..fb50d30 --- /dev/null +++ b/lib/auth/login_or_register.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:mapsee/pages/login_page.dart'; +import 'package:mapsee/pages/register_page.dart'; + +class LoginOrRegister extends StatefulWidget { + const LoginOrRegister({super.key}); + + @override + State createState() => _LoginOrRegisterState(); +} + +class _LoginOrRegisterState extends State { + // initially show login page + bool showLoginPage = true; + + // toggle between login and register page + void togglePages() { + setState(() { + showLoginPage = !showLoginPage; + }); + } + + @override + Widget build(BuildContext context) { + if (showLoginPage) { + return LoginPage( + onTap: togglePages, + ); + } else { + return RegisterPage( + onTap: togglePages, + ); + } + } +} diff --git a/lib/components/date_input_form.dart b/lib/components/date_input_form.dart new file mode 100644 index 0000000..d598af5 --- /dev/null +++ b/lib/components/date_input_form.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +class DateInputForm extends StatelessWidget { + final TextEditingController yearController; + final TextEditingController monthController; + final TextEditingController dayController; + + const DateInputForm({ + Key? key, + required this.yearController, + required this.monthController, + required this.dayController, + }) : super(key: key); + + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(0.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // YYYY 필드 + Flexible( + child: Row( + children: [ + Expanded( + child: TextField( + controller: yearController, + decoration: InputDecoration( + hintText: 'YYYY', + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.outline), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + ), + keyboardType: TextInputType.number, + ), + ), + SizedBox(width: 4), // 여백 + Text('년'), + ], + ), + ), + SizedBox(width: 8), + // MM 필드 + Flexible( + child: Row( + children: [ + Expanded( + child: TextField( + controller: monthController, + decoration: InputDecoration( + hintText: 'MM', + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.outline), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + ), + keyboardType: TextInputType.number, + ), + ), + SizedBox(width: 4), // 여백 + Text('월'), + ], + ), + ), + SizedBox(width: 8), + // DD 필드 + Flexible( + child: Row( + children: [ + Expanded( + child: TextField( + controller: dayController, + decoration: InputDecoration( + hintText: 'DD', + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.outline), + border: OutlineInputBorder(), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + ), + keyboardType: TextInputType.number, + ), + ), + SizedBox(width: 4), // 여백 + Text( + '일', + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/components/gender_selection_form.dart b/lib/components/gender_selection_form.dart new file mode 100644 index 0000000..1ffb758 --- /dev/null +++ b/lib/components/gender_selection_form.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class GenderSelectionForm extends StatefulWidget { + final ValueChanged onGenderChanged; + GenderSelectionForm({required this.onGenderChanged}); + + @override + _GenderSelectionFormState createState() => _GenderSelectionFormState(); +} + +class _GenderSelectionFormState extends State { + String? _selectedGender; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(0.0), + child: GenderSelection( + selectedGender: _selectedGender, + onGenderChanged: (value) { + setState(() { + _selectedGender = value; + }); + widget.onGenderChanged(value); + }, + ), + ); + } +} + +class GenderSelection extends StatelessWidget { + final String? selectedGender; + final ValueChanged onGenderChanged; + + const GenderSelection({ + Key? key, + required this.selectedGender, + required this.onGenderChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final genderOptions = [ + {'label': '남성', 'value': '남성'}, + {'label': '여성', 'value': '여성'}, + {'label': '선택하지 않음', 'value': '선택하지 않음'}, + ]; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: genderOptions.map((option) { + return Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 2.0), + child: SizedBox( + height: 40, + child: ChoiceChip( + label: Container( + alignment: Alignment.topCenter, + child: Text( + option['label']!, + textAlign: TextAlign.center, + style: TextStyle( + color: selectedGender == option['value'] + ? Colors.white + : Colors.black, + ), + ), + ), + selected: selectedGender == option['value'], + selectedColor: Theme.of(context).colorScheme.primary, + backgroundColor: Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + onSelected: (selected) { + if (selected) onGenderChanged(option['value']); + }, + showCheckmark: false, + ), + ), + ), + ); + }).toList(), + ); + } +} diff --git a/lib/components/my_button.dart b/lib/components/my_button.dart new file mode 100644 index 0000000..4aca3e0 --- /dev/null +++ b/lib/components/my_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class MyButton extends StatelessWidget { + final void Function()? onTap; + final String text; + + const MyButton({super.key, required this.text, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(15), + margin: const EdgeInsets.symmetric(horizontal: 0), + child: Center( + child: Text( + text, + style: TextStyle(color: Theme.of(context).colorScheme.background), + ), + )), + ); + } +} diff --git a/lib/components/my_textfield.dart b/lib/components/my_textfield.dart new file mode 100644 index 0000000..f084aaa --- /dev/null +++ b/lib/components/my_textfield.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class MyTextfield extends StatelessWidget { + final String hintText; + final bool obscureText; + final TextEditingController controller; + + const MyTextfield( + {super.key, + required this.hintText, + required this.obscureText, + required this.controller}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0), + child: TextField( + obscureText: obscureText, + controller: controller, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.outline), + ), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + fillColor: Theme.of(context).colorScheme.background, + filled: true, + hintText: hintText, + hintStyle: TextStyle(color: Theme.of(context).colorScheme.outline)), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 8e94089..d7b7a12 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,14 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; - -void main() { +import 'package:mapsee/auth/auth_gate.dart'; +import 'package:mapsee/firebase_options.dart'; +import 'package:mapsee/theme/light_mode.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +void main() async{ + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: ".env"); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); runApp(const MyApp()); } @@ -11,115 +19,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + debugShowCheckedModeBanner: false, + home: const AuthGate(), + theme: lightMode, ); } } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 0000000..2935d83 --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:mapsee/auth/auth_service.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + void logout(){ + //get auth service + final _auth = AuthService(); + _auth.signOut(); + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("맵시"), + actions: [ + // logout button + IconButton(onPressed: logout, icon: Icon(Icons.logout)) + ], + ), + drawer: Drawer(), + ); + } +} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 0000000..e322273 --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:mapsee/auth/auth_service.dart'; +import 'package:mapsee/components/my_button.dart'; +import 'package:mapsee/components/my_textfield.dart'; + +class LoginPage extends StatelessWidget { + // id, pw text controller + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _pwController = TextEditingController(); + + // tap to go register page + final void Function()? onTap; + + LoginPage({super.key, required this.onTap}); + + // login method + void login(BuildContext context) async { + // auth service + final authService = AuthService(); + + // try login + try { + await authService.signInWithEmailAndPassword( + _emailController.text, _pwController.text); + } catch (e) { + showDialog(context: context, builder: (context) => AlertDialog( + title: Text(e.toString()), + )); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme + .of(context) + .colorScheme + .background, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 80.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // logo + Image.asset( + 'assets/images/mapsee_logo.png', + width: 126, + height: 100, + ), + // Id field + MyTextfield( + hintText: "ID를 입력하세요", + obscureText: false, + controller: _emailController, + ), + const SizedBox(height: 7), + // PW field + MyTextfield( + hintText: "비밀번호를 입력하세요", + obscureText: true, + controller: _pwController, + ), + // 아이디 비번 잊으셨나요 + const SizedBox(height: 7), + Center( + child: SizedBox( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'ID', + style: TextStyle( + color: Color(0xFFB3B3B3), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + height: 0, + letterSpacing: 1.69, + ), + ), + TextSpan( + text: ' 또는 ', + style: TextStyle( + color: Color(0xFFB3B3B3), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w200, + height: 0, + letterSpacing: 1.69, + ), + ), + TextSpan( + text: '비밀번호', + style: TextStyle( + color: Color(0xFFB3B3B3), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + height: 0, + letterSpacing: 1.69, + ), + ), + TextSpan( + text: '를 잊으셨나요?', + style: TextStyle( + color: Color(0xFFB3B3B3), + fontSize: 13, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w200, + height: 0, + letterSpacing: 1.69, + ), + ), + ], + ), + ), + ), + ), + + const SizedBox(height: 7), + // 로그인 버튼 + MyButton( + text: "로그인", + onTap: () => login(context), + ), + const SizedBox(height: 7), + // 더욱 간편한 로그인 + Center( + child: Container( + width: 265, + height: 14.35, + child: Stack( + children: [ + Positioned( + left: 80.60, + top: 0, + child: SizedBox( + width: 104.90, + height: 14.35, + child: Text( + '더욱 간편한 로그인', + style: TextStyle( + color: Color(0xFFB3B3B3), + fontSize: 11, + fontFamily: 'Pretendard', + fontWeight: FontWeight.w300, + height: 0, + letterSpacing: 1.43, + ), + ), + ), + ), + Positioned( + left: 0, + top: 6.62, + child: Container( + width: 68.46, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + width: 0.50, + strokeAlign: BorderSide.strokeAlignCenter, + color: Color(0xFFD9D9D9), + ), + ), + ), + ), + ), + Positioned( + left: 196.54, + top: 6.62, + child: Container( + width: 68.46, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + width: 0.50, + strokeAlign: BorderSide.strokeAlignCenter, + color: Color(0xFFD9D9D9), + ), + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 7), + // 구글, 카카오, 네이버 + MyButton( + text: "구글", + onTap: ()=>{}, + ), + const SizedBox(height: 7), + MyButton( + text: "카카오", + onTap: ()=>{}, + ), + const SizedBox(height: 7), + MyButton( + text: "네이버", + onTap: ()=>{}, + ), + const SizedBox(height: 7), + // 회원가입 이동 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "아직 계정이 없으신가요?", + style: + TextStyle(color: Theme + .of(context) + .colorScheme + .outline), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: onTap, + child: Text("회원가입", + style: TextStyle( + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + color: Theme + .of(context) + .colorScheme + .outline)), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/register_page.dart b/lib/pages/register_page.dart new file mode 100644 index 0000000..c38720e --- /dev/null +++ b/lib/pages/register_page.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:mapsee/auth/auth_service.dart'; +import 'package:mapsee/components/date_input_form.dart'; +import 'package:mapsee/components/gender_selection_form.dart'; +import 'package:mapsee/components/my_button.dart'; +import 'package:mapsee/components/my_textfield.dart'; + +class RegisterPage extends StatefulWidget { + final void Function()? onTap; + + RegisterPage({super.key, required this.onTap}); + + @override + _RegisterPageState createState() => _RegisterPageState(); +} + +class _RegisterPageState extends State { + // ID, PW, 전화번호 등의 TextEditingController + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _pwController = TextEditingController(); + final TextEditingController _confirmPwController = TextEditingController(); + final TextEditingController _telNumController = TextEditingController(); + final TextEditingController _confirmTelNumController = + TextEditingController(); + final TextEditingController _yearController = TextEditingController(); + final TextEditingController _monthController = TextEditingController(); + final TextEditingController _dayController = TextEditingController(); + + String? _selectedGender; + + void register(BuildContext context) { + final _auth = AuthService(); + // pasword match -> create user + if (_pwController.text == _confirmPwController.text) { + try { + _auth.signUpWithEmailPassword( + _emailController.text, _pwController.text); + } catch (e) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(e.toString()), + )); + } + } + + // pasword don't match -> show error + else { + showDialog( + context: context, + builder: (context) => const AlertDialog( + title: Text("비밀번호가 일치하지 않습니다."), + )); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 80.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // logo + Image.asset( + 'assets/images/mapsee_logo.png', + width: 126, + height: 100, + ), + // Id field + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("아이디"), + MyTextfield( + hintText: "ID를 입력하세요", + obscureText: false, + controller: _emailController, + ), + ], + ), + const SizedBox(height: 7), + // PW field + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("비밀번호"), + MyTextfield( + hintText: "비밀번호를 입력하세요", + obscureText: true, + controller: _pwController, + ), + const SizedBox(height: 5), + // 비번 확인 + MyTextfield( + hintText: "비밀번호를 다시 입력하세요", + obscureText: true, + controller: _confirmPwController, + ), + ], + ), + const SizedBox(height: 7), + + // 휴대전화 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("휴대전화"), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: MyTextfield( + hintText: "전화번호를 입력하세요", + obscureText: false, + controller: _telNumController, + ), + ), + const SizedBox(width: 10), + MyButton( + text: "인증번호 발송", + onTap: () { + // 인증번호 발송 로직 + }, + ), + ], + ), + const SizedBox(height: 5), + // 인증번호 입력 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: MyTextfield( + hintText: "인증번호를 입력하세요", + obscureText: false, + controller: _confirmTelNumController, + ), + ), + const SizedBox(width: 10), + MyButton( + text: "인증번호 확인", + onTap: () { + // 인증번호 확인 로직 + }, + ), + ], + ), + ], + ), + const SizedBox(height: 7), + // 생년월일 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("생년월일"), + DateInputForm( + yearController: _yearController, + monthController: _monthController, + dayController: _dayController, + ), + ], + ), + // 성별 선택 + const SizedBox(height: 7), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("성별"), + GenderSelectionForm( + onGenderChanged: (value) { + setState(() { + _selectedGender = value; + }); + }, + ), + ], + ), + const SizedBox(height: 14), + //약관 + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.check_box_outlined, + color: Theme.of(context).colorScheme.outline, + ), + SizedBox( + width: 4, + ), + Text("약관 모두 동의") + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.check_box_outlined, + color: Theme.of(context).colorScheme.outline, + ), + SizedBox( + width: 4, + ), + Text("개인 정보 및 정보 수집 동의") + ], + ), + ], + ), + const SizedBox(height: 7), + + // 회원가입 버튼 + MyButton( + text: "회원가입", + onTap: () => register(context), + ), + const SizedBox(height: 7), + // 로그인 이동 + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "이미 계정이 있으신가요?", + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + const SizedBox(width: 20), + GestureDetector( + onTap: widget.onTap, + child: Text("로그인", + style: TextStyle( + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.outline)), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/theme/light_mode.dart b/lib/theme/light_mode.dart new file mode 100644 index 0000000..0e25345 --- /dev/null +++ b/lib/theme/light_mode.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +ThemeData lightMode = ThemeData( + colorScheme: ColorScheme.light( + background: Colors.white, + primary: Color(0xFFFF8200), + secondary: Color(0xFF002E5D), + outline: Color(0xFFD9D9D9) + ) +); \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 07514d0..c880ca8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "5fdcea390499dd26c808a3c662df5f4208d6bbc0643072eee94f1476249e2818" + url: "https://pub.dev" + source: hosted + version: "1.3.43" async: dependency: transitive description: @@ -57,11 +65,67 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "16b0b70e837539b27d4cec059b5169eaf116af9deca896b923e9f84d83af9cc6" + url: "https://pub.dev" + source: hosted + version: "5.3.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: f1f32cdba3f5620082c63d18545dd58dde8f5ffc66caa43feb9b9fe57e93d4b6 + url: "https://pub.dev" + source: hosted + version: "7.4.6" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "5731d0e51ef7aa93f9c421798b92b6961411c15c18adaff939a3c9f84e8e456b" + url: "https://pub.dev" + source: hosted + version: "5.13.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: c7de9354eb2cd8bfe8059e1112174c9a58beda7051807207306bc48283277cfb + url: "https://pub.dev" + source: hosted + version: "3.5.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 + url: "https://pub.dev" + source: hosted + version: "5.3.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + url: "https://pub.dev" + source: hosted + version: "2.18.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_lints: dependency: "direct dev" description: @@ -75,6 +139,19 @@ 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 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" leak_tracker: dependency: transitive description: @@ -139,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -192,6 +277,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -208,6 +301,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: dart: ">=3.5.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index f304026..40d1497 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,9 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + firebase_core: ^3.5.0 + firebase_auth: ^5.3.0 + flutter_dotenv: ^5.1.0 dev_dependencies: flutter_test: @@ -59,9 +62,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/images/mapsee_logo.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images