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