├── courageous_people ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── .gitignore ├── lib │ ├── common │ │ ├── classes.dart │ │ ├── hive │ │ │ ├── token_hive.dart │ │ │ └── user_hive.dart │ │ └── constants.dart │ ├── model │ │ ├── tag_data.dart │ │ ├── token.dart │ │ ├── menu_data.dart │ │ ├── user_data.dart │ │ ├── review_data.dart │ │ └── store_data.dart │ ├── log_out │ │ ├── cubit │ │ │ ├── log_out_state.dart │ │ │ └── log_out_cubit.dart │ │ └── repository │ │ │ └── log_out_repository.dart │ ├── menu │ │ ├── cubit │ │ │ ├── menu_state.dart │ │ │ └── menu_cubit.dart │ │ └── repository │ │ │ └── menu_repository.dart │ ├── utils │ │ ├── get_widget_information.dart │ │ ├── user_verification.dart │ │ ├── show_alert_dialog.dart │ │ ├── interpreters.dart │ │ └── http_client.dart │ ├── log_in │ │ ├── cubit │ │ │ ├── log_in_state.dart │ │ │ └── log_in_cubit.dart │ │ ├── repository │ │ │ └── log_in_repository.dart │ │ └── log_In_screen.dart │ ├── widget │ │ ├── my_rating_bar.dart │ │ ├── transparent_app_bar.dart │ │ ├── tag_widget.dart │ │ ├── menu_tile.dart │ │ ├── menu_button.dart │ │ ├── my_input_form.dart │ │ ├── my_drop_down.dart │ │ └── image_picker_section.dart │ ├── service │ │ └── token_service.dart │ ├── splash_screen.dart │ ├── sign_in │ │ ├── cubit │ │ │ ├── sign_in_state.dart │ │ │ └── sign_in_cubit.dart │ │ ├── repository │ │ │ └── sign_in_repository.dart │ │ └── screen │ │ │ ├── sign_in_success_screen.dart │ │ │ └── sign_in_select_screen.dart │ ├── store │ │ ├── cubit │ │ │ ├── store_state.dart │ │ │ └── store_cubit.dart │ │ ├── repository │ │ │ └── store_repository.dart │ │ └── screen │ │ │ └── store_main_screen.dart │ ├── review │ │ ├── cubit │ │ │ ├── review_state.dart │ │ │ └── review_cubit.dart │ │ ├── repository │ │ │ └── review_repository.dart │ │ ├── widget │ │ │ └── review_tile.dart │ │ └── screen │ │ │ ├── show_review_screen.dart │ │ │ └── rewrite_review_screen.dart │ ├── main.dart │ └── home │ │ ├── widget │ │ ├── store_box.dart │ │ └── main_menu.dart │ │ └── screen │ │ └── home.dart ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── courageous_people │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── assets │ └── images │ │ ├── 1L.png │ │ ├── 2L.png │ │ ├── logo.png │ │ ├── menu.png │ │ ├── user.png │ │ ├── 300ml.png │ │ ├── 500ml.png │ │ ├── store.png │ │ ├── container.png │ │ ├── customer.png │ │ ├── logo_text.png │ │ ├── manager.png │ │ ├── logo_color.png │ │ ├── logo_notext.png │ │ ├── kakao_circular.png │ │ ├── naver_circular.png │ │ ├── google_circular.png │ │ └── logo_transparent.png ├── .metadata ├── README.md ├── .gitignore ├── test │ └── widget_test.dart └── pubspec.yaml ├── LICENSE ├── README.md └── 3rd-party-licenses.md /courageous_people/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /courageous_people/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /courageous_people/lib/common/classes.dart: -------------------------------------------------------------------------------- 1 | typedef Json = Map; 2 | typedef JsonList = List>; -------------------------------------------------------------------------------- /courageous_people/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /courageous_people/assets/images/1L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/1L.png -------------------------------------------------------------------------------- /courageous_people/assets/images/2L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/2L.png -------------------------------------------------------------------------------- /courageous_people/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/logo.png -------------------------------------------------------------------------------- /courageous_people/assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/menu.png -------------------------------------------------------------------------------- /courageous_people/assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/user.png -------------------------------------------------------------------------------- /courageous_people/assets/images/300ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/300ml.png -------------------------------------------------------------------------------- /courageous_people/assets/images/500ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/500ml.png -------------------------------------------------------------------------------- /courageous_people/assets/images/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/store.png -------------------------------------------------------------------------------- /courageous_people/assets/images/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/container.png -------------------------------------------------------------------------------- /courageous_people/assets/images/customer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/customer.png -------------------------------------------------------------------------------- /courageous_people/assets/images/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/logo_text.png -------------------------------------------------------------------------------- /courageous_people/assets/images/manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/manager.png -------------------------------------------------------------------------------- /courageous_people/assets/images/logo_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/logo_color.png -------------------------------------------------------------------------------- /courageous_people/assets/images/logo_notext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/logo_notext.png -------------------------------------------------------------------------------- /courageous_people/assets/images/kakao_circular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/kakao_circular.png -------------------------------------------------------------------------------- /courageous_people/assets/images/naver_circular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/naver_circular.png -------------------------------------------------------------------------------- /courageous_people/lib/model/tag_data.dart: -------------------------------------------------------------------------------- 1 | class TagData { 2 | final String content; 3 | final int colorIndex; 4 | 5 | const TagData(this.content, this.colorIndex); 6 | } -------------------------------------------------------------------------------- /courageous_people/assets/images/google_circular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/google_circular.png -------------------------------------------------------------------------------- /courageous_people/assets/images/logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/assets/images/logo_transparent.png -------------------------------------------------------------------------------- /courageous_people/lib/model/token.dart: -------------------------------------------------------------------------------- 1 | class Token{ 2 | final String? accessToken; 3 | final String? refreshToken; 4 | 5 | const Token(this.accessToken, this.refreshToken); 6 | } -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/kotlin/com/example/courageous_people/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.courageous_people 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /courageous_people/lib/model/menu_data.dart: -------------------------------------------------------------------------------- 1 | class MenuData { 2 | final String title; 3 | final String price; 4 | final int storeId; 5 | final String? imageUrl; 6 | 7 | const MenuData(this.title, this.price, this.storeId, this.imageUrl); 8 | } -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Courageous-Developer/courageous-people-app/HEAD/courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /courageous_people/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /courageous_people/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /courageous_people/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /courageous_people/lib/model/user_data.dart: -------------------------------------------------------------------------------- 1 | class UserData { 2 | final int id; 3 | final String nickname; 4 | final String email; 5 | final int managerFlag; 6 | // final List favorites; 7 | 8 | const UserData( 9 | this.id, 10 | this.nickname, 11 | this.email, 12 | this.managerFlag, 13 | // this.favorites, 14 | ); 15 | } -------------------------------------------------------------------------------- /courageous_people/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /courageous_people/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /courageous_people/lib/log_out/cubit/log_out_state.dart: -------------------------------------------------------------------------------- 1 | abstract class LogOutState { 2 | const LogOutState(); 3 | } 4 | 5 | class LogOutInitialState extends LogOutState {} 6 | 7 | class LogOutErrorState extends LogOutState { 8 | final String message; 9 | 10 | const LogOutErrorState(this.message); 11 | } 12 | 13 | class LogOutLoadingState extends LogOutState {} 14 | 15 | class LogOutSuccessState extends LogOutState { 16 | 17 | const LogOutSuccessState(); 18 | } -------------------------------------------------------------------------------- /courageous_people/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /courageous_people/lib/model/review_data.dart: -------------------------------------------------------------------------------- 1 | import 'tag_data.dart'; 2 | 3 | class ReviewData { 4 | final int reviewId; 5 | final int storeId; 6 | final String userNickname; 7 | final String comment; 8 | final String createAt; 9 | final List imageUri; 10 | final List tags; 11 | 12 | const ReviewData( 13 | this.reviewId, 14 | this.storeId, 15 | this.userNickname, 16 | this.comment, 17 | this.createAt, 18 | this.imageUri, 19 | this.tags, 20 | ); 21 | } -------------------------------------------------------------------------------- /courageous_people/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /courageous_people/lib/menu/cubit/menu_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/menu_data.dart'; 2 | 3 | abstract class MenuState { 4 | const MenuState(); 5 | } 6 | 7 | class MenuInitialState extends MenuState {} 8 | 9 | class MenuLoadingState extends MenuState {} 10 | 11 | class MenuLoadedState extends MenuState { 12 | final List menuList; 13 | 14 | const MenuLoadedState(this.menuList); 15 | } 16 | 17 | class MenuErrorState extends MenuState { 18 | final String message; 19 | 20 | const MenuErrorState(this.message); 21 | } 22 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /courageous_people/lib/menu/cubit/menu_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:courageous_people/menu/repository/menu_repository.dart'; 3 | import 'package:courageous_people/menu/cubit/menu_state.dart'; 4 | 5 | class MenuCubit extends Cubit { 6 | final MenuRepository repository; 7 | 8 | MenuCubit(this.repository) : super(MenuInitialState()); 9 | 10 | Future getMenu(int storeId) async { 11 | emit(MenuLoadingState()); 12 | final menuList = await repository.getMenu(storeId); 13 | emit(MenuLoadedState(menuList)); 14 | } 15 | } -------------------------------------------------------------------------------- /courageous_people/lib/utils/get_widget_information.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Size? getWidgetSizeByKey(GlobalKey key) { 4 | if(key.currentContext != null) { 5 | final renderBox = key.currentContext!.findRenderObject() as RenderBox; 6 | 7 | return renderBox.size; 8 | } 9 | 10 | return null; 11 | } 12 | 13 | Offset? getWidgetPositionByKey(GlobalKey key) { 14 | if(key.currentContext != null) { 15 | final renderBox = key.currentContext!.findRenderObject() as RenderBox; 16 | 17 | return renderBox.localToGlobal(Offset.zero); 18 | } 19 | 20 | return null; 21 | } -------------------------------------------------------------------------------- /courageous_people/lib/log_in/cubit/log_in_state.dart: -------------------------------------------------------------------------------- 1 | import '../../model/user_data.dart'; 2 | import '../../model/token.dart'; 3 | 4 | abstract class LogInState { 5 | const LogInState(); 6 | } 7 | 8 | class LogInInitialState extends LogInState {} 9 | 10 | class LogInErrorState extends LogInState { 11 | final String message; 12 | 13 | const LogInErrorState(this.message); 14 | } 15 | 16 | class LogInLoadingState extends LogInState {} 17 | 18 | class LogInSuccessState extends LogInState {} 19 | 20 | class LogInFailedState extends LogInState { 21 | final String message; 22 | 23 | const LogInFailedState(this.message); 24 | } -------------------------------------------------------------------------------- /courageous_people/lib/menu/repository/menu_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/menu_data.dart'; 2 | import 'package:courageous_people/utils/http_client.dart'; 3 | import 'package:courageous_people/utils/interpreters.dart'; 4 | 5 | class MenuRepository { 6 | Future> getMenu(int storeId) async { 7 | final response = await httpRequestWithoutToken( 8 | requestType: 'GET', 9 | // path: '/board/menu/storeId', 10 | path: '/board/menu', 11 | ); 12 | 13 | print(response.statusCode); 14 | print(response.body); 15 | 16 | return toMenuList(response.body); 17 | } 18 | } -------------------------------------------------------------------------------- /courageous_people/README.md: -------------------------------------------------------------------------------- 1 | # courageous_people 2 | 3 | A new Flutter application. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /courageous_people/lib/model/store_data.dart: -------------------------------------------------------------------------------- 1 | import 'menu_data.dart'; 2 | 3 | class StoreData { 4 | final int id; 5 | final String name; 6 | final String address; 7 | final String? introduction; 8 | final double latitude; 9 | final double longitude; 10 | final String? businessNumber; 11 | final List imageUrl; 12 | final List menuList; 13 | 14 | const StoreData( 15 | this.id, 16 | this.name, 17 | this.address, 18 | this.introduction, 19 | this.latitude, 20 | this.longitude, 21 | this.businessNumber, 22 | this.imageUrl, 23 | this.menuList, 24 | ); 25 | } -------------------------------------------------------------------------------- /courageous_people/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /courageous_people/lib/log_out/repository/log_out_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/user_hive.dart'; 2 | import 'package:courageous_people/utils/http_client.dart'; 3 | import 'package:http/http.dart' as http; 4 | 5 | import '../../service/token_service.dart'; 6 | 7 | class LogOutRepository { 8 | const LogOutRepository(); 9 | 10 | Future logOut() async { 11 | final response = await httpRequestWithToken( 12 | requestType: 'POST', 13 | path: '/account/logout', 14 | body: { 15 | "refresh": TokenService().refreshToken, 16 | }, 17 | ); 18 | 19 | if(response.statusCode == 205) { 20 | await TokenService().clearTokens(); 21 | await UserHive().clearUser(); 22 | } 23 | 24 | return response.statusCode; 25 | } 26 | } -------------------------------------------------------------------------------- /courageous_people/lib/widget/my_rating_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_rating_bar/flutter_rating_bar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class MyRatingBar extends StatelessWidget { 5 | final double rating; 6 | 7 | const MyRatingBar(this.rating, {Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return RatingBar.builder( 12 | itemSize: 12.5, 13 | itemCount: 5, 14 | initialRating: rating, 15 | minRating: 1, 16 | direction: Axis.horizontal, 17 | allowHalfRating: true, 18 | itemBuilder: (context, _) => Icon( 19 | Icons.star, 20 | color: Colors.yellow[700], 21 | ), 22 | onRatingUpdate: (rating) { 23 | print(rating); 24 | }, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /courageous_people/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | maven { 19 | url 'https://naver.jfrog.io/artifactory/maven/' 20 | } 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /courageous_people/lib/service/token_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/token_hive.dart'; 2 | 3 | import '../model/token.dart'; 4 | 5 | class TokenService { 6 | late Token _token; 7 | 8 | TokenService() : 9 | _token = Token(TokenHive().accessToken, TokenHive().refreshToken); 10 | 11 | String? get accessToken => _token.accessToken; 12 | String? get refreshToken => _token.refreshToken; 13 | 14 | Future setAccessToken(String token) async { 15 | await TokenHive().setAccessToken(token); 16 | } 17 | 18 | Future setRefreshToken(String token) async { 19 | await TokenHive().setRefreshToken(token); 20 | } 21 | 22 | Future setTokens(String accessToken, String refreshToken) async { 23 | await TokenHive().setTokens(accessToken, refreshToken); 24 | } 25 | 26 | Future clearTokens() async => await TokenHive().setTokens('', ''); 27 | } -------------------------------------------------------------------------------- /courageous_people/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /courageous_people/lib/log_out/cubit/log_out_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../repository/log_out_repository.dart'; 6 | import 'log_out_state.dart'; 7 | 8 | class LogOutCubit extends Cubit { 9 | final LogOutRepository repository; 10 | 11 | static LogOutCubit of(BuildContext context) => context.read(); 12 | 13 | LogOutCubit(this.repository) : super(LogOutInitialState()); 14 | 15 | Future logOut() async { 16 | try { 17 | emit(LogOutLoadingState()); 18 | final logOutStatusCode = await repository.logOut(); 19 | 20 | if(logOutStatusCode == 205) { 21 | emit(LogOutSuccessState()); 22 | return; 23 | } 24 | 25 | emit(LogOutErrorState('로그아웃에 실패했습니다')); 26 | } on Exception catch(exception) { 27 | emit(LogOutErrorState(exception.toString())); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /courageous_people/lib/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashScreen extends StatelessWidget { 4 | 5 | 6 | const SplashScreen({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | backgroundColor: Color.fromRGBO(196,246,130,1), 12 | body: Center( 13 | child: Container( 14 | width: MediaQuery.of(context).size.width*0.8, 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.start, 17 | children: [ 18 | SizedBox( 19 | height: MediaQuery.of(context).size.height*0.25, 20 | ), 21 | Image.asset( 22 | 'assets/images/logo.png' 23 | ), 24 | ], 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /courageous_people/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # excel file 49 | .xlsx -------------------------------------------------------------------------------- /courageous_people/lib/log_in/cubit/log_in_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:courageous_people/log_in/repository/log_in_repository.dart'; 3 | import 'package:courageous_people/log_in/cubit/log_in_state.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import ''; 7 | 8 | class LogInCubit extends Cubit { 9 | final LogInRepository repository; 10 | 11 | LogInCubit(this.repository) : super(LogInInitialState()); 12 | 13 | Future logIn(BuildContext context, String email, String password) async { 14 | try { 15 | emit(LogInLoadingState()); 16 | final resultCode = await repository.logIn(context, email, password); 17 | print(resultCode); 18 | 19 | if(resultCode == 200) { 20 | emit(LogInSuccessState()); 21 | return; 22 | } 23 | 24 | if(resultCode == 401) { 25 | emit(LogInFailedState('비밀번호가 틀렸거나\n 이메일 인증을 하지 않은 계정입니다')); 26 | return; 27 | } 28 | 29 | } on Exception catch(exception) { 30 | emit(LogInErrorState(exception.toString())); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /courageous_people/lib/widget/transparent_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TransparentAppBar extends StatelessWidget with PreferredSizeWidget { 4 | String? title; // current location where user is at 5 | Widget? leading; 6 | List? actions; 7 | double? elevation; 8 | 9 | 10 | TransparentAppBar({ 11 | Key? key, 12 | this.title, 13 | this.leading, 14 | this.actions, 15 | this.elevation, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return AppBar( 21 | title: Text( 22 | title ?? '', 23 | style: TextStyle( 24 | color: Colors.black, 25 | ), 26 | ), 27 | centerTitle: true, 28 | leading: leading, 29 | iconTheme: IconThemeData( 30 | color: Colors.black, 31 | ), 32 | actions: actions, 33 | backgroundColor: Colors.transparent, 34 | elevation: elevation ?? 0, 35 | ); 36 | } 37 | 38 | @override 39 | // TODO: implement preferredSize 40 | Size get preferredSize => Size.fromHeight(kToolbarHeight); 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Courageous-Developer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /courageous_people/lib/common/hive/token_hive.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | class TokenHive { 4 | // Singleton 5 | static late Box _box; 6 | 7 | static final String tokenStore = 'TOKEN_BOX'; 8 | final String _accessTokenKey = "ACCESS_TOKEN"; 9 | final String _refreshTokenKey = "REFRESH_TOKEN"; 10 | 11 | static final TokenHive _tokenHive = TokenHive._(); 12 | factory TokenHive() => _tokenHive; 13 | 14 | TokenHive._() { 15 | // 오리지널 생성자 16 | _box = Hive.box(tokenStore); 17 | } 18 | 19 | String? get accessToken { 20 | String? token; 21 | token = _box.get(_accessTokenKey, defaultValue: null); 22 | 23 | return token; 24 | } 25 | 26 | String? get refreshToken { 27 | String? token; 28 | token = _box.get(_refreshTokenKey, defaultValue: null); 29 | 30 | return token; 31 | } 32 | 33 | Future setAccessToken(String token) async { 34 | await _box.put(_accessTokenKey, token); 35 | } 36 | 37 | Future setRefreshToken(String token) async { 38 | await _box.put(_refreshTokenKey, token); 39 | } 40 | 41 | Future setTokens(String accessToken, String refreshToken) async { 42 | await setAccessToken(accessToken); 43 | await setRefreshToken(refreshToken); 44 | } 45 | } -------------------------------------------------------------------------------- /courageous_people/lib/common/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // theme 4 | const THEME_COLOR = Color.fromRGBO(153, 205, 50, 1); 5 | 6 | // salt 7 | const SALT = 'courageous_key'; 8 | 9 | // naver api 10 | const NAVER_API_CLINET_ID = 'urZ9JPDMKhTzoEHs95Kf'; 11 | const NAVER_API_CLINET_SECRET = 'PotBDueQ_J'; 12 | 13 | const X_NCP_APIGW_API_KEY_ID = 'b6dk9ntigx'; 14 | const X_NCP_APIGW_API_KEY = 'b0rXqu99a0l3GKcamz51t4PzkB6HQ8F6ppzm5JSG'; 15 | 16 | // user hive 17 | const USER_HIVE_STORE = '_user'; 18 | const USER_HIVE_ID_FIELD = "_id"; 19 | const USER_HIVE_NICKNAME_FIELD = "_nickname"; 20 | const USER_HIVE_BIRTH_DATE_FIELD = '_birthDate'; 21 | const USER_HIVE_EMAIL_FIELD = "_email"; 22 | const USER_HIVE_FAVORITE_FIELD = "_favorite"; 23 | const USER_HIVE_MANAGER_FLAG_FIELD = "_managerFlag"; 24 | 25 | // DB server url 26 | const REQUEST_URL = 27 | 'http://ec2-13-209-14-10.ap-northeast-2.compute.amazonaws.com:8000'; 28 | 29 | // naver api request url 30 | const PLACE_SEARCH_REQUEST_URL = 31 | 'https://openapi.naver.com/v1/search/local.json'; 32 | const REVERSE_GEOCODE_REQUEST_URL = 33 | 'https://naveropenapi.apigw.ntruss.com/map-reversegeocode/v2/gc'; 34 | const GEOCODE_REQUEST_URL = 35 | 'https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode'; -------------------------------------------------------------------------------- /courageous_people/lib/log_in/repository/log_in_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:courageous_people/common/constants.dart'; 4 | import 'package:courageous_people/common/hive/user_hive.dart'; 5 | import 'package:courageous_people/service/token_service.dart'; 6 | import 'package:courageous_people/utils/http_client.dart'; 7 | import 'package:crypt/crypt.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:http/http.dart' as http; 10 | 11 | class LogInRepository { 12 | Future logIn(BuildContext context, String email, String password) async { 13 | http.Response response = await httpRequestWithoutToken( 14 | requestType: 'POST', 15 | path: '/account/login', 16 | body: { 17 | "email": email, 18 | "password": password, 19 | // "password": digest(password), 20 | }, 21 | ); 22 | 23 | final result = jsonDecode(response.body); 24 | 25 | if(response.statusCode == 200) { 26 | await TokenService().setTokens( 27 | result['access'], 28 | result['refresh'], 29 | ); 30 | 31 | await UserHive().setEmail(email); 32 | } 33 | 34 | return response.statusCode; 35 | } 36 | 37 | String digest(String password) => Crypt.sha256(password, salt: SALT).toString(); 38 | } -------------------------------------------------------------------------------- /courageous_people/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // // This is a basic Flutter widget test. 2 | // // 3 | // // To perform an interaction with a widget in your test, use the WidgetTester 4 | // // utility that Flutter provides. For example, you can send tap and scroll 5 | // // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // // tree, read text, and verify that the values of widget properties are correct. 7 | // 8 | // import 'package:flutter/material.dart'; 9 | // import 'package:flutter_test/flutter_test.dart'; 10 | // 11 | // import 'package:courageous_people/main.dart'; 12 | // 13 | // void main() { 14 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // // Build our app and trigger a frame. 16 | // await tester.pumpWidget(MyApp()); 17 | // 18 | // // Verify that our counter starts at 0. 19 | // expect(find.text('0'), findsOneWidget); 20 | // expect(find.text('1'), findsNothing); 21 | // 22 | // // Tap the '+' icon and trigger a frame. 23 | // await tester.tap(find.byIcon(Icons.add)); 24 | // await tester.pump(); 25 | // 26 | // // Verify that our counter has incremented. 27 | // expect(find.text('0'), findsNothing); 28 | // expect(find.text('1'), findsOneWidget); 29 | // }); 30 | // } 31 | -------------------------------------------------------------------------------- /courageous_people/lib/sign_in/cubit/sign_in_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/user_data.dart'; 2 | 3 | abstract class SignInState { 4 | const SignInState(); 5 | } 6 | 7 | class SignInInitialState extends SignInState {} 8 | class SignInLoadingState extends SignInState {} 9 | class SignInSuccessState extends SignInState { 10 | final String message; 11 | 12 | const SignInSuccessState(this.message); 13 | } 14 | class SignInErrorState extends SignInState { 15 | final String message; 16 | 17 | const SignInErrorState(this.message); 18 | } 19 | 20 | class NicknameCheckingState extends SignInState {} 21 | 22 | class NicknameCheckedState extends SignInState { 23 | final String message; 24 | 25 | const NicknameCheckedState(this.message); 26 | } 27 | 28 | class NicknameCheckErrorState extends SignInState { 29 | final String message; 30 | 31 | const NicknameCheckErrorState(this.message); 32 | } 33 | 34 | class BusinessNumberCheckingState extends SignInState {} 35 | 36 | class BusinessNumberCheckedState extends SignInState { 37 | final String message; 38 | 39 | const BusinessNumberCheckedState(this.message); 40 | } 41 | 42 | class BusinessNumberCheckErrorState extends SignInState { 43 | final String message; 44 | 45 | const BusinessNumberCheckErrorState(this.message); 46 | } -------------------------------------------------------------------------------- /courageous_people/lib/widget/tag_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../model/tag_data.dart'; 3 | 4 | class TagWidget extends StatelessWidget { 5 | final TagData tag; 6 | final List colors = [Colors.red, Colors.blue, Colors.amber, Colors.pink, 7 | Colors.green, Colors.purple, Colors.teal, Colors.brown]; 8 | 9 | final FontWeight? fontWeight; 10 | 11 | TagWidget({ 12 | Key? key, 13 | required this.tag, 14 | this.fontWeight = FontWeight.normal, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | margin: EdgeInsets.only(right: 8), 21 | padding: EdgeInsets.symmetric(vertical: 5), 22 | decoration: BoxDecoration( 23 | borderRadius: BorderRadius.all(Radius.circular(15.0)), 24 | color: colors[tag.colorIndex].shade300, 25 | ), 26 | child: Row( 27 | mainAxisSize: MainAxisSize.min, 28 | children: [ 29 | SizedBox(width: 10), 30 | Text( 31 | tag.content, 32 | style: TextStyle( 33 | fontSize: 13, 34 | color: Colors.white, 35 | fontWeight: fontWeight, 36 | ), 37 | ), 38 | SizedBox(width: 10), 39 | ], 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /courageous_people/lib/store/cubit/store_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/store_data.dart'; 2 | 3 | abstract class StoreState { 4 | const StoreState(); 5 | } 6 | 7 | class StoreInitialState extends StoreState {} 8 | 9 | class MapInitializeState extends StoreState {} 10 | 11 | // 가게 추가 12 | 13 | class AddingStoreSLoadingState extends StoreState {} 14 | 15 | class AddingStoreSuccessState extends StoreState { 16 | final String message; 17 | 18 | const AddingStoreSuccessState(this.message); 19 | } 20 | 21 | class AddingStoreErrorState extends StoreState { 22 | final String message; 23 | 24 | const AddingStoreErrorState(this.message); 25 | } 26 | 27 | // 가게 불러오기 28 | 29 | class StoreErrorState extends StoreState { 30 | final String message; 31 | 32 | const StoreErrorState(this.message); 33 | } 34 | 35 | class StoreLoadingState extends StoreState {} 36 | 37 | class StoreLoadedState extends StoreState { 38 | final List storeList; 39 | 40 | const StoreLoadedState(this.storeList); 41 | } 42 | 43 | class StoreCrawlingState extends StoreState {} 44 | 45 | class StoreCrawlSuccessState extends StoreState { 46 | final List crawledList; 47 | final List duplicatedList; 48 | 49 | const StoreCrawlSuccessState(this.crawledList, this.duplicatedList); 50 | } 51 | 52 | class StoreCrawlErrorState extends StoreState { 53 | final String message; 54 | 55 | const StoreCrawlErrorState(this.message); 56 | } 57 | 58 | // class StoreDuplicatedCheckingState extends StoreState {} 59 | // 60 | // class StoreDuplicatedCheckedState extends StoreState { 61 | // final StoreData? store; 62 | // 63 | // const StoreDuplicatedCheckedState(this.store); 64 | // } -------------------------------------------------------------------------------- /courageous_people/lib/review/cubit/review_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/review_data.dart'; 2 | 3 | abstract class ReviewState { 4 | const ReviewState(); 5 | } 6 | 7 | class ReviewInitialState extends ReviewState {} 8 | 9 | class ReviewErrorState extends ReviewState { 10 | final String message; 11 | 12 | const ReviewErrorState(this.message); 13 | } 14 | 15 | // 리뷰 등록 16 | 17 | class AddingReviewLoadingState extends ReviewState {} 18 | 19 | class AddingReviewSuccessState extends ReviewState { 20 | final String message; 21 | 22 | const AddingReviewSuccessState(this.message); 23 | } 24 | 25 | class AddingReviewErrorState extends ReviewState { 26 | final String message; 27 | 28 | const AddingReviewErrorState(this.message); 29 | } 30 | 31 | // 리뷰 불러오기 32 | 33 | class ReviewLoadingState extends ReviewState {} 34 | 35 | class ReviewLoadedState extends ReviewState { 36 | final List reviewList; 37 | 38 | const ReviewLoadedState(this.reviewList); 39 | } 40 | 41 | class DeleteReviewLoadingState extends ReviewState {} 42 | 43 | class ReviewDeletedState extends ReviewState { 44 | final String message; 45 | 46 | const ReviewDeletedState(this.message); 47 | } 48 | 49 | class ReviewDeleteErrorState extends ReviewState { 50 | final String message; 51 | 52 | const ReviewDeleteErrorState(this.message); 53 | } 54 | 55 | class RewriteReviewLoadingState extends ReviewState {} 56 | 57 | class ReviewRewrittenState extends ReviewState { 58 | final String message; 59 | 60 | const ReviewRewrittenState(this.message); 61 | } 62 | 63 | class ReviewRewriteErrorState extends ReviewState { 64 | final String message; 65 | 66 | const ReviewRewriteErrorState(this.message); 67 | } -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | courageous_people 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /courageous_people/lib/sign_in/repository/sign_in_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/utils/http_client.dart'; 2 | import 'package:crypt/crypt.dart'; 3 | import 'package:http/http.dart' as http; 4 | 5 | import '../../common/constants.dart'; 6 | 7 | class SignInRepository { 8 | Future signIn( 9 | String nickname, 10 | String email, 11 | String password, 12 | String birthDate, 13 | int manageFlag 14 | ) async { 15 | http.Response response = await httpRequestWithoutToken( 16 | requestType: 'POST', 17 | path: '/account/register', 18 | body: { 19 | "nickname": nickname, 20 | "email": email, 21 | "password": password, 22 | // "password": digest(password), 23 | "date_of_birth": birthDate, 24 | "user_type": manageFlag, 25 | }, 26 | ); 27 | 28 | print(response.statusCode); 29 | print("body: ${response.body}"); 30 | 31 | return response.statusCode; 32 | } 33 | 34 | Future checkNicknameDuplicated(String nickname) async { 35 | final response = await httpRequestWithoutToken( 36 | requestType: 'POST', 37 | path: '/account/nickname', 38 | body: { 39 | "nickname": nickname 40 | }, 41 | ); 42 | 43 | return response.statusCode; 44 | } 45 | 46 | Future checkRegisterNumber(String registerNumber) async { 47 | final response = await httpRequestWithoutToken( 48 | requestType: 'POST', 49 | path: '/board/biz-auth', 50 | body: { 51 | "biz_num": registerNumber, 52 | }, 53 | ); 54 | 55 | print(response.statusCode); 56 | print(response.body); 57 | 58 | return response.statusCode; 59 | } 60 | 61 | String digest(String password) => Crypt.sha256(password, salt: SALT).toString(); 62 | } -------------------------------------------------------------------------------- /courageous_people/lib/widget/menu_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MenuTile extends StatelessWidget { 4 | const MenuTile({Key? key}) : super(key: key); 5 | 6 | //ToDo:이미지랑 메뉴 정보 인자로 받아오기 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | height: MediaQuery.of(context).size.height*0.15, 12 | child: ElevatedButton( 13 | style: ButtonStyle( 14 | backgroundColor: MaterialStateProperty.resolveWith( 15 | (Set states) { 16 | if (states.contains(MaterialState.pressed)) 17 | return Colors.white60; 18 | return Colors.white; // Use the component's default. 19 | }, 20 | ), 21 | ), 22 | onPressed: (){}, 23 | child: Row( 24 | children: [ 25 | SizedBox( 26 | height: MediaQuery.of(context).size.height*0.13, 27 | child: ClipRRect( 28 | borderRadius: BorderRadius.circular(12.0), 29 | child: Image.asset('assets/images/menu.png'), 30 | ), 31 | ), 32 | Padding( 33 | padding: const EdgeInsets.fromLTRB(5,5, 0,0), 34 | child: Column( 35 | children: [ 36 | Text( 37 | '메뉴 이름', 38 | style: TextStyle( 39 | color: Colors.black, 40 | fontWeight: FontWeight.bold, 41 | fontSize: 16, 42 | ), 43 | ), 44 | Text( 45 | '메뉴 설명', 46 | style: TextStyle( 47 | color: Colors.black, 48 | ), 49 | ), 50 | ], 51 | ), 52 | ) 53 | ], 54 | 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /courageous_people/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.courageous_people" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | implementation 'com.naver.maps:map-sdk:3.12.0' 60 | } 61 | -------------------------------------------------------------------------------- /courageous_people/lib/common/hive/user_hive.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/constants.dart'; 2 | import 'package:courageous_people/model/user_data.dart'; 3 | import 'package:hive/hive.dart'; 4 | 5 | class UserHive { 6 | // Singleton 7 | static late Box _box; 8 | static String userStore = 'USER_HIVE'; 9 | 10 | static final UserHive _userHive = UserHive._(); 11 | factory UserHive() => _userHive; 12 | 13 | UserHive._() { 14 | // 오리지널 생성자 15 | _box = Hive.box(userStore); 16 | } 17 | 18 | int get userId { 19 | int id = _box.get(USER_HIVE_ID_FIELD, defaultValue: -1); 20 | return id; 21 | } 22 | 23 | String? get userEmail { 24 | String? email = _box.get(USER_HIVE_EMAIL_FIELD, defaultValue: null); 25 | return email; 26 | } 27 | 28 | String? get userNickname { 29 | String? nickname = _box.get(USER_HIVE_NICKNAME_FIELD, defaultValue: null); 30 | return nickname; 31 | } 32 | 33 | int get userManagerFlag { 34 | int managerFlag = _box.get(USER_HIVE_MANAGER_FLAG_FIELD, defaultValue: -1); 35 | return managerFlag; 36 | } 37 | 38 | Future setId(int id) async { 39 | await _box.put(USER_HIVE_ID_FIELD, id); 40 | } 41 | 42 | Future setNickname(String? nickname) async { 43 | await _box.put(USER_HIVE_NICKNAME_FIELD, nickname); 44 | } 45 | 46 | Future setEmail(String? email) async { 47 | await _box.put(USER_HIVE_EMAIL_FIELD, email); 48 | } 49 | 50 | Future setManagerFlag(int managerFlag) async { 51 | await _box.put(USER_HIVE_MANAGER_FLAG_FIELD, managerFlag); 52 | } 53 | 54 | // Future setUser( 55 | // int id, 56 | // String nickname, 57 | // String email, 58 | // // String birthDate, 59 | // int manageFlag) async { 60 | // await setId(id); 61 | // await setNickname(nickname); 62 | // await setEmail(email); 63 | // // await setBirthDate(birthDate); 64 | // await setManageFlag(manageFlag); 65 | // } 66 | 67 | Future setUser(UserData user) async { 68 | await setId(user.id); 69 | await setNickname(user.nickname); 70 | await setEmail(user.email); 71 | await setManagerFlag(user.managerFlag); 72 | } 73 | 74 | Future clearUser() async { 75 | print('clear'); 76 | await setId(-1); 77 | await setNickname(null); 78 | await setEmail(null); 79 | await setManagerFlag(-1); 80 | } 81 | } -------------------------------------------------------------------------------- /courageous_people/lib/store/cubit/store_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:courageous_people/model/store_data.dart'; 4 | import 'package:courageous_people/store/repository/store_repository.dart'; 5 | import 'package:bloc/bloc.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'store_state.dart'; 9 | 10 | class StoreCubit extends Cubit { 11 | final StoreRepository repository; 12 | 13 | StoreCubit(this.repository) : super(StoreInitialState()) { 14 | emit(StoreInitialState()); 15 | } 16 | 17 | static StoreCubit of(BuildContext context) => context.read(); 18 | 19 | Future getStores() async { 20 | try { 21 | emit(StoreLoadingState()); 22 | List storeList = await repository.getStores(); 23 | emit(StoreLoadedState(storeList)); 24 | } on Exception catch (_) { 25 | emit(StoreErrorState('가게 불러오기에 실패했습니다\n앱을 재시작해주세요')); 26 | } 27 | } 28 | 29 | Future addStore( 30 | String storeName, String address, String post, 31 | Uint8List? imageToByte, double latitude, double longitude, 32 | int registrant, int managerFlag, List> menuList, 33 | ) async { 34 | try { 35 | emit(AddingStoreSLoadingState()); 36 | final resultCode = await repository.addStore( 37 | storeName, 38 | address, 39 | post, 40 | imageToByte, 41 | latitude, 42 | longitude, 43 | registrant, 44 | managerFlag, 45 | menuList, 46 | ); 47 | 48 | resultCode == 200 || resultCode == 201 49 | ? emit(AddingStoreSuccessState('가게가 등록 되었습니다')) 50 | : emit(AddingStoreErrorState('가게 등록에 실패했습니다')); 51 | } on Exception catch (exception) { 52 | emit(AddingStoreErrorState(exception.toString())); 53 | } 54 | } 55 | 56 | Future crawlStore(String location, String storeName) async { 57 | try { 58 | emit(StoreCrawlingState()); 59 | final result = await repository.crawlStore(location, storeName); 60 | 61 | final crawledList = result['crawled'] as List; 62 | final duplicatedList = result['duplicated'] as List; 63 | 64 | emit(StoreCrawlSuccessState(crawledList, duplicatedList)); 65 | } on Exception catch (_) { 66 | emit(StoreCrawlErrorState('가게 검색에 실패했습니다')); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /courageous_people/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/user_hive.dart'; 2 | import 'package:courageous_people/log_in/repository/log_in_repository.dart'; 3 | import 'package:courageous_people/log_out/cubit/log_out_cubit.dart'; 4 | import 'package:courageous_people/service/token_service.dart'; 5 | import 'package:courageous_people/sign_in/cubit/sign_in_cubit.dart'; 6 | import 'package:courageous_people/sign_in/repository/sign_in_repository.dart'; 7 | import 'package:courageous_people/store/cubit/store_cubit.dart'; 8 | import 'package:courageous_people/store/repository/store_repository.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_bloc/flutter_bloc.dart'; 11 | import 'common/hive/token_hive.dart'; 12 | import 'home/screen/home.dart'; 13 | import 'package:provider/provider.dart'; 14 | import 'package:graphql_flutter/graphql_flutter.dart'; 15 | 16 | import 'log_in/cubit/log_in_cubit.dart'; 17 | import 'log_out/repository/log_out_repository.dart'; 18 | import 'review/cubit/review_cubit.dart'; 19 | import 'review/repository/review_repository.dart'; 20 | 21 | Future main() async { 22 | // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 23 | // statusBarColor: Colors.transparent, 24 | // )); 25 | 26 | await initHiveForFlutter(boxes: [ 27 | HiveStore.defaultBoxName, 28 | TokenHive.tokenStore, 29 | UserHive.userStore, 30 | ]); 31 | 32 | runApp(MyApp()); 33 | } 34 | 35 | class MyApp extends StatelessWidget { 36 | const MyApp({Key? key}) : super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return MultiProvider( 41 | providers: [ 42 | Provider( 43 | create: (_) => TokenService(), 44 | ), 45 | BlocProvider( 46 | create: (_) => StoreCubit(StoreRepository()), 47 | ), 48 | BlocProvider( 49 | create: (_) => ReviewCubit(ReviewRepository()), 50 | ), 51 | BlocProvider( 52 | create: (_) => SignInCubit(SignInRepository()), 53 | ), 54 | BlocProvider( 55 | create: (_) => LogInCubit(LogInRepository()), 56 | ), 57 | BlocProvider( 58 | create: (_) => LogOutCubit(LogOutRepository()), 59 | ), 60 | ], 61 | child: MaterialApp( 62 | home: Home(), 63 | debugShowCheckedModeBanner: false, 64 | ), 65 | ); 66 | } 67 | } 68 | //stash test -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /courageous_people/lib/sign_in/cubit/sign_in_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:courageous_people/sign_in/repository/sign_in_repository.dart'; 3 | import 'package:courageous_people/sign_in/cubit/sign_in_state.dart'; 4 | 5 | class SignInCubit extends Cubit{ 6 | final SignInRepository _repository; 7 | 8 | SignInCubit(this._repository) : super(SignInInitialState()); 9 | 10 | Future signIn( 11 | String nickname, 12 | String email, 13 | String password, 14 | String birthDate, 15 | int manageFlag, 16 | ) async { 17 | try { 18 | emit(SignInLoadingState()); 19 | final resultCode = await _repository.signIn( 20 | nickname, 21 | email, 22 | password, 23 | birthDate, 24 | manageFlag, 25 | ); 26 | 27 | if (resultCode == 201) { 28 | emit(SignInSuccessState('회원가입을 완료했습니다')); 29 | return; 30 | } 31 | 32 | if (resultCode == 400) { 33 | emit(SignInErrorState('이미 사용중인 이메일입니다')); 34 | return; 35 | } 36 | 37 | emit(SignInErrorState('회원가입에 실패했습니다')); 38 | } on Exception catch (_) { 39 | emit(SignInErrorState('회원가입에 실패했습니다')); 40 | } 41 | } 42 | 43 | Future checkNicknameDuplicated(String nickname) async { 44 | try { 45 | emit(NicknameCheckingState()); 46 | final resultCode = await _repository.checkNicknameDuplicated(nickname); 47 | 48 | if (resultCode == 200) { 49 | emit(NicknameCheckedState('사용 가능한 닉네임입니다')); 50 | return; 51 | } 52 | 53 | if (resultCode == 400) { 54 | emit(NicknameCheckErrorState('닉네임이 중복되었습니다')); 55 | return; 56 | } 57 | 58 | emit(NicknameCheckErrorState('중복 조회에 실패했습니다')); 59 | } on Exception catch (_) { 60 | emit(NicknameCheckErrorState('중복 조회에 실패했습니다')); 61 | } 62 | } 63 | 64 | Future checkRegisterNumber(String businessNumber) async { 65 | try { 66 | emit(BusinessNumberCheckingState()); 67 | final resultCode = await _repository.checkRegisterNumber(businessNumber); 68 | 69 | if (resultCode == 200) { 70 | emit(BusinessNumberCheckedState('인증이 완료되었습니다')); 71 | return; 72 | } 73 | 74 | if (resultCode == 400) { 75 | emit(BusinessNumberCheckErrorState('올바르지 않은 사업자 등록 번호입니다')); 76 | return; 77 | } 78 | 79 | emit(BusinessNumberCheckErrorState('인증에 실패했습니다')); 80 | } on Exception catch (_) { 81 | emit(BusinessNumberCheckErrorState('인증에 실패했습니다')); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /courageous_people/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 10 | 17 | 18 | 22 | 26 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /courageous_people/lib/review/cubit/review_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | 5 | import '../repository/review_repository.dart'; 6 | import 'review_state.dart'; 7 | 8 | class ReviewCubit extends Cubit { 9 | final ReviewRepository repository; 10 | 11 | ReviewCubit(this.repository) : super(ReviewInitialState()); 12 | 13 | Future getReviews(int storeId) async { 14 | emit(ReviewLoadingState()); 15 | final reviewList = await repository.getReviews(storeId); 16 | emit(ReviewLoadedState(reviewList)); 17 | } 18 | 19 | Future addReview({ 20 | required int storeId, 21 | required int userId, 22 | required String menu, 23 | required String container, 24 | required String comment, 25 | Uint8List? pictureToByte, 26 | }) async { 27 | try { 28 | emit(AddingReviewLoadingState()); 29 | final statusCode = await repository.addReview( 30 | storeId: storeId, 31 | userId: userId, 32 | comment: comment, 33 | menu: menu, 34 | container: container, 35 | pictureToByte: pictureToByte, 36 | ); 37 | 38 | if (statusCode == 200 || statusCode == 201) { 39 | print(statusCode); 40 | emit(AddingReviewSuccessState('리뷰를 등록했습니다')); 41 | return; 42 | } 43 | 44 | emit(AddingReviewErrorState('리뷰 등록에 실패했습니다')); 45 | } on Exception catch (exception) { 46 | emit(AddingReviewErrorState(exception.toString())); 47 | } 48 | } 49 | 50 | 51 | Future rewriteReview({ 52 | required int reviewId, 53 | required int storeId, 54 | required int userId, 55 | required String comment, 56 | }) async { 57 | try { 58 | emit(RewriteReviewLoadingState()); 59 | final statusCode = await repository.rewriteReview( 60 | reviewId: reviewId, 61 | storeId: storeId, 62 | userId: userId, 63 | comment: comment 64 | ); 65 | 66 | if (statusCode == 201) { 67 | emit(ReviewRewrittenState('수정이 완료되었습니다')); 68 | return; 69 | } 70 | 71 | emit(ReviewRewriteErrorState('수정에 실패했습니다')); 72 | } on Exception catch (_) { 73 | emit(ReviewRewriteErrorState('수정에 실패했습니다')); 74 | } 75 | } 76 | 77 | Future deleteReview(int reviewId) async { 78 | try { 79 | emit(DeleteReviewLoadingState()); 80 | final reviewDeleteResponse = await repository.deleteReview(reviewId); 81 | 82 | print(reviewDeleteResponse); 83 | if(reviewDeleteResponse == 204) { 84 | emit(ReviewDeletedState('리뷰를 삭제했습니다')); 85 | return; 86 | } 87 | 88 | emit(ReviewDeleteErrorState('리뷰 삭제에 실패했습니다')); 89 | } on Exception catch (_) { 90 | emit(ReviewDeleteErrorState('리뷰 삭제에 실패했습니다')); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /courageous_people/lib/widget/menu_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MenuButton extends StatelessWidget { 4 | final void Function()? onPressed; 5 | final IconData iconData; 6 | final double? left; 7 | final double? right; 8 | final double? top; 9 | final double? bottom; 10 | final String? menuTitle; 11 | final Color? backgroundColor; 12 | final double? size; 13 | final String? heroTag; 14 | final Color? iconColor; 15 | final double? borderRadiusValue; 16 | final EdgeInsetsGeometry? margin; 17 | 18 | const MenuButton({ 19 | Key? key, 20 | required this.onPressed, 21 | required this.iconData, 22 | this.left, 23 | this.right, 24 | this.top, 25 | this.bottom, 26 | this.heroTag, 27 | this.menuTitle, 28 | this.size = 40, 29 | this.backgroundColor, 30 | this.iconColor = Colors.white, 31 | this.borderRadiusValue, 32 | this.margin = EdgeInsets.zero, 33 | }) : super(key: key); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Padding( 38 | padding: margin!, 39 | child: GestureDetector( 40 | onTap: onPressed, 41 | child: Row( 42 | mainAxisSize: MainAxisSize.min, 43 | children: [ 44 | Container( 45 | width: size, 46 | height: size, 47 | decoration: BoxDecoration( 48 | borderRadius: BorderRadius.circular(borderRadiusValue ?? 0), 49 | color: backgroundColor, 50 | ), 51 | child: Icon(iconData, color: iconColor, size: size!-14), 52 | ), 53 | const SizedBox(width: 5), 54 | if(menuTitle != null) Text( 55 | menuTitle!, 56 | style: TextStyle(fontWeight: FontWeight.bold), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | // Positioned( 78 | // left: left, 79 | // right: right, 80 | // top: top, 81 | // bottom: bottom, 82 | // child: Row( 83 | // mainAxisSize: MainAxisSize.min, 84 | // children: [ 85 | // FloatingActionButton( 86 | // onPressed: onPressed, 87 | // backgroundColor: backgroundColor ?? Colors.grey.shade700, 88 | // elevation: 0, 89 | // mini: isMini, 90 | // heroTag: heroTag, 91 | // shape: RoundedRectangleBorder( 92 | // borderRadius: BorderRadius.circular(borderRadiusValue ?? 0), 93 | // ), 94 | // child: Icon(iconData, color: iconColor ?? Colors.white), 95 | // ), 96 | // Container(), 97 | // const SizedBox(width: 5), 98 | // if(menuTitle != null) Text( 99 | // menuTitle!, 100 | // style: TextStyle(fontWeight: FontWeight.bold), 101 | // ), 102 | // ], 103 | // ), 104 | // ); -------------------------------------------------------------------------------- /courageous_people/lib/sign_in/screen/sign_in_success_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/constants.dart'; 2 | import 'package:courageous_people/log_in/log_In_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../log_in/log_In_screen.dart'; 6 | 7 | class SignInSuccessScreen extends StatelessWidget { 8 | const SignInSuccessScreen({Key? key, required this.email}) : super(key: key); 9 | 10 | final String email; 11 | 12 | final String _mainText = '회원가입이 완료되었습니다!\n'; 13 | final String _subText1 = '로\n 계정 활설화 링크 메일을 발송하였습니다\n'; 14 | final String _subText2 = '계정을 활성화하고 앱을 시작해보세요!'; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | body: SafeArea( 20 | child: Column( 21 | mainAxisSize: MainAxisSize.max, 22 | children: [ 23 | Expanded(child: SizedBox(height: 0)), 24 | Text( 25 | _mainText, 26 | style: TextStyle( 27 | fontWeight: FontWeight.bold, 28 | fontSize: 25, 29 | color: Colors.black, 30 | ), 31 | ), 32 | Text.rich( 33 | TextSpan( 34 | style: TextStyle( 35 | color: Colors.grey.shade500, 36 | fontSize: 15, 37 | ), 38 | children: [ 39 | TextSpan(text: '가입하신 이메일 '), 40 | TextSpan( 41 | text: '$email ', 42 | style: TextStyle( 43 | color: Colors.green.shade500, 44 | fontWeight: FontWeight.bold, 45 | ), 46 | ), 47 | TextSpan(text: '$_subText1'), 48 | TextSpan(text: '$_subText2'), 49 | ], 50 | ), 51 | textAlign: TextAlign.center, 52 | ), 53 | Expanded(child: SizedBox(height: 0)), 54 | _bottomButton( 55 | onTap: () => Navigator.pushAndRemoveUntil( 56 | context, 57 | MaterialPageRoute(builder: (context) => LogInScreen()), 58 | (route) => false, 59 | ), 60 | ), 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | 67 | Widget _bottomButton({ 68 | required void Function()? onTap, 69 | }) { 70 | return GestureDetector( 71 | onTap: onTap, 72 | child: Container( 73 | width: double.maxFinite, 74 | height: kToolbarHeight, 75 | color: THEME_COLOR, 76 | alignment: Alignment.center, 77 | child: Text( 78 | '로그인하러 가기', 79 | style: TextStyle( 80 | color: Colors.white, 81 | fontWeight: FontWeight.bold, 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /courageous_people/lib/utils/user_verification.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:courageous_people/common/constants.dart'; 4 | import 'package:courageous_people/common/hive/user_hive.dart'; 5 | import 'package:courageous_people/service/token_service.dart'; 6 | import 'package:courageous_people/utils/http_client.dart'; 7 | import 'package:courageous_people/utils/interpreters.dart'; 8 | import 'package:http/http.dart' as http; 9 | 10 | Future getAuthorization() async { 11 | final refreshToken = TokenService().refreshToken; 12 | final noRefreshToken = refreshToken == null || refreshToken == ''; 13 | if(noRefreshToken) return false; 14 | 15 | final isRefreshTokenVerified = await _verifyRefreshToken(refreshToken!); 16 | if(!isRefreshTokenVerified) return false; 17 | 18 | final isRefreshed = await _refresh(refreshToken); 19 | if(!isRefreshed) return false; 20 | 21 | return true; 22 | } 23 | 24 | Future verifyUser() async { 25 | final refreshToken = TokenService().refreshToken; 26 | final noRefreshToken = refreshToken == null || refreshToken == ''; 27 | if(noRefreshToken) return false; 28 | 29 | final isRefreshTokenVerified = await _verifyRefreshToken(refreshToken!); 30 | 31 | if(!isRefreshTokenVerified) { 32 | await TokenService().clearTokens(); 33 | await UserHive().clearUser(); 34 | return false; 35 | } 36 | 37 | final userEmail = UserHive().userEmail; 38 | 39 | final setUserSucceed = await _setUser(userEmail ?? ''); 40 | if(!setUserSucceed) return false; 41 | 42 | return true; 43 | } 44 | 45 | Future _verifyRefreshToken(String refreshToken) async { 46 | final response = await httpRequestWithoutToken( 47 | requestType: 'post', 48 | path: '/account/verify', 49 | body: { 50 | "token": refreshToken, 51 | }, 52 | ); 53 | 54 | print('verify response code: ${response.statusCode}'); 55 | 56 | if(response.statusCode != 200) return false; 57 | return true; 58 | } 59 | 60 | Future _refresh(String refreshToken) async { 61 | final response = await httpRequestWithoutToken( 62 | requestType: 'post', 63 | path: '/account/refresh', 64 | body: { 65 | "refresh": refreshToken, 66 | }, 67 | ); 68 | 69 | print('refresh response code: ${response.statusCode}'); 70 | 71 | if(response.statusCode != 200) return false; 72 | 73 | await TokenService().setAccessToken(jsonDecode(response.body)['access']); 74 | return true; 75 | } 76 | 77 | Future _setUser(String email) async { 78 | if(email == '') return false; 79 | 80 | final response = await httpRequestWithToken( 81 | requestType: 'GET', 82 | path: '/account/user?email=$email', 83 | ); 84 | 85 | if(response.statusCode != 200) return false; 86 | 87 | print('user response: ${response.statusCode}'); 88 | print('user response: ${response.body}'); 89 | 90 | final userData = toUser(response.body); 91 | UserHive().setUser(userData); 92 | 93 | return true; 94 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Courageous-Developer-APP 2 | 3 | ![image](https://user-images.githubusercontent.com/55770848/132479866-83b5d1e9-545e-43f4-8a4a-516c0b034aca.png) 4 | 5 | #### 용기내 캠페인 후기들이 궁금하다면 용기낸 사람들 6 | 내 주변 다회용기 사용 가능 가게 검색하고 리뷰를 보세요! 7 | 8 | #### 용기내 캠페인 앱 '용기낸 사람들'은 사용자가 직접 가게를 등록하고 리뷰를 쓰는 사용자 정보 기반의 앱입니다. 9 | 10 | ## 기능 11 | #### 회원가입 & 로그인: 손님 사용자 / 사장님 사용자 둘로 구분하여 가입합니다. 12 | 13 | 14 | 15 | 16 | #### 가게 검색: 사용자가 직접 가게를 검색하여 등록할 수 있습니다. 17 | -> 왼쪽 상단 18 | 19 | 20 | -> 지역(시 / 구 / 동) 입력 21 | 22 | 23 | -> 가게 패널 등장 & 하단에 나타난 가게 패널 상단 숫자를 클릭하면 해당 가게 위치로 이동 24 | -> 등록 안 된 가게 등록 가능 25 | 26 | 27 | #### 가게 등록: 검색한 가게가 등록되어 있지 않을 경우 등록 가능 28 | -> 일반 사용자: 가게 썸네일 사진 추가 등록 가능 29 | 30 | 31 | -> 가게 주인: 가게 썸네일 사진, 한 줄 소개, 메뉴 등록 가능 32 | 33 | 34 | #### 가게 조회: 지도에 띄워진 등록된 가게들에 들어가서 메뉴 / 리뷰를 볼 수 있습니다. 35 | -> 가게 클릭 시 가게 패널 노출 36 | 37 | 38 | -> 패널 클릭 시 가게 페이지 이동 39 | 40 | 41 | #### 리뷰 조회 42 | -> 리뷰 리스트 보여줌 43 | 44 | 45 | #### 리뷰 등록: 로그인 후 리뷰를 등록할 수 있습니다. 46 | 47 | 48 | 59 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /courageous_people/lib/widget/my_input_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyInputForm extends StatelessWidget { 4 | final TextEditingController? controller; 5 | final Widget? title; 6 | final Widget? additionalButton; 7 | final TextInputType? textInputType; 8 | final bool? obscureText; 9 | final String? errorText; 10 | final String? helperText; 11 | final bool enabled; 12 | final bool filled; 13 | final String? controllerText; 14 | final void Function(String)? onChanged; 15 | 16 | MyInputForm({ 17 | Key? key, 18 | this.controller, 19 | this.title, 20 | this.additionalButton, 21 | this.textInputType, 22 | this.obscureText, 23 | this.errorText, 24 | this.helperText, 25 | this.onChanged, 26 | this.enabled = true, 27 | this.filled = false, 28 | this.controllerText 29 | }) : super(key: key); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Column( 34 | mainAxisSize: MainAxisSize.min, 35 | crossAxisAlignment: CrossAxisAlignment.start, 36 | children: [ 37 | title ?? SizedBox(height: 0), 38 | Row( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | Expanded( 42 | child: Container( 43 | height: 30, 44 | child: TextFormField( 45 | enabled: enabled, 46 | onChanged: onChanged, 47 | obscureText: obscureText ?? false, 48 | keyboardType: textInputType, 49 | decoration: InputDecoration( 50 | errorText: errorText, 51 | border: UnderlineInputBorder( 52 | borderSide: BorderSide( 53 | color: helperText == null ? Colors.black : Colors.green, 54 | ), 55 | ), 56 | focusedBorder: UnderlineInputBorder( 57 | borderSide: BorderSide( 58 | color: helperText == null ? Colors.blue : Colors.green, 59 | width: 2, 60 | ), 61 | ), 62 | helperText: helperText, 63 | helperStyle: TextStyle(color: Colors.green), 64 | // filled: filled, 65 | // fillColor: Colors.grey[300], 66 | // contentPadding: EdgeInsets.symmetric( 67 | // vertical: 0, 68 | // horizontal: 15, 69 | // ), 70 | // border: OutlineInputBorder( 71 | // borderSide: BorderSide( 72 | // color: Colors.red, 73 | // ), 74 | // borderRadius: BorderRadius.all(Radius.circular(5)), 75 | // ), 76 | // focusedBorder: OutlineInputBorder( 77 | // borderRadius: BorderRadius.all(Radius.circular(5)), 78 | // borderSide: BorderSide( 79 | // color: Colors.black 80 | // ), 81 | // ), 82 | ), 83 | controller: controller, 84 | ), 85 | ), 86 | ), 87 | SizedBox(width: additionalButton == null ? 0 : 8), 88 | additionalButton ?? SizedBox(width: 0), 89 | ], 90 | ), 91 | ], 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /3rd-party-licenses.md: -------------------------------------------------------------------------------- 1 | # License overview of included 3rd party libraries 2 | 3 | The Courageous-Developer-App project is licensed under the terms of the [MIT](LICENSE). 4 | 5 | 6 | However, Courageous-Developer-App includes several third-party Open-Source libraries, 7 | which are licensed under their own respective Open-Source licenses. 8 | 9 | ## Libraries included in Courageous-Developer-App 10 | 11 | ### Dart: 12 | License: BSD-3-Clause License 13 | Copyright 2012, the Dart project authors. 14 | https://github.com/dart-lang/sdk/blob/main/LICENSE 15 | 16 | ### flutter: 17 | License: BSD-3-Clause License 18 | Copyright 2014 The Flutter Authors. All rights reserved. 19 | https://github.com/flutter/flutter/blob/master/LICENSE 20 | 21 | ### hive: ^2.0.4 22 | License : Apache-2.0 License 23 | Copyright 2019 Simon Leier 24 | https://github.com/hivedb/hive/blob/master/LICENSE 25 | 26 | ### hive_flutter: ^1.1.0 27 | License : Apache-2.0 License 28 | Copyright 2019 Simon Leier 29 | https://github.com/hivedb/hive_flutter/blob/master/LICENSE 30 | 31 | ### provider: ^6.0.0 32 | License : MIT License 33 | Copyright (c) 2019 Remi Rousselet 34 | https://github.com/rrousselGit/provider/blob/master/LICENSE 35 | 36 | ### flutter_provider: ^2.0.0 37 | License : MIT License 38 | Copyright (c) 2021 Petrus Nguyễn Thái Học 39 | https://pub.dev/packages/flutter_provider/license 40 | 41 | ### bloc: ^7.1.0 42 | License : MIT License 43 | Copyright (c) 2018 Felix Angelov 44 | https://github.com/felangel/bloc/blob/master/LICENSE 45 | 46 | ### flutter_bloc: ^7.2.0 47 | License : MIT License 48 | Copyright (c) 2018 Felix Angelov 49 | https://pub.dev/packages/flutter_bloc/license 50 | 51 | ### graphql_flutter: ^5.0.0 52 | License : MIT License 53 | Copyright (c) 2018-present, Zino App B.V. 54 | https://github.com/zino-app/graphql-flutter/blob/beta/LICENSE 55 | 56 | ### flutter_rating_bar: ^4.0.0 57 | License : MIT License 58 | Copyright (c) 2021 Sarbagya Dhaubanjar 59 | https://github.com/sarbagyastha/flutter_rating_bar/blob/master/LICENSE 60 | 61 | ### http: ^0.13.3 62 | License: BSD-3-Clause License 63 | Copyright 2014, the Dart project authors. 64 | https://github.com/dart-lang/http/blob/master/LICENSE 65 | 66 | ### flutter_hooks: ^0.18.0 67 | License : MIT License 68 | Copyright (c) 2018 Remi Rousselet 69 | https://github.com/rrousselGit/flutter_hooks/blob/master/LICENSE 70 | 71 | ### image_picker: ^0.8.4 72 | License : Apache-2.0 License 73 | Copyright 2013 The Flutter Authors. All rights reserved. 74 | https://pub.dev/packages/image_picker/license 75 | 76 | ### http_parser: ^4.0.0 77 | License: BSD-3-Clause License 78 | Copyright 2014, the Dart project authors. All rights reserved. 79 | https://pub.dev/packages/http_parser/license 80 | 81 | ### dio: ^4.0.0 82 | License : MIT License 83 | Copyright (c) 2018 wendux 84 | https://pub.dev/packages/dio/license 85 | 86 | ### email_validator: ^2.0.1 87 | License : MIT License 88 | Copyright (c) 2018 Fredrik Eilertsen 89 | https://pub.dev/packages/email_validator/license 90 | 91 | ### encrypt: ^5.0.1 92 | License: BSD-3-Clause License 93 | Copyright (c) 2018, Leo Cavalcante 94 | https://github.com/leocavalcante/encrypt/blob/5.x/LICENSE 95 | 96 | ### naver_map_plugin: 97 | License: BSD-3-Clause License 98 | Copyright (c) <2020>, 99 | https://github.com/LBSTECH/naver_map_plugin/blob/master/LICENSE 100 | -------------------------------------------------------------------------------- /courageous_people/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /courageous_people/lib/review/repository/review_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:math'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:courageous_people/service/token_service.dart'; 6 | import 'package:courageous_people/utils/http_client.dart'; 7 | import 'package:dio/dio.dart'; 8 | import 'package:http_parser/http_parser.dart'; 9 | 10 | import '../../model/review_data.dart'; 11 | import '../../common/constants.dart'; 12 | import '../../utils/interpreters.dart'; 13 | 14 | class ReviewRepository { 15 | Future> getReviews(int storeId) async { 16 | final response = await httpRequestWithoutToken( 17 | requestType: 'GET', 18 | path: '/board/review/$storeId', 19 | ); 20 | 21 | return toReviewList(response.body); 22 | } 23 | 24 | Future addReview({ 25 | required int storeId, 26 | required int userId, 27 | required String menu, 28 | required String container, 29 | required String comment, 30 | Uint8List? pictureToByte, 31 | }) async { 32 | final addingReviewResponse = await httpRequestWithToken( 33 | requestType: 'POST', 34 | path: '/board/review', 35 | body: { 36 | 'user': userId, 37 | 'store': storeId, 38 | 'content': comment, 39 | 'tag': [ 40 | { 41 | 'tag_content': menu, 42 | 'type': 1, 43 | 'color_index': Random().nextInt(8), 44 | }, 45 | { 46 | 'tag_content': container, 47 | 'type': 2, 48 | 'color_index': Random().nextInt(8), 49 | }, 50 | ], 51 | }, 52 | ); 53 | 54 | print('review add statusCode: ${addingReviewResponse.statusCode}'); 55 | print('review add body: ${addingReviewResponse.body}'); 56 | 57 | if(pictureToByte == null || addingReviewResponse.statusCode != 201) { 58 | return addingReviewResponse.statusCode; 59 | } 60 | 61 | final reviewId = jsonDecode(addingReviewResponse.body)['id']; 62 | 63 | final multiPartData = MultipartFile.fromBytes( 64 | pictureToByte, 65 | filename: 'review_img_$reviewId.jpg', // use the real name if available, or omit 66 | contentType: MediaType('image', 'png'), 67 | ); 68 | 69 | final formData = FormData.fromMap({ 70 | "review_img": multiPartData, 71 | "review": reviewId, 72 | }); 73 | 74 | final sendingPictureResponse = await Dio().post( 75 | '$REQUEST_URL/board/review-img', 76 | data: formData, 77 | options: Options( 78 | headers: { 79 | "Accept": "application/json", 80 | "Content-Type": "application/octet-stream", 81 | "Authorization": "Bearer ${TokenService().accessToken!}", 82 | }, 83 | ), 84 | ); 85 | 86 | print('review image add status: ${sendingPictureResponse.statusCode}'); 87 | print('review image add body: ${sendingPictureResponse.data}'); 88 | 89 | return sendingPictureResponse.statusCode!; 90 | } 91 | 92 | Future rewriteReview({ 93 | required int reviewId, 94 | required int storeId, 95 | required int userId, 96 | required String comment, 97 | }) async { 98 | final reviewResponse = await httpRequestWithToken( 99 | requestType: 'PUT', 100 | path: '/board/review/$reviewId', 101 | body: { 102 | 'user': userId, 103 | 'store': storeId, 104 | 'content': comment, 105 | }, 106 | ); 107 | 108 | print(reviewResponse.body); 109 | print(reviewResponse.statusCode); 110 | 111 | return reviewResponse.statusCode; 112 | } 113 | 114 | Future deleteReview(int reviewId) async { 115 | final response = await httpRequestWithToken( 116 | requestType: 'DELETE', 117 | path: '/board/review/$reviewId', 118 | ); 119 | 120 | return response.statusCode; 121 | } 122 | } -------------------------------------------------------------------------------- /courageous_people/lib/widget/my_drop_down.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_hooks/flutter_hooks.dart'; 3 | 4 | class MyDropDown extends HookWidget{ 5 | final void Function(String) onSelect; 6 | List? contents; 7 | List? widgetContents; 8 | List? contentImageUrl; 9 | String title; 10 | double? width; 11 | 12 | MyDropDown({ 13 | required this.title, 14 | required this.onSelect, 15 | this.contents, 16 | this.widgetContents, 17 | this.contentImageUrl, 18 | this.width, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final titleNotifier = useState(title); 24 | 25 | final _contents = contents ?? widgetContents; 26 | 27 | if(_contents == null) return Container(); 28 | 29 | return GestureDetector( 30 | child: Container( 31 | padding: EdgeInsets.all(8), 32 | width: width, 33 | alignment: Alignment.center, 34 | child: Text( 35 | titleNotifier.value ?? title, 36 | ), 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.circular(4), 39 | border: Border.all( 40 | width: 0.5, 41 | color: Colors.grey.shade700, 42 | ), 43 | ), 44 | ), 45 | onTap: () async { 46 | titleNotifier.value = await showDialog( 47 | context: context, 48 | builder: (context) => Padding( 49 | padding: EdgeInsets.all(40), 50 | child: Container( 51 | decoration: BoxDecoration( 52 | color: Colors.white, 53 | borderRadius: BorderRadius.circular(8), 54 | border: Border.all( 55 | width: 0, 56 | color: Colors.black, 57 | ), 58 | ), 59 | child: ListView.separated( 60 | itemCount: _contents.length, 61 | itemBuilder: (context, index) => GestureDetector( 62 | onTap: () { 63 | onSelect(_contents[index]); 64 | Navigator.pop(context, _contents[index]); 65 | }, 66 | child: contents != null 67 | ? _contentItem(_contents[index]) 68 | : 69 | _widgetContentItem( 70 | _contents[index], 71 | contentImageUrl == null 72 | ? 'assets/images/pukka.png' 73 | : contentImageUrl![index], 74 | ), 75 | ), 76 | separatorBuilder: (context, index) => Divider(thickness: 1), 77 | ), 78 | ), 79 | ), 80 | ); 81 | }, 82 | ); 83 | } 84 | 85 | Widget _contentItem(String title) { 86 | return Container( 87 | alignment: Alignment.center, 88 | padding: EdgeInsets.symmetric(vertical: 20), 89 | child: Text( 90 | title, 91 | style: TextStyle(fontSize: 24), 92 | ), 93 | ); 94 | } 95 | 96 | Widget _widgetContentItem( 97 | String title, 98 | String imageUri, 99 | ) { 100 | return Padding( 101 | padding: EdgeInsets.all(20), 102 | child: Row( 103 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 104 | children: [ 105 | Center( 106 | child: Text( 107 | title, 108 | style: TextStyle( 109 | fontSize: 25, 110 | fontWeight: FontWeight.bold, 111 | color: Colors.grey, 112 | ), 113 | ), 114 | ), 115 | ClipRRect( 116 | borderRadius: BorderRadius.circular(10), 117 | child: Image.asset(imageUri, width: 80, height: 80), 118 | ), 119 | ], 120 | ), 121 | ); 122 | } 123 | } -------------------------------------------------------------------------------- /courageous_people/lib/utils/show_alert_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/constants.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Future showAlertDialog({ 5 | required BuildContext context, 6 | required String title, 7 | void Function()? onSubmit, 8 | void Function()? onCancel, 9 | }) async { 10 | return await showDialog( 11 | context: context, 12 | builder: (context) => _MyAlertDialog( 13 | title: title, 14 | onSubmit: onSubmit, 15 | onCancel: onCancel, 16 | ), 17 | ); 18 | } 19 | 20 | class _MyAlertDialog extends StatelessWidget { 21 | _MyAlertDialog({ 22 | Key? key, 23 | required this.title, 24 | this.onSubmit, 25 | this.onCancel, 26 | }) : super(key: key); 27 | 28 | final String title; 29 | void Function()? onSubmit; 30 | void Function()? onCancel; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Dialog( 35 | insetPadding: EdgeInsets.all(50), 36 | shape: RoundedRectangleBorder( 37 | borderRadius: BorderRadius.circular(5) 38 | ), 39 | child: Container( 40 | height: 150, 41 | child: Column( 42 | children: [ 43 | Expanded( 44 | child: Center( 45 | child: Text( 46 | title, 47 | textAlign: TextAlign.center, 48 | ), 49 | ), 50 | ), 51 | Container( 52 | height: 45, 53 | decoration: BoxDecoration( 54 | borderRadius: BorderRadius.only( 55 | bottomLeft: Radius.circular(5), 56 | bottomRight: Radius.circular(5), 57 | ), 58 | ), 59 | child: Row( 60 | children: [ 61 | _submitButton( 62 | borderRadius: onCancel == null 63 | ? null 64 | : BorderRadius.only(bottomLeft: Radius.circular(5)), 65 | onSubmit: onSubmit ?? () => Navigator.pop(context, true), 66 | ), 67 | if(onCancel != null) 68 | _cancelButton(onCancel: onCancel!), 69 | ], 70 | ), 71 | ), 72 | ], 73 | ), 74 | ), 75 | ); 76 | } 77 | 78 | Widget _submitButton({ 79 | required void Function() onSubmit, 80 | required BorderRadius? borderRadius, 81 | }) { 82 | return Expanded( 83 | child: GestureDetector( 84 | onTap: onSubmit, 85 | child: Container( 86 | decoration: BoxDecoration( 87 | color: THEME_COLOR, 88 | borderRadius: borderRadius ?? BorderRadius.only( 89 | bottomLeft: Radius.circular(5), 90 | bottomRight: Radius.circular(5), 91 | ), 92 | ), 93 | child: Center( 94 | child: Text( 95 | '확인', 96 | style: TextStyle( 97 | color: Colors.white, 98 | fontWeight: FontWeight.bold, 99 | ), 100 | ), 101 | ), 102 | ), 103 | ), 104 | ); 105 | } 106 | 107 | Widget _cancelButton({required void Function() onCancel}) { 108 | return Expanded( 109 | child: GestureDetector( 110 | onTap: onCancel, 111 | child: Container( 112 | decoration: BoxDecoration( 113 | color: Colors.grey.shade500, 114 | borderRadius: BorderRadius.only(bottomRight: Radius.circular(5)), 115 | ), 116 | child: Center( 117 | child: Text( 118 | '취소', 119 | style: TextStyle( 120 | color: Colors.white, 121 | fontWeight: FontWeight.bold, 122 | ), 123 | ), 124 | ), 125 | ), 126 | ), 127 | ); 128 | } 129 | } -------------------------------------------------------------------------------- /courageous_people/lib/utils/interpreters.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:courageous_people/model/menu_data.dart'; 4 | 5 | import '../model/store_data.dart'; 6 | import '../model/user_data.dart'; 7 | import '../model/review_data.dart'; 8 | import '../model/tag_data.dart'; 9 | import '../common/classes.dart'; 10 | 11 | List toStoreList(String source) { 12 | final List dataList = jsonDecode(source); 13 | 14 | final storeList = dataList.map( 15 | (data) { 16 | Json store = data; 17 | 18 | return StoreData( 19 | store['id'], 20 | store['store_name'], 21 | store['address'], 22 | store['post'], 23 | double.parse(store['latitude']), 24 | double.parse(store['longitude']), 25 | store['biz_num'], 26 | toImageList(jsonEncode(store['store_img'])), 27 | toMenuList(jsonEncode(store['menu'])), 28 | ); 29 | } 30 | ).toList(); 31 | 32 | return storeList; 33 | } 34 | 35 | UserData toUser(String source) { 36 | final dynamic data = jsonDecode(source); 37 | final Json user = data; 38 | 39 | return UserData( 40 | user['id'], 41 | user['nickname'], 42 | user['email'], 43 | user['user_type'], 44 | ); 45 | } 46 | 47 | List toReviewList(String source) { 48 | final List dataList = jsonDecode(source); 49 | 50 | final reviewList = dataList.map( 51 | (data) { 52 | Json review = data; 53 | 54 | return ReviewData( 55 | review['id'], 56 | review['store'], 57 | review['nickname'], 58 | review['content'], 59 | review['insrt_dt'], 60 | (review['review_img'] as List).map( 61 | (data) => data['review_img'] as String 62 | ).toList(), 63 | toTagList(jsonEncode(review['tag'])), 64 | ); 65 | } 66 | ).toList(); 67 | 68 | return reviewList; 69 | } 70 | 71 | List toTagList(String source) { 72 | final List dataList = jsonDecode(source); 73 | if(dataList.length == 0) return []; 74 | 75 | final tagList = dataList.map( 76 | (data) { 77 | Json tag = data; 78 | 79 | return TagData( 80 | tag['tag_content'], 81 | tag['color_index'], 82 | ); 83 | } 84 | ).toList(); 85 | 86 | return tagList; 87 | } 88 | 89 | List toMenuList(String source) { 90 | final List dataList = jsonDecode(source); 91 | 92 | if(dataList.length == 0) return []; 93 | 94 | final menuList = dataList.map( 95 | (data) { 96 | Json menu = data; 97 | 98 | final List menuImageList = menu['menu_img']; 99 | String? imageUrl; 100 | 101 | if(menuImageList.length > 0) { 102 | imageUrl = menuImageList[0]['menu_img']; 103 | } 104 | 105 | return MenuData( 106 | menu['menu'] ?? '', 107 | menu['price'] ?? '', 108 | menu['store'], 109 | imageUrl, 110 | ); 111 | } 112 | ).toList(); 113 | 114 | return menuList; 115 | } 116 | 117 | StoreData toStore(String source) { 118 | final Json store = jsonDecode(source); 119 | 120 | return StoreData( 121 | store['id'], 122 | store['store_name'], 123 | store['address'], 124 | store['post'], 125 | double.parse(store['latitude']), 126 | double.parse(store['longitude']), 127 | store['biz_num'], 128 | toImageList(jsonEncode(store['store_img'])), 129 | toMenuList(jsonEncode(store['menu'])), 130 | ); 131 | } 132 | 133 | List toImageList(String source) { 134 | final List dataList = jsonDecode(source); 135 | 136 | if(dataList.length == 0) return []; 137 | 138 | final imageList = dataList.map( 139 | (data) { 140 | Json imageUrl = data; 141 | 142 | final String image = imageUrl['store_img']; 143 | return image; 144 | } 145 | ).toList(); 146 | 147 | return imageList; 148 | } -------------------------------------------------------------------------------- /courageous_people/lib/home/widget/store_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/store_data.dart'; 2 | import 'package:courageous_people/store/screen/store_main_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class StoreBox extends StatelessWidget { 6 | final StoreData? store; 7 | 8 | const StoreBox({Key? key, required this.store}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | if(store == null) return SizedBox(width: 0); 13 | print(store!.imageUrl); 14 | 15 | return GestureDetector( 16 | onTap: () { 17 | Navigator.push(context, MaterialPageRoute( 18 | builder: (_) => StoreMainScreen(store: store!), 19 | )); 20 | }, 21 | child: Container( 22 | margin: EdgeInsets.fromLTRB( 23 | 30, 24 | MediaQuery.of(context).size.height * 0.68 + 50, 25 | 30, 26 | MediaQuery.of(context).size.height * 0.07, 27 | ), 28 | decoration: BoxDecoration( 29 | color: Colors.white, 30 | borderRadius: BorderRadius.all(Radius.circular(15.0)), 31 | border: Border.all( 32 | width: 2, 33 | color: Colors.teal.shade200, 34 | ), 35 | ), 36 | child: Container( 37 | child: Row( 38 | children: [ 39 | Expanded( 40 | child: Container( 41 | padding: EdgeInsets.only(left: 25, top: 15, bottom: 15), 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | children: [ 46 | Expanded( 47 | flex: 2, 48 | child: Container( 49 | alignment: Alignment.centerLeft, 50 | child: Text( 51 | store!.name, 52 | overflow: TextOverflow.ellipsis, 53 | style: TextStyle( 54 | fontSize: 21, 55 | fontWeight: FontWeight.bold, 56 | ), 57 | ), 58 | ), 59 | ), 60 | Divider( 61 | thickness: 0.5, 62 | color: Colors.grey.shade900, 63 | ), 64 | Expanded( 65 | flex: 1, 66 | child: Container( 67 | alignment: Alignment.centerLeft, 68 | child: Text( 69 | store!.address, 70 | overflow: TextOverflow.ellipsis, 71 | softWrap: true, 72 | style: TextStyle( 73 | fontSize: 15, 74 | color: Colors.grey.shade700, 75 | ), 76 | ), 77 | ), 78 | ), 79 | ], 80 | ), 81 | ), 82 | ), 83 | Container( 84 | width: MediaQuery.of(context).size.height / 4 - 90, 85 | height: MediaQuery.of(context).size.height / 4 - 90, 86 | padding: EdgeInsets.all(15), 87 | child: store!.imageUrl.length != 0 88 | ? ClipRRect(child: Image.network(store!.imageUrl[0])) 89 | : 90 | Container( 91 | decoration: BoxDecoration( 92 | color: Colors.teal.shade100, 93 | borderRadius: BorderRadius.circular(10), 94 | border: Border.all(width: 2, color: Colors.grey.shade400), 95 | ), 96 | child: Center( 97 | child: Text( 98 | 'No Image', 99 | style: TextStyle( 100 | color: Colors.grey.shade500, 101 | fontWeight: FontWeight.bold, 102 | ), 103 | ), 104 | ), 105 | ), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /courageous_people/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: courageous_people 2 | description: A new Flutter application. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.13.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | crypt: ^4.0.1 27 | hive: ^2.0.4 28 | hive_flutter: ^1.1.0 29 | provider: ^6.0.0 30 | flutter_provider: ^2.0.0 31 | bloc: ^7.1.0 32 | flutter_bloc: ^7.2.0 33 | graphql_flutter: ^5.0.0 34 | flutter_rating_bar: ^4.0.0 35 | http: ^0.13.3 36 | flutter_hooks: ^0.18.0 37 | image_picker: ^0.8.4 38 | http_parser: ^4.0.0 39 | dio: ^4.0.0 40 | email_validator: ^2.0.1 41 | encrypt: ^5.0.1 42 | naver_map_plugin: 43 | git: 44 | url: https://github.com/LBSTECH/naver_map_plugin.git 45 | # wh_flutter_widget: 46 | # git: 47 | # url: https://github.com/DDOLDDOL/wh_flutter_widget.git 48 | 49 | 50 | # The following adds the Cupertino Icons font to your application. 51 | # Use with the CupertinoIcons class for iOS style icons. 52 | cupertino_icons: ^1.0.2 53 | 54 | dev_dependencies: 55 | flutter_test: 56 | sdk: flutter 57 | 58 | # For information on the generic Dart part of this file, see the 59 | # following page: https://dart.dev/tools/pub/pubspec 60 | 61 | # The following section is specific to Flutter. 62 | flutter: 63 | 64 | # The following line ensures that the Material Icons font is 65 | # included with your application, so that you can use the icons in 66 | # the material Icons class. 67 | uses-material-design: true 68 | 69 | # To add assets to your application, add an assets section, like this: 70 | assets: 71 | - assets/images/container.png 72 | - assets/images/google_circular.png 73 | - assets/images/kakao_circular.png 74 | - assets/images/logo.png 75 | - assets/images/logo_notext.png 76 | - assets/images/logo_text.png 77 | - assets/images/logo_transparent.png 78 | - assets/images/naver_circular.png 79 | - assets/images/profile.png 80 | - assets/images/store.png 81 | - assets/images/pukka.png 82 | - assets/images/menu.png 83 | - assets/images/picture.png 84 | - assets/images/logo_color.png 85 | - assets/images/user.png 86 | - assets/images/300ml.png 87 | - assets/images/500ml.png 88 | - assets/images/1L.png 89 | - assets/images/2L.png 90 | - assets/images/customer.png 91 | - assets/images/manager.png 92 | 93 | # An image asset can refer to one or more resolution-specific "variants", see 94 | # https://flutter.dev/assets-and-images/#resolution-aware. 95 | 96 | # For details regarding adding assets from package dependencies, see 97 | # https://flutter.dev/assets-and-images/#from-packages 98 | 99 | # To add custom fonts to your application, add a fonts section here, 100 | # in this "flutter" section. Each entry in this list should have a 101 | # "family" key with the font family name, and a "fonts" key with a 102 | # list giving the asset and other descriptors for the font. For 103 | # example: 104 | # fonts: 105 | # - family: Schyler 106 | # fonts: 107 | # - asset: fonts/Schyler-Regular.ttf 108 | # - asset: fonts/Schyler-Italic.ttf 109 | # style: italic 110 | # - family: Trajan Pro 111 | # fonts: 112 | # - asset: fonts/TrajanPro.ttf 113 | # - asset: fonts/TrajanPro_Bold.ttf 114 | # weight: 700 115 | # 116 | # For details regarding fonts from package dependencies, 117 | # see https://flutter.dev/custom-fonts/#from-packages 118 | -------------------------------------------------------------------------------- /courageous_people/lib/utils/http_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:courageous_people/common/constants.dart'; 4 | import 'package:http/http.dart' as http; 5 | import '../common/classes.dart'; 6 | import '../service/token_service.dart'; 7 | import 'user_verification.dart'; 8 | 9 | Future httpRequestWithToken({ 10 | required String requestType, 11 | required String path, 12 | Map? headers, 13 | Json? body, 14 | Encoding? encoding, 15 | }) async { 16 | final uri = Uri.parse('$REQUEST_URL$path'); 17 | 18 | final requestHeaders = headers ?? { 19 | "Accept": "application/json", 20 | "Content-Type": "application/json", 21 | "Authorization": "Bearer ${TokenService().accessToken!}", 22 | }; 23 | 24 | final Future Function( 25 | Uri, {Map? headers} 26 | ) requestWithoutBody = http.get; 27 | 28 | late Future Function( 29 | Uri, {Object? body, Encoding? encoding, Map? headers} 30 | ) requestWithBody; 31 | 32 | bool hasBody = true; 33 | 34 | switch (requestType.toLowerCase()) { 35 | case 'get': 36 | hasBody = false; 37 | break; 38 | 39 | case 'post': 40 | requestWithBody = http.post; 41 | break; 42 | 43 | case 'put': 44 | requestWithBody = http.put; 45 | break; 46 | 47 | case 'delete': 48 | requestWithBody = http.delete; 49 | break; 50 | } 51 | 52 | final response = hasBody 53 | ? 54 | await requestWithBody( 55 | uri, 56 | headers: requestHeaders, 57 | body: jsonEncode(body), 58 | encoding: encoding, 59 | ) 60 | : 61 | await requestWithoutBody( 62 | uri, 63 | headers: requestHeaders, 64 | ); 65 | 66 | if (response.statusCode != 401) return response; 67 | 68 | final isAuthorized = await getAuthorization(); 69 | if (!isAuthorized) { 70 | return http.Response( 71 | jsonEncode({"message": "Cannot get authorization"}), 72 | 400, 73 | ); 74 | } 75 | 76 | final authorizedResponse = hasBody 77 | ? 78 | await requestWithBody( 79 | uri, 80 | headers: requestHeaders, 81 | body: jsonEncode(body), 82 | encoding: encoding, 83 | ) 84 | : 85 | await requestWithoutBody( 86 | uri, 87 | headers: requestHeaders, 88 | ); 89 | 90 | return authorizedResponse; 91 | } 92 | 93 | Future httpRequestWithoutToken({ 94 | required String requestType, 95 | required String path, 96 | Map? headers, 97 | Json? body, 98 | Encoding? encoding, 99 | }) async { 100 | final uri = Uri.parse('$REQUEST_URL$path'); 101 | 102 | final Map requestHeaders = headers ?? { 103 | "Accept": "application/json", 104 | "Content-Type": "application/json", 105 | }; 106 | 107 | final Future Function( 108 | Uri, {Map? headers} 109 | ) requestWithoutBody = http.get; 110 | 111 | late Future Function( 112 | Uri, {Object? body, Encoding? encoding, Map? headers} 113 | ) requestWithBody; 114 | 115 | http.Response response; 116 | bool hasBody = true; 117 | 118 | switch (requestType.toLowerCase()) { 119 | case 'get': 120 | hasBody = false; 121 | break; 122 | 123 | case 'post': 124 | requestWithBody = http.post; 125 | break; 126 | 127 | case 'put': 128 | requestWithBody = http.put; 129 | break; 130 | 131 | case 'delete': 132 | requestWithBody = http.delete; 133 | break; 134 | } 135 | 136 | response = hasBody 137 | ? await requestWithBody( 138 | uri, 139 | headers: requestHeaders, 140 | body: jsonEncode(body), 141 | encoding: encoding, 142 | ) 143 | : await requestWithoutBody( 144 | uri, 145 | headers: requestHeaders, 146 | ); 147 | 148 | return response; 149 | } 150 | 151 | Future naverRequestResponse({ 152 | required String url, 153 | required String queryString, 154 | required String requestApi, 155 | }) async { 156 | http.Response response = await http.get( 157 | Uri.parse('$url$queryString'), 158 | headers: requestApi == 'place' 159 | ? 160 | { 161 | "Accept": "application/json", 162 | "X-Naver-Client-Id": NAVER_API_CLINET_ID, 163 | "X-Naver-Client-Secret": NAVER_API_CLINET_SECRET 164 | } 165 | : 166 | { 167 | "Accept": "application/json", 168 | "X-NCP-APIGW-API-KEY-ID": X_NCP_APIGW_API_KEY_ID, 169 | "X-NCP-APIGW-API-KEY": X_NCP_APIGW_API_KEY, 170 | }, 171 | ); 172 | 173 | return response; 174 | } 175 | 176 | // todo: http.Response를 반환하지 말고, statuscode에 따른 예외처리 후 json decode 된 객체 반환 -------------------------------------------------------------------------------- /courageous_people/lib/review/widget/review_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/user_hive.dart'; 2 | import 'package:courageous_people/utils/show_alert_dialog.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../../model/review_data.dart'; 6 | import '../../widget/tag_widget.dart'; 7 | 8 | class ReviewTile extends StatelessWidget { 9 | final ReviewData review; 10 | 11 | final void Function() onCorrectionPressed; 12 | final void Function() onDeletionPressed; 13 | 14 | ReviewTile({ 15 | Key? key, 16 | required this.review, 17 | required this.onCorrectionPressed, 18 | required this.onDeletionPressed, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | 24 | return Container( 25 | padding: EdgeInsets.only(bottom: 45), 26 | child: Column( 27 | mainAxisSize: MainAxisSize.min, 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | _topSection( 31 | onCorrectionPressed: () async { 32 | await showAlertDialog( 33 | context: context, 34 | title: '수정하시겠습니까?', 35 | onSubmit: onCorrectionPressed, 36 | onCancel: () => Navigator.pop(context), 37 | ); 38 | }, 39 | onDeletionPressed: () async { 40 | await showAlertDialog( 41 | context: context, 42 | title: '삭제하시겠습니까?', 43 | onSubmit: () { 44 | Navigator.pop(context); 45 | onDeletionPressed(); 46 | }, 47 | onCancel: () => Navigator.pop(context), 48 | ); 49 | }, 50 | ), 51 | _imageSection(context), 52 | _commentSection(), 53 | _tagSection(), 54 | ], 55 | ), 56 | ); 57 | } 58 | 59 | Widget _topSection({ 60 | required void Function()? onCorrectionPressed, 61 | required void Function()? onDeletionPressed, 62 | }) { 63 | final userNickname = UserHive().userNickname ?? ''; 64 | final isMyReview = review.userNickname == userNickname; 65 | 66 | print(UserHive().userNickname); 67 | print(UserHive().userEmail); 68 | print(UserHive().userId); 69 | print(UserHive().userManagerFlag); 70 | 71 | return Row( 72 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 73 | crossAxisAlignment: CrossAxisAlignment.center, 74 | children: [ 75 | _userSection(), 76 | if(isMyReview) 77 | _correctionButtonSection( 78 | onCorrectionPressed: onCorrectionPressed, 79 | onDeletionPressed: onDeletionPressed, 80 | ), 81 | ], 82 | ); 83 | } 84 | 85 | Widget _userSection() => Container( 86 | child: Row( 87 | mainAxisSize: MainAxisSize.min, 88 | crossAxisAlignment: CrossAxisAlignment.center, 89 | children: [ 90 | CircleAvatar( 91 | backgroundImage: AssetImage('assets/images/user.png'), 92 | backgroundColor: Colors.transparent, 93 | radius: 18, 94 | ), 95 | SizedBox(width: 9), 96 | Column( 97 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 98 | crossAxisAlignment: CrossAxisAlignment.start, 99 | children: [ 100 | Text( 101 | review.userNickname, 102 | style: TextStyle( 103 | fontSize: 15, 104 | fontWeight: FontWeight.w600, 105 | ), 106 | ), 107 | SizedBox(height: 5), 108 | Text( 109 | // todo: 날짜 수정 110 | review.createAt.split('T')[0], 111 | style: TextStyle( 112 | fontSize: 11, 113 | color: Colors.grey, 114 | ), 115 | ), 116 | ], 117 | ), 118 | ], 119 | ), 120 | ); 121 | 122 | Widget _correctionButtonSection({ 123 | required void Function()? onCorrectionPressed, 124 | required void Function()? onDeletionPressed, 125 | }) { 126 | return Row( 127 | mainAxisSize: MainAxisSize.min, 128 | crossAxisAlignment: CrossAxisAlignment.center, 129 | children: [ 130 | TextButton( 131 | onPressed: onCorrectionPressed, 132 | child: Text( 133 | '수정', 134 | style: TextStyle(fontSize: 13, color: Colors.blue), 135 | ), 136 | ), 137 | TextButton( 138 | onPressed: onDeletionPressed, 139 | child: Text( 140 | '삭제', 141 | style: TextStyle(fontSize: 13, color: Colors.blue), 142 | ), 143 | ), 144 | ], 145 | ); 146 | } 147 | 148 | Widget _imageSection(BuildContext context) { 149 | return Container( 150 | margin: EdgeInsets.only(top: review.imageUri.length > 0 ? 15 : 0), 151 | alignment: Alignment.topLeft, 152 | child: review.imageUri.length > 0 153 | ? 154 | ClipRRect( 155 | borderRadius: BorderRadius.circular(10), 156 | child: Image.network(review.imageUri[0]), 157 | ) 158 | : SizedBox(height: 0), 159 | ); 160 | } 161 | 162 | Widget _commentSection() => Container( 163 | padding: EdgeInsets.only(top: 18.5), 164 | alignment: Alignment.centerLeft, 165 | child: Text(review.comment), 166 | ); 167 | 168 | Widget _tagSection() => Container( 169 | padding: EdgeInsets.only(top: 25), 170 | alignment: Alignment.centerLeft, 171 | child: review.tags.length > 0 172 | ? 173 | Row( 174 | mainAxisSize: MainAxisSize.min, 175 | children: review.tags.map( 176 | (tag) => TagWidget(tag: tag), 177 | ).toList(), 178 | ) 179 | : SizedBox(height: 0), 180 | ); 181 | } -------------------------------------------------------------------------------- /courageous_people/lib/sign_in/screen/sign_in_select_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'sign_in_screen.dart'; 3 | 4 | class SignInSelectScreen extends StatelessWidget { 5 | const SignInSelectScreen({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | backgroundColor: Colors.white, 11 | // appBar: AppBar( 12 | // backgroundColor: Colors.white, 13 | // elevation: 1, 14 | // centerTitle: true, 15 | // title: Text( 16 | // '회원가입', 17 | // style: TextStyle(color: Colors.black), 18 | // ), 19 | // leading: Icon(Icons.arrow_back, color: Colors.black), 20 | // ), 21 | body: SafeArea( 22 | child: Column( 23 | mainAxisAlignment: MainAxisAlignment.center, 24 | children: [ 25 | _selectBox( 26 | right: 60, 27 | bottom: 20, 28 | padding: EdgeInsets.only( 29 | top: 80, bottom: 40, left: 30, right: 30, 30 | ), 31 | onTap: () { 32 | Navigator.push( 33 | context, 34 | MaterialPageRoute( 35 | builder: (_) => SignInScreen(managerFlag: 2), 36 | ), 37 | ); 38 | }, 39 | child: Column( 40 | crossAxisAlignment: CrossAxisAlignment.start, 41 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 42 | children: [ 43 | Text( 44 | '사장님으로 회원가입', 45 | style: TextStyle( 46 | fontWeight: FontWeight.bold, 47 | fontSize: 30 48 | ), 49 | ), 50 | Text(''), 51 | ], 52 | ), 53 | ), 54 | _selectBox( 55 | right: 60, 56 | bottom: 60, 57 | padding: EdgeInsets.only( 58 | top: 40, bottom: 80, left: 30, right: 30, 59 | ), 60 | onTap: () { 61 | Navigator.push( 62 | context, 63 | MaterialPageRoute( 64 | builder: (_) => SignInScreen(managerFlag: 1), 65 | ), 66 | ); 67 | }, 68 | child: Column( 69 | crossAxisAlignment: CrossAxisAlignment.start, 70 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 71 | children: [ 72 | Text( 73 | '손님으로 회원가입', 74 | style: TextStyle( 75 | fontWeight: FontWeight.bold, 76 | fontSize: 30 77 | ), 78 | ), 79 | Text(''), 80 | ], 81 | ), 82 | ), 83 | ], 84 | ), 85 | ), 86 | ); 87 | } 88 | 89 | Widget _selectBox({ 90 | required EdgeInsetsGeometry padding, 91 | required double right, 92 | required double bottom, 93 | required void Function()? onTap, 94 | Widget? child, 95 | }) { 96 | return Expanded( 97 | child: 98 | // child: Stack( 99 | // children: [ 100 | Container( 101 | width: double.maxFinite, 102 | padding: padding, 103 | child: GestureDetector( 104 | onTap: onTap, 105 | child: Container( 106 | padding: EdgeInsets.all(20), 107 | decoration: BoxDecoration( 108 | color: Colors.lightGreen.shade100, 109 | border: Border.all(width: 0.2, color: Colors.grey.shade500), 110 | borderRadius: BorderRadius.circular(12), 111 | // boxShadow: [ 112 | // BoxShadow( 113 | // color: Colors.grey.shade200, 114 | // offset: const Offset(2.4, 2.4), 115 | // ), 116 | // ], 117 | ), 118 | child: child, 119 | ), 120 | ), 121 | ), 122 | // Positioned( 123 | // right: right, 124 | // bottom: bottom, 125 | // child: Container( 126 | // width: 40, 127 | // height: 40, 128 | // decoration: BoxDecoration( 129 | // color: Colors.green.shade200, 130 | // borderRadius: BorderRadius.circular(20), 131 | // boxShadow: [ 132 | // BoxShadow( 133 | // color: Colors.grey.shade200, 134 | // offset: const Offset(2, 2), 135 | // ), 136 | // ], 137 | // ), 138 | // child: Icon(Icons.arrow_forward, color: Colors.white), 139 | // ), 140 | // ), 141 | // ], 142 | // ), 143 | ); 144 | } 145 | } 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | // Center( 154 | // child: Row( 155 | // mainAxisAlignment: MainAxisAlignment.center, 156 | // children: [ 157 | // Container( 158 | // child: GestureDetector( 159 | // onTap: () => _onTap(context, 2), //ToDo:회원가입 화면 전환 160 | // child: Stack( 161 | // children: [ 162 | // Container( 163 | // decoration: BoxDecoration( 164 | // borderRadius: BorderRadius.circular(15), 165 | // border: Border.all(color:Colors.lightGreenAccent,width: 5) 166 | // ), 167 | // height: 230, 168 | // width: 190, 169 | // ), 170 | // Positioned.fill( 171 | // bottom: 27, 172 | // child: Align(alignment: Alignment.bottomCenter, 173 | // child: Text('가게회원', 174 | // style: TextStyle( 175 | // fontSize: 22, 176 | // fontWeight: FontWeight.bold 177 | // ), 178 | // 179 | // ), 180 | // ) 181 | // ), 182 | // Image.asset('assets/images/store.png',width: 190,), 183 | // ], 184 | // ), 185 | // ), 186 | // ), 187 | // SizedBox(width: 10), 188 | // Container( 189 | // child: GestureDetector( 190 | // onTap: () => _onTap(context, 1),//ToDo:회원가입 화면 전환 191 | // child: Stack( 192 | // children: [ 193 | // Container( 194 | // decoration: BoxDecoration( 195 | // borderRadius: BorderRadius.circular(15), 196 | // border: Border.all(color:Colors.lightGreenAccent,width: 5), 197 | // ), 198 | // height: 230, 199 | // width: 190, 200 | // ), 201 | // Positioned.fill( 202 | // bottom: 27, 203 | // child: Align(alignment: Alignment.bottomCenter, 204 | // child: Text('일반회원', 205 | // style: TextStyle( 206 | // fontSize: 22, 207 | // fontWeight: FontWeight.bold 208 | // ), 209 | // ), 210 | // ) 211 | // ), 212 | // Image.asset('assets/images/container.png', width: 190), 213 | // ], 214 | // ), 215 | // ), 216 | // ), 217 | // ], 218 | // ), 219 | // ), -------------------------------------------------------------------------------- /courageous_people/lib/home/widget/main_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/user_hive.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:courageous_people/widget/menu_button.dart'; 4 | import 'package:flutter_hooks/flutter_hooks.dart'; 5 | 6 | class MainMenu extends StatelessWidget { 7 | const MainMenu({ 8 | Key? key, 9 | required this.succeedLogIn, 10 | required this.isMenuExpanded, 11 | required this.onMainMenuPressed, 12 | required this.onLogInPressed, 13 | required this.onLogOutPressed, 14 | required this.onAddingStorePressed, 15 | required this.onFavoriteListPressed, 16 | required this.onNearStoreListPressed, 17 | }) : super(key: key); 18 | 19 | final bool succeedLogIn; 20 | final bool isMenuExpanded; 21 | final void Function() onMainMenuPressed; 22 | final void Function() onLogInPressed; 23 | final void Function() onLogOutPressed; 24 | final void Function() onAddingStorePressed; 25 | final void Function() onFavoriteListPressed; 26 | final void Function() onNearStoreListPressed; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final managerFlag = UserHive().userManagerFlag; 31 | 32 | return Column( 33 | mainAxisSize: MainAxisSize.min, 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | MenuButton( 37 | size: 40, 38 | borderRadiusValue: 8, 39 | iconData: Icons.menu, 40 | backgroundColor: Colors.black, 41 | onPressed: onMainMenuPressed, 42 | ), 43 | const SizedBox(height: 12), 44 | if(isMenuExpanded) 45 | succeedLogIn 46 | ? Column( 47 | mainAxisSize: MainAxisSize.min, 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | // if(managerFlag == 2) 51 | // Column( 52 | // mainAxisSize: MainAxisSize.min, 53 | // crossAxisAlignment: CrossAxisAlignment.start, 54 | // children: [ 55 | // _myStoreMenuButton(onPressed: () {}), 56 | // const SizedBox(height: 10), 57 | // _registerMyStoreMenuButton(onPressed: () {}), 58 | // const SizedBox(height: 10), 59 | // ], 60 | // ), 61 | _addStoreMenuButton(onPressed: onAddingStorePressed), 62 | const SizedBox(height: 10), 63 | // _favoriteMenuButton(onPressed: onFavoriteListPressed), 64 | // const SizedBox(height: 10), 65 | // _nearStoreMenuButton(onPressed: onNearStoreListPressed), 66 | // const SizedBox(height: 10), 67 | _logOutButton(onPressed: onLogOutPressed), 68 | ], 69 | ) 70 | : _logInButton(onPressed: onLogInPressed), 71 | ], 72 | ); 73 | } 74 | 75 | Widget _logInButton({required void Function()? onPressed}) { 76 | return MenuButton( 77 | margin: EdgeInsets.only(left: 3), 78 | size: 32, 79 | borderRadiusValue: 16, 80 | iconData: Icons.login, 81 | menuTitle: '로그인', 82 | backgroundColor: Colors.grey.shade500, 83 | heroTag: "registerStore", 84 | onPressed: onPressed, 85 | ); 86 | } 87 | 88 | Widget _logOutButton({required void Function()? onPressed}) { 89 | return MenuButton( 90 | margin: EdgeInsets.only(left: 3), 91 | size: 32, 92 | borderRadiusValue: 16, 93 | iconData: Icons.sensor_door, 94 | menuTitle: "로그아웃", 95 | backgroundColor: Colors.grey.shade500, 96 | heroTag: "logout", 97 | onPressed: onPressed, 98 | ); 99 | } 100 | 101 | Widget _addStoreMenuButton({required void Function()? onPressed}) { 102 | return MenuButton( 103 | margin: EdgeInsets.only(left: 3), 104 | size: 32, 105 | borderRadiusValue: 16, 106 | iconData: Icons.add, 107 | menuTitle: '가게 추가', 108 | backgroundColor: Colors.blue.shade300, 109 | heroTag: "registerStore", 110 | onPressed: onPressed, 111 | ); 112 | } 113 | 114 | Widget _myStoreMenuButton({required void Function()? onPressed}) { 115 | return MenuButton( 116 | margin: EdgeInsets.only(left: 3), 117 | size: 32, 118 | borderRadiusValue: 16, 119 | iconData: Icons.store, 120 | menuTitle: "내 가게 보기", 121 | backgroundColor: Colors.teal.shade300, 122 | heroTag: "near", 123 | onPressed: onPressed, 124 | ); 125 | } 126 | 127 | Widget _registerMyStoreMenuButton({required void Function()? onPressed}) { 128 | return MenuButton( 129 | margin: EdgeInsets.only(left: 3), 130 | size: 32, 131 | borderRadiusValue: 16, 132 | iconData: Icons.store, 133 | menuTitle: "내 가게 등록", 134 | backgroundColor: Colors.pink.shade300, 135 | heroTag: "near", 136 | onPressed: onPressed, 137 | ); 138 | } 139 | 140 | Widget _favoriteMenuButton({required void Function()? onPressed}) { 141 | return MenuButton( 142 | margin: EdgeInsets.only(left: 3), 143 | size: 32, 144 | borderRadiusValue: 16, 145 | iconData: Icons.favorite, 146 | menuTitle: "찜한 가게", 147 | backgroundColor: Colors.pink.shade300, 148 | heroTag: "favorite", 149 | onPressed: onPressed, 150 | ); 151 | } 152 | 153 | Widget _nearStoreMenuButton({required void Function()? onPressed}) { 154 | return MenuButton( 155 | margin: EdgeInsets.only(left: 3), 156 | size: 32, 157 | borderRadiusValue: 16, 158 | iconData: Icons.store, 159 | menuTitle: "가까운 가게", 160 | backgroundColor: Colors.teal.shade300, 161 | heroTag: "near", 162 | onPressed: onPressed, 163 | ); 164 | } 165 | } 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | class MyStatefulWidget extends StatefulWidget { 178 | const MyStatefulWidget({Key? key}) : super(key: key); 179 | 180 | @override 181 | State createState() => _MyStatefulWidgetState(); 182 | } 183 | 184 | /// This is the private State class that goes with MyStatefulWidget. 185 | class _MyStatefulWidgetState extends State with SingleTickerProviderStateMixin { 186 | late final AnimationController _controller = AnimationController( 187 | duration: const Duration(milliseconds: 400), 188 | vsync: this, 189 | )..repeat(reverse: true); 190 | 191 | late final Animation _offsetAnimation = Tween( 192 | begin: Offset.zero, 193 | end: const Offset(0.0, 1.0), 194 | ).animate(CurvedAnimation( 195 | parent: _controller, 196 | curve: Curves.ease, 197 | )); 198 | 199 | @override 200 | void dispose() { 201 | super.dispose(); 202 | _controller.dispose(); 203 | } 204 | 205 | @override 206 | Widget build(BuildContext context) { 207 | return SlideTransition( 208 | position: _offsetAnimation, 209 | child: const Padding( 210 | padding: EdgeInsets.all(8.0), 211 | child: FlutterLogo(size: 150.0), 212 | ), 213 | ); 214 | } 215 | } -------------------------------------------------------------------------------- /courageous_people/lib/review/screen/show_review_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/hive/user_hive.dart'; 2 | import 'package:courageous_people/log_in/log_In_screen.dart'; 3 | import 'package:courageous_people/model/menu_data.dart'; 4 | import 'package:courageous_people/model/review_data.dart'; 5 | import 'package:courageous_people/review/cubit/review_cubit.dart'; 6 | import 'package:courageous_people/review/cubit/review_state.dart'; 7 | import 'package:courageous_people/review/screen/add_review_screen.dart'; 8 | import 'package:courageous_people/review/screen/rewrite_review_screen.dart'; 9 | import 'package:courageous_people/utils/show_alert_dialog.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_bloc/flutter_bloc.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | import '../widget/review_tile.dart'; 15 | 16 | class ShowReviewScreen extends StatelessWidget { 17 | const ShowReviewScreen({ 18 | Key? key, 19 | required this.storeId, 20 | required this.menuList, 21 | }) : super(key: key); 22 | 23 | final int storeId; 24 | final List menuList; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final reviewCubit = context.read(); 29 | final userId = UserHive().userId; 30 | 31 | reviewCubit.getReviews(storeId); 32 | 33 | return Scaffold( 34 | appBar: AppBar(title: Text('리뷰')), 35 | body: BlocConsumer( 36 | bloc: reviewCubit, 37 | listener: (context, state) async { 38 | if(state is ReviewDeletedState) { 39 | await showAlertDialog( 40 | context: context, 41 | title: state.message, 42 | ); 43 | 44 | reviewCubit.getReviews(storeId); 45 | } 46 | 47 | if(state is ReviewDeleteErrorState) { 48 | await showAlertDialog(context: context, title: state.message); 49 | } 50 | 51 | if(state is ReviewRewrittenState) reviewCubit.getReviews(storeId); 52 | 53 | if(state is ReviewRewriteErrorState) reviewCubit.getReviews(storeId); 54 | }, 55 | builder: (context, state) { 56 | print('review reloading state: $state'); 57 | 58 | return _reviewListSection( 59 | state: state, 60 | onCorrectionPressed: (review) async { 61 | Navigator.pop(context); 62 | 63 | Navigator.push( 64 | context, 65 | MaterialPageRoute( 66 | builder: (context) => RewriteReviewScreen( 67 | reviewId: review.reviewId, 68 | storeId: storeId, 69 | userId: userId, 70 | menuList: menuList, 71 | initialMenuText: review.tags[0].content, 72 | initialVolume: review.tags[1].content, 73 | initialComment: review.comment, 74 | initialImageByte: null, 75 | ), 76 | ), 77 | ); 78 | }, 79 | onDeletionPressed: (reviewId) async { 80 | await reviewCubit.deleteReview(reviewId); 81 | }, 82 | ); 83 | }, 84 | buildWhen: (state, previousState) { 85 | if(state is DeleteReviewLoadingState) return false; 86 | if(state is ReviewDeletedState) return false; 87 | if(state is ReviewDeleteErrorState) return false; 88 | 89 | return true; 90 | }, 91 | ), 92 | floatingActionButton: FloatingActionButton( 93 | child: Icon(Icons.edit), 94 | onPressed: () async { 95 | if(UserHive().userId == -1) { 96 | await showAlertDialog( 97 | context: context, 98 | title: '로그인이 필요합니다', 99 | onSubmit: () async { 100 | Navigator.pop(context); 101 | 102 | Navigator.push( 103 | context, 104 | MaterialPageRoute(builder: (context) => LogInScreen()), 105 | ); 106 | }, 107 | ); 108 | 109 | return; 110 | } 111 | 112 | await Navigator.push( 113 | context, 114 | MaterialPageRoute( 115 | builder: (context) => AddReviewScreen( 116 | storeId: storeId, 117 | userId: userId, 118 | menuList: menuList, 119 | ), 120 | ), 121 | ); 122 | 123 | reviewCubit.getReviews(storeId); 124 | }, 125 | ), 126 | ); 127 | } 128 | 129 | Widget _reviewListSection({ 130 | required ReviewState state, 131 | required void Function(ReviewData) onCorrectionPressed, 132 | required void Function(int) onDeletionPressed, 133 | }) { 134 | if (state is ReviewLoadingState) { 135 | return Center(child: CircularProgressIndicator()); 136 | } 137 | 138 | if (state is ReviewLoadedState) { 139 | final reviews = state.reviewList; 140 | 141 | if (reviews.length == 0) { 142 | return Center( 143 | child: Column( 144 | mainAxisSize: MainAxisSize.min, 145 | crossAxisAlignment: CrossAxisAlignment.center, 146 | children: [ 147 | Text('등록된 리뷰가 없습니다!'), 148 | ], 149 | ), 150 | ); 151 | } 152 | 153 | final List reviewTileList = reviews.map( 154 | (review) => ReviewTile( 155 | review: review, 156 | onCorrectionPressed: () => onCorrectionPressed(review), 157 | onDeletionPressed: () => onDeletionPressed(review.reviewId), 158 | ), 159 | ).toList(); 160 | 161 | return SingleChildScrollView( 162 | child: Padding( 163 | padding: EdgeInsets.symmetric(horizontal: 25, vertical: 30), 164 | child: Column( 165 | crossAxisAlignment: CrossAxisAlignment.start, 166 | mainAxisSize: MainAxisSize.min, 167 | children: [ 168 | Container( 169 | child: Text.rich( 170 | TextSpan( 171 | children: [ 172 | TextSpan( 173 | text: '리뷰 ', 174 | style: TextStyle( 175 | fontSize: 21.5, 176 | fontWeight: FontWeight.bold, 177 | ), 178 | ), 179 | TextSpan( 180 | text: '${reviews.length}개', 181 | style: TextStyle( 182 | fontSize: 13, 183 | color: Colors.grey.shade500, 184 | ), 185 | ), 186 | ], 187 | ), 188 | ), 189 | ), 190 | SizedBox(height: 15), 191 | Column(children: reviewTileList), 192 | ], 193 | ), 194 | ), 195 | ); 196 | } 197 | 198 | return Container(); 199 | } 200 | } 201 | 202 | 203 | -------------------------------------------------------------------------------- /courageous_people/lib/log_in/log_In_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/constants.dart'; 2 | import 'package:courageous_people/service/token_service.dart'; 3 | import 'package:courageous_people/utils/show_alert_dialog.dart'; 4 | import 'package:courageous_people/widget/transparent_app_bar.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:courageous_people/sign_in/screen/sign_in_select_screen.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:bloc/bloc.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | 11 | import '../home/screen/home.dart'; 12 | import 'cubit/log_in_cubit.dart'; 13 | import 'cubit/log_in_state.dart'; 14 | 15 | class LogInScreen extends StatefulWidget { 16 | const LogInScreen({Key? key}) : super(key: key); 17 | 18 | @override 19 | _LoginState createState() => _LoginState(); 20 | } 21 | 22 | class _LoginState extends State { 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold(body: _body()); 26 | } 27 | 28 | Widget _body() { 29 | return SingleChildScrollView( 30 | child: Container( 31 | padding: EdgeInsets.symmetric(horizontal: 55, vertical: 45), 32 | child: Column( 33 | crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | _logoSection(), 36 | _LogInForm(), 37 | SizedBox(height: 50), 38 | _socialLogInSection(), 39 | ], 40 | ), 41 | ), 42 | ); 43 | } 44 | 45 | Widget _logoSection() => Padding( 46 | padding: EdgeInsets.only(left: 30, right: 30, top: 30), 47 | child: Image.asset('assets/images/logo_color.png'), 48 | ); 49 | 50 | Widget _socialLogInSection() { 51 | return Center( 52 | child: Column( 53 | mainAxisSize: MainAxisSize.max, 54 | mainAxisAlignment: MainAxisAlignment.center, 55 | children: [ 56 | Row( 57 | children: [ 58 | Expanded( 59 | child: Divider( 60 | thickness: 0.5, 61 | endIndent: 10, 62 | color: Colors.grey.shade700, 63 | ), 64 | ), 65 | Text("소셜 로그인"), 66 | Expanded( 67 | child: Divider( 68 | thickness: 0.5, 69 | indent: 10, 70 | color: Colors.grey.shade700, 71 | ), 72 | ), 73 | ], 74 | ), 75 | SizedBox(height: 10), 76 | Row( 77 | mainAxisAlignment: MainAxisAlignment.center, 78 | mainAxisSize: MainAxisSize.min, 79 | children: [ 80 | GestureDetector( 81 | onTap: () {}, 82 | child: Container( 83 | width: 40, 84 | height: 40, 85 | child: Image.asset('assets/images/kakao_circular.png'), 86 | ), 87 | ), 88 | SizedBox(width: 20), 89 | GestureDetector( 90 | onTap: () {}, 91 | child: Container( 92 | width: 40, 93 | height: 40, 94 | child: Image.asset('assets/images/naver_circular.png'), 95 | ), 96 | ), 97 | SizedBox(width: 20), 98 | GestureDetector( 99 | onTap: () {}, 100 | child: Container( 101 | width: 40, 102 | height: 40, 103 | child: Image.asset( 104 | 'assets/images/google_circular.png', 105 | ), 106 | ), 107 | ), 108 | ], 109 | ), 110 | ], 111 | ), 112 | ); 113 | } 114 | } 115 | 116 | class _LogInForm extends StatelessWidget { 117 | const _LogInForm({Key? key}) : super(key: key); 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | final _logInCubit = context.read(); 122 | final emailController = TextEditingController(); 123 | final passwordController = TextEditingController(); 124 | 125 | return Column( 126 | mainAxisSize: MainAxisSize.min, 127 | crossAxisAlignment: CrossAxisAlignment.end, 128 | children: [ 129 | TextField( 130 | decoration: InputDecoration(hintText: 'Email'), 131 | controller: emailController, 132 | ), 133 | TextField( 134 | obscureText: true, 135 | decoration: InputDecoration(hintText: 'Password'), 136 | controller: passwordController, 137 | ), 138 | SizedBox(height: 15), 139 | BlocListener( 140 | bloc: _logInCubit, 141 | listener: (context, state) async { 142 | if (state is LogInSuccessState) _logInSuccessCallBack(context); 143 | if (state is LogInFailedState) { 144 | showAlertDialog( 145 | context: context, 146 | title: state.message, 147 | ); 148 | } 149 | }, 150 | child: GestureDetector( 151 | child: Container( 152 | padding: EdgeInsets.all(15), 153 | alignment: Alignment.center, 154 | decoration: BoxDecoration( 155 | color: THEME_COLOR, 156 | borderRadius: BorderRadius.circular(100), 157 | ), 158 | child: Text( 159 | '로그인', 160 | style: TextStyle( 161 | color: Colors.white, 162 | ), 163 | ), 164 | ), 165 | onTap: () async { 166 | if(emailController.text == '') { 167 | await showAlertDialog( 168 | context: context, 169 | title: '이메일을 입력해주세요', 170 | ); 171 | 172 | return; 173 | } 174 | 175 | if(passwordController.text == '') { 176 | await showAlertDialog( 177 | context: context, 178 | title: '비밀번호를 입력해주세요', 179 | ); 180 | 181 | return; 182 | } 183 | 184 | await _logInCubit.logIn( 185 | context, 186 | emailController.text, 187 | passwordController.text, 188 | ); 189 | }, 190 | ), 191 | ), 192 | SizedBox(height: 10), 193 | Row( 194 | mainAxisSize: MainAxisSize.min, 195 | mainAxisAlignment: MainAxisAlignment.center, 196 | children: [ 197 | Text( 198 | '처음이신가요?', 199 | style: TextStyle(fontSize: 13), 200 | ), 201 | SizedBox(width: 4), 202 | InkWell( 203 | onTap: () { 204 | Navigator.push( 205 | context, 206 | MaterialPageRoute( 207 | builder: (context) => SignInSelectScreen(), 208 | ), 209 | ); 210 | }, 211 | child: Text( 212 | '회원가입', 213 | style: TextStyle( 214 | fontSize: 13, 215 | color: Colors.blue, 216 | ), 217 | ), 218 | ), 219 | ], 220 | ), 221 | ], 222 | ); 223 | } 224 | 225 | void _logInSuccessCallBack(BuildContext context) { 226 | Navigator.pushAndRemoveUntil( 227 | context, 228 | MaterialPageRoute(builder: (_) => Home()), 229 | (route) => false, 230 | ); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /courageous_people/lib/store/repository/store_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:courageous_people/common/constants.dart'; 5 | import 'package:courageous_people/model/store_data.dart'; 6 | import 'package:courageous_people/service/token_service.dart'; 7 | import 'package:courageous_people/utils/interpreters.dart'; 8 | import 'package:dio/dio.dart'; 9 | import 'package:http_parser/http_parser.dart'; 10 | import 'package:naver_map_plugin/naver_map_plugin.dart'; 11 | import '../../utils/http_client.dart'; 12 | 13 | class StoreRepository { 14 | Future> getStores() async { 15 | final response = await httpRequestWithoutToken( 16 | requestType: 'GET', 17 | path: '/board/store', 18 | ); 19 | 20 | return toStoreList(response.body); 21 | } 22 | 23 | Future addStore( 24 | String storeName, String address, String post, 25 | Uint8List? imageToByte, double latitude, double longitude, 26 | int registrant, int managerFlag, List> menuList, 27 | ) async { 28 | final addStoreResponse = await httpRequestWithToken( 29 | requestType: 'POST', 30 | path: '/board/store', 31 | body: { 32 | "store_name": storeName, 33 | "address": address, 34 | "post": post, 35 | "latitude": latitude, 36 | "longitude": longitude, 37 | "user": registrant, 38 | }, 39 | ); 40 | 41 | print('store add code: ${addStoreResponse.statusCode}'); 42 | print('store add body: ${addStoreResponse.body}'); 43 | 44 | if(addStoreResponse.statusCode != 201) return addStoreResponse.statusCode; 45 | 46 | final storeId = jsonDecode(addStoreResponse.body)['id']; 47 | 48 | if(imageToByte != null) { 49 | final multiPartData = MultipartFile.fromBytes( 50 | imageToByte, 51 | filename: 'store_img_$storeId.jpg', 52 | contentType: MediaType('image', 'png'), 53 | ); 54 | 55 | final formData = FormData.fromMap({ 56 | "store_img": multiPartData, 57 | "store": storeId, 58 | }); 59 | 60 | final sendingPictureResponse = await Dio().post( 61 | '$REQUEST_URL/board/store-img', 62 | data: formData, 63 | options: Options( 64 | headers: { 65 | "Accept": "application/json", 66 | "Content-Type": "application/octet-stream", 67 | "Authorization": "Bearer ${TokenService().accessToken!}", 68 | }, 69 | ), 70 | ); 71 | 72 | print('code: ${sendingPictureResponse.statusCode}'); 73 | print('body: ${sendingPictureResponse.data}'); 74 | 75 | if (sendingPictureResponse.statusCode != 200) { 76 | return sendingPictureResponse.statusCode!; 77 | } 78 | } 79 | 80 | for(Map menuData in menuList) { 81 | print('menu start'); 82 | final menuResponse = await addMenu(storeId, menuData); 83 | print('menu response code: $menuResponse'); 84 | print('menu finished'); 85 | } 86 | 87 | // todo: (수정사항 아님) 메뉴 post 오류 무시 88 | 89 | return 201; 90 | } 91 | 92 | Future> crawlStore( 93 | String location, 94 | String storeName, 95 | ) async { 96 | String queryString = 97 | "?query=$location$storeName&display=10&start=1&sort=random"; 98 | 99 | final response = await naverRequestResponse( 100 | requestApi: 'place', 101 | url: PLACE_SEARCH_REQUEST_URL, 102 | queryString: queryString, 103 | ); 104 | 105 | final crawledStoreData = jsonDecode(response.body)['items']; 106 | 107 | print(crawledStoreData); 108 | final List duplicatedStoreList = []; 109 | 110 | for(dynamic data in crawledStoreData) { 111 | final index = crawledStoreData.indexOf(data); 112 | final storePosition = await _addressToLatLng(data['address']); 113 | 114 | crawledStoreData[index]['title'] = _pureTitle(data['title']); 115 | crawledStoreData[index]['latitude'] = storePosition.latitude; 116 | crawledStoreData[index]['longitude'] = storePosition.longitude; 117 | 118 | final duplicated = await _duplicatedStore( 119 | data['title'], 120 | data['address'], 121 | ); 122 | 123 | duplicatedStoreList.add(duplicated); 124 | 125 | // duplicatedStoreList.add(toStore(duplicated)); 126 | 127 | // crawledStoreData[index]['duplicated'] = duplicated == null 128 | // ? false 129 | // : true; 130 | } 131 | 132 | return { 133 | "crawled": crawledStoreData, 134 | "duplicated": duplicatedStoreList, 135 | }; 136 | } 137 | 138 | Future addMenu( 139 | int storeId, 140 | Map menuMap, 141 | ) async { 142 | print('add menu'); 143 | 144 | final String menuName = menuMap['name']; 145 | final String menuPrice = menuMap['price']; 146 | final Uint8List? menuImageByte = menuMap['imageByte']; 147 | 148 | final menuResponse = await httpRequestWithToken( 149 | requestType: 'POST', 150 | path: '/board/menu', 151 | body: { 152 | 'menu': menuName, 153 | 'price': menuPrice, 154 | 'store': storeId, 155 | }, 156 | ); 157 | 158 | print('menu response code: ${menuResponse.body}'); 159 | print('menu response code: ${menuResponse.statusCode}'); 160 | 161 | if (menuImageByte == null || menuResponse.statusCode != 201) { 162 | return menuResponse.statusCode; 163 | } 164 | 165 | 166 | final menuId = jsonDecode(menuResponse.body)['id']; 167 | 168 | final multiPartData = MultipartFile.fromBytes( 169 | menuImageByte, 170 | filename: 'menu_img_${storeId}_$menuId.jpg', 171 | // use the real name if available, or omit 172 | contentType: MediaType('image', 'png'), 173 | ); 174 | 175 | final formData = FormData.fromMap({ 176 | "menu_img": multiPartData, 177 | "menu": menuId, 178 | }); 179 | 180 | final sendingPictureResponse = await Dio().post( 181 | '$REQUEST_URL/board/menu-img', 182 | data: formData, 183 | options: Options( 184 | headers: { 185 | "Accept": "application/json", 186 | "Content-Type": "application/octet-stream", 187 | "Authorization": "Bearer ${TokenService().accessToken!}", 188 | }, 189 | ), 190 | ); 191 | 192 | print('menu code: ${sendingPictureResponse.statusCode}'); 193 | print('menu body: ${sendingPictureResponse.data}'); 194 | 195 | return sendingPictureResponse.statusCode!; 196 | } 197 | 198 | Future _duplicatedStore( 199 | String storeName, String address, 200 | ) async { 201 | final response = await httpRequestWithToken( 202 | requestType: 'POST', 203 | path: '/board/store-verify', 204 | body: { 205 | 'store_name': storeName, 206 | 'address': address, 207 | }, 208 | ); 209 | 210 | if(response.statusCode == 404) return null; 211 | 212 | return toStore(response.body); 213 | } 214 | 215 | String _pureTitle(String title) { 216 | String refined = ''; 217 | 218 | final bTagSplit = title.split(''); 219 | if (bTagSplit.length == 1) return bTagSplit[0]; 220 | 221 | 222 | refined = bTagSplit[0]; 223 | 224 | for(int index=1; index < bTagSplit.length; index++) { 225 | final slashBTagSplit = bTagSplit[index].split(''); 226 | 227 | refined += (slashBTagSplit[0] + slashBTagSplit[1]); 228 | } 229 | 230 | List splitByAmp = refined.split('amp;'); 231 | refined = ''; 232 | 233 | for(String split in splitByAmp) { 234 | refined += split; 235 | } 236 | 237 | return refined; 238 | } 239 | 240 | Future _addressToLatLng(String address) async { 241 | final response = await naverRequestResponse( 242 | requestApi: 'geocode', 243 | url: GEOCODE_REQUEST_URL, 244 | queryString: 245 | "?query=$address", 246 | ); 247 | 248 | final latitude = jsonDecode(response.body)['addresses'][0]['y']; 249 | final longitude = jsonDecode(response.body)['addresses'][0]['x']; 250 | 251 | return LatLng( 252 | double.parse(latitude), 253 | double.parse(longitude), 254 | ); 255 | } 256 | } 257 | 258 | -------------------------------------------------------------------------------- /courageous_people/lib/widget/image_picker_section.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:courageous_people/common/constants.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_hooks/flutter_hooks.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | 8 | class ImagePickerSection extends HookWidget { 9 | ImagePickerSection({ 10 | Key? key, 11 | this.size, 12 | required this.maxImageNumber, 13 | required this.onImageChanged, 14 | }) : super(key: key); 15 | 16 | final _picker = ImagePicker(); 17 | 18 | final int maxImageNumber; 19 | final double? size; 20 | final void Function(List) onImageChanged; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | final imageSourceNotifier = useState(null); 25 | final imageByteListNotifier = useState>([]); 26 | 27 | return Row( 28 | children: [ 29 | GestureDetector( 30 | onTap: () async { 31 | imageSourceNotifier.value = null; 32 | 33 | await showDialog( 34 | context: context, 35 | builder: (context) => SelectImagePickerDialog( 36 | onSelectImageSource: (imageSource) { 37 | imageSourceNotifier.value = imageSource; 38 | }, 39 | ), 40 | ); 41 | 42 | if(imageSourceNotifier.value == null) return; 43 | 44 | imageByteListNotifier.value = await _onImagePicked( 45 | imageSourceNotifier.value!, 46 | ); 47 | 48 | onImageChanged(imageByteListNotifier.value); 49 | }, 50 | child: Container( 51 | padding: EdgeInsets.all(size == null ? 15 : size!/10), 52 | width: size, 53 | height: size, 54 | decoration: BoxDecoration( 55 | borderRadius: BorderRadius.all(Radius.circular(5)), 56 | border: Border.all(width: 1), 57 | color: Colors.white, 58 | ), 59 | child: Stack( 60 | children: [ 61 | Center( 62 | child: Icon( 63 | Icons.camera_alt_outlined, 64 | size: size == null ? 80 : size!/2, 65 | ), 66 | ), 67 | Container( 68 | alignment: Alignment.topCenter, 69 | child: Text( 70 | "사진 등록", 71 | style: TextStyle( 72 | fontWeight: FontWeight.bold, 73 | fontSize: size == null ? 10 : size!/8, 74 | ), 75 | ), 76 | ), 77 | Container( 78 | alignment: Alignment.bottomCenter, 79 | child: Text( 80 | "${imageByteListNotifier.value.length}/$maxImageNumber", 81 | style: TextStyle( 82 | fontWeight: FontWeight.bold, 83 | fontSize: size == null ? 10 : size!/8, 84 | ), 85 | ), 86 | ), 87 | ], 88 | ), 89 | ), 90 | ), 91 | SizedBox(width: 30), 92 | Row( 93 | mainAxisSize: MainAxisSize.min, 94 | children: _imageWidgetList( 95 | imageByteList: imageByteListNotifier.value, 96 | removeImage: (index) { 97 | if(index < 0) return; 98 | 99 | var copiedList = imageByteListNotifier.value; 100 | copiedList.removeAt(index); 101 | imageByteListNotifier.value = [...copiedList]; 102 | }, 103 | ), 104 | ), 105 | ], 106 | ); 107 | } 108 | 109 | Future> _onImagePicked(ImageSource imageSource) async { 110 | List imageByteList = []; 111 | 112 | if(imageSource == ImageSource.camera) { 113 | final image = await _picker.pickImage(source: imageSource); 114 | if(image == null) return []; 115 | 116 | imageByteList.add(await image.readAsBytes()); 117 | 118 | return imageByteList; 119 | } 120 | 121 | final files = await _picker.pickMultiImage(); 122 | if(files == null) return []; 123 | 124 | for(XFile image in files) { 125 | final index = files.indexOf(image); 126 | if(index >= maxImageNumber) break; 127 | 128 | imageByteList.add(await image.readAsBytes()); 129 | } 130 | 131 | return imageByteList; 132 | } 133 | 134 | List _imageWidgetList({ 135 | required List imageByteList, 136 | required void Function(int) removeImage, 137 | }) { 138 | if(imageByteList.isEmpty) return []; 139 | 140 | return imageByteList.map( 141 | (imageByte) => _ImageWidget( 142 | index: imageByteList.indexOf(imageByte), 143 | size: size ?? 80, 144 | imageByte: imageByte, 145 | removeImage: (index) => removeImage(index), 146 | ), 147 | ).toList(); 148 | } 149 | } 150 | 151 | class _ImageWidget extends StatelessWidget { 152 | const _ImageWidget({ 153 | Key? key, 154 | required this.index, 155 | required this.size, 156 | required this.imageByte, 157 | required this.removeImage, 158 | }) : super(key: key); 159 | 160 | final int index; 161 | final double size; 162 | final Uint8List imageByte; 163 | final void Function(int) removeImage; 164 | 165 | @override 166 | Widget build(BuildContext context) { 167 | return Container( 168 | width: size, 169 | height: size, 170 | margin: EdgeInsets.only(right: 8), 171 | child: Stack( 172 | alignment: Alignment.center, 173 | children: [ 174 | ClipRRect(child: Image.memory(imageByte)), 175 | Positioned( 176 | right: 0, 177 | top: 0, 178 | child: GestureDetector( 179 | onTap: () => removeImage(index), 180 | child: Container( 181 | width: 20, 182 | height: 20, 183 | alignment: Alignment.center, 184 | decoration: BoxDecoration( 185 | borderRadius: BorderRadius.circular(10), 186 | color: Colors.grey.shade900, 187 | ), 188 | child: Center( 189 | child: Icon( 190 | Icons.close, 191 | color: Colors.white, 192 | size: 18, 193 | ), 194 | ), 195 | ), 196 | ), 197 | ), 198 | ], 199 | ), 200 | ); 201 | } 202 | } 203 | 204 | class SelectImagePickerDialog extends StatelessWidget { 205 | SelectImagePickerDialog({ 206 | Key? key, 207 | required this.onSelectImageSource, 208 | }) : super(key: key); 209 | 210 | final void Function(ImageSource) onSelectImageSource; 211 | 212 | @override 213 | Widget build(BuildContext context) { 214 | return Dialog( 215 | child: Container( 216 | decoration: BoxDecoration(borderRadius: BorderRadius.circular(5)), 217 | child: Column( 218 | mainAxisSize: MainAxisSize.min, 219 | children: [ 220 | Container( 221 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), 222 | decoration: BoxDecoration( 223 | color: THEME_COLOR, 224 | borderRadius: BorderRadius.vertical(top: Radius.circular(5)), 225 | ), 226 | alignment: Alignment.centerLeft, 227 | child: Text( 228 | '사진 선택', 229 | style: TextStyle( 230 | color: Colors.white, 231 | fontWeight: FontWeight.bold, 232 | ), 233 | ), 234 | ), 235 | _contentItem( 236 | imageSource: ImageSource.camera, 237 | title: '카메라로 촬영', 238 | icon: Icons.camera_alt_outlined, 239 | onTap: (imageSource) { 240 | onSelectImageSource(imageSource); 241 | Navigator.pop(context); 242 | }, 243 | ), 244 | Divider(thickness: 1.5), 245 | _contentItem( 246 | imageSource: ImageSource.gallery, 247 | title: '갤러리에서 선택', 248 | icon: Icons.image_outlined, 249 | onTap: (imageSource) { 250 | onSelectImageSource(imageSource); 251 | Navigator.pop(context); 252 | }, 253 | ), 254 | ], 255 | ), 256 | ), 257 | ); 258 | } 259 | 260 | Widget _contentItem({ 261 | required ImageSource imageSource, 262 | required String title, 263 | required IconData icon, 264 | required void Function(ImageSource) onTap, 265 | }) { 266 | return GestureDetector( 267 | onTap: () => onTap(imageSource), 268 | child: Padding( 269 | padding: EdgeInsets.all(20), 270 | child: Row( 271 | mainAxisAlignment: MainAxisAlignment.start, 272 | crossAxisAlignment: CrossAxisAlignment.center, 273 | children: [ 274 | Icon(icon, color: Colors.grey.shade500), 275 | SizedBox(width: 20), 276 | Text( 277 | title, 278 | style: TextStyle( 279 | fontSize: 16, 280 | color: Colors.grey.shade700, 281 | ), 282 | ), 283 | ], 284 | ), 285 | ), 286 | ); 287 | } 288 | } 289 | 290 | -------------------------------------------------------------------------------- /courageous_people/lib/store/screen/store_main_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/model/menu_data.dart'; 2 | import 'package:courageous_people/model/store_data.dart'; 3 | import 'package:courageous_people/review/screen/show_review_screen.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_hooks/flutter_hooks.dart'; 6 | import 'package:naver_map_plugin/naver_map_plugin.dart'; 7 | 8 | class StoreMainScreen extends HookWidget { 9 | final StoreData store; 10 | bool isFavorite = false; // todo: 즐겨찾기이면 반영 (hive) 11 | 12 | StoreMainScreen({Key? key, required this.store}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | backgroundColor: Colors.white, 18 | body: CustomScrollView( 19 | slivers: [ 20 | SliverAppBar( 21 | pinned: true, 22 | floating: true, 23 | expandedHeight: 230.0, 24 | elevation: 1, 25 | backgroundColor: Colors.grey.shade100, 26 | flexibleSpace: FlexibleSpaceBar( 27 | background: _flexibleSpaceSection(), 28 | ), 29 | titleTextStyle: TextStyle(color: Colors.black), 30 | centerTitle: true, 31 | leading: GestureDetector( 32 | onTap: () => Navigator.pop(context), 33 | child: Icon( 34 | Icons.arrow_back, 35 | color: Colors.grey, 36 | ), 37 | ), 38 | actions: [ 39 | _favoriteIcon( 40 | onPressed: () { 41 | // todo: 즐찾 추가 해제 로직 작성 42 | // todo: 츨찾 추가 시에 db 서버에 수정 put 요청 43 | }, 44 | ), 45 | ], 46 | ), 47 | SliverFillRemaining(child: _body(context)), 48 | ], 49 | ), 50 | ); 51 | } 52 | 53 | Widget _body(BuildContext context) { 54 | return Container( 55 | padding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), 56 | alignment: Alignment.center, 57 | child: Column( 58 | crossAxisAlignment: CrossAxisAlignment.start, 59 | children: [ 60 | _storeIntroduceSection( 61 | sectionMaxWidth: MediaQuery.of(context).size.width * 3 / 4, 62 | ), 63 | InkWell( 64 | onTap: () => Navigator.push( 65 | context, 66 | MaterialPageRoute( 67 | builder: (context) => ShowReviewScreen( 68 | storeId: store.id, 69 | menuList: store.menuList, 70 | ), 71 | ), 72 | ), 73 | child: Container( 74 | alignment: Alignment.centerRight, 75 | child: Text( 76 | '리뷰 보기 〉', 77 | style: TextStyle( 78 | color: Colors.black, 79 | fontSize: 15, 80 | fontWeight: FontWeight.bold, 81 | ), 82 | ), 83 | ), 84 | ), 85 | _simpleCommentSection(), 86 | _menuListSection(), 87 | ], 88 | ), 89 | ); 90 | } 91 | 92 | Widget _flexibleSpaceSection() { 93 | return NaverMap( 94 | initLocationTrackingMode: LocationTrackingMode.None, 95 | scrollGestureEnable: false, 96 | initialCameraPosition: CameraPosition( 97 | target: LatLng(store.latitude, store.longitude), 98 | ), 99 | markers: [ 100 | Marker( 101 | markerId: "current", 102 | position: LatLng(store.latitude, store.longitude), 103 | ), 104 | ], 105 | ); 106 | } 107 | 108 | Widget _storeIntroduceSection({required double sectionMaxWidth}) => Stack( 109 | children: [ 110 | _storeSubInformationBox(), 111 | _storeNameSection( 112 | maxWidth: sectionMaxWidth 113 | ), 114 | ], 115 | ); 116 | 117 | Widget _storeSubInformationBox() { 118 | return Container( 119 | decoration: BoxDecoration( 120 | borderRadius: BorderRadius.all(Radius.circular(12)), 121 | border: Border.all(color: Colors.grey.shade700, width: 2), 122 | ), 123 | margin: EdgeInsets.only(top: 18, bottom: 2), 124 | child: Container( 125 | padding: EdgeInsets.only(top: 27, bottom: 20, left: 20, right: 20), 126 | child: Row( 127 | children: [ 128 | Column( 129 | crossAxisAlignment: CrossAxisAlignment.start, 130 | children: [ 131 | Text( 132 | '주소', 133 | textAlign: TextAlign.start, 134 | style: TextStyle( 135 | fontWeight: FontWeight.bold, 136 | fontSize: 14, 137 | ), 138 | ), 139 | SizedBox(height: 15), 140 | Text( 141 | '대표 메뉴', 142 | textAlign: TextAlign.start, 143 | style: TextStyle( 144 | fontWeight: FontWeight.bold, 145 | fontSize: 14, 146 | ), 147 | ), 148 | ], 149 | ), 150 | SizedBox(width: 15), 151 | Column( 152 | crossAxisAlignment: CrossAxisAlignment.start, 153 | children: [ 154 | Text( 155 | store.address, 156 | overflow: TextOverflow.ellipsis, 157 | textAlign: TextAlign.start, 158 | style: TextStyle( 159 | fontSize: 14, 160 | color: Colors.grey.shade700, 161 | ), 162 | ), 163 | SizedBox(height: 15), 164 | Text('대표 메뉴가 없습니다'), 165 | ], 166 | ), 167 | ], 168 | ), 169 | ), 170 | ); 171 | } 172 | 173 | Widget _storeNameSection({required double maxWidth}) { 174 | return Container( 175 | color: Colors.white, 176 | margin: EdgeInsets.only(left: 20), 177 | child: Text( 178 | ' ${store.name} ', 179 | overflow: TextOverflow.ellipsis, 180 | style: TextStyle( 181 | fontSize: 25, 182 | fontWeight: FontWeight.bold, 183 | ), 184 | ), 185 | ); 186 | } 187 | 188 | Widget _simpleCommentSection() { 189 | final introduction = store.introduction ?? ''; 190 | return Column( 191 | mainAxisSize: MainAxisSize.min, 192 | children: [ 193 | Row( 194 | children: [ 195 | Icon( 196 | Icons.lightbulb, 197 | color: Colors.yellow.shade900, 198 | size: 13, 199 | ), 200 | Text( 201 | ' 사장님의 한 줄 소개!', 202 | style: TextStyle(fontSize: 12), 203 | ), 204 | ], 205 | ), 206 | SizedBox(height: 3), 207 | Container( 208 | padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10), 209 | width: Size.infinite.width, 210 | decoration: BoxDecoration( 211 | borderRadius: BorderRadius.all(Radius.circular(4)), 212 | color: Colors.yellow.shade300, 213 | ), 214 | child: Text( 215 | introduction == '' ? '한 줄 소개가 없습니다' : introduction, 216 | style: TextStyle( 217 | color: Colors.grey.shade500, 218 | ), 219 | ), 220 | ), 221 | ], 222 | ); 223 | } 224 | 225 | Widget _favoriteIcon({required void Function()? onPressed}) => IconButton( 226 | onPressed: onPressed, 227 | icon: Icon( 228 | Icons.favorite, 229 | color: isFavorite ? Colors.red : Colors.grey, 230 | ), 231 | ); 232 | 233 | Widget _menuListSection() { 234 | if (store.menuList.length == 0) { 235 | return Expanded( 236 | child: Center( 237 | child: Text('메뉴가 아직 등록되지 않았습니다'), 238 | ), 239 | ); 240 | } 241 | 242 | final List menuTileList = store.menuList.map( 243 | (menu) => _MenuTile(menu: menu), 244 | ).toList(); 245 | 246 | return Container( 247 | padding: EdgeInsets.symmetric(vertical: 20), 248 | child: Column( 249 | crossAxisAlignment: CrossAxisAlignment.start, 250 | children: [ 251 | Text( 252 | ' 메뉴 ', 253 | style: TextStyle( 254 | fontSize: 25, 255 | fontWeight: FontWeight.bold, 256 | ), 257 | ), 258 | SizedBox(height: 12), 259 | Container( 260 | // padding: EdgeInsets.symmetric(horizontal: 15), 261 | // margin: EdgeInsets.symmetric(horizontal: 5, vertical: 18), 262 | child: Column( 263 | mainAxisSize: MainAxisSize.min, 264 | children: menuTileList, 265 | ), 266 | ), 267 | ], 268 | ), 269 | ); 270 | } 271 | } 272 | 273 | class _MenuTile extends StatelessWidget { 274 | final MenuData menu; 275 | 276 | const _MenuTile({Key? key, required this.menu}) : super(key: key); 277 | 278 | @override 279 | Widget build(BuildContext context) { 280 | return Container( 281 | margin: EdgeInsets.only(top: 10), 282 | padding: EdgeInsets.only(left: 7), 283 | height: 90, 284 | child: Row( 285 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 286 | children: [ 287 | Column( 288 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 289 | crossAxisAlignment: CrossAxisAlignment.start, 290 | children: [ 291 | Text( 292 | menu.title, 293 | style: TextStyle( 294 | fontSize: 22, 295 | fontWeight: FontWeight.bold, 296 | ), 297 | ), 298 | Text( 299 | '${menu.price.toString()}원', 300 | style: TextStyle(fontSize: 17), 301 | ), 302 | ], 303 | ), 304 | ClipRRect( 305 | borderRadius: BorderRadius.circular(10), 306 | child: Image.network( 307 | menu.imageUrl ?? 'assets/images/picture.png', 308 | width: 70, 309 | height: 70, 310 | ), 311 | // child: Image.network( 312 | // menu.imageUrl, 313 | // width: 70, 314 | // height: 70, 315 | // ), 316 | ), 317 | ], 318 | ), 319 | ); 320 | } 321 | } -------------------------------------------------------------------------------- /courageous_people/lib/home/screen/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:courageous_people/common/constants.dart'; 2 | import 'package:courageous_people/log_out/cubit/log_out_cubit.dart'; 3 | import 'package:courageous_people/store/cubit/store_cubit.dart'; 4 | import 'package:courageous_people/store/cubit/store_state.dart'; 5 | import 'package:courageous_people/utils/show_alert_dialog.dart'; 6 | import 'package:courageous_people/utils/user_verification.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | import 'package:flutter_hooks/flutter_hooks.dart'; 10 | import 'package:naver_map_plugin/naver_map_plugin.dart'; 11 | 12 | import '../../log_in/log_In_screen.dart'; 13 | import '../../model/store_data.dart'; 14 | import '../widget/main_menu.dart'; 15 | import '../widget/store_box.dart'; 16 | import '../widget/store_searching_section.dart'; 17 | 18 | class Home extends HookWidget { 19 | Home({Key? key}) : super(key: key); 20 | 21 | String myCurrentLocation = ''; // 내 현재 위치 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final storeCubit = StoreCubit.of(context); 26 | final logOutCubit = LogOutCubit.of(context); 27 | 28 | final mapControllerNotifier = useState(null); 29 | final storeNotifier = useState(null); // 클릭해서 선택한 가게 30 | final markerNotifier = useState>([]); 31 | final crawledMarkerNotifier = useState>([]); 32 | final duplicatedListNotifier = useState>([]); 33 | final mergedMarkerListNotifier = useState>([]); 34 | 35 | final crawledStoreNotifier = useState?>(null); 36 | final searchStoreSectionNotifier = useState(false); 37 | final menuExpandedNotifier = useState(false); 38 | 39 | return SafeArea( 40 | child: Scaffold( 41 | body: FutureBuilder( 42 | future: verifyUser(), 43 | builder: (context, snapshot) { 44 | final logInSucceed = snapshot.data as bool?; 45 | 46 | if(logInSucceed == null) return Container(); 47 | 48 | return BlocConsumer( 49 | bloc: storeCubit, 50 | listener: (context, state) async { 51 | if (state is StoreLoadedState) { 52 | final image = await OverlayImage.fromAssetImage( 53 | assetName: 'assets/images/store.png', 54 | ); 55 | 56 | markerNotifier.value = state.storeList.map( 57 | (store) { 58 | return Marker( 59 | markerId: 'store${store.id}', 60 | position: LatLng(store.latitude, store.longitude), 61 | icon: image, 62 | width: 35, 63 | height: 35, 64 | onMarkerTab: (a, b) { 65 | storeNotifier.value = store; 66 | }, 67 | ); 68 | } 69 | ).toList(); 70 | 71 | mergedMarkerListNotifier.value = markerNotifier.value; 72 | } 73 | 74 | if (state is StoreErrorState) { 75 | showAlertDialog( 76 | context: context, 77 | title: state.message, 78 | ); 79 | } 80 | 81 | if (state is StoreCrawlSuccessState) { 82 | if(state.crawledList.length == 0) { 83 | showAlertDialog( 84 | context: context, 85 | title: '검색 결과가 없습니다', 86 | ); 87 | } 88 | 89 | crawledStoreNotifier.value = state.crawledList; 90 | duplicatedListNotifier.value = state.duplicatedList; 91 | if(state.crawledList.isEmpty) return; 92 | 93 | crawledMarkerNotifier.value = []; 94 | 95 | for (dynamic store in crawledStoreNotifier.value!) { 96 | final index = crawledStoreNotifier.value!.indexOf(store); 97 | 98 | if(duplicatedListNotifier.value[index] != null) continue; 99 | 100 | crawledMarkerNotifier.value.add( 101 | Marker( 102 | markerId: 'crawl$index', 103 | position: LatLng(store['latitude'], store['longitude']), 104 | ), 105 | ); 106 | } 107 | 108 | mergedMarkerListNotifier.value = [ 109 | ...markerNotifier.value, 110 | ...crawledMarkerNotifier.value, 111 | ]; 112 | 113 | _moveMapCamera( 114 | mapControllerNotifier.value, 115 | crawledStoreNotifier.value![0]['latitude'], 116 | crawledStoreNotifier.value![0]['longitude'], 117 | ); 118 | } 119 | 120 | if (state is StoreCrawlErrorState) { 121 | showAlertDialog( 122 | context: context, 123 | title: state.message, 124 | ); 125 | } 126 | }, 127 | builder: (context, state) => Stack( 128 | children: [ 129 | NaverMap( 130 | initLocationTrackingMode: LocationTrackingMode.Follow, 131 | locationButtonEnable: true, 132 | onMapCreated: (controller) { 133 | mapControllerNotifier.value = controller; 134 | 135 | storeCubit.getStores(); 136 | }, 137 | onMapTap: (latLng) { 138 | storeNotifier.value = null; 139 | }, 140 | rotationGestureEnable: false, 141 | markers: mergedMarkerListNotifier.value, 142 | ), 143 | StoreBox(store: storeNotifier.value), 144 | if(searchStoreSectionNotifier.value) 145 | Positioned( 146 | top: 60, 147 | child: StoreSearchSection( 148 | crawledData: crawledStoreNotifier.value, 149 | duplicatedStoreList: duplicatedListNotifier.value, 150 | onSearchPressed: (location, storeName) async { 151 | FocusScope.of(context).unfocus(); 152 | 153 | await storeCubit.crawlStore(location, storeName); 154 | }, 155 | onCloseButtonPressed: () { 156 | searchStoreSectionNotifier.value = false; 157 | crawledStoreNotifier.value = []; 158 | duplicatedListNotifier.value = []; 159 | mergedMarkerListNotifier.value = markerNotifier.value; 160 | }, 161 | onStoreTap: (latitude, longitude) { 162 | _moveMapCamera( 163 | mapControllerNotifier.value, 164 | latitude, 165 | longitude, 166 | ); 167 | } 168 | ), 169 | ), 170 | if(state is StoreCrawlingState) 171 | const Center( 172 | child: CircularProgressIndicator( 173 | color: THEME_COLOR, 174 | strokeWidth: 7, 175 | ), 176 | ), 177 | Positioned( 178 | left: 30, 179 | top: 30, 180 | child: MainMenu( 181 | succeedLogIn: logInSucceed, 182 | isMenuExpanded: menuExpandedNotifier.value, 183 | onMainMenuPressed: () { 184 | menuExpandedNotifier.value = !(menuExpandedNotifier.value); 185 | }, 186 | onLogInPressed: () { 187 | menuExpandedNotifier.value = false; 188 | 189 | Navigator.push(context, 190 | MaterialPageRoute( 191 | builder: (_) => LogInScreen(), 192 | ), 193 | ); 194 | }, 195 | onLogOutPressed: () { 196 | menuExpandedNotifier.value = false; 197 | 198 | showAlertDialog( 199 | context: context, 200 | title: '로그아웃합니다', 201 | onSubmit: () async { 202 | await logOutCubit.logOut(); 203 | 204 | Navigator.pushAndRemoveUntil( 205 | context, 206 | MaterialPageRoute( 207 | builder: (_) => Home(), 208 | ), 209 | (route) => false, 210 | ); 211 | }, 212 | ); 213 | }, 214 | onAddingStorePressed: () { 215 | menuExpandedNotifier.value = false; 216 | searchStoreSectionNotifier.value = true; 217 | }, 218 | onNearStoreListPressed: () {}, 219 | onFavoriteListPressed: () {}, 220 | ), 221 | ), 222 | ], 223 | ), 224 | ); 225 | }, 226 | ), 227 | ), 228 | ); 229 | } 230 | 231 | Widget _userSection() { 232 | return Container( 233 | width: 150, 234 | alignment: Alignment.centerLeft, 235 | decoration: BoxDecoration( 236 | color: Colors.teal.shade100, 237 | borderRadius: BorderRadius.circular( 238 | 22.5, 239 | // getWidgetSizeByKey(_menuIconKey). 240 | ), 241 | border: Border.all( 242 | width: 2.5, 243 | color: Colors.white, 244 | ), 245 | ), 246 | child: Row( 247 | mainAxisSize: MainAxisSize.min, 248 | children: [ 249 | CircleAvatar( 250 | backgroundImage: AssetImage('assets/images/profile.png'), 251 | radius: 22.5, 252 | ), 253 | 254 | Column( 255 | children: [ 256 | 257 | ], 258 | ) 259 | ], 260 | ), 261 | ); 262 | } 263 | 264 | void _moveMapCamera( 265 | NaverMapController? mapController, 266 | double latitude, 267 | double longitude, 268 | ) { 269 | if (mapController == null) return; 270 | 271 | final newPosition = CameraUpdate.toCameraPosition( 272 | CameraPosition( 273 | target: LatLng( 274 | latitude, 275 | longitude, 276 | ), 277 | ), 278 | ); 279 | 280 | mapController.moveCamera(newPosition); 281 | } 282 | } -------------------------------------------------------------------------------- /courageous_people/lib/review/screen/rewrite_review_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:courageous_people/common/constants.dart'; 4 | import 'package:courageous_people/model/menu_data.dart'; 5 | import 'package:courageous_people/review/cubit/review_cubit.dart'; 6 | import 'package:courageous_people/review/cubit/review_state.dart'; 7 | import 'package:courageous_people/utils/show_alert_dialog.dart'; 8 | import 'package:courageous_people/widget/my_drop_down.dart'; 9 | import 'package:courageous_people/widget/transparent_app_bar.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_bloc/flutter_bloc.dart'; 12 | import 'package:flutter_hooks/flutter_hooks.dart'; 13 | import 'package:image_picker/image_picker.dart'; 14 | import 'package:provider/provider.dart'; 15 | 16 | class RewriteReviewScreen extends HookWidget { 17 | const RewriteReviewScreen({ 18 | Key? key, 19 | required this.reviewId, 20 | required this.storeId, 21 | required this.userId, 22 | required this.menuList, 23 | required this.initialMenuText, 24 | required this.initialVolume, 25 | required this.initialComment, 26 | required this.initialImageByte, 27 | }) : super(key: key); 28 | 29 | final int reviewId; 30 | final int storeId; 31 | final int userId; 32 | final List menuList; 33 | final String initialMenuText; 34 | final String initialVolume; 35 | final String initialComment; 36 | final Uint8List? initialImageByte; 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | final reviewCubit = context.read(); 41 | 42 | final menuNotifier = useState(initialMenuText); 43 | final containerNotifier = useState(initialVolume); 44 | final commentNotifier = useState(initialComment); 45 | final pictureNotifier = useState(initialImageByte); 46 | final picker = ImagePicker(); 47 | 48 | return BlocListener( 49 | bloc: reviewCubit, 50 | listener: (context, state) async { 51 | if(state is ReviewRewrittenState) { 52 | await showAlertDialog( 53 | context: context, 54 | title: state.message, 55 | ); 56 | 57 | Navigator.pop(context, true); 58 | } 59 | 60 | if(state is ReviewRewriteErrorState) { 61 | await showAlertDialog( 62 | context: context, 63 | title: state.message, 64 | ); 65 | 66 | Navigator.pop(context, false); 67 | } 68 | }, 69 | child: Scaffold( 70 | appBar: TransparentAppBar(title: '수정하기'), 71 | body: _Body( 72 | menuList: menuList, 73 | storeId: storeId, 74 | userId: userId, 75 | initialMenuText: initialMenuText, 76 | initialVolume: initialVolume, 77 | initialComment: initialComment, 78 | pictureToByte: initialImageByte, 79 | onMenuChanged: (menu) => menuNotifier.value = menu, 80 | onContainerChanged: (container) => containerNotifier.value = container, 81 | onCommentChanged: (comment) => commentNotifier.value = comment, 82 | onPhotoTap: () async { 83 | final picture = await picker.pickImage(source: ImageSource.gallery); 84 | if(picture != null) { 85 | pictureNotifier.value = await picture.readAsBytes(); 86 | } 87 | }, 88 | onSubmit: () async { 89 | final reviewCommitted = await showAlertDialog( 90 | context: context, 91 | title: '리뷰를 수정하시겠습니까?', 92 | onCancel: () => Navigator.pop(context, false), 93 | ); 94 | 95 | if(!reviewCommitted!) return; 96 | 97 | print(reviewId); 98 | print(storeId); 99 | print(userId); 100 | 101 | await reviewCubit.rewriteReview( 102 | reviewId: reviewId, 103 | storeId: storeId, 104 | userId: userId, 105 | comment: commentNotifier.value, 106 | ); 107 | }, 108 | ), 109 | ), 110 | ); 111 | } 112 | } 113 | 114 | class _Body extends HookWidget { 115 | final List menuList; 116 | final int storeId; 117 | final int userId; 118 | final Uint8List? pictureToByte; 119 | String initialMenuText; 120 | final String initialVolume; 121 | final String initialComment; 122 | final void Function() onSubmit; 123 | final void Function(String) onMenuChanged; 124 | final void Function(String) onContainerChanged; 125 | final void Function(String) onCommentChanged; 126 | final void Function() onPhotoTap; 127 | 128 | _Body({ 129 | Key? key, 130 | required this.menuList, 131 | required this.storeId, 132 | required this.userId, 133 | required this.initialMenuText, 134 | required this.initialVolume, 135 | required this.initialComment, 136 | required this.onSubmit, 137 | required this.onMenuChanged, 138 | required this.onContainerChanged, 139 | required this.onCommentChanged, 140 | required this.onPhotoTap, 141 | this.pictureToByte, 142 | }) : super(key: key); 143 | 144 | bool menuExist(List menuList, String menu) { 145 | return menuList.indexOf(menu) != -1; 146 | } 147 | 148 | @override 149 | Widget build(BuildContext context) { 150 | final menuTitleList = menuList.map((menu) => menu.title).toList(); 151 | 152 | final selectedMenuStringNotifier = useState(initialMenuText); 153 | 154 | return Stack( 155 | children: [ 156 | SingleChildScrollView( 157 | child: Container( 158 | alignment: Alignment.topCenter, 159 | padding: EdgeInsets.all(20), 160 | child: Column( 161 | crossAxisAlignment: CrossAxisAlignment.start, 162 | mainAxisSize: MainAxisSize.min, 163 | children: [ 164 | _menuField( 165 | selected: menuExist( 166 | menuTitleList, 167 | selectedMenuStringNotifier.value, 168 | ) 169 | ? selectedMenuStringNotifier.value : '직접 입력', 170 | onSelectedChanged: (selected) { 171 | selectedMenuStringNotifier.value = selected; 172 | } 173 | ), 174 | SizedBox(height: 25), 175 | _containerField(), 176 | SizedBox(height: 25), 177 | _reviewSection(onCommentChanged: onCommentChanged), 178 | SizedBox(height: 25), 179 | Text('사진'), 180 | SizedBox(height: 5), 181 | _pictureSection(), 182 | SizedBox(height: kToolbarHeight), 183 | ], 184 | ), 185 | ), 186 | ), 187 | _bottomButton( 188 | context: context, 189 | onSubmit: onSubmit, 190 | ), 191 | ], 192 | ); 193 | } 194 | 195 | Widget _pictureSection() { 196 | return GestureDetector( 197 | onTap: onPhotoTap, 198 | child: Container( 199 | padding: pictureToByte != null 200 | ? null 201 | : EdgeInsets.all(15), 202 | width: 200, 203 | height: 200, 204 | decoration: pictureToByte != null 205 | ? null 206 | : BoxDecoration( 207 | borderRadius: BorderRadius.all(Radius.circular(10)), 208 | border: Border.all(width: 1), 209 | color: Colors.white, 210 | ), 211 | child: pictureToByte != null 212 | ? Image.memory(pictureToByte!) 213 | : _nonPictureForm(), 214 | ), 215 | ); 216 | } 217 | 218 | Widget _nonPictureForm() { 219 | return Stack( 220 | children: [ 221 | Center(child: Icon(Icons.camera_alt_outlined, size: 80)), 222 | Container( 223 | alignment: Alignment.bottomCenter, 224 | child: Text( 225 | "No Image", 226 | style: TextStyle( 227 | fontWeight: FontWeight.bold, 228 | fontSize: 25 229 | ), 230 | ), 231 | ), 232 | Container( 233 | alignment: Alignment.topCenter, 234 | child: Text( 235 | "사진을 추가하려면 터치하세요", 236 | style: TextStyle( 237 | fontSize: 12, 238 | color: Colors.grey.shade700, 239 | ), 240 | ), 241 | ), 242 | ], 243 | ); 244 | } 245 | 246 | Widget _bottomButton({ 247 | required BuildContext context, 248 | required void Function() onSubmit, 249 | }) { 250 | return Container( 251 | alignment: Alignment.bottomCenter, 252 | child: GestureDetector( 253 | onTap: onSubmit, 254 | child: Container( 255 | height: kToolbarHeight, 256 | color: THEME_COLOR, 257 | child: Center( 258 | child: 259 | Text( 260 | '수정하기', 261 | style: TextStyle( 262 | color: Colors.white, 263 | fontWeight: FontWeight.bold, 264 | fontSize: 16, 265 | ), 266 | ), 267 | ), 268 | ), 269 | ), 270 | ); 271 | } 272 | 273 | Widget _menuField({ 274 | required String selected, 275 | required void Function(String) onSelectedChanged, 276 | }) { 277 | return Column( 278 | mainAxisSize: MainAxisSize.min, 279 | children: [ 280 | MyDropDown( 281 | title: selected, 282 | contents: [ 283 | ...(menuList.map((menuData) => menuData.title).toList()), 284 | '직접 입력' 285 | ], 286 | onSelect: (container) { 287 | onSelectedChanged(container); 288 | onMenuChanged(container); 289 | }, 290 | ), 291 | if(selected == '직접 입력') 292 | TextFormField( 293 | initialValue: initialMenuText, 294 | onChanged: (menu) => onMenuChanged(menu), 295 | decoration: InputDecoration( 296 | hintText: '메뉴를 입력해주세요', 297 | ), 298 | ), 299 | ], 300 | ); 301 | } 302 | 303 | Widget _containerField() { 304 | return MyDropDown( 305 | title: initialVolume, 306 | widgetContents: _containerList, 307 | onSelect: (container) => onContainerChanged(container), 308 | ); 309 | } 310 | 311 | Widget _reviewSection({required void Function(String) onCommentChanged}) { 312 | return TextFormField( 313 | maxLines: 7, 314 | onChanged: (menu) => onCommentChanged(menu), 315 | cursorWidth: 1.0, 316 | initialValue: initialComment, 317 | autofocus: false, 318 | decoration: InputDecoration( 319 | contentPadding: EdgeInsets.all(8), 320 | hintText: '리뷰를 작성하세요', 321 | hintStyle: TextStyle(fontSize: 13, color: Colors.grey.shade500), 322 | fillColor: Colors.grey.shade200, 323 | filled: true, 324 | focusedBorder: InputBorder.none, 325 | focusColor: Colors.transparent, 326 | border: OutlineInputBorder( 327 | borderRadius: BorderRadius.circular(5), 328 | borderSide: BorderSide.none, 329 | ), 330 | ), 331 | ); 332 | } 333 | 334 | List get _containerList { 335 | return ['300ml 미만', '300ml - 500ml', '500ml - 1L', '1L 이상']; 336 | } 337 | } --------------------------------------------------------------------------------