[Flutter] 스나이퍼팩토리 Flutter 중급과정 (12)

GONG·2023년 5월 9일
0
post-thumbnail

과제 내용

33일차 과제 - 비밀듣는고양이(최종)


  1. 비밀듣는고양이를 secret_cat_sdk를 사용하지 않고 제작하시오.
  2. 아래의 API 명세서를 보고, 플러터에서 과제풀이에 활용할 수 있도록 하시오.
  3. 아래의 필수 기능을 포함해야 함.
    1. 로그인/회원가입 기능
    2. 유저 인증상태가 바뀌면 자동 페이지 리다이렉트 기능
    3. 디자인을 반드시 포함할 것

API명세서

👉 Users

📄 Read 유저리스트를 불러오는 기능

  • GET http://52.79.115.43:8090/api/collections/users/records?sort=-created
  • Response
    {
      "page": 1,
      "perPage": 30,
      "totalPages": 1,
      "totalItems": 2,
      "items": [
        {
          "id": "**USER_RECORD_ID** (주말과제시 필요)",
          "collectionId": "_pb_users_auth_",
          "collectionName": "users",
          "created": "2022-01-01 01:00:00Z",
          "updated": "2022-01-01 23:59:59Z",
          "username": "username123",
          "verified": false,
          "emailVisibility": true,
          "email": "test@example.com",
          "name": "test",
          "avatar": "filename.jpg"
        }
    	]
    }

📄 Login 로그인

  • POST http://52.79.115.43:8090/api/collections/users/auth-with-password
  • Request
    • identity (String - required)
    • password (String -required, 9글자 이상)
  • Success Response
    {
      "token": "JWT_TOKEN",
      "record": {
        "id": "RECORD_ID",
        "collectionId": "_pb_users_auth_",
        "collectionName": "users",
        "created": "2022-01-01 01:00:00Z",
        "updated": "2022-01-01 23:59:59Z",
        "username": "username123",
        "verified": false,
        "emailVisibility": true,
        "email": "test@example.com",
        "name": "test",
        "avatar": "filename.jpg"
      }
    }

📄 SignUp 회원가입

  • POST http://52.79.115.43:8090/api/collections/users/records
  • REQUEST
    • email (String - required, 올바른 이메일형식일 것)
    • password (String - required, 9자 이상일 것)
    • passwordConfirm (String - required, 9자 이상일 것)
    • username (String)
  • Success Response
    {
      "id": "RECORD_ID",
      "collectionId": "_pb_users_auth_",
      "collectionName": "users",
      "created": "2022-01-01 01:00:00Z",
      "updated": "2022-01-01 23:59:59Z",
      "username": "username123",
      "verified": false,
      "emailVisibility": true,
      "email": "test@example.com",
      "name": "test",
      "avatar": "filename.jpg"
    }

👉 Secrets

📄 Read 비밀리스트를 불러오는 기능

  • GET http://52.79.115.43:8090/api/collections/secrets/records?sort=-created
  • Success Response
    {
      "page": 1,
      "perPage": 30,
      "totalPages": 1,
      "totalItems": 2,
      "items": [
        {
          "id": "RECORD_ID",
          "collectionId": "5647cebjvtwtcu1",
          "collectionName": "secrets",
          "created": "2022-01-01 01:00:00Z",
          "updated": "2022-01-01 23:59:59Z",
          "secret": "test",
          "author": "RELATION_RECORD_ID",
    			"authorName": "test",
        }
      ]
    }

📄 Upload 비밀을 업로드하는 기능

  • POST http://52.79.115.43:8090/api/collections/secrets/records
  • Request
    • secret (String)
    • author (String, (optional))
      • 해당 author는 User Record ID를 입력할 것
    • authorName (String, (optional))
      • 해당 authorName은 닉네임을 입력할 것
  • Success Response
    {
      "id": "RECORD_ID",
      "collectionId": "5647cebjvtwtcu1",
      "collectionName": "secrets",
      "created": "2022-01-01 01:00:00Z",
      "updated": "2022-01-01 23:59:59Z",
      "secret": "test",
      "author": "RELATION_RECORD_ID",
    	"authorName":"test"
    }

디자인

Figma → 비밀먹는햄버거

링크

[github 링크]


이미지 파일 > assets/images/secret_hamburger
코드 > day33

코드

📄 main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'controller/auth_controller.dart';
import 'controller/login_controller.dart';
import 'controller/sign_up_controller.dart';
import 'controller/secret_controller.dart';
import 'controller/upload_controller.dart';
import 'util/pages.dart';
import 'view/page/splash_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialBinding: BindingsBuilder(() {
        Get.put(AuthController());
        Get.lazyPut(() => LoginController(), fenix: true);
        Get.lazyPut(() => SignUpController(), fenix: true);
        Get.lazyPut(() => SecretController(), fenix: true);
        Get.lazyPut(() => UploadController(), fenix: true);
      }),
      getPages: AppPages.pages,
      home: SplashPage()
    );
  }
}

📁 model

📄 secret.dart

  • 비밀 모델
    class Secret {
      String id;
      String secret;
      String? author;
      String authorName;
    
      Secret({
        required this.id,
        required this.secret,
        this.author,
        required this.authorName,
      });
    
      factory Secret.fromMap(Map<String, dynamic> map) {
        return Secret(
          id: map['id'] as String,
          secret: map['secret'] as String,
          author: map['author'] as String,
          authorName: map['authorName'] as String,
        );
      }
    }

📄 user.dart

  • 유저 모델
    class User {
      String id;
      String username;
      String email;
      String name;
    
      User({
        required this.id,
        required this.username,
        required this.email,
        required this.name,
      });
    
      factory User.fromMap(Map<String, dynamic> map) {
        return User(
          id: map['id'] as String,
          username: map['username'] as String,
          email: map['email'] as String,
          name: map['name'] as String,
        );
      }
    }

📁 view

🗂️ page

📄 splash_page.dart

  • splash 화면
    import 'package:flutter/material.dart';
    
    import 'login_page.dart';
    
    class SplashPage extends StatefulWidget {
      
      _SplashPageState createState() => _SplashPageState();
    }
    
    class _SplashPageState extends State<SplashPage> {
      
      void initState() {
        super.initState();
        Future.delayed(
          Duration(seconds: 3), // 3초 동안 Splash Screen 표시
              () => Navigator.pushReplacement(
            context,
            MaterialPageRoute(builder: (context) => LoginPage()),
          ),
        );
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/images/secret_hamburger/Splash.png'),
                fit: BoxFit.fill
              )
            ),
          )
        );
      }
    }

📄 login_page.dart

  • 로그인 페이지
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../widget/burgers.dart';
    import '../../util/app_routes.dart';
    import '../../controller/login_controller.dart';
    
    class LoginPage extends GetView<LoginController> {
      const LoginPage({Key? key}) : super(key: key);
      static const String route = '/login';
    
      
      Widget build(BuildContext context) {
    
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
              child: SingleChildScrollView(
                physics: NeverScrollableScrollPhysics(),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Container(width: 150, height: 150, child: Image.asset(Burgers.mainHam)),
                    SizedBox(height: 50),
                    TextField(
                      controller: controller.idController,
                      decoration: InputDecoration(
                        labelText: '아이디',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      controller: controller.pwController,
                      decoration: InputDecoration(
                        labelText: '비밀번호',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: controller.login,
                        child: Text('로그인'),
                      ),
                    ),
                    TextButton(
                      onPressed: () {
                        Get.toNamed(AppRoutes.signUp);
                      },
                      child: Text('회원가입')
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }

📄 sign_up_page.dart

  • 회원가입 페이지
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../controller/sign_up_controller.dart';
    import '../widget/burgers.dart';
    
    class SignUpPage extends GetView<SignUpController> {
      const SignUpPage({Key? key}) : super(key: key);
      static const String route = '/signup';
    
      
      Widget build(BuildContext context) {
    
        return Scaffold(
          backgroundColor: Colors.white,
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            foregroundColor: Colors.black,
            elevation: 0,
          ),
          body: Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
              child: SingleChildScrollView(
                physics: NeverScrollableScrollPhysics(),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Container(width: 150, height: 150, child: Image.asset(Burgers.mainHam)),
                    SizedBox(height: 50),
                    TextField(
                      controller: controller.emailController,
                      decoration: InputDecoration(
                        labelText: '이메일',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      controller: controller.pwController,
                      decoration: InputDecoration(
                        labelText: '비밀번호',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      controller: controller.pwConfirmController,
                      decoration: InputDecoration(
                        labelText: '비밀번호 확인',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    TextField(
                      controller: controller.userNameController,
                      decoration: InputDecoration(
                        labelText: '닉네임',
                        border: OutlineInputBorder(),
                      ),
                    ),
                    SizedBox(height: 16.0),
                    Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: controller.signUp,
                        child: Text('회원가입'),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }

📄 main_page.dart

  • 메인 화면
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../util/app_routes.dart';
    import '../widget/burgers.dart';
    import 'secret_page.dart';
    import 'upload_page.dart';
    
    class MainPage extends StatelessWidget {
      const MainPage({Key? key}) : super(key: key);
      static const String route = '/main';
    
      
      Widget build(BuildContext context) {
    
        List menu = ['비밀 보러가기', '비밀 알려주기', '설정'];
    
        return Scaffold(
          backgroundColor: Colors.white,
          body: Center(
            child: SingleChildScrollView(
              physics: NeverScrollableScrollPhysics(),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Padding(
                    padding: const EdgeInsets.only(bottom: 50.0),
                    child: Image.asset(Burgers.mainHam),
                  ),
                  Column(
                    children: List.generate(
                      menu.length, (index) => Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
                        child: ListTile(
                          leading: Image.asset(Burgers.mainHam),
                          title: Text(menu[index]),
                          onTap: () {
                            switch(index) {
                              case 0:
                                Get.toNamed(AppRoutes.secret);
                                break;
                              case 1:
                                Get.toNamed(AppRoutes.upload);
                                break;
                              case 2:
                                Get.toNamed(AppRoutes.setting);
                                break;
                            }
                          },
                        ),
                      ),
                    ),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }

📄 secret_page.dart

  • 비밀 보러가기 화면
    import 'package:animate_do/animate_do.dart';
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../controller/secret_controller.dart';
    import '../widget/burgers.dart';
    
    class SecretPage extends GetView<SecretController> {
      const SecretPage({Key? key}) : super(key: key);
      static const String route = '/secret';
    
      
      Widget build(BuildContext context) {
    
        return Scaffold(
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            foregroundColor: Colors.black,
            backgroundColor: Colors.transparent,
            elevation: 0,
          ),
          body: Container(
            decoration: BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/images/secret_hamburger/SecretPage-background.png'),
                fit: BoxFit.fill
              )
            ),
            child: Column(
              children: [
                SizedBox(height: 50),
                Expanded(
                  child: Obx(() => PageView.builder(
                    controller: controller.pageController,
                      onPageChanged: (int index) {
                        controller.currentPageIndex = index;
                      },
                    itemCount: controller.secrets.length,
                    itemBuilder: (context, index) {
                      var secret = controller.secrets[index];
                      var author = secret.authorName == '' ? '익명' : secret.authorName;
                      return Padding(
                        padding: const EdgeInsets.all(50.0),
                        child: Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: [
                              ListTile(
                                leading: ZoomIn(child: SizedBox(child: Image.asset(Burgers.mainHam))),
                                title: FadeInLeft(child: Text(author)),
                              ),
                              Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: ZoomIn(
                                  child: Container(
                                    width: 200,
                                    height: 100,
                                    color: Colors.grey[200],
                                    child: Text(secret.secret),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                    }
                  )),
                ),
                Obx(() => Text(
                  controller.isPageChanging.value
                      ? controller.pageController.page!.toInt() > controller.currentPageIndex
                        ? '먹을게!!!!!'  // 안뜸
                        : '냠냠'
                      : '짜잔',
                  style: TextStyle(fontSize: 20),
                )),
                SizedBox(height: 30,)
              ],
            ),
          ),
        );
      }
    }

📄 upload_page.dart

  • 비밀 등록 화면
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../controller/upload_controller.dart';
    
    class UploadPage extends GetView<UploadController> {
      const UploadPage({Key? key}) : super(key: key);
      static const String route = '/upload';
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          resizeToAvoidBottomInset: false,
          backgroundColor: Colors.white,
          appBar: AppBar(
            foregroundColor: Colors.black,
            backgroundColor: Colors.transparent,
            elevation: 0,
          ),
          body: Center(
            child: Padding(
              padding: EdgeInsets.symmetric(horizontal: 60),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  SizedBox(height: 20),
                  Container(
                    child: Stack(
                      children: [
                        Image.asset('assets/images/secret_hamburger/UploadPage-burger.png'),
                        Padding(
                          padding: const EdgeInsets.only(left: 120.0, top: 20),
                          child: Align(
                            child: Obx(() => Text('${controller.hamText}'))
                          ),
                        )
                      ]
                    ),
                  ),
                  SizedBox(height: 50),
                  Container(
                    width: double.infinity,
                    height: 200,
                    decoration: BoxDecoration(
                      color: Colors.grey[200],
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: TextField(
                      controller: controller.secretController,
                      maxLines: null,
                      textAlignVertical: TextAlignVertical.top,
                      decoration: InputDecoration(
                        hintText: '비밀을 말해보렴!!!',
                        border: InputBorder.none,
                        contentPadding: EdgeInsets.all(10),
                      ),
                    ),
                  ),
                  SizedBox(height: 20),
                  Container(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: controller.upload,
                      child: Text('비밀 등록'),
                    ),
                  ),
                  Spacer()
                ],
              ),
            ),
          ),
        );
      }
    }

📄 setting_page.dart

  • 설정 화면
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../../controller/auth_controller.dart';
    import '../widget/burgers.dart';
    import '../widget/logout_dialog.dart';
    
    class SettingPage extends StatelessWidget {
      const SettingPage({Key? key}) : super(key: key);
      static const String route = '/setting';
    
      
      Widget build(BuildContext context) {
    
        var controller = Get.find<AuthController>();
    
        return Scaffold(
          backgroundColor: Colors.white,
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            foregroundColor: Colors.black,
            elevation: 0,
          ),
          body: Padding(
            padding: const EdgeInsets.all(40.0),
            child: SingleChildScrollView(
              physics: NeverScrollableScrollPhysics(),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey),
                      borderRadius: BorderRadius.circular(8.0),
                    ),
                    child: ListTile(
                      leading: CircleAvatar(
                        child: Text(controller.user!.username.substring(0, 1)),
                      ),
                      title: Text(controller.user!.username),
                      subtitle: Text(controller.user!.email),
                    ),
                  ),
                  ListTile(
                    leading: Icon(Icons.logout),
                    title: Text('로그아웃하기'),
                    onTap: () {
                      Get.dialog(LogoutDialog());
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }

🗂️ widget

📄 burgers.dart

  • 햄버거 이미지들
    class Burgers {
      static const mainHam = 'assets/images/secret_hamburger/main-icon.png';
      static const uploadHam = 'assets/images/secret_hamburger/UploadPage-burger.png';
    
    }

📄 logout_dialog.dart

  • 로그아웃 버튼 클릭시 보여질 다이얼로그
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import 'burgers.dart';
    import '../../controller/auth_controller.dart';
    
    class LogoutDialog extends StatelessWidget {
      const LogoutDialog({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return AlertDialog(
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 150,
                height: 150,
                child: Image.asset(Burgers.mainHam)
              ),
              Text('정말 로그아웃하실건가요...', style: TextStyle(fontWeight: FontWeight.bold)),
            ],
          ),
          actions: [
            TextButton(
              child: Text('아닙니다...'),
              onPressed: () {
                Get.back();
              },
            ),
            TextButton(
              child: Text('네', style: TextStyle(color: Colors.red)),
              onPressed: Get.find<AuthController>().logout,
            ),
          ],
        );
      }
    }

📁 controller

📄 auth_controller.dart

  • 로그인, 로그아웃, 회원가입 등 인증 컨트롤러
    import 'package:dio/dio.dart';
    import 'package:get/get.dart';
    
    import '../model/user.dart';
    import '../util/app_routes.dart';
    import '../util/api_routes.dart';
    
    class AuthController extends GetxController {
      final Rxn<User> _user = Rxn();
      final RxString _token = RxString('');
      Dio dio = Dio();
    
      User? get user => _user.value;
      String get token => _token.value;
    
      login(String id, String pw) async {
        dio.options.baseUrl = ApiRoutes.baseUrl;
    
        try {
          var res = await dio.post(
            ApiRoutes.authWithPassword,
            data: {'identity': id, 'password': pw}
          );
          if (res.statusCode == 200) {
            var user = User.fromMap(res.data['record']);
            var token = res.data['token'];
            _user(user);
            _token(token);
          }
        } on DioError catch(e) {
          Get.snackbar('로그인 실패!', '아이디와 비밀번호를 다시 확인해보게...');
          print(e.message);
        }
      }
    
      signUp(String email, String password, String passwordConfirm, String username) async {
        dio.options.baseUrl = ApiRoutes.baseUrl;
    
        try {
          await dio.post(
            ApiRoutes.signUp,
            data: {
              'email': email,
              'password': password,
              'passwordConfirm': passwordConfirm,
              'username': username,
            }
          );
          Get.snackbar('회원가입 성공~', '환영합니다');
          Get.back();
        } on DioError catch(e) {
          print(e.message);
          Get.snackbar('회원가입 실패', '환영할뻔');
        }
      }
    
      logout() {
        _user.value = null;
      }
    
      _handleAuthChanged(User? data) {
        if (data != null) {
          Get.offNamed(AppRoutes.main);
          return;
        }
        Get.offAllNamed(AppRoutes.login);
        return;
      }
    
      
      void onInit() {
        super.onInit();
        ever(_user, _handleAuthChanged);
      }
    }

📄 login_controller.dart

  • 로그인 페이지에서 사용할 컨트롤러
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import 'auth_controller.dart';
    
    class LoginController extends GetxController {
      var idController = TextEditingController();
      var pwController = TextEditingController();
    
      login() {
        Get.find<AuthController>().login(idController.text, pwController.text);
      }
    }

📄 sign_up_controller.dart

  • 회원가입 페이지에서 사용할 컨트롤러
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import 'auth_controller.dart';
    
    class SignUpController extends GetxController {
      TextEditingController emailController = TextEditingController();
      TextEditingController pwController = TextEditingController();
      TextEditingController pwConfirmController = TextEditingController();
      TextEditingController userNameController = TextEditingController();
    
      signUp() {
        String email = emailController.text.trim();
        String password = pwController.text.trim();
        String passwordConfirm = pwConfirmController.text.trim();
        String username = userNameController.text.trim();
    
        if (!isEmailValid(email)) {
          // 이메일이 올바른 형식이 아닌 경우
          snackBar('이메일이 비어있거나 올바른 형식이 아닌듯?');
          return false;
        }
    
        if (password.length < 9) {
          // 비밀번호가 9자 미만인 경우
          snackBar('비밀번호가 비어있거나 9글자 미만인듯?');
          return false;
        }
    
        if (password != passwordConfirm) {
          // 비밀번호와 비밀번호 확인이 일치하지 않는 경우
          snackBar('비밀번호가 일치하지 않는듯?');
          return false;
        }
    
        Get.find<AuthController>().signUp(email, password, passwordConfirm, username);
    
        return true;
      }
    
      bool isEmailValid(String email) {
        // 이메일 형식이 올바른지 확인하는 정규식
        final emailRegExp = RegExp(r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$');
        return emailRegExp.hasMatch(email);
      }
    
      void snackBar(String message) {
        Get.snackbar('제대로 입력하쇼', message);
      }
    }

📄 secret_controller.dart

  • 비밀 불러오기, 페이지 이동 상태에 따라 텍스트 변경
    import 'package:dio/dio.dart';
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../model/secret.dart';
    import '../util/api_routes.dart';
    
    class SecretController extends GetxController {
      RxList<Secret> secrets = RxList();
      Dio dio = Dio();
    
      PageController pageController = PageController();
      RxBool isPageChanging = false.obs;
      int currentPageIndex = 0;
    
      readSecrets() async {
        dio.options.baseUrl = ApiRoutes.baseUrl;
    
        try {
          var res = await dio.get(ApiRoutes.readSecret);
          if (res.statusCode == 200) {
            List<Map<String, dynamic>> data = List<Map<String, dynamic>>.from(res.data['items']);
            secrets(data.map((e) => Secret.fromMap(e)).toList().obs);
          }
        } on DioError catch(e) {
          print(e.message);
        }
      }
    
      void _handlePageChange() {
        if (pageController.page!.toInt() != currentPageIndex) {
          isPageChanging.value = true;
        } else {
          isPageChanging.value = false;
        }
      }
    
      
      void onInit() {
        super.onInit();
        readSecrets();
    
        pageController = PageController(initialPage: currentPageIndex);
        pageController.addListener(_handlePageChange);
      }
    
      
      void onClose() {
        super.onClose();
        pageController.dispose();
      }
    }

📄 upload_controller.dart

  • 비밀 업로드, 업로드 결과 스낵바 출력, 성공 시 텍스트 변경
    import 'package:dio/dio.dart';
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import '../util/api_routes.dart';
    import 'auth_controller.dart';
    
    class UploadController extends GetxController {
      TextEditingController secretController = TextEditingController();
      Dio dio = Dio();
    
      RxString hamText = '진짜\n나만 알고\n잇을게'.obs;
    
      var user = Get.find<AuthController>().user;
    
      upload() async {
        dio.options.baseUrl = ApiRoutes.baseUrl;
    
        if (secretController.text == '') return;
    
        try {
          var res = await dio.post(
            ApiRoutes.uploadSecret,
            data: {
              'secret': secretController.text,
              'author': user!.id,
              'authorName': user!.username,
            }
          );
          if (res.statusCode == 200) {
            Get.snackbar('비밀 등록 성공!', '야호');
            hamText('헤헤\n뻥이지롱');
            Future.delayed(Duration(seconds: 3), () {
              hamText('진짜\n나만 알고\n잇을게');
            });
          }
        } on DioError catch(e) {
          Get.snackbar('비밀 등록 실패', 'ㅠㅠ');
          print(e.message);
        }
      }
    }

📁 util

📄 pages.dart

  • pages 리스트에 전체 페이지 등록
    import 'package:get/get.dart';
    
    import '../view/page/main_page.dart';
    import '../view/page/login_page.dart';
    import '../view/page/secret_page.dart';
    import '../view/page/upload_page.dart';
    import '../view/page/sign_up_page.dart';
    import '../view/page/setting_page.dart';
    
    class AppPages {
      static final pages = [
        GetPage(name: MainPage.route, page: () => const MainPage()),
        GetPage(name: LoginPage.route, page: () => const LoginPage()),
        GetPage(name: SecretPage.route, page: () => const SecretPage()),
        GetPage(name: UploadPage.route, page: () => const UploadPage()),
        GetPage(name: SignUpPage.route, page: () => const SignUpPage()),
        GetPage(name: SettingPage.route, page: () => const SettingPage()),
      ];
    }

📄 app_routes.dart

  • 페이지 이동시 필요한 페이지 라우트 관리
    import '../view/page/login_page.dart';
    import '../view/page/main_page.dart';
    import '../view/page/secret_page.dart';
    import '../view/page/upload_page.dart';
    import '../view/page/sign_up_page.dart';
    import '../view/page/setting_page.dart';
    
    class AppRoutes {
      static const login = LoginPage.route;
      static const main = MainPage.route;
      static const secret = SecretPage.route;
      static const upload = UploadPage.route;
      static const signUp = SignUpPage.route;
      static const setting = SettingPage.route;
    }

📄 api_routes.dart

  • api 호출시 필요한 api 라우트 관리
    class ApiRoutes {
      static const String baseUrl = 'http://52.79.115.43:8090/';
      static const String authWithPassword = 'api/collections/users/auth-with-password';
      static const String signUp = 'api/collections/users/records';
      static const String readSecret = 'api/collections/secrets/records?sort=-created';
      static const String uploadSecret = 'api/collections/secrets/records';
    }

결과

  • 로그인
  • 비밀 조회
  • 비밀 업로드

33일차 끝!

저번 과제에서는 페이지 이동을 판별하여 하단에 먹을게, 뱉을게를 출력하는 먹뱉기능을 구현하지 못해 '먹을게'를 고정 텍스트로 뒀었는데, 이번 과제에서는 먹기만 할 줄 알았던 햄버거가 이제 멈추는 방법도 알게 되엇습니다...
먹뱉기능은 아니지만 냠냠기능도 조금 뿌듯하네요👍

profile
우와재밋다

0개의 댓글