반려동물 정보 db등록 (파이어베이스)

This commit is contained in:
youngbeom 2026-01-25 13:41:27 +09:00
parent c36fe45df2
commit 8dc2524ba6
41 changed files with 1406 additions and 312 deletions

View File

@ -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

View File

@ -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",

View File

@ -0,0 +1,5 @@
package com.daoblock.rup
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@ -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()

View File

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 485 B

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View 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

View File

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 741 B

View File

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View 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

View 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

View File

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

View File

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

View File

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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
View File

@ -0,0 +1,135 @@
class PetData {
static const List<String> diseaseList = [
"피부질환",
"눈 질환",
"치아 / 구강 질환",
"뼈 / 관절 질환",
"생식기 / 비뇨기 질환",
"심장 / 혈관 질환",
"소화기 질환",
"호흡기 질환",
"내분비계 질환",
"뇌신경 질환",
"생식기 질환",
"귀 질환",
"코 질환",
"기타",
];
static const Map<String, Map<String, List<String>>> breedsData = {
"포유류": {
"강아지": [
"말티즈",
"푸들",
"포메라니안",
"믹스견",
"치와와",
"시츄",
"비숑 프리제",
"골든 리트리버",
"진돗개",
"웰시 코기",
"프렌치 불독",
"시바견",
"닥스후트",
"요크셔 테리어",
"보더 콜리",
"사모예드",
"허스키",
"말라뮤트",
"기타(직접 입력)",
],
"고양이": [
"코리안 숏헤어",
"브리티시 숏헤어",
"아메리칸 숏헤어",
"뱅갈",
"메인쿤",
"데본 렉스",
"페르시안",
"러시안 블루",
"",
"렉돌",
"스코티시 폴드",
"먼치킨",
"노르웨이 숲",
"믹스묘",
"기타(직접 입력)",
],
"햄스터": ["정글리안", "", "푸딩", "골든 햄스터", "로보로브스키", "기타(직접 입력)"],
"토끼": ["롭이어", "더치", "라이언 헤드", "드워프", "렉스", "기타(직접 입력)"],
"기니피그": ["잉글리쉬", "아비시니안", "페루비안", "실키", "기타(직접 입력)"],
"고슴도치": ["플라티나", "화이트 초코", "알비노", "핀토", "기타(직접 입력)"],
"기타(직접 입력)": ["기타(직접 입력)"],
},
"파충류": {
"거북이": ["커먼 머스크 터틀", "레이저백", "육지거북", "붉은귀거북", "남생이", "기타(직접 입력)"],
"도마뱀": [
"크레스티드 게코",
"리키에너스 게코",
"가고일 게코",
"레오파드 게코",
"비어디 드래곤",
"블루텅 스킨크",
"이구아나",
"기타(직접 입력)",
],
"": [
"볼 파이톤",
"가터 스네이크",
"호그노즈 스네이크",
"콘 스네이크",
"킹 스네이크",
"밀크 스네이크",
"기타(직접 입력)",
],
"기타(직접 입력)": ["기타(직접 입력)"],
},
"조류": {
"앵무새(소/중형)": [
"사랑앵무(잉꼬)",
"코카티엘(왕관앵무)",
"모란앵무",
"코뉴어",
"퀘이커",
"카카리키",
"사자나미(빗금앵무)",
"유리앵무",
"기타(직접 입력)",
],
"앵무새(대형)": [
"뉴기니아",
"회색앵무",
"금강앵무(마카우)",
"유황앵무(코카투)",
"아마존앵무",
"대본영",
"기타(직접 입력)",
],
"핀치/관상조": ["카나리아", "십자매", "문조", "금화조", "호금조", "백문조", "기타(직접 입력)"],
"비둘기/닭/메추리": [
"애완용 비둘기",
"관상닭(실키 등)",
"메추리",
"미니메추리",
"오리/거위",
"기타(직접 입력)",
],
"기타(직접 입력)": ["직접 입력"],
},
"양서류": {
"개구리": ["청개구리", "팩맨", "다트 프록", "화이트 트리 프록", "기타(직접 입력)"],
"도룡뇽": ["우파루파", "파이어 벨리 뉴트", "타이거 살라만더", "기타(직접 입력)"],
"기타(직접 입력)": ["기타(직접 입력)"],
},
"어류": {
"열대어": ["구피", "베타", "테트라", "디스커스", "엔젤피쉬", "기타(직접 입력)"],
"금붕어/잉어": ["금붕어", "비단잉어", "기타(직접 입력)"],
"해수어": ["크라운피쉬(니모)", "블루탱", "기타(직접 입력)"],
"기타(직접 입력)": ["기타(직접 입력)"],
},
"기타(직접 입력)": {
"기타(직접 입력)": ["기타(직접 입력)"],
},
};
}

View File

@ -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(),
},
); );
}, },
); );

View 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(),
);
}
}

View File

@ -1,26 +1,131 @@
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 @override
Widget build(BuildContext context) { State<HomeScreen> createState() => _HomeScreenState();
return Scaffold( }
backgroundColor: Colors.white,
body: SafeArea( 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( child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( SizedBox(height: 20.h),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h), Text(
child: Material( '반려동물 선택',
color: Colors.transparent, style: TextStyle(
child: InkWell( fontFamily: 'SCDream',
borderRadius: BorderRadius.circular( fontSize: 18.sp,
8.r, fontWeight: FontWeight.bold,
), // Optional: Add radius ),
),
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: () { onTap: () {
Navigator.pop(context);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -28,8 +133,64 @@ class HomeScreen extends StatelessWidget {
), ),
); );
}, },
),
SizedBox(height: 30.h),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
if (_userId == null) {
return const Scaffold(body: Center(child: Text('로그인이 필요합니다.')));
}
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: StreamBuilder<List<Pet>>(
stream: _firestoreService.getPets(_userId!),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('오류가 발생했습니다: ${snapshot.error}'));
}
final pets = snapshot.data ?? [];
// : UI ( )
if (pets.isEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.w,
vertical: 20.h,
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(8.r),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const PetRegistrationScreen(),
),
);
},
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, // Wrap content only mainAxisSize: MainAxisSize.min,
children: [ children: [
Image.asset( Image.asset(
'assets/img/profile.png', 'assets/img/profile.png',
@ -52,20 +213,108 @@ class HomeScreen extends StatelessWidget {
), ),
), ),
), ),
),
Expanded( Expanded(
child: Center( child: Center(
child: Text( child: Text(
'로그인 성공!\n여기는 메인 홈 화면입니다.', '등록된 반려동물이 없습니다.\n새로운 가족을 등록해주세요!',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontFamily: 'SCDream', fontFamily: 'SCDream',
fontWeight: FontWeight.w500, fontSize: 16.sp,
fontSize: 18.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(
child: Text(
'안녕, ${displayPet.name}!',
style: TextStyle(
fontFamily: 'SCDream',
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
},
), ),
), ),
); );

View File

@ -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,
), ),
], ],

View File

@ -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: '내 정보',
), ),

View File

@ -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,8 +811,8 @@ 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();
// '기타(직접 입력)' , // '기타(직접 입력)' ,
@ -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,15 +1706,26 @@ 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(
width: 24.w,
height: 24.w,
child: const CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Text(
'반려동물 등록', '반려동물 등록',
style: TextStyle( style: TextStyle(
fontFamily: 'SCDream', fontFamily: 'SCDream',
@ -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;
}
}

View 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),
],
),
),
),
);
}
}

View File

@ -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;
} }

View 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();
}
}

View 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;
}
}

View 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,
),
),
),
),
),
],
),
);
}
}

View File

@ -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.

View File

@ -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"))

View File

@ -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";

View File

@ -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
View 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'

View File

@ -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:

View File

@ -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:

View File

@ -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"));
} }

View File

@ -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
) )