반려동물 정보 db등록 (파이어베이스)
@ -7,7 +7,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.app"
|
namespace = "com.daoblock.rup"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.example.rup"
|
applicationId = "com.daoblock.rup"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|||||||
@ -5,6 +5,43 @@
|
|||||||
"storage_bucket": "rup-project-9d4c4.firebasestorage.app"
|
"storage_bucket": "rup-project-9d4c4.firebasestorage.app"
|
||||||
},
|
},
|
||||||
"client": [
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:379988243470:android:348749e6ea9404d61cb85f",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.daoblock.rup"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "379988243470-gdevefapm92n317jo6rri67n83beql44.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.daoblock.rup",
|
||||||
|
"certificate_hash": "7bda16a71840f93ab7c41fecc24da2bde3702424"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "379988243470-g6490l8gucc3ljras93i28c3l4qlroi4.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyAql00w1JB6n5wb4StXi9eyixVyIuT3-Hg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "379988243470-g6490l8gucc3ljras93i28c3l4qlroi4.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_info": {
|
"client_info": {
|
||||||
"mobilesdk_app_id": "1:379988243470:android:b4a4761b906eb44a1cb85f",
|
"mobilesdk_app_id": "1:379988243470:android:b4a4761b906eb44a1cb85f",
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.daoblock.rup
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
@ -1,5 +1,3 @@
|
|||||||
package com.example.app
|
// This file should be deleted.
|
||||||
|
// package com.example.app
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
// Moved to com.daoblock.rup
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
3
app/assets/icons/general_schedule_icon.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="20" height="20" rx="3.87222" fill="#FBB800"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 162 B |
|
Before Width: | Height: | Size: 741 B After Width: | Height: | Size: 741 B |
|
Before Width: | Height: | Size: 437 B After Width: | Height: | Size: 437 B |
3
app/assets/icons/important_schedule_icon.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="20" height="20" rx="3.87222" fill="#FF7500"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 162 B |
3
app/assets/icons/incomplete_icon.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="20" height="20" rx="3.87222" fill="#C8C8C8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 162 B |
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 459 B |
|
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 203 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -368,7 +368,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -384,7 +384,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@ -401,7 +401,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
@ -416,7 +416,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
@ -547,7 +547,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@ -569,7 +569,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
135
app/lib/data/pet_data.dart
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
class PetData {
|
||||||
|
static const List<String> diseaseList = [
|
||||||
|
"피부질환",
|
||||||
|
"눈 질환",
|
||||||
|
"치아 / 구강 질환",
|
||||||
|
"뼈 / 관절 질환",
|
||||||
|
"생식기 / 비뇨기 질환",
|
||||||
|
"심장 / 혈관 질환",
|
||||||
|
"소화기 질환",
|
||||||
|
"호흡기 질환",
|
||||||
|
"내분비계 질환",
|
||||||
|
"뇌신경 질환",
|
||||||
|
"생식기 질환",
|
||||||
|
"귀 질환",
|
||||||
|
"코 질환",
|
||||||
|
"기타",
|
||||||
|
];
|
||||||
|
|
||||||
|
static const Map<String, Map<String, List<String>>> breedsData = {
|
||||||
|
"포유류": {
|
||||||
|
"강아지": [
|
||||||
|
"말티즈",
|
||||||
|
"푸들",
|
||||||
|
"포메라니안",
|
||||||
|
"믹스견",
|
||||||
|
"치와와",
|
||||||
|
"시츄",
|
||||||
|
"비숑 프리제",
|
||||||
|
"골든 리트리버",
|
||||||
|
"진돗개",
|
||||||
|
"웰시 코기",
|
||||||
|
"프렌치 불독",
|
||||||
|
"시바견",
|
||||||
|
"닥스후트",
|
||||||
|
"요크셔 테리어",
|
||||||
|
"보더 콜리",
|
||||||
|
"사모예드",
|
||||||
|
"허스키",
|
||||||
|
"말라뮤트",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"고양이": [
|
||||||
|
"코리안 숏헤어",
|
||||||
|
"브리티시 숏헤어",
|
||||||
|
"아메리칸 숏헤어",
|
||||||
|
"뱅갈",
|
||||||
|
"메인쿤",
|
||||||
|
"데본 렉스",
|
||||||
|
"페르시안",
|
||||||
|
"러시안 블루",
|
||||||
|
"샴",
|
||||||
|
"렉돌",
|
||||||
|
"스코티시 폴드",
|
||||||
|
"먼치킨",
|
||||||
|
"노르웨이 숲",
|
||||||
|
"믹스묘",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"햄스터": ["정글리안", "펄", "푸딩", "골든 햄스터", "로보로브스키", "기타(직접 입력)"],
|
||||||
|
"토끼": ["롭이어", "더치", "라이언 헤드", "드워프", "렉스", "기타(직접 입력)"],
|
||||||
|
"기니피그": ["잉글리쉬", "아비시니안", "페루비안", "실키", "기타(직접 입력)"],
|
||||||
|
"고슴도치": ["플라티나", "화이트 초코", "알비노", "핀토", "기타(직접 입력)"],
|
||||||
|
"기타(직접 입력)": ["기타(직접 입력)"],
|
||||||
|
},
|
||||||
|
"파충류": {
|
||||||
|
"거북이": ["커먼 머스크 터틀", "레이저백", "육지거북", "붉은귀거북", "남생이", "기타(직접 입력)"],
|
||||||
|
"도마뱀": [
|
||||||
|
"크레스티드 게코",
|
||||||
|
"리키에너스 게코",
|
||||||
|
"가고일 게코",
|
||||||
|
"레오파드 게코",
|
||||||
|
"비어디 드래곤",
|
||||||
|
"블루텅 스킨크",
|
||||||
|
"이구아나",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"뱀": [
|
||||||
|
"볼 파이톤",
|
||||||
|
"가터 스네이크",
|
||||||
|
"호그노즈 스네이크",
|
||||||
|
"콘 스네이크",
|
||||||
|
"킹 스네이크",
|
||||||
|
"밀크 스네이크",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"기타(직접 입력)": ["기타(직접 입력)"],
|
||||||
|
},
|
||||||
|
"조류": {
|
||||||
|
"앵무새(소/중형)": [
|
||||||
|
"사랑앵무(잉꼬)",
|
||||||
|
"코카티엘(왕관앵무)",
|
||||||
|
"모란앵무",
|
||||||
|
"코뉴어",
|
||||||
|
"퀘이커",
|
||||||
|
"카카리키",
|
||||||
|
"사자나미(빗금앵무)",
|
||||||
|
"유리앵무",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"앵무새(대형)": [
|
||||||
|
"뉴기니아",
|
||||||
|
"회색앵무",
|
||||||
|
"금강앵무(마카우)",
|
||||||
|
"유황앵무(코카투)",
|
||||||
|
"아마존앵무",
|
||||||
|
"대본영",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"핀치/관상조": ["카나리아", "십자매", "문조", "금화조", "호금조", "백문조", "기타(직접 입력)"],
|
||||||
|
"비둘기/닭/메추리": [
|
||||||
|
"애완용 비둘기",
|
||||||
|
"관상닭(실키 등)",
|
||||||
|
"메추리",
|
||||||
|
"미니메추리",
|
||||||
|
"오리/거위",
|
||||||
|
"기타(직접 입력)",
|
||||||
|
],
|
||||||
|
"기타(직접 입력)": ["직접 입력"],
|
||||||
|
},
|
||||||
|
"양서류": {
|
||||||
|
"개구리": ["청개구리", "팩맨", "다트 프록", "화이트 트리 프록", "기타(직접 입력)"],
|
||||||
|
"도룡뇽": ["우파루파", "파이어 벨리 뉴트", "타이거 살라만더", "기타(직접 입력)"],
|
||||||
|
"기타(직접 입력)": ["기타(직접 입력)"],
|
||||||
|
},
|
||||||
|
"어류": {
|
||||||
|
"열대어": ["구피", "베타", "테트라", "디스커스", "엔젤피쉬", "기타(직접 입력)"],
|
||||||
|
"금붕어/잉어": ["금붕어", "비단잉어", "기타(직접 입력)"],
|
||||||
|
"해수어": ["크라운피쉬(니모)", "블루탱", "기타(직접 입력)"],
|
||||||
|
"기타(직접 입력)": ["기타(직접 입력)"],
|
||||||
|
},
|
||||||
|
"기타(직접 입력)": {
|
||||||
|
"기타(직접 입력)": ["기타(직접 입력)"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'screens/splash_screen.dart';
|
import 'screens/splash_screen.dart';
|
||||||
|
import 'screens/register_complete_screen.dart';
|
||||||
import 'utils/log_manager.dart';
|
import 'utils/log_manager.dart';
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
@ -45,6 +46,9 @@ class RupApp extends StatelessWidget {
|
|||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
home: const SplashScreen(),
|
home: const SplashScreen(),
|
||||||
|
routes: {
|
||||||
|
'/register_complete': (context) => const RegisterCompleteScreen(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
79
app/lib/models/pet_model.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
|
class Pet {
|
||||||
|
final String id;
|
||||||
|
final String ownerId;
|
||||||
|
final String name;
|
||||||
|
final String species; // "강아지", "고양이", etc.
|
||||||
|
final String breed; // "말티즈", "코숏", etc.
|
||||||
|
final String gender; // "남아", "여아"
|
||||||
|
final bool isNeutered;
|
||||||
|
final DateTime? birthDate;
|
||||||
|
final bool isDateUnknown;
|
||||||
|
final String? registrationNumber;
|
||||||
|
final String? profileImageUrl;
|
||||||
|
final List<String> diseases;
|
||||||
|
final List<String> pastDiseases;
|
||||||
|
final List<String> healthConcerns;
|
||||||
|
final DateTime createdAt;
|
||||||
|
|
||||||
|
Pet({
|
||||||
|
required this.id,
|
||||||
|
required this.ownerId,
|
||||||
|
required this.name,
|
||||||
|
required this.species,
|
||||||
|
required this.breed,
|
||||||
|
required this.gender,
|
||||||
|
required this.isNeutered,
|
||||||
|
this.birthDate,
|
||||||
|
required this.isDateUnknown,
|
||||||
|
this.registrationNumber,
|
||||||
|
this.profileImageUrl,
|
||||||
|
required this.diseases,
|
||||||
|
required this.pastDiseases,
|
||||||
|
required this.healthConcerns,
|
||||||
|
required this.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'ownerId': ownerId,
|
||||||
|
'name': name,
|
||||||
|
'species': species,
|
||||||
|
'breed': breed,
|
||||||
|
'gender': gender,
|
||||||
|
'isNeutered': isNeutered,
|
||||||
|
'birthDate': birthDate != null ? Timestamp.fromDate(birthDate!) : null,
|
||||||
|
'isDateUnknown': isDateUnknown,
|
||||||
|
'registrationNumber': registrationNumber,
|
||||||
|
'profileImageUrl': profileImageUrl,
|
||||||
|
'diseases': diseases,
|
||||||
|
'pastDiseases': pastDiseases,
|
||||||
|
'healthConcerns': healthConcerns,
|
||||||
|
'createdAt': Timestamp.fromDate(createdAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Pet.fromMap(Map<String, dynamic> map) {
|
||||||
|
return Pet(
|
||||||
|
id: map['id'] ?? '',
|
||||||
|
ownerId: map['ownerId'] ?? '',
|
||||||
|
name: map['name'] ?? '',
|
||||||
|
species: map['species'] ?? '',
|
||||||
|
breed: map['breed'] ?? '',
|
||||||
|
gender: map['gender'] ?? '',
|
||||||
|
isNeutered: map['isNeutered'] ?? false,
|
||||||
|
birthDate: map['birthDate'] != null
|
||||||
|
? (map['birthDate'] as Timestamp).toDate()
|
||||||
|
: null,
|
||||||
|
isDateUnknown: map['isDateUnknown'] ?? false,
|
||||||
|
registrationNumber: map['registrationNumber'],
|
||||||
|
profileImageUrl: map['profileImageUrl'],
|
||||||
|
diseases: List<String>.from(map['diseases'] ?? []),
|
||||||
|
pastDiseases: List<String>.from(map['pastDiseases'] ?? []),
|
||||||
|
healthConcerns: List<String>.from(map['healthConcerns'] ?? []),
|
||||||
|
createdAt: (map['createdAt'] as Timestamp).toDate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,71 +1,320 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart'; // Import screenutil
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'pet_registration_screen.dart';
|
import 'pet_registration_screen.dart';
|
||||||
|
import '../services/firestore_service.dart';
|
||||||
|
import '../models/pet_model.dart';
|
||||||
|
import '../theme/app_colors.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
final FirestoreService _firestoreService = FirestoreService();
|
||||||
|
String? _userId;
|
||||||
|
Pet? _selectedPet;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_userId = _firestoreService.getCurrentUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 반려동물 선택 시 호출
|
||||||
|
void _selectPet(Pet pet) {
|
||||||
|
setState(() {
|
||||||
|
_selectedPet = pet;
|
||||||
|
});
|
||||||
|
Navigator.pop(context); // 모달 닫기
|
||||||
|
}
|
||||||
|
|
||||||
|
// 반려동물 선택 모달 표시
|
||||||
|
void _showPetSelectionModal(List<Pet> pets) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Text(
|
||||||
|
'반려동물 선택',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
// 반려동물 리스트
|
||||||
|
Flexible(
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: pets.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final pet = pets[index];
|
||||||
|
final isSelected = pet.id == _selectedPet?.id;
|
||||||
|
return ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
radius: 24.r,
|
||||||
|
backgroundColor: Colors.grey[200],
|
||||||
|
backgroundImage: pet.profileImageUrl != null
|
||||||
|
? NetworkImage(pet.profileImageUrl!)
|
||||||
|
: null,
|
||||||
|
child: pet.profileImageUrl == null
|
||||||
|
? SvgPicture.asset(
|
||||||
|
'assets/icons/profile_icon.svg',
|
||||||
|
width: 24.w,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Colors.grey[400]!,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
pet.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: isSelected
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
color: isSelected
|
||||||
|
? AppColors.highlight
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: isSelected
|
||||||
|
? const Icon(Icons.check, color: AppColors.highlight)
|
||||||
|
: null,
|
||||||
|
onTap: () => _selectPet(pet),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(thickness: 1, color: Colors.grey[200]),
|
||||||
|
// 반려동물 추가 버튼
|
||||||
|
ListTile(
|
||||||
|
leading: Container(
|
||||||
|
width: 48.r,
|
||||||
|
height: 48.r,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[100],
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.add, color: Colors.black54),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'반려동물 추가하기',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const PetRegistrationScreen(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 30.h),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (_userId == null) {
|
||||||
|
return const Scaffold(body: Center(child: Text('로그인이 필요합니다.')));
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: StreamBuilder<List<Pet>>(
|
||||||
children: [
|
stream: _firestoreService.getPets(_userId!),
|
||||||
Padding(
|
builder: (context, snapshot) {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h),
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
child: Material(
|
return const Center(child: CircularProgressIndicator());
|
||||||
color: Colors.transparent,
|
}
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(
|
if (snapshot.hasError) {
|
||||||
8.r,
|
return Center(child: Text('오류가 발생했습니다: ${snapshot.error}'));
|
||||||
), // Optional: Add radius
|
}
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
final pets = snapshot.data ?? [];
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
// 등록된 반려동물이 없을 때: 기존 UI 유지 (등록 버튼 강조)
|
||||||
builder: (context) => const PetRegistrationScreen(),
|
if (pets.isEmpty) {
|
||||||
),
|
return Column(
|
||||||
);
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
},
|
children: [
|
||||||
child: Row(
|
Padding(
|
||||||
mainAxisSize: MainAxisSize.min, // Wrap content only
|
padding: EdgeInsets.symmetric(
|
||||||
children: [
|
horizontal: 20.w,
|
||||||
Image.asset(
|
vertical: 20.h,
|
||||||
'assets/img/profile.png',
|
),
|
||||||
width: 40.w,
|
child: Material(
|
||||||
height: 40.h,
|
color: Colors.transparent,
|
||||||
),
|
child: InkWell(
|
||||||
SizedBox(width: 10.w),
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
Text(
|
onTap: () {
|
||||||
'반려동물 등록 +',
|
Navigator.push(
|
||||||
style: TextStyle(
|
context,
|
||||||
fontFamily: 'SCDream',
|
MaterialPageRoute(
|
||||||
fontWeight: FontWeight.w500,
|
builder: (context) =>
|
||||||
fontSize: 15.sp,
|
const PetRegistrationScreen(),
|
||||||
letterSpacing: 0.45.sp,
|
),
|
||||||
color: const Color(0xFF1f1f1f),
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4.h),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/img/profile.png',
|
||||||
|
width: 40.w,
|
||||||
|
height: 40.h,
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
Text(
|
||||||
|
'반려동물 등록 +',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 15.sp,
|
||||||
|
letterSpacing: 0.45.sp,
|
||||||
|
color: const Color(0xFF1f1f1f),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'등록된 반려동물이 없습니다.\n새로운 가족을 등록해주세요!',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 등록된 반려동물이 있을 때
|
||||||
|
// 선택된 펫이 없거나 리스트에 없으면 첫 번째 펫 선택
|
||||||
|
if (_selectedPet == null ||
|
||||||
|
!pets.any((p) => p.id == _selectedPet!.id)) {
|
||||||
|
// We shouldn't update state directly in build, but for initialization it's tricky.
|
||||||
|
// Using the first pet as default display.
|
||||||
|
// Better approach: use a local variable for display, update state in callbacks.
|
||||||
|
_selectedPet = pets.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ensure _selectedPet is valid (e.g. after deletion), find it in the new list
|
||||||
|
final displayPet = pets.firstWhere(
|
||||||
|
(p) => p.id == _selectedPet?.id,
|
||||||
|
orElse: () => pets.first,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20.w,
|
||||||
|
vertical: 20.h,
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _showPetSelectionModal(pets),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// 프로필 이미지
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 20.r,
|
||||||
|
backgroundColor: Colors.grey[200],
|
||||||
|
backgroundImage: displayPet.profileImageUrl != null
|
||||||
|
? NetworkImage(displayPet.profileImageUrl!)
|
||||||
|
: null,
|
||||||
|
child: displayPet.profileImageUrl == null
|
||||||
|
? SvgPicture.asset(
|
||||||
|
'assets/icons/profile_icon.svg',
|
||||||
|
width: 20.w,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Colors.grey[400]!,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
// 이름
|
||||||
|
Text(
|
||||||
|
displayPet.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18.sp,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
// 드롭다운 화살표
|
||||||
|
Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
size: 24.w,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
),
|
child: Center(
|
||||||
Expanded(
|
child: Text(
|
||||||
child: Center(
|
'안녕, ${displayPet.name}!',
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
'로그인 성공!\n여기는 메인 홈 화면입니다.',
|
fontFamily: 'SCDream',
|
||||||
textAlign: TextAlign.center,
|
fontSize: 24.sp,
|
||||||
style: TextStyle(
|
fontWeight: FontWeight.bold,
|
||||||
fontFamily: 'SCDream',
|
),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
fontSize: 18.sp,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -156,7 +156,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
fontSize: 15.sp,
|
fontSize: 15.sp,
|
||||||
backgroundColor: const Color(0xFF00D03F),
|
backgroundColor: const Color(0xFF00D03F),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
iconPath: 'assets/icons/navericon.svg',
|
iconPath: 'assets/icons/naver_icon.svg',
|
||||||
),
|
),
|
||||||
SizedBox(height: 15.h),
|
SizedBox(height: 15.h),
|
||||||
// Kakao Login Button
|
// Kakao Login Button
|
||||||
@ -168,7 +168,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
fontSize: 15.sp,
|
fontSize: 15.sp,
|
||||||
backgroundColor: const Color(0xFFFAE100),
|
backgroundColor: const Color(0xFFFAE100),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
iconPath: 'assets/icons/kakaoicon.svg',
|
iconPath: 'assets/icons/kakao_icon.svg',
|
||||||
),
|
),
|
||||||
SizedBox(height: 15.h),
|
SizedBox(height: 15.h),
|
||||||
// Google Login Button
|
// Google Login Button
|
||||||
@ -180,7 +180,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
fontSize: 15.sp,
|
fontSize: 15.sp,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
onPressed: _handleGoogleLogin,
|
onPressed: _handleGoogleLogin,
|
||||||
iconPath: 'assets/icons/googleicon.svg',
|
iconPath: 'assets/icons/google_icon.svg',
|
||||||
isBordered: true,
|
isBordered: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -87,14 +87,14 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10.h),
|
padding: EdgeInsets.only(bottom: 10.h),
|
||||||
child: _buildSvgIcon('assets/icons/homeicon.svg', 0),
|
child: _buildSvgIcon('assets/icons/home_icon.svg', 0),
|
||||||
),
|
),
|
||||||
label: '홈',
|
label: '홈',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10.h),
|
padding: EdgeInsets.only(bottom: 10.h),
|
||||||
child: _buildSvgIcon('assets/icons/appointmenticon.svg', 1),
|
child: _buildSvgIcon('assets/icons/appointment_icon.svg', 1),
|
||||||
),
|
),
|
||||||
label: '예약/조회',
|
label: '예약/조회',
|
||||||
),
|
),
|
||||||
@ -115,14 +115,14 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10.h),
|
padding: EdgeInsets.only(bottom: 10.h),
|
||||||
child: _buildSvgIcon('assets/icons/shopicon.svg', 3),
|
child: _buildSvgIcon('assets/icons/shop_icon.svg', 3),
|
||||||
),
|
),
|
||||||
label: '상점',
|
label: '상점',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 10.h),
|
padding: EdgeInsets.only(bottom: 10.h),
|
||||||
child: _buildSvgIcon('assets/icons/myicon.svg', 4),
|
child: _buildSvgIcon('assets/icons/my_icon.svg', 4),
|
||||||
),
|
),
|
||||||
label: '내 정보',
|
label: '내 정보',
|
||||||
),
|
),
|
||||||
|
|||||||
@ -5,6 +5,11 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import '../theme/app_colors.dart';
|
import '../theme/app_colors.dart';
|
||||||
|
import '../data/pet_data.dart'; // Import Data
|
||||||
|
import '../widgets/pet_registration/selection_modal.dart'; // Import SelectionModal
|
||||||
|
import '../widgets/pet_registration/input_formatters.dart'; // Import InputFormatters
|
||||||
|
import '../services/firestore_service.dart';
|
||||||
|
import '../models/pet_model.dart';
|
||||||
|
|
||||||
class PetRegistrationScreen extends StatefulWidget {
|
class PetRegistrationScreen extends StatefulWidget {
|
||||||
const PetRegistrationScreen({super.key});
|
const PetRegistrationScreen({super.key});
|
||||||
@ -17,27 +22,53 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
// 정확한 날짜를 몰라요 상태
|
// 정확한 날짜를 몰라요 상태
|
||||||
bool _isDateUnknown = false;
|
bool _isDateUnknown = false;
|
||||||
|
|
||||||
final TextEditingController _yearController = TextEditingController();
|
|
||||||
final TextEditingController _monthController = TextEditingController();
|
final TextEditingController _monthController = TextEditingController();
|
||||||
|
final TextEditingController _yearController =
|
||||||
|
TextEditingController(); // Added back
|
||||||
final TextEditingController _dayController = TextEditingController();
|
final TextEditingController _dayController = TextEditingController();
|
||||||
|
final TextEditingController _registrationNumberController =
|
||||||
|
TextEditingController(); // Added Registration Number Controller
|
||||||
|
final FocusNode _yearFocus = FocusNode();
|
||||||
|
final FocusNode _monthFocus = FocusNode();
|
||||||
|
final FocusNode _dayFocus = FocusNode();
|
||||||
|
|
||||||
// 보유 질환 데이터
|
bool get _isFormValid {
|
||||||
final List<String> _diseaseList = [
|
// 1. 이름 확인
|
||||||
"피부질환",
|
if (_nameController.text.trim().isEmpty) return false;
|
||||||
"눈 질환",
|
// 2. 종 확인
|
||||||
"치아 / 구강 질환",
|
if (_speciesController.text.trim().isEmpty) return false;
|
||||||
"뼈 / 관절 질환",
|
// 3. 품종 확인
|
||||||
"생식기 / 비뇨기 질환",
|
if (_breedController.text.trim().isEmpty) return false;
|
||||||
"심장 / 혈관 질환",
|
// 4. 성별 확인
|
||||||
"소화기 질환",
|
if (_selectedGender == null) return false;
|
||||||
"호흡기 질환",
|
// 5. 생년월일 확인
|
||||||
"내분비계 질환",
|
if (!_isDateUnknown) {
|
||||||
"뇌신경 질환",
|
if (_yearController.text.length != 4 ||
|
||||||
"생식기 질환",
|
_monthController.text.length != 2 ||
|
||||||
"귀 질환",
|
_dayController.text.length != 2) {
|
||||||
"코 질환",
|
return false;
|
||||||
"기타",
|
}
|
||||||
];
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 폼 상태 변경 감지를 위한 리스너 등록
|
||||||
|
_nameController.addListener(_updateState);
|
||||||
|
_speciesController.addListener(_updateState);
|
||||||
|
_breedController.addListener(_updateState);
|
||||||
|
_yearController.addListener(_updateState);
|
||||||
|
_monthController.addListener(_updateState);
|
||||||
|
_dayController.addListener(_updateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateState() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 보유 질환 데이터 (Removed: Use PetData.diseaseList)
|
||||||
|
|
||||||
// 각 항목별 선택 상태 및 컨트롤러
|
// 각 항목별 선택 상태 및 컨트롤러
|
||||||
final TextEditingController _nameController =
|
final TextEditingController _nameController =
|
||||||
@ -49,85 +80,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
final TextEditingController _genderController =
|
final TextEditingController _genderController =
|
||||||
TextEditingController(); // 성별 컨트롤러
|
TextEditingController(); // 성별 컨트롤러
|
||||||
|
|
||||||
// 종 데이터 (대분류 -> 중분류 -> 품종)
|
// 종 데이터 (Removed: Use PetData.breedsData)
|
||||||
final Map<String, Map<String, List<String>>> _petData = {
|
|
||||||
"포유류": {
|
|
||||||
"강아지": [
|
|
||||||
"말티즈",
|
|
||||||
"푸들",
|
|
||||||
"포메라니안",
|
|
||||||
"믹스견",
|
|
||||||
"치와와",
|
|
||||||
"시츄",
|
|
||||||
"비숑 프리제",
|
|
||||||
"골든 리트리버",
|
|
||||||
"진돗개",
|
|
||||||
"웰시 코기",
|
|
||||||
"기타(직접 입력)",
|
|
||||||
],
|
|
||||||
"고양이": [
|
|
||||||
"코리안 숏헤어",
|
|
||||||
"페르시안",
|
|
||||||
"러시안 블루",
|
|
||||||
"샴",
|
|
||||||
"렉돌",
|
|
||||||
"스코티시 폴드",
|
|
||||||
"먼치킨",
|
|
||||||
"노르웨이 숲",
|
|
||||||
"믹스묘",
|
|
||||||
"기타(직접 입력)",
|
|
||||||
],
|
|
||||||
"햄스터": ["정글리안", "펄", "푸딩", "골든 햄스터", "로보로브스키", "기타(직접 입력)"],
|
|
||||||
"토끼": ["롭이어", "더치", "라이언 헤드", "드워프", "렉스", "기타(직접 입력)"],
|
|
||||||
"기니피그": ["잉글리쉬", "아비시니안", "페루비안", "실키", "기타(직접 입력)"],
|
|
||||||
"고슴도치": ["플라티나", "화이트 초코", "알비노", "핀토", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"파충류": {
|
|
||||||
"거북이": ["커먼 머스크 터틀", "레이저백", "육지거북", "붉은귀거북", "남생이", "기타(직접 입력)"],
|
|
||||||
"도마뱀": ["크레스티드 게코", "레오파드 게코", "비어디 드래곤", "블루텅 스킨크", "이구아나", "기타(직접 입력)"],
|
|
||||||
"뱀": ["볼 파이톤", "콘 스네이크", "킹 스네이크", "밀크 스네이크", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"조류": {
|
|
||||||
"앵무새": [
|
|
||||||
"사랑앵무(잉꼬)",
|
|
||||||
"코카티엘(왕관앵무)",
|
|
||||||
"모란앵무",
|
|
||||||
"코뉴어",
|
|
||||||
"퀘이커",
|
|
||||||
"금강앵무",
|
|
||||||
"기타(직접 입력)",
|
|
||||||
],
|
|
||||||
"카나리아": ["옐로우 카나리아", "레드 카나리아", "보더 카나리아", "기타(직접 입력)"],
|
|
||||||
"핀치": ["문조", "십자매", "금화조", "호금조", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"어류": {
|
|
||||||
"금붕어": ["오란다", "유금", "단정", "진주린", "코메트", "기타(직접 입력)"],
|
|
||||||
"열대어": ["네온 테트라", "엔젤피쉬", "플래티", "몰리", "디스커스", "기타(직접 입력)"],
|
|
||||||
"구피": ["고정 구피", "막구피(믹스)", "기타(직접 입력)"],
|
|
||||||
"잉어": ["비단잉어", "향어", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"곤충": {
|
|
||||||
"장수풍뎅이": ["국산 장수풍뎅이", "헤라클레스 장수풍뎅이", "코카서스 장수풍뎅이", "기타(직접 입력)"],
|
|
||||||
"사슴벌레": ["넓적사슴벌레", "왕사슴벌레", "톱사슴벌레", "애사슴벌레", "기타(직접 입력)"],
|
|
||||||
"나비/나방": ["배추흰나비", "호랑나비", "누에나방", "기타(직접 입력)"],
|
|
||||||
"사마귀": ["왕사마귀", "사마귀", "넓적배사마귀", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"절지동물": {
|
|
||||||
"타란툴라(거미)": ["로즈헤어", "골든니", "화이트니", "핑크토", "기타(직접 입력)"],
|
|
||||||
"전갈": ["황제전갈", "극동전갈", "아시안 포레스트 전갈", "기타(직접 입력)"],
|
|
||||||
"지네": ["왕지네", "청지네", "기타(직접 입력)"],
|
|
||||||
"소라게": ["인도 소라게", "딸기 소라게", "바이오라센트", "기타(직접 입력)"],
|
|
||||||
"기타": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
"기타": {
|
|
||||||
"기타(직접 입력)": ["기타(직접 입력)"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 선택된 종 정보 (품종 선택을 위해 필요)
|
// 선택된 종 정보 (품종 선택을 위해 필요)
|
||||||
String? _currentMajorCategory;
|
String? _currentMajorCategory;
|
||||||
@ -211,6 +164,27 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 24.w,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'기본 이미지로 변경',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
setState(() {
|
||||||
|
_profileImage = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -239,9 +213,98 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
_diseaseController.dispose();
|
_diseaseController.dispose();
|
||||||
_pastDiseaseController.dispose();
|
_pastDiseaseController.dispose();
|
||||||
_healthConcernController.dispose();
|
_healthConcernController.dispose();
|
||||||
|
_yearFocus.dispose();
|
||||||
|
_monthFocus.dispose();
|
||||||
|
_dayFocus.dispose();
|
||||||
|
_registrationNumberController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
Future<void> _registerPet() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final firestoreService = FirestoreService();
|
||||||
|
final userId = firestoreService.getCurrentUserId();
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
throw Exception('로그인이 필요합니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 날짜 처리
|
||||||
|
DateTime? birthDate;
|
||||||
|
if (!_isDateUnknown) {
|
||||||
|
birthDate = DateTime(
|
||||||
|
int.parse(_yearController.text),
|
||||||
|
int.parse(_monthController.text),
|
||||||
|
int.parse(_dayController.text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리스트 + 기타 텍스트 합치기 (보유 질환)
|
||||||
|
List<String> finalDiseases = List.from(_selectedDiseases);
|
||||||
|
if (finalDiseases.contains('기타') && _otherDiseaseText.isNotEmpty) {
|
||||||
|
finalDiseases.remove('기타');
|
||||||
|
finalDiseases.add('기타($_otherDiseaseText)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리스트 + 기타 텍스트 합치기 (과거 질환)
|
||||||
|
List<String> finalPastDiseases = List.from(_selectedPastDiseases);
|
||||||
|
if (finalPastDiseases.contains('기타') &&
|
||||||
|
_otherPastDiseaseText.isNotEmpty) {
|
||||||
|
finalPastDiseases.remove('기타');
|
||||||
|
finalPastDiseases.add('기타($_otherPastDiseaseText)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리스트 + 기타 텍스트 합치기 (염려 건강)
|
||||||
|
List<String> finalHealthConcerns = List.from(_selectedHealthConcerns);
|
||||||
|
if (finalHealthConcerns.contains('기타') &&
|
||||||
|
_otherHealthConcernText.isNotEmpty) {
|
||||||
|
finalHealthConcerns.remove('기타');
|
||||||
|
finalHealthConcerns.add('기타($_otherHealthConcernText)');
|
||||||
|
}
|
||||||
|
|
||||||
|
final newPet = Pet(
|
||||||
|
id: firestoreService.generatePetId(),
|
||||||
|
ownerId: userId,
|
||||||
|
name: _nameController.text,
|
||||||
|
species: _speciesController.text, // 중분류 or 직접입력
|
||||||
|
breed: _breedController.text,
|
||||||
|
gender: _selectedGender!,
|
||||||
|
isNeutered: _isNeutered,
|
||||||
|
birthDate: birthDate,
|
||||||
|
isDateUnknown: _isDateUnknown,
|
||||||
|
registrationNumber: _registrationNumberController.text.isNotEmpty
|
||||||
|
? _registrationNumberController.text
|
||||||
|
: null,
|
||||||
|
diseases: finalDiseases,
|
||||||
|
pastDiseases: finalPastDiseases,
|
||||||
|
healthConcerns: finalHealthConcerns,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await firestoreService.registerPet(newPet, _profileImage);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pushReplacementNamed(context, '/register_complete');
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text('등록 실패: $e')));
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _toggleDateUnknown() {
|
void _toggleDateUnknown() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDateUnknown = !_isDateUnknown;
|
_isDateUnknown = !_isDateUnknown;
|
||||||
@ -301,10 +364,10 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
// 리스트 영역
|
// 리스트 영역
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _diseaseList.length,
|
itemCount: PetData.diseaseList.length,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final disease = _diseaseList[index];
|
final disease = PetData.diseaseList[index];
|
||||||
final isSelected = tempSelected.contains(disease);
|
final isSelected = tempSelected.contains(disease);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@ -523,7 +586,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
// 뒤로가기 버튼 (대분류 선택 상태이거나 입력창 상태일 때 표시)
|
// 뒤로가기 버튼
|
||||||
(selectedMajor != null || showInput)
|
(selectedMajor != null || showInput)
|
||||||
? GestureDetector(
|
? GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -644,11 +707,10 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
: (selectedMajor == null
|
: (selectedMajor == null
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
// 대분류 리스트
|
// 대분류 리스트
|
||||||
itemCount: _petData.keys.length,
|
itemCount: PetData.breedsData.keys.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final major = _petData.keys.elementAt(
|
final major = PetData.breedsData.keys
|
||||||
index,
|
.elementAt(index);
|
||||||
);
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
major,
|
major,
|
||||||
@ -664,7 +726,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setModalState(() {
|
setModalState(() {
|
||||||
if (major == '기타') {
|
if (major == '기타(직접 입력)') {
|
||||||
showInput = true;
|
showInput = true;
|
||||||
} else {
|
} else {
|
||||||
selectedMajor = major;
|
selectedMajor = major;
|
||||||
@ -676,9 +738,12 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
// 중분류 리스트
|
// 중분류 리스트
|
||||||
itemCount: _petData[selectedMajor]!.length,
|
itemCount:
|
||||||
|
PetData.breedsData[selectedMajor]!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final minor = _petData[selectedMajor]!.keys
|
final minor = PetData
|
||||||
|
.breedsData[selectedMajor]!
|
||||||
|
.keys
|
||||||
.elementAt(index);
|
.elementAt(index);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
@ -746,10 +811,10 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 품종 리스트 가져오기
|
// 3. 품종 리스트 가져오기
|
||||||
final List<String> originalList =
|
final List<String> originalList = PetData
|
||||||
_petData[_currentMajorCategory]![_currentMinorCategory]!
|
.breedsData[_currentMajorCategory]![_currentMinorCategory]!
|
||||||
.where((e) => e != '기타(직접 입력)')
|
.where((e) => e != '기타(직접 입력)')
|
||||||
.toList();
|
.toList();
|
||||||
// '기타(직접 입력)'은 리스트 마지막에 고정하거나 별도 처리, 여기서는 필터링 후 맨 뒤에 붙일 예정
|
// '기타(직접 입력)'은 리스트 마지막에 고정하거나 별도 처리, 여기서는 필터링 후 맨 뒤에 붙일 예정
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@ -1359,7 +1424,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
child: _profileImage == null
|
child: _profileImage == null
|
||||||
? Center(
|
? Center(
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
'assets/icons/profileicon.svg',
|
'assets/icons/profile_icon.svg',
|
||||||
width: 40.w,
|
width: 40.w,
|
||||||
colorFilter: ColorFilter.mode(
|
colorFilter: ColorFilter.mode(
|
||||||
Colors.grey[400]!,
|
Colors.grey[400]!,
|
||||||
@ -1401,15 +1466,14 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
|
|
||||||
// 2. 반려동물 이름 입력
|
// 2. 반려동물 이름 입력
|
||||||
_buildLabel('반려동물 이름 입력', isRequired: true),
|
_buildLabel('반려동물 이름 입력', isRequired: true),
|
||||||
SizedBox(height: 8.h),
|
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
hint: '이름 입력 (2~10글자/영문/숫자/한글)',
|
hint: '이름 입력 (2~10글자/한글/영문/숫자)',
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(10), // 최대 10글자 제한
|
LengthLimitingTextInputFormatter(10), // 최대 10글자 제한
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
// 3. 선택 박스들 (종, 품종, 성별)
|
// 3. 선택 박스들 (종, 품종, 성별)
|
||||||
_buildSearchField(
|
_buildSearchField(
|
||||||
@ -1417,31 +1481,34 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
controller: _speciesController,
|
controller: _speciesController,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: _showSpeciesSelectionModal,
|
onTap: _showSpeciesSelectionModal,
|
||||||
|
isRequired: true,
|
||||||
),
|
),
|
||||||
SizedBox(height: 12.h),
|
SizedBox(height: 20.h),
|
||||||
_buildSearchField(
|
_buildSearchField(
|
||||||
'반려동물 품종 선택',
|
'반려동물 품종 선택',
|
||||||
controller: _breedController,
|
controller: _breedController,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: _showBreedSelectionModal,
|
onTap: _showBreedSelectionModal,
|
||||||
|
isRequired: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
SizedBox(height: 20.h),
|
||||||
_buildSearchField(
|
_buildSearchField(
|
||||||
'반려동물 성별',
|
'반려동물 성별',
|
||||||
controller: _genderController,
|
controller: _genderController,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTap: _showGenderSelectionModal,
|
onTap: _showGenderSelectionModal,
|
||||||
|
isRequired: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
// 4. 생년월일
|
// 4. 생년월일
|
||||||
_buildLabel('반려동물 생년월일', isRequired: true),
|
_buildLabel('반려동물 생년월일', isRequired: true),
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
controller: _yearController,
|
controller: _yearController,
|
||||||
|
focusNode: _yearFocus,
|
||||||
hint: 'YYYY',
|
hint: 'YYYY',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
hintColor: _isDateUnknown
|
hintColor: _isDateUnknown
|
||||||
@ -1453,12 +1520,18 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
LengthLimitingTextInputFormatter(4),
|
LengthLimitingTextInputFormatter(4),
|
||||||
],
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value.length == 4) {
|
||||||
|
FocusScope.of(context).requestFocus(_monthFocus);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
controller: _monthController,
|
controller: _monthController,
|
||||||
|
focusNode: _monthFocus,
|
||||||
hint: 'MM',
|
hint: 'MM',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
hintColor: _isDateUnknown
|
hintColor: _isDateUnknown
|
||||||
@ -1469,14 +1542,20 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
LengthLimitingTextInputFormatter(2),
|
LengthLimitingTextInputFormatter(2),
|
||||||
_DateRangeInputFormatter(min: 1, max: 12),
|
DateRangeInputFormatter(min: 1, max: 12),
|
||||||
],
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value.length == 2) {
|
||||||
|
FocusScope.of(context).requestFocus(_dayFocus);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
controller: _dayController,
|
controller: _dayController,
|
||||||
|
focusNode: _dayFocus,
|
||||||
hint: 'DD',
|
hint: 'DD',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
hintColor: _isDateUnknown
|
hintColor: _isDateUnknown
|
||||||
@ -1487,7 +1566,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
LengthLimitingTextInputFormatter(2),
|
LengthLimitingTextInputFormatter(2),
|
||||||
_DayInputFormatter(
|
DayInputFormatter(
|
||||||
monthController: _monthController,
|
monthController: _monthController,
|
||||||
yearController: _yearController,
|
yearController: _yearController,
|
||||||
),
|
),
|
||||||
@ -1525,9 +1604,10 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
|
|
||||||
// 5. 동물 등록 번호
|
// 5. 동물 등록 번호
|
||||||
_buildLabel('동물 등록 번호', isRequired: false),
|
_buildLabel('동물 등록 번호', isRequired: false),
|
||||||
const SizedBox(height: 8),
|
// const SizedBox(height: 8),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
hint: '동물 등록 번호 입력',
|
controller: _registrationNumberController,
|
||||||
|
hint: '숫자만 입력',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.digitsOnly,
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
@ -1564,7 +1644,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
SizedBox(height: 20.h),
|
||||||
_buildSearchField(
|
_buildSearchField(
|
||||||
'과거 진단받은 질병',
|
'과거 진단받은 질병',
|
||||||
controller: _pastDiseaseController,
|
controller: _pastDiseaseController,
|
||||||
@ -1591,7 +1671,7 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
SizedBox(height: 20.h),
|
||||||
_buildSearchField(
|
_buildSearchField(
|
||||||
'염려되는 건강 문제',
|
'염려되는 건강 문제',
|
||||||
controller: _healthConcernController,
|
controller: _healthConcernController,
|
||||||
@ -1626,23 +1706,34 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 52.h,
|
height: 52.h,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {},
|
onPressed: (_isFormValid && !_isLoading) ? _registerPet : null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.highlight,
|
backgroundColor: AppColors.highlight,
|
||||||
|
disabledBackgroundColor: AppColors.inactive,
|
||||||
|
disabledForegroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(30.r),
|
borderRadius: BorderRadius.circular(30.r),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: _isLoading
|
||||||
'반려동물 등록',
|
? SizedBox(
|
||||||
style: TextStyle(
|
width: 24.w,
|
||||||
fontFamily: 'SCDream',
|
height: 24.w,
|
||||||
fontSize: 16.sp,
|
child: const CircularProgressIndicator(
|
||||||
fontWeight: FontWeight.bold,
|
color: Colors.white,
|
||||||
color: Colors.white,
|
strokeWidth: 2,
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
: Text(
|
||||||
|
'반려동물 등록',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
@ -1655,19 +1746,27 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
// Helper Widget: 라벨 (필수 표시 포함)
|
// Helper Widget: 라벨 (필수 표시 포함)
|
||||||
Widget _buildLabel(String text, {bool isRequired = false}) {
|
Widget _buildLabel(String text, {bool isRequired = false}) {
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (isRequired)
|
if (isRequired)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 4.w),
|
padding: EdgeInsets.only(right: 4.w),
|
||||||
child: Icon(Icons.circle, size: 4.w, color: Colors.red),
|
child: Container(
|
||||||
|
width: 4.w,
|
||||||
|
height: 4.w,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
text,
|
text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'SCDream',
|
fontFamily: 'SCDream',
|
||||||
fontSize: 13.sp,
|
fontSize: 14.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.black87,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -1683,9 +1782,13 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
TextEditingController? controller,
|
TextEditingController? controller,
|
||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
List<TextInputFormatter>? inputFormatters,
|
List<TextInputFormatter>? inputFormatters,
|
||||||
|
FocusNode? focusNode,
|
||||||
|
ValueChanged<String>? onChanged,
|
||||||
}) {
|
}) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
onChanged: onChanged,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
@ -1750,19 +1853,15 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
TextEditingController? controller,
|
TextEditingController? controller,
|
||||||
VoidCallback? onTap,
|
VoidCallback? onTap,
|
||||||
bool readOnly = false,
|
bool readOnly = false,
|
||||||
|
bool isRequired = false, // Added isRequired param
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
_buildLabel(
|
||||||
label,
|
label,
|
||||||
style: TextStyle(
|
isRequired: isRequired,
|
||||||
fontFamily: 'SCDream',
|
), // Use _buildLabel to show label with red dot
|
||||||
fontSize: 13.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller, // 컨트롤러 연결
|
controller: controller, // 컨트롤러 연결
|
||||||
onTap: onTap, // 탭 이벤트 연결
|
onTap: onTap, // 탭 이벤트 연결
|
||||||
@ -1792,86 +1891,3 @@ class _PetRegistrationScreenState extends State<PetRegistrationScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 범위 제한 Formatter (월: 1~12)
|
|
||||||
class _DateRangeInputFormatter extends TextInputFormatter {
|
|
||||||
final int min;
|
|
||||||
final int max;
|
|
||||||
|
|
||||||
_DateRangeInputFormatter({required this.min, required this.max});
|
|
||||||
|
|
||||||
@override
|
|
||||||
TextEditingValue formatEditUpdate(
|
|
||||||
TextEditingValue oldValue,
|
|
||||||
TextEditingValue newValue,
|
|
||||||
) {
|
|
||||||
if (newValue.text.isEmpty) {
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int? value = int.tryParse(newValue.text);
|
|
||||||
if (value == null) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < 0) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 입력 중에는 자릿수 제한이 있으므로, max값 초과 여부만 확인
|
|
||||||
if (value > max) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 일(Day) 입력 Formatter (월에 따라 28~31일 제한)
|
|
||||||
class _DayInputFormatter extends TextInputFormatter {
|
|
||||||
final TextEditingController monthController;
|
|
||||||
final TextEditingController yearController;
|
|
||||||
|
|
||||||
_DayInputFormatter({
|
|
||||||
required this.monthController,
|
|
||||||
required this.yearController,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
TextEditingValue formatEditUpdate(
|
|
||||||
TextEditingValue oldValue,
|
|
||||||
TextEditingValue newValue,
|
|
||||||
) {
|
|
||||||
if (newValue.text.isEmpty) {
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int? day = int.tryParse(newValue.text);
|
|
||||||
if (day == null) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxDay = 31;
|
|
||||||
final int? month = int.tryParse(monthController.text);
|
|
||||||
|
|
||||||
if (month != null) {
|
|
||||||
if (month == 2) {
|
|
||||||
maxDay = 29; // 기본 29일
|
|
||||||
// 윤년 계산
|
|
||||||
final int? year = int.tryParse(yearController.text);
|
|
||||||
if (year != null) {
|
|
||||||
bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
|
||||||
maxDay = isLeap ? 29 : 28;
|
|
||||||
}
|
|
||||||
} else if ([4, 6, 9, 11].contains(month)) {
|
|
||||||
maxDay = 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (day > maxDay) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
87
app/lib/screens/register_complete_screen.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import '../theme/app_colors.dart';
|
||||||
|
import 'main_screen.dart';
|
||||||
|
|
||||||
|
class RegisterCompleteScreen extends StatelessWidget {
|
||||||
|
const RegisterCompleteScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
// TODO: Add a success image/icon here if available
|
||||||
|
Icon(
|
||||||
|
Icons.check_circle_outline,
|
||||||
|
size: 100.w,
|
||||||
|
color: AppColors.highlight,
|
||||||
|
),
|
||||||
|
SizedBox(height: 24.h),
|
||||||
|
Text(
|
||||||
|
'반려동물 등록 완료!',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 24.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: const Color(0xFF1F1F1F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
Text(
|
||||||
|
'이제 RUP과 함께\n똑똑한 반려생활을 시작해보세요.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: const Color(0xFF7D7C7C),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 52.h,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// 메인 화면으로 이동하면서 스택 초기화
|
||||||
|
Navigator.pushAndRemoveUntil(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const MainScreen(),
|
||||||
|
),
|
||||||
|
(route) => false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.highlight,
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'홈으로 가기',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -193,11 +193,13 @@ class AuthService {
|
|||||||
await _storage.write(key: 'accessToken', value: accessToken);
|
await _storage.write(key: 'accessToken', value: accessToken);
|
||||||
await _storage.write(key: 'refreshToken', value: refreshToken);
|
await _storage.write(key: 'refreshToken', value: refreshToken);
|
||||||
|
|
||||||
// Firebase 로그인 처리 (필요시) - idToken으로 credential 생성 가능하지만 access token이 없으므로 생략하거나
|
// Firebase 로그인 처리
|
||||||
// 이미 signInWithGoogle에서 받아온 credential을 재사용할 수 있으면 좋음.
|
// idToken으로 Credential 생성하여 로그인
|
||||||
// 하지만 여기서는 백엔드 세션이 중요하므로 일단 백엔드 토큰만 저장.
|
final OAuthCredential credential = GoogleAuthProvider.credential(
|
||||||
// (Firebase Auth와 Custom Backend Auth를 혼용 중이라 복잡함.
|
idToken: idToken,
|
||||||
// 일단 백엔드 로직이 메인이므로 백엔드 토큰 처리에 집중)
|
accessToken: null, // accessToken은 필수가 아님 (idToken만으로 가능)
|
||||||
|
);
|
||||||
|
await _auth.signInWithCredential(credential);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
98
app/lib/services/firestore_service.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_storage/firebase_storage.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
import '../models/pet_model.dart';
|
||||||
|
import '../utils/log_manager.dart';
|
||||||
|
|
||||||
|
class FirestoreService {
|
||||||
|
final FirebaseFirestore _db = FirebaseFirestore.instance;
|
||||||
|
final FirebaseStorage _storage = FirebaseStorage.instance;
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
|
||||||
|
// 반려동물 등록
|
||||||
|
Future<void> registerPet(Pet pet, File? imageFile) async {
|
||||||
|
try {
|
||||||
|
String? imageUrl;
|
||||||
|
|
||||||
|
// 1. 이미지 업로드 (이미지가 있는 경우)
|
||||||
|
if (imageFile != null) {
|
||||||
|
final String fileName =
|
||||||
|
'${pet.id}_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
final Reference storageRef = _storage
|
||||||
|
.ref()
|
||||||
|
.child('pet_images')
|
||||||
|
.child(fileName);
|
||||||
|
|
||||||
|
LogManager().addLog(
|
||||||
|
'[Storage] Starting upload to ${storageRef.fullPath}',
|
||||||
|
);
|
||||||
|
LogManager().addLog('[Storage] Bucket: ${_storage.bucket}');
|
||||||
|
|
||||||
|
final TaskSnapshot snapshot = await storageRef.putFile(imageFile);
|
||||||
|
|
||||||
|
if (snapshot.state == TaskState.success) {
|
||||||
|
LogManager().addLog(
|
||||||
|
'[Storage] Upload success. Validating metadata...',
|
||||||
|
);
|
||||||
|
// 잠시 대기 (consistency 이슈 방지)
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
imageUrl = await storageRef.getDownloadURL();
|
||||||
|
LogManager().addLog('[Storage] Download URL retrieved: $imageUrl');
|
||||||
|
} else {
|
||||||
|
throw Exception('이미지 업로드 실패 (상태: ${snapshot.state})');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Pet 객체에 이미지 URL 업데이트 (새로운 객체 생성)
|
||||||
|
Pet petWithImage = Pet(
|
||||||
|
id: pet.id,
|
||||||
|
ownerId: pet.ownerId,
|
||||||
|
name: pet.name,
|
||||||
|
species: pet.species,
|
||||||
|
breed: pet.breed,
|
||||||
|
gender: pet.gender,
|
||||||
|
isNeutered: pet.isNeutered,
|
||||||
|
birthDate: pet.birthDate,
|
||||||
|
isDateUnknown: pet.isDateUnknown,
|
||||||
|
registrationNumber: pet.registrationNumber,
|
||||||
|
profileImageUrl: imageUrl, // 이미지 URL 설정
|
||||||
|
diseases: pet.diseases,
|
||||||
|
pastDiseases: pet.pastDiseases,
|
||||||
|
healthConcerns: pet.healthConcerns,
|
||||||
|
createdAt: pet.createdAt,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Firestore에 저장
|
||||||
|
await _db.collection('pets').doc(pet.id).set(petWithImage.toMap());
|
||||||
|
|
||||||
|
LogManager().addLog('[Firestore] Pet registered successfully: ${pet.id}');
|
||||||
|
} catch (e) {
|
||||||
|
LogManager().addLog('[Firestore] Error registering pet: $e');
|
||||||
|
throw Exception('반려동물 등록 실패: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 로그인한 사용자의 ID 가져오기
|
||||||
|
String? getCurrentUserId() {
|
||||||
|
return _auth.currentUser?.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 반려동물 리스트 스트림 가져오기
|
||||||
|
Stream<List<Pet>> getPets(String userId) {
|
||||||
|
return _db
|
||||||
|
.collection('pets')
|
||||||
|
.where('ownerId', isEqualTo: userId)
|
||||||
|
.orderBy('createdAt', descending: true) // 최신 등록순
|
||||||
|
.snapshots()
|
||||||
|
.map((snapshot) {
|
||||||
|
return snapshot.docs.map((doc) => Pet.fromMap(doc.data())).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 Pet ID 생성
|
||||||
|
String generatePetId() {
|
||||||
|
return const Uuid().v4();
|
||||||
|
}
|
||||||
|
}
|
||||||
78
app/lib/widgets/pet_registration/input_formatters.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
// 범위 제한 Formatter (월: 1~12)
|
||||||
|
class DateRangeInputFormatter extends TextInputFormatter {
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
|
||||||
|
DateRangeInputFormatter({required this.min, required this.max});
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue,
|
||||||
|
TextEditingValue newValue,
|
||||||
|
) {
|
||||||
|
if (newValue.text.isEmpty) {
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? value = int.tryParse(newValue.text);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return oldValue; // 숫자가 아닌 경우 이전 값 유지
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < min || value > max) {
|
||||||
|
// 범위를 벗어나면 이전 값 유지 (단, 입력 중인 상태 고려 - 예: 3을 입력하려는데 30이 되면 안됨)
|
||||||
|
// 여기서는 단순하게 max보다 크면 입력 불가 처리 (사용자 경험상 이게 나을 수 있음)
|
||||||
|
if (newValue.text.length > max.toString().length) {
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
// 더 정교한 로직이 필요할 수 있으나, 기본적으로 입력 막음
|
||||||
|
if (value > max) return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 일(Day) 입력 Formatter (월에 따라 28~31일 제한)
|
||||||
|
class DayInputFormatter extends TextInputFormatter {
|
||||||
|
final TextEditingController yearController;
|
||||||
|
final TextEditingController monthController;
|
||||||
|
|
||||||
|
DayInputFormatter({
|
||||||
|
required this.yearController,
|
||||||
|
required this.monthController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue,
|
||||||
|
TextEditingValue newValue,
|
||||||
|
) {
|
||||||
|
if (newValue.text.isEmpty) {
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? day = int.tryParse(newValue.text);
|
||||||
|
if (day == null) {
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int year = int.tryParse(yearController.text) ?? DateTime.now().year;
|
||||||
|
int month = int.tryParse(monthController.text) ?? 1;
|
||||||
|
|
||||||
|
int maxDay = DateTime(year, month + 1, 0).day; // 해당 월의 마지막 날짜 계산
|
||||||
|
|
||||||
|
if (day < 1 || day > maxDay) {
|
||||||
|
if (newValue.text.length > maxDay.toString().length) {
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
if (day > maxDay) return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
215
app/lib/widgets/pet_registration/selection_modal.dart
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
class SelectionModal extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final List<String> currentSelected;
|
||||||
|
final String currentOtherText;
|
||||||
|
final Function(List<String>, String) onComplete;
|
||||||
|
final bool isSingleSelection;
|
||||||
|
final List<String> itemList;
|
||||||
|
|
||||||
|
const SelectionModal({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.currentSelected,
|
||||||
|
required this.currentOtherText,
|
||||||
|
required this.onComplete,
|
||||||
|
this.isSingleSelection = false,
|
||||||
|
required this.itemList,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SelectionModal> createState() => _SelectionModalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectionModalState extends State<SelectionModal> {
|
||||||
|
late List<String> _tempSelected;
|
||||||
|
final TextEditingController _otherController = TextEditingController();
|
||||||
|
bool _isOtherSelected = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_tempSelected = List.from(widget.currentSelected);
|
||||||
|
_otherController.text = widget.currentOtherText;
|
||||||
|
|
||||||
|
// "기타"가 선택되어 있는지 확인 (여기서는 단순히 리스트에 있는지로 판단하지 않고,
|
||||||
|
// "기타"라는 항목 텍스트 자체를 체크하거나, 외부에서 넘겨받은 텍스트가 있으면 체크)
|
||||||
|
// *로직 수정*: itemList에 "기타"가 포함되어 있다면 그것을 기준으로 함.
|
||||||
|
if (_tempSelected.contains("기타")) {
|
||||||
|
_isOtherSelected = true;
|
||||||
|
} else if (widget.currentOtherText.isNotEmpty) {
|
||||||
|
// 기존 로직상 기타 텍스트가 있으면 기타가 선택된 것으로 간주할 수도 있음
|
||||||
|
// 하지만 리스트 기반이므로 명시적으로 "기타" 아이템이 있어야 함.
|
||||||
|
// 여기서는 단순화하여 UI 상태만 초기화.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_otherController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onItemTap(String item) {
|
||||||
|
setState(() {
|
||||||
|
if (item == "기타") {
|
||||||
|
if (widget.isSingleSelection) {
|
||||||
|
_tempSelected.clear();
|
||||||
|
_tempSelected.add(item);
|
||||||
|
_isOtherSelected = true;
|
||||||
|
} else {
|
||||||
|
if (_tempSelected.contains(item)) {
|
||||||
|
_tempSelected.remove(item);
|
||||||
|
_isOtherSelected = false;
|
||||||
|
} else {
|
||||||
|
_tempSelected.add(item);
|
||||||
|
_isOtherSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (widget.isSingleSelection) {
|
||||||
|
_tempSelected.clear();
|
||||||
|
_tempSelected.add(item);
|
||||||
|
// 단일 선택에서 다른거 누르면 기타 해제
|
||||||
|
_isOtherSelected = false;
|
||||||
|
} else {
|
||||||
|
if (_tempSelected.contains(item)) {
|
||||||
|
_tempSelected.remove(item);
|
||||||
|
} else {
|
||||||
|
_tempSelected.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.7,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||||
|
itemCount: widget.itemList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = widget.itemList[index];
|
||||||
|
final isSelected = _tempSelected.contains(item);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
|
side: BorderSide(
|
||||||
|
color: isSelected
|
||||||
|
? const Color(0xFFFF7500)
|
||||||
|
: Colors.grey[300]!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tileColor: isSelected
|
||||||
|
? const Color(0xFFFF7500).withValues(
|
||||||
|
alpha: 0.1,
|
||||||
|
) // Fixed deprecated
|
||||||
|
: Colors.white,
|
||||||
|
title: Text(
|
||||||
|
item,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontSize: 15.sp,
|
||||||
|
color: isSelected
|
||||||
|
? const Color(0xFFFF7500)
|
||||||
|
: Colors.black,
|
||||||
|
fontWeight: isSelected
|
||||||
|
? FontWeight.bold
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: isSelected
|
||||||
|
? Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: const Color(0xFFFF7500),
|
||||||
|
size: 20.w,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onTap: () => _onItemTap(item),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
// 기타 선택 시 입력창 표시
|
||||||
|
if (item == "기타" && isSelected)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 10.w,
|
||||||
|
right: 10.w,
|
||||||
|
bottom: 20.h,
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _otherController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '내용을 입력해주세요',
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
border: UnderlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(20.w),
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.h,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.onComplete(
|
||||||
|
_tempSelected,
|
||||||
|
_isOtherSelected ? _otherController.text : '',
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFFF7500),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'선택 완료',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'SCDream',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
|
|||||||
set(BINARY_NAME "app")
|
set(BINARY_NAME "app")
|
||||||
# The unique GTK application identifier for this application. See:
|
# The unique GTK application identifier for this application. See:
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
set(APPLICATION_ID "com.example.app")
|
set(APPLICATION_ID "com.daoblock.rup")
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
# versions of CMake.
|
# versions of CMake.
|
||||||
|
|||||||
@ -5,17 +5,21 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import cloud_firestore
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
|
import firebase_storage
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import google_sign_in_ios
|
import google_sign_in_ios
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
|
|||||||
@ -385,7 +385,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
||||||
@ -399,7 +399,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
||||||
@ -413,7 +413,7 @@
|
|||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup.RunnerTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app";
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
PRODUCT_NAME = app
|
PRODUCT_NAME = app
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app
|
PRODUCT_BUNDLE_IDENTIFIER = com.daoblock.rup
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
|
||||||
|
|||||||
3
app/move_file.ps1
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
New-Item -ItemType Directory -Force -Path 'android/app/src/main/kotlin/com/daoblock/rup'
|
||||||
|
Move-Item -Path 'android/app/src/main/kotlin/com/example/app/MainActivity.kt' -Destination 'android/app/src/main/kotlin/com/daoblock/rup/MainActivity.kt'
|
||||||
|
Remove-Item -Recurse -Force 'android/app/src/main/kotlin/com/example/app'
|
||||||
@ -49,6 +49,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
cloud_firestore:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cloud_firestore
|
||||||
|
sha256: "2d33da4465bdb81b6685c41b535895065adcb16261beb398f5f3bbc623979e9c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.6.12"
|
||||||
|
cloud_firestore_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cloud_firestore_platform_interface
|
||||||
|
sha256: "413c4e01895cf9cb3de36fa5c219479e06cd4722876274ace5dfc9f13ab2e39b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.6.12"
|
||||||
|
cloud_firestore_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cloud_firestore_web
|
||||||
|
sha256: c1e30fc4a0fcedb08723fb4b1f12ee4e56d937cbf9deae1bda43cbb6367bb4cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.4.12"
|
||||||
code_assets:
|
code_assets:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -217,6 +241,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.24.1"
|
version: "2.24.1"
|
||||||
|
firebase_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_storage
|
||||||
|
sha256: "958fc88a7ef0b103e694d30beed515c8f9472dde7e8459b029d0e32b8ff03463"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.4.10"
|
||||||
|
firebase_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_storage_platform_interface
|
||||||
|
sha256: d2661c05293c2a940c8ea4bc0444e1b5566c79dd3202c2271140c082c8cd8dd4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.10"
|
||||||
|
firebase_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_storage_web
|
||||||
|
sha256: "629a557c5e1ddb97a3666cbf225e97daa0a66335dbbfdfdce113ef9f881e833f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.10.17"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -717,6 +773,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
uuid:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -43,6 +43,9 @@ dependencies:
|
|||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
image_picker: ^1.2.1
|
image_picker: ^1.2.1
|
||||||
flutter_screenutil: ^5.9.3
|
flutter_screenutil: ^5.9.3
|
||||||
|
cloud_firestore: ^5.0.0
|
||||||
|
firebase_storage: ^12.0.0
|
||||||
|
uuid: ^4.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@ -6,18 +6,24 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
#include <firebase_auth/firebase_auth_plugin_c_api.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
FirebaseAuthPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
cloud_firestore
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_auth
|
firebase_auth
|
||||||
firebase_core
|
firebase_core
|
||||||
|
firebase_storage
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||