diff --git a/.DS_Store b/.DS_Store index 2a559d6..c86c2f8 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml index 75e5d7d..cfc0249 100644 --- a/frontend/android/app/src/main/AndroidManifest.xml +++ b/frontend/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,8 @@ + + + + + diff --git a/frontend/assets/icons/bookmark.svg b/frontend/assets/icons/bookmark.svg new file mode 100644 index 0000000..5772dc0 --- /dev/null +++ b/frontend/assets/icons/bookmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icons/camera.svg b/frontend/assets/icons/camera.svg new file mode 100644 index 0000000..7a43902 --- /dev/null +++ b/frontend/assets/icons/camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icons/home.svg b/frontend/assets/icons/home.svg new file mode 100644 index 0000000..b3c34d8 --- /dev/null +++ b/frontend/assets/icons/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icons/main_background.svg b/frontend/assets/icons/main_background.svg new file mode 100644 index 0000000..607b2d4 --- /dev/null +++ b/frontend/assets/icons/main_background.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/assets/icons/mypage.svg b/frontend/assets/icons/mypage.svg new file mode 100644 index 0000000..81f7de7 --- /dev/null +++ b/frontend/assets/icons/mypage.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/assets/icons/search.svg b/frontend/assets/icons/search.svg index 73710ad..7009ddc 100644 --- a/frontend/assets/icons/search.svg +++ b/frontend/assets/icons/search.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/frontend/assets/icons/camera.png b/frontend/assets/icons/splash_camera.png similarity index 100% rename from frontend/assets/icons/camera.png rename to frontend/assets/icons/splash_camera.png diff --git a/frontend/assets/images/frame1.png b/frontend/assets/images/frame1.png new file mode 100644 index 0000000..13ac0c4 Binary files /dev/null and b/frontend/assets/images/frame1.png differ diff --git a/frontend/assets/images/frame2.png b/frontend/assets/images/frame2.png new file mode 100644 index 0000000..79364b9 Binary files /dev/null and b/frontend/assets/images/frame2.png differ diff --git a/frontend/assets/images/frame3.png b/frontend/assets/images/frame3.png new file mode 100644 index 0000000..43c6468 Binary files /dev/null and b/frontend/assets/images/frame3.png differ diff --git a/frontend/ios/Podfile.lock b/frontend/ios/Podfile.lock index 54d472e..1afc32c 100644 --- a/frontend/ios/Podfile.lock +++ b/frontend/ios/Podfile.lock @@ -1,37 +1,28 @@ PODS: + - camera_avfoundation (0.0.1): + - Flutter - Flutter (1.0.0) - - Google-Maps-iOS-Utils (5.0.0): - - GoogleMaps (~> 8.0) - - google_maps_flutter_ios (0.0.1): + - path_provider_foundation (0.0.1): - Flutter - - Google-Maps-iOS-Utils (< 7.0, >= 5.0) - - GoogleMaps (< 10.0, >= 8.4) - - GoogleMaps (8.4.0): - - GoogleMaps/Maps (= 8.4.0) - - GoogleMaps/Base (8.4.0) - - GoogleMaps/Maps (8.4.0): - - GoogleMaps/Base + - FlutterMacOS DEPENDENCIES: + - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - Flutter (from `Flutter`) - - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) - -SPEC REPOS: - trunk: - - Google-Maps-iOS-Utils - - GoogleMaps + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) EXTERNAL SOURCES: + camera_avfoundation: + :path: ".symlinks/plugins/camera_avfoundation/ios" Flutter: :path: Flutter - google_maps_flutter_ios: - :path: ".symlinks/plugins/google_maps_flutter_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" SPEC CHECKSUMS: + camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 - google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 - GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PODFILE CHECKSUM: 1ae9405747c555b46415ec1fb8dd3e15c1cb409e diff --git a/frontend/ios/Runner.xcodeproj/project.pbxproj b/frontend/ios/Runner.xcodeproj/project.pbxproj index 85c470b..5da95c3 100644 --- a/frontend/ios/Runner.xcodeproj/project.pbxproj +++ b/frontend/ios/Runner.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E734B2CE766F2A51B71DDC0C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCFB601F6DD8C7C9F338624E /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -32,6 +33,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3D3114C7B8486E364A41DE02 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -42,6 +44,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5185CD4AFAB24EED1F5A9FD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + FCFB601F6DD8C7C9F338624E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FDC8C249584EA57F36AB8655 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,31 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E734B2CE766F2A51B71DDC0C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 11F484E32DBBFA57728D0092 /* Pods */ = { + isa = PBXGroup; + children = ( + 3D3114C7B8486E364A41DE02 /* Pods-Runner.debug.xcconfig */, + FDC8C249584EA57F36AB8655 /* Pods-Runner.release.xcconfig */, + B5185CD4AFAB24EED1F5A9FD /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 23808300D93881BD2EE67EF3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FCFB601F6DD8C7C9F338624E /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +96,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 11F484E32DBBFA57728D0092 /* Pods */, + 23808300D93881BD2EE67EF3 /* Frameworks */, ); sourceTree = ""; }; @@ -105,12 +131,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + D1C57AC243232AF71A47000D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 9444F69BED7E6069CC700313 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,6 +213,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 9444F69BED7E6069CC700313 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -200,6 +245,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + D1C57AC243232AF71A47000D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -298,7 +365,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = com.pknu.duckdam; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -427,7 +494,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = com.pknu.duckdam; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -450,7 +517,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.frontend; + PRODUCT_BUNDLE_IDENTIFIER = com.pknu.duckdam; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/frontend/ios/Runner.xcworkspace/contents.xcworkspacedata b/frontend/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/frontend/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/frontend/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist index 1392c3a..ce8e69d 100644 --- a/frontend/ios/Runner/Info.plist +++ b/frontend/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,12 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCameraUsageDescription + We need access to the camera to take pictures. + NSMicrophoneUsageDescription + We need access to the microphone to record videos. + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,9 +51,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/frontend/lib/colors/app_colors.dart b/frontend/lib/colors/app_colors.dart index 0db7482..2b26362 100644 --- a/frontend/lib/colors/app_colors.dart +++ b/frontend/lib/colors/app_colors.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; class AppColors { - static const Color primaryColor = Color(0xffffffff); - static const Color selectedColor = Color(0xa0a0a0a0); - static const Color noSelectedColor = Color(0xcccccccc); - static const Color backgroundColor = Color(0xd9d9d9d9); + static const Color mainSkyColor = Color(0xFFB9EEF7); + static const Color mainWhiteColor = Color(0xFFFFFFFF); + static const Color mainOrangeColor = Color(0xFFFE8E5E); + static const Color recentSearchColor = Color(0xFFDCEDF0); + static const Color thinFontsColor = Color(0xFF93A5A9); + static const Color boldFontsColor = Color(0xFF567278); } diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index d79b070..f4cac07 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:frontend/pages/my_page.dart'; -import 'package:frontend/pages/detail_page.dart'; +import 'package:frontend/pages/home_page.dart'; +import 'package:frontend/pages/search_page.dart'; void main() { runApp(const MyApp()); @@ -13,6 +13,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + // debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData(fontFamily: "Pretendard"), home: const MyHomePage(title: 'Flutter Demo Home Page'), @@ -32,9 +33,6 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { @override Widget build(BuildContext context) { - // return const HomePage(); - // return const SearchPage(); - // return const MyPage(); - return const DetailPage(); + return const HomePage(); } } diff --git a/frontend/lib/models/content_detail.dart b/frontend/lib/models/content_detail.dart new file mode 100644 index 0000000..63ed560 --- /dev/null +++ b/frontend/lib/models/content_detail.dart @@ -0,0 +1,89 @@ +class ContentDetail { + final int contentId; + final String contentTitle; + final String contentImage; + final String contentType; + final String year; + final bool isScraped; + final List addressTag; + final List placeDtos; + + ContentDetail({ + required this.contentId, + required this.contentTitle, + required this.contentImage, + required this.contentType, + required this.year, + required this.isScraped, + required this.addressTag, + required this.placeDtos, + }); + + factory ContentDetail.fromJson(Map json) { + return ContentDetail( + contentId: json['contentId'], + contentTitle: json['contentTitle'], + contentImage: json['contentImage'], + contentType: json['contentType'], + year: json['year'], + isScraped: json['isScraped'], + addressTag: List.from(json['addressTag']), + placeDtos: (json['placeDtos'] as List) + .map((place) => PlaceDto.fromJson(place)) + .toList(), + ); + } + + Map toJson() { + return { + 'contentId': contentId, + 'contentTitle': contentTitle, + 'contentImage': contentImage, + 'contentType': contentType, + 'year': year, + 'isScraped': isScraped, + 'addressTag': addressTag, + 'placeDtos': placeDtos.map((place) => place.toJson()).toList(), + }; + } +} + +class PlaceDto { + final int placeId; + final String placeName; + final String placeOverview; + final String placeImage; + final String placeAddress; + final String openTime; + + PlaceDto({ + required this.placeId, + required this.placeName, + required this.placeOverview, + required this.placeImage, + required this.placeAddress, + required this.openTime, + }); + + factory PlaceDto.fromJson(Map json) { + return PlaceDto( + placeId: json['placeId'], + placeName: json['placeName'], + placeOverview: json['placeOverview'], + placeImage: json['placeImage'], + placeAddress: json['placeAddress'], + openTime: json['openTime'], + ); + } + + Map toJson() { + return { + 'placeId': placeId, + 'placeName': placeName, + 'placeOverview': placeOverview, + 'placeImage': placeImage, + 'placeAddress': placeAddress, + 'openTime': openTime, + }; + } +} diff --git a/frontend/lib/models/content_dto.dart b/frontend/lib/models/content_dto.dart new file mode 100644 index 0000000..0e05e51 --- /dev/null +++ b/frontend/lib/models/content_dto.dart @@ -0,0 +1,57 @@ +class ContentDto { + final int? contentId; + final String? contentTitle; + final String? contentImage; + final bool? isScraped; + final String? address; + + ContentDto({ + required this.contentId, + required this.contentTitle, + required this.contentImage, + required this.isScraped, + required this.address, + }); + + // JSON 데이터를 Dart 객체로 변환하는 factory 생성자 + factory ContentDto.fromJson(Map json) { + return ContentDto( + contentId: json['contentId'], + contentTitle: json['contentTitle'], + contentImage: json['contentImage'], + isScraped: json['isScraped'], + address: json['address'], + ); + } + + // Dart 객체를 JSON 데이터로 변환하는 메서드 + Map toJson() { + return { + 'contentId': contentId, + 'contentTitle': contentTitle, + 'contentImage': contentImage, + 'isScraped': isScraped, + 'address': address, + }; + } +} + +class ContentList { + final List contentDtos; + + ContentList({required this.contentDtos}); + + factory ContentList.fromJson(Map json) { + var list = json['contentDtos'] as List; + List contentDtoList = + list.map((item) => ContentDto.fromJson(item)).toList(); + + return ContentList(contentDtos: contentDtoList); + } + + Map toJson() { + return { + 'contentDtos': contentDtos.map((item) => item.toJson()).toList(), + }; + } +} diff --git a/frontend/lib/models/search_content_dto.dart b/frontend/lib/models/search_content_dto.dart new file mode 100644 index 0000000..33dc0b4 --- /dev/null +++ b/frontend/lib/models/search_content_dto.dart @@ -0,0 +1,55 @@ +class SearchContentResponse { + final bool hasNextPage; + final List searchContentDtos; + + SearchContentResponse({ + required this.hasNextPage, + required this.searchContentDtos, + }); + + factory SearchContentResponse.fromJson(Map json) { + return SearchContentResponse( + hasNextPage: json['hasNextPage'], + searchContentDtos: List.from( + json['searchContentDtos'] + .map((item) => SearchContentDto.fromJson(item)), + ), + ); + } + + Map toJson() { + return { + 'hasNextPage': hasNextPage, + 'searchContentDtos': + searchContentDtos.map((item) => item.toJson()).toList(), + }; + } +} + +class SearchContentDto { + final int contentId; + final String contentTitle; + final String contentImage; + + SearchContentDto({ + required this.contentId, + required this.contentTitle, + required this.contentImage, + }); + + factory SearchContentDto.fromJson(Map json) { + return SearchContentDto( + contentId: json['contentId'], + contentTitle: json['contentTitle'], + contentImage: json['contentImage'], + ); + } + + Map toJson() { + return { + 'contentId': contentId, + 'contentTitle': contentTitle, + 'contentImage': contentImage, + }; + } +} diff --git a/frontend/lib/pages/camera_page.dart b/frontend/lib/pages/camera_page.dart new file mode 100644 index 0000000..9f7df3b --- /dev/null +++ b/frontend/lib/pages/camera_page.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:camera/camera.dart'; +import 'package:path_provider/path_provider.dart'; +import 'dart:io'; +import 'dart:math' as math; +import 'package:path/path.dart' show join; + +class CameraPage extends StatefulWidget { + final String? imagePath; + + const CameraPage({ + super.key, + required this.imagePath, + }); + + @override + _CameraPageState createState() => _CameraPageState(); +} + +class _CameraPageState extends State { + CameraController? _cameraController; + List? cameras; + + @override + void initState() { + super.initState(); + _initializeCamera(); + } + + Future _initializeCamera() async { + cameras = await availableCameras(); + _cameraController = CameraController(cameras![0], ResolutionPreset.high); + await _cameraController!.initialize(); + setState(() {}); + } + + Future _takePicture() async { + if (!_cameraController!.value.isInitialized) { + return; + } + + if (_cameraController!.value.isTakingPicture) { + return; + } + + try { + final directory = await getTemporaryDirectory(); + final String filePath = join( + directory.path, + '${DateTime.now().millisecondsSinceEpoch}.png', + ); + + XFile picture = await _cameraController!.takePicture(); + final File image = File(picture.path); + final savedImagePath = await image.copy(filePath); + + print('Picture saved to $savedImagePath'); + } catch (e) { + print(e); + } + } + + @override + void dispose() { + _cameraController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _cameraController == null || !_cameraController!.value.isInitialized + ? const Center(child: CircularProgressIndicator()) + : Stack( + children: [ + CameraPreview(_cameraController!), + Positioned.fill( + child: FittedBox( + fit: BoxFit.contain, + child: Transform.rotate( + angle: math.pi / 2, + child: Image.asset( + widget.imagePath ?? '', + fit: BoxFit.cover, + ), + ), + ), + ), + Positioned( + bottom: 30, + right: 30, + child: IconButton( + icon: const Icon(Icons.camera_alt, + color: Colors.white, size: 30), + onPressed: _takePicture, + ), + ), + ], + ), + ); + } +} diff --git a/frontend/lib/pages/detail_page.dart b/frontend/lib/pages/detail_page.dart index d300e93..be56e3f 100644 --- a/frontend/lib/pages/detail_page.dart +++ b/frontend/lib/pages/detail_page.dart @@ -1,96 +1,166 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/models/content_detail.dart'; +import 'package:frontend/services/api_service.dart'; import 'package:frontend/widgets/custom_bottom_navigation_bar.dart'; import 'package:frontend/widgets/detail_app_bar.dart'; +import 'package:frontend/pages/camera_page.dart'; -class DetailPage extends StatelessWidget { - const DetailPage({super.key}); +class DetailPage extends StatefulWidget { + const DetailPage({super.key, required this.contentId}); + + final int contentId; + + @override + _DetailPageState createState() => _DetailPageState(); +} + +class _DetailPageState extends State { + ContentDetail? contentDetail; + String contentName = ''; + + @override + void initState() { + super.initState(); + print(widget.contentId); + _fetchContentDetails(); + } + + Future _fetchContentDetails() async { + ContentDetail? fetchedContent = + await APIService.getContentDetail(widget.contentId); + print(fetchedContent); + setState(() { + contentDetail = fetchedContent; + contentName = contentDetail!.contentTitle; + }); + } @override Widget build(BuildContext context) { return Scaffold( + extendBodyBehindAppBar: true, appBar: const DetailAppBar(), - body: CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 400.0, - pinned: true, - flexibleSpace: FlexibleSpaceBar( - background: Stack( - fit: StackFit.expand, - children: [ - Image.asset( - 'assets/icons/logo.png', - fit: BoxFit.cover, - ), - Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.transparent, Colors.black54], - ), - ), - ), - Positioned( - bottom: 16.0, // 텍스트와 버튼의 위치 조정 - left: 16.0, - right: 16.0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + body: contentDetail == null + ? const Center(child: CircularProgressIndicator()) + : CustomScrollView( + slivers: [ + SliverAppBar( + automaticallyImplyLeading: false, + expandedHeight: 400.0, + pinned: true, + flexibleSpace: FlexibleSpaceBar( + background: Stack( + fit: StackFit.expand, children: [ - const Text( - '아기 공룡 둘리', - style: TextStyle( - color: Colors.white, - fontSize: 24.0, - fontWeight: FontWeight.bold, + Image.network( + contentDetail!.contentImage, + fit: BoxFit.cover, + ), + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black54], + ), ), ), - const SizedBox(height: 8.0), - Row( - children: [ - _buildLocationButton('부산'), - const SizedBox(width: 8.0), - _buildLocationButton('울산'), - const SizedBox(width: 8.0), - _buildLocationButton('경남'), - ], + Positioned( + bottom: 16.0, + left: 16.0, + right: 16.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + contentDetail!.contentTitle, + style: const TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + width: 5, + ), + Text( + contentDetail!.year, + style: const TextStyle( + color: Colors.white, + fontSize: 20.0, + fontWeight: FontWeight.normal, + ), + ), + ], + ), + const SizedBox(height: 8.0), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + SvgPicture.asset( + 'assets/icons/location.svg', + color: AppColors.mainWhiteColor, + height: 20, + ), + const SizedBox( + width: 8.0), // 아이콘과 첫 번째 버튼 사이 간격 + ...contentDetail!.addressTag.map((tag) { + return Padding( + padding: + const EdgeInsets.only(right: 8.0), + child: _buildLocationButton(tag), + ); + }).toList(), + ], + ), + ) + ], + ), ), ], ), ), - ], - ), - ), - ), - SliverToBoxAdapter( - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), ), - ), - padding: const EdgeInsets.all(16.0), - child: ListView.builder( - shrinkWrap: true, - physics: - const NeverScrollableScrollPhysics(), // ListView 자체의 스크롤을 막고 전체 페이지 스크롤 사용 - itemCount: 10, - itemBuilder: (context, index) { - return _buildListItem(index + 1, '항목 제목', '항목 설명'); - }, - ), + SliverToBoxAdapter( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + padding: const EdgeInsets.all(16.0), + child: ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: contentDetail!.placeDtos.length, + itemBuilder: (context, index) { + final place = contentDetail!.placeDtos[index]; + return _buildListItem( + place.placeImage, + place.placeName, + place.placeOverview, + place.placeAddress, + ); + }, + ), + ), + ), + ], ), - ), - ], - ), bottomNavigationBar: const CustomBottomNavigationBar(), ); } - Widget _buildListItem(int index, String title, String description) { + Widget _buildListItem(String placeImage, String title, String description, + String placeAddress) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -105,9 +175,15 @@ class DetailPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( - height: 80.0, - width: 80.0, - color: Colors.grey, + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14.0), + image: DecorationImage( + image: NetworkImage(placeImage), + fit: BoxFit.cover, + ), + ), ), const SizedBox(width: 16.0), Expanded( @@ -115,7 +191,7 @@ class DetailPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '$index화 / $title', + title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16.0, @@ -130,18 +206,18 @@ class DetailPage extends StatelessWidget { ), ), const SizedBox(height: 4.0), - const Row( + Row( children: [ - Icon( + const Icon( Icons.place, color: Colors.grey, size: 14.0, ), - SizedBox(width: 4.0), + const SizedBox(width: 4.0), Expanded( child: Text( - '부산광역시 남구 용소로45 국립부산대학교', - style: TextStyle( + placeAddress, + style: const TextStyle( fontSize: 12.0, color: Colors.grey, ), @@ -152,12 +228,20 @@ class DetailPage extends StatelessWidget { ], ), ), - IconButton( - icon: const Icon(Icons.file_download), - onPressed: () { - // 다운로드 버튼 동작 + InkWell( + child: SvgPicture.asset('assets/icons/camera.svg'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CameraPage( + imagePath: contentName == '극한직업' + ? 'assets/images/frame1.png' + : 'assets/images/frame2.png', + ), + ), + ); }, - alignment: Alignment.center, // 아이콘 위치를 중앙으로 맞춤 ), ], ), @@ -165,26 +249,26 @@ class DetailPage extends StatelessWidget { const Divider( color: Colors.orange, thickness: 2.0, - height: 32.0, // 항목 위아래 간격을 동일하게 맞추기 위해 높이 설정 + height: 32.0, ), ], ); } -} -Widget _buildLocationButton(String label) { - return ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white.withOpacity(0.8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), + Widget _buildLocationButton(String label) { + return ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white.withOpacity(0.8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric(horizontal: 16.0), + ), + child: Text( + label, + style: const TextStyle(color: Colors.black), ), - padding: const EdgeInsets.symmetric(horizontal: 16.0), - ), - child: Text( - label, - style: const TextStyle(color: Colors.black), - ), - ); + ); + } } diff --git a/frontend/lib/pages/home_page.dart b/frontend/lib/pages/home_page.dart index 5d4be52..9200041 100644 --- a/frontend/lib/pages/home_page.dart +++ b/frontend/lib/pages/home_page.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:frontend/widgets/main_app_bar.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/models/content_dto.dart'; +import 'package:frontend/services/api_service.dart'; import 'package:frontend/widgets/custom_bottom_navigation_bar.dart'; import 'package:frontend/widgets/home_recommend_list.dart'; +import 'package:frontend/widgets/main_app_bar.dart'; import 'package:frontend/widgets/search_box.dart'; class HomePage extends StatefulWidget { @@ -13,27 +16,156 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { + List contentDto = []; + List simpleContentDto = []; + bool isLoading = true; + + @override + void initState() { + super.initState(); + _fetchData(); + print(simpleContentDto); + } + + Future _fetchData() async { + try { + final fetchedContentDto = await APIService.getContentDto(); + final fetchedSimpleContentDto = await APIService.getSimpleContentDto(); + + setState(() { + contentDto = fetchedContentDto; + simpleContentDto = fetchedSimpleContentDto; + isLoading = false; + }); + } catch (error) { + setState(() { + isLoading = false; // 오류가 발생해도 로딩 상태 해제 + }); + print('데이터를 불러오는 중 오류가 발생했습니다: $error'); + } + } + @override Widget build(BuildContext context) { - return const Scaffold( - appBar: MainAppBar(), - backgroundColor: AppColors.backgroundColor, - body: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Column( - children: [ - Text('data'), - SizedBox( - height: 200, - child: HomeRecommendList(), + return Scaffold( + extendBodyBehindAppBar: true, + appBar: const MainAppBar(), + body: isLoading + ? const Center(child: CircularProgressIndicator()) // 로딩 중일 때 표시 + : SingleChildScrollView( + child: Column( + children: [ + Container( + height: 600, + width: double.infinity, + decoration: const BoxDecoration( + color: AppColors.mainSkyColor, + ), + child: Stack( + children: [ + Positioned( + top: 80, + right: 30, + child: SvgPicture.asset( + 'assets/icons/main_duckcheol.svg'), + ), + Positioned( + top: 60, + left: 20, + width: 100, + child: Image.asset('assets/icons/logo.png'), + ), + const Positioned( + top: 150, + left: 20, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '안녕하세요, 오리님', + style: TextStyle( + color: AppColors.boldFontsColor, + fontSize: 23, + fontWeight: FontWeight.bold, + ), + ), + SizedBox( + height: 20, + ), + Text( + '오늘의 추천 촬영지는\n바로 여기임', + style: TextStyle( + color: AppColors.thinFontsColor, + fontSize: 16, + ), + ), + ], + ), + ), + Positioned( + bottom: 10, + left: 0, + child: SizedBox( + height: 280, + child: HomeRecommendList( + contentDtos: contentDto, + type: 1, + ), + ), + ), + ], + ), + ), + Container( + decoration: const BoxDecoration( + color: AppColors.mainWhiteColor, + ), + child: Column( + children: [ + const SizedBox( + height: 30, + ), + const SearchBox( + isComeFromHome: true, + isDrama: false, + ), + const SizedBox( + height: 30, + ), + const Row( + children: [ + Padding( + padding: EdgeInsets.only(left: 20), + child: Text( + "오리님의 관심있는 콘텐츠", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox( + height: 15, + ), + SizedBox( + height: 250, + child: HomeRecommendList( + contentDtos: simpleContentDto, + type: 2, + ), + ), + ], + ), + ), + const SizedBox( + height: 15, + ), + ], ), - SearchBox(), - ], - ), - ), - ), - bottomNavigationBar: CustomBottomNavigationBar(), + ), + bottomNavigationBar: const CustomBottomNavigationBar(), ); } } diff --git a/frontend/lib/pages/my_page.dart b/frontend/lib/pages/my_page.dart index 70c4110..154b686 100644 --- a/frontend/lib/pages/my_page.dart +++ b/frontend/lib/pages/my_page.dart @@ -17,7 +17,7 @@ class _MyPageState extends State { Widget build(BuildContext context) { return const Scaffold( appBar: MyAppBar(), - backgroundColor: AppColors.backgroundColor, + backgroundColor: AppColors.mainWhiteColor, body: Column( children: [ Padding( diff --git a/frontend/lib/pages/search_page.dart b/frontend/lib/pages/search_page.dart index 847252b..d902887 100644 --- a/frontend/lib/pages/search_page.dart +++ b/frontend/lib/pages/search_page.dart @@ -1,41 +1,171 @@ import 'package:flutter/material.dart'; import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/models/search_content_dto.dart'; +import 'package:frontend/services/api_service.dart'; import 'package:frontend/widgets/custom_bottom_navigation_bar.dart'; import 'package:frontend/widgets/search_box.dart'; import 'package:frontend/widgets/recent_search_word.dart'; import 'package:frontend/widgets/select_movie_drama_tab.dart'; import 'package:frontend/widgets/selectable_circle.dart'; +import 'package:frontend/pages/detail_page.dart'; // DetailPage 임포트 class SearchPage extends StatefulWidget { - const SearchPage({super.key}); + const SearchPage({ + super.key, + required this.isDrama, + this.searchContentResponse, + this.hint, + }); + + final bool isDrama; + final String? hint; + final SearchContentResponse? searchContentResponse; @override _SearchPageState createState() => _SearchPageState(); } class _SearchPageState extends State { + late bool isDrama; + String? hint; + late List keywords = []; + late List selectableCircles = []; + + @override + void initState() { + super.initState(); + isDrama = widget.isDrama; + hint = widget.hint; + _fetchRecentSearches(); + _generateSelectableCircles(); + } + + Future _fetchRecentSearches() async { + final recentKeywords = await APIService.getRecentSearch( + hint ?? '', isDrama ? 'Drama' : 'Movie'); + if (recentKeywords != null) { + setState(() { + keywords = recentKeywords; + }); + } + } + + void _generateSelectableCircles() { + if (widget.searchContentResponse != null) { + selectableCircles = widget.searchContentResponse!.searchContentDtos + .map((dto) => SelectableCircle( + contentTitle: dto.contentTitle, + contentImage: dto.contentImage, + onTap: () { + // DetailPage로 이동 + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + DetailPage(contentId: dto.contentId), + ), + ); + }, + )) + .toList(); + } + } + + void _onTabSelected(int index) { + setState( + () { + isDrama = index == 1; // '드라마' 탭이 선택되면 true, '영화' 탭이 선택되면 false + }, + ); + + // 선택된 탭에 따라 필요한 작업을 수행 + _fetchRecentSearches(); + } + @override Widget build(BuildContext context) { - return const Scaffold( - backgroundColor: AppColors.backgroundColor, - body: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only( - left: 10.0, - right: 10.0, - top: 70.0, - ), - child: Column( + return Scaffold( + backgroundColor: AppColors.mainWhiteColor, + body: Stack( + children: [ + Column( children: [ - SearchBox(), - RecentSearchWord(), - SelectMovieDramaTab(), - SelectableCircle(), + Container( + height: 115, // 원하는 높이 설정 (SearchBox 높이의 절반) + color: AppColors.mainSkyColor, + ), + Expanded( + child: Container( + color: AppColors.mainWhiteColor, + ), + ), ], ), - ), + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: 15.0, + right: 15.0, + top: 70.0, + ), + child: Column( + children: [ + SearchBox( + isComeFromHome: false, + isDrama: isDrama, + hint: hint, // hint를 SearchBox에 전달 + ), + const SizedBox( + height: 5.0, + ), + const Row( + children: [ + Text( + "최근 검색어", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox( + height: 10.0, + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: keywords + .map((keyword) => RecentSearchWord(text: keyword)) + .toList(), + ), + ), + const SizedBox( + height: 30.0, + ), + SelectMovieDramaTab(onTabSelected: _onTabSelected), + GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 가로로 3개씩 배치 + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + ), + itemCount: selectableCircles.length, + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), // 전체 페이지 스크롤 사용 + itemBuilder: (context, index) { + return selectableCircles[index]; + }, + ), + ], + ), + ), + ), + ], ), - bottomNavigationBar: CustomBottomNavigationBar(), + bottomNavigationBar: const CustomBottomNavigationBar(), ); } } diff --git a/frontend/lib/services/api_service.dart b/frontend/lib/services/api_service.dart new file mode 100644 index 0000000..35f4aff --- /dev/null +++ b/frontend/lib/services/api_service.dart @@ -0,0 +1,153 @@ +import 'dart:convert'; +import 'package:frontend/models/content_detail.dart'; +import 'package:http/http.dart' as http; +import 'dart:async'; +import 'package:http_parser/http_parser.dart'; +import 'package:frontend/models/content_dto.dart'; +import 'package:frontend/models/search_content_dto.dart'; + +const String domainUrl = + 'https://port-0-hackathon-be-lyqylohp8957ca6e.sel5.cloudtype.app'; + +class APIService { + static Future> getContentDto() async { + const url = '$domainUrl/api/content/interest?userId=1&pagesize=5'; + + try { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + print("불러오기 성공"); + final responseData = json.decode(utf8.decode(response.bodyBytes)); + final data = responseData['contentDtos']; + + if (data != null && data is List) { + return data + .map((item) => ContentDto.fromJson(item)) + .toList(); + } + } + return []; + } catch (error) { + return []; + } + } + + static Future> getSimpleContentDto() async { + const url = '$domainUrl/api/content/scrap?userId=1'; + + try { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + print("불러오기 성공"); + final responseData = json.decode(utf8.decode(response.bodyBytes)); + final data = responseData['simpleContentDtos']; + + if (data != null && data is List) { + return data + .map((item) => ContentDto.fromJson(item)) + .toList(); + } + } + return []; + } catch (error) { + return []; + } + } + + static Future getContentDetail(int contentId) async { + final url = '$domainUrl/api/detail/content?userId=1&contentId=$contentId'; + + print(url); + + try { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + print("불러오기 성공"); + final responseData = json.decode(utf8.decode(response.bodyBytes)); + print(responseData); + + if (responseData != null && responseData is Map) { + return ContentDetail.fromJson(responseData); + } + } + return null; + } catch (error) { + print("에러 발생: $error"); + return null; + } + } + + static Future search( + String keyword, String type) async { + final url = + '$domainUrl/api/content/search?userId=1&keyword=$keyword&searchType=$type&pagesize=9&pageindex=0'; + try { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + print("불러오기 성공"); + final responseData = json.decode(utf8.decode(response.bodyBytes)); + print(responseData); + + if (responseData != null && responseData is Map) { + return SearchContentResponse.fromJson(responseData); + } + } + return null; + } catch (error) { + print("에러 발생: $error"); + return null; + } + } + + static Future?> getRecentSearch( + String keyword, String type) async { + const url = '$domainUrl/api/content/search/recent/keyword?userId=1'; + try { + final response = await http.get( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + print("불러오기 성공"); + final responseData = json.decode(utf8.decode(response.bodyBytes)); + print(responseData); + + if (responseData != null && responseData is Map) { + print(responseData['keywords']); + return responseData['keywords']; + } + } + return null; + } catch (error) { + print("에러 발생: $error"); + return null; + } + } +} diff --git a/frontend/lib/widgets/custom_bottom_navigation_bar.dart b/frontend/lib/widgets/custom_bottom_navigation_bar.dart index f21e20e..690e878 100644 --- a/frontend/lib/widgets/custom_bottom_navigation_bar.dart +++ b/frontend/lib/widgets/custom_bottom_navigation_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:frontend/colors/app_colors.dart'; class CustomBottomNavigationBar extends StatefulWidget { @@ -12,6 +13,8 @@ class CustomBottomNavigationBar extends StatefulWidget { class _CustomBottomNavigationBarState extends State { int _selectedIndex = 0; + final List _pages = []; + // 탭 클릭시 호출될 함수 void _onItemTapped(int index) { setState(() { @@ -22,27 +25,27 @@ class _CustomBottomNavigationBarState extends State { @override Widget build(BuildContext context) { return BottomNavigationBar( - items: const [ + items: [ BottomNavigationBarItem( - icon: Icon(Icons.home), + icon: SvgPicture.asset('assets/icons/home.svg'), label: '홈', ), BottomNavigationBarItem( - icon: Icon(Icons.search), + icon: SvgPicture.asset('assets/icons/search.svg'), label: '검색', ), BottomNavigationBarItem( - icon: Icon(Icons.person), + icon: SvgPicture.asset('assets/icons/camera.svg'), label: '카메라', ), BottomNavigationBarItem( - icon: Icon(Icons.person), + icon: SvgPicture.asset('assets/icons/mypage.svg'), label: '마이페이지', ), ], currentIndex: _selectedIndex, - selectedItemColor: AppColors.selectedColor, - unselectedItemColor: AppColors.noSelectedColor, + selectedItemColor: AppColors.mainOrangeColor, + unselectedItemColor: AppColors.mainOrangeColor, onTap: _onItemTapped, type: BottomNavigationBarType.fixed, ); diff --git a/frontend/lib/widgets/detail_app_bar.dart b/frontend/lib/widgets/detail_app_bar.dart index 761cfaf..f8165e1 100644 --- a/frontend/lib/widgets/detail_app_bar.dart +++ b/frontend/lib/widgets/detail_app_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/colors/app_colors.dart'; class DetailAppBar extends StatelessWidget implements PreferredSizeWidget { const DetailAppBar({super.key}); @@ -7,21 +8,25 @@ class DetailAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - leading: Padding( - padding: const EdgeInsets.only(left: 20.0), - child: SvgPicture.asset( - 'assets/icons/menu.svg', - height: 20, + leading: IconButton( + icon: SvgPicture.asset( + 'assets/icons/back.svg', + color: AppColors.mainWhiteColor, + height: 25, ), + onPressed: () { + Navigator.of(context).pop(); // 뒤로 가기 버튼 동작 + }, + padding: const EdgeInsets.only(left: 16.0), // 아이콘과 화면 가장자리 사이의 여백 조정 ), + backgroundColor: Colors.transparent, actions: [ Padding( padding: const EdgeInsets.only(right: 20.0), child: SvgPicture.asset( - 'assets/icons/menu.svg', - height: 20, + 'assets/icons/bookmark.svg', + color: AppColors.mainWhiteColor, + height: 25, ), ), ], diff --git a/frontend/lib/widgets/home_recommend_list.dart b/frontend/lib/widgets/home_recommend_list.dart index f6aa6b7..5795fd9 100644 --- a/frontend/lib/widgets/home_recommend_list.dart +++ b/frontend/lib/widgets/home_recommend_list.dart @@ -1,7 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/models/content_dto.dart'; +import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/pages/detail_page.dart'; class HomeRecommendList extends StatefulWidget { - const HomeRecommendList({super.key}); + const HomeRecommendList( + {super.key, required this.contentDtos, required this.type}); + + final List contentDtos; + final int type; @override _HomeRecommendListState createState() => _HomeRecommendListState(); @@ -11,22 +19,50 @@ class _HomeRecommendListState extends State { @override Widget build(BuildContext context) { return ListView.builder( + shrinkWrap: true, scrollDirection: Axis.horizontal, - itemCount: 10, + itemCount: widget.contentDtos.length, itemBuilder: (context, index) { - return Column( - children: [ - Container( - width: 150, - height: 150, - margin: const EdgeInsets.all(8), - color: Colors.greenAccent, - ), - Text( - 'Item $index', - style: const TextStyle(color: Colors.white, fontSize: 24), - ), - ], + final content = widget.contentDtos[index]; + + return InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailPage( + contentId: content.contentId!, + )), + ); + }, + child: Column( + children: [ + Container( + width: 150, + height: 200, + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14.0), + image: DecorationImage( + image: NetworkImage(content.contentImage!), + fit: BoxFit.cover, + ), + ), + ), + Text( + content.contentTitle!, + style: const TextStyle( + color: AppColors.boldFontsColor, fontSize: 13), + ), + widget.type == 1 + ? Text( + content.address!, + style: const TextStyle( + color: AppColors.thinFontsColor, fontSize: 11), + ) + : Container(), + ], + ), ); }, ); diff --git a/frontend/lib/widgets/main_app_bar.dart b/frontend/lib/widgets/main_app_bar.dart index 552ac7a..078f55d 100644 --- a/frontend/lib/widgets/main_app_bar.dart +++ b/frontend/lib/widgets/main_app_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/colors/app_colors.dart'; class MainAppBar extends StatelessWidget implements PreferredSizeWidget { const MainAppBar({super.key}); @@ -9,18 +10,12 @@ class MainAppBar extends StatelessWidget implements PreferredSizeWidget { return AppBar( backgroundColor: Colors.transparent, elevation: 0, - // leading: Padding( - // padding: const EdgeInsets.only(left: 16.0), - // child: SvgPicture.asset( - // 'assets/icons/logo.svg', - // height: 40, - // ), - // ), actions: [ Padding( padding: const EdgeInsets.only(right: 20), child: SvgPicture.asset( 'assets/icons/menu.svg', + color: AppColors.mainOrangeColor, height: 20, ), ), diff --git a/frontend/lib/widgets/recent_search_word.dart b/frontend/lib/widgets/recent_search_word.dart index cc10106..50693c5 100644 --- a/frontend/lib/widgets/recent_search_word.dart +++ b/frontend/lib/widgets/recent_search_word.dart @@ -1,43 +1,41 @@ import 'package:flutter/material.dart'; +import 'package:frontend/colors/app_colors.dart'; class RecentSearchWord extends StatelessWidget { - const RecentSearchWord({super.key}); + final String text; - // final String text; - // final VoidCallback onDelete; - - // const RecentSearchWord({ - // Key? key, - // required this.text, - // required this.onDelete, - // }) : super(key: key); + const RecentSearchWord({ + super.key, + required this.text, + }); @override Widget build(BuildContext context) { return Container( - width: 100, margin: const EdgeInsets.symmetric(horizontal: 4.0), decoration: BoxDecoration( - color: Colors.lightBlueAccent.shade100, - borderRadius: BorderRadius.circular(15.0), + color: AppColors.recentSearchColor, + borderRadius: BorderRadius.circular(18.0), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Flexible( + Flexible( child: Text( - "안녕안녕안녕안녕안녕", + text, overflow: TextOverflow.fade, maxLines: 1, softWrap: false, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), ), ), const SizedBox(width: 4.0), GestureDetector( - // onTap: , + onTap: () { + // 삭제 로직 구현 + }, child: const Icon( Icons.cancel, size: 16.0, diff --git a/frontend/lib/widgets/search_box.dart b/frontend/lib/widgets/search_box.dart index 0b63d56..ca0e585 100644 --- a/frontend/lib/widgets/search_box.dart +++ b/frontend/lib/widgets/search_box.dart @@ -1,35 +1,112 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/colors/app_colors.dart'; +import 'package:frontend/pages/search_page.dart'; +import 'package:frontend/services/api_service.dart'; +import 'package:frontend/models/search_content_dto.dart'; class SearchBox extends StatefulWidget { - const SearchBox({super.key}); + const SearchBox({ + super.key, + required this.isComeFromHome, + required this.isDrama, + this.hint, + }); + + final bool isComeFromHome; + final bool isDrama; + final String? hint; @override _SearchBoxState createState() => _SearchBoxState(); } class _SearchBoxState extends State { + final TextEditingController textEditingController = TextEditingController(); + late String keyword; + + @override + void initState() { + super.initState(); + textEditingController.text = widget.hint ?? ''; + keyword = widget.hint ?? ''; + } + + void _onSearchIconTapped() async { + String type = widget.isDrama ? 'Drama' : 'Movie'; + SearchContentResponse? data = await APIService.search(keyword, type); + + if (widget.isComeFromHome) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SearchPage( + isDrama: widget.isDrama, + hint: textEditingController.text, + searchContentResponse: data, + ), + ), + ); + }); + } else { + // 홈에서 오지 않은 경우의 처리 + } + } + @override Widget build(BuildContext context) { return Container( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - TextField( - decoration: const InputDecoration( - hintText: '다양한 컨텐츠들을 검색해보아요.', - suffixIcon: Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)), + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + TextField( + controller: textEditingController, + decoration: InputDecoration( + hintText: widget.hint ?? '다양한 컨텐츠들을 검색해보아요.', + suffixIcon: InkWell( + onTap: _onSearchIconTapped, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + 'assets/icons/search.svg', + width: 20, + height: 20, + color: AppColors.mainOrangeColor, + ), + ), + ), + filled: true, + fillColor: Colors.white, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide( + color: AppColors.mainOrangeColor, + width: 3.0, + ), + ), + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide( + color: AppColors.mainOrangeColor, + width: 3.0, + ), + ), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide( + color: AppColors.mainOrangeColor, + width: 3.0, ), ), - onChanged: (text) { - // 검색어가 입력될 때 수행할 동작 - }, ), - // 다른 위젯 추가 가능 - ], - ), + onChanged: (text) { + setState(() { + keyword = text; + }); + }, + ), + ], ), ); } diff --git a/frontend/lib/widgets/select_movie_drama_tab.dart b/frontend/lib/widgets/select_movie_drama_tab.dart index df63e08..029d7a7 100644 --- a/frontend/lib/widgets/select_movie_drama_tab.dart +++ b/frontend/lib/widgets/select_movie_drama_tab.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:frontend/colors/app_colors.dart'; class SelectMovieDramaTab extends StatefulWidget { - const SelectMovieDramaTab({super.key}); + final ValueChanged onTabSelected; + + const SelectMovieDramaTab({super.key, required this.onTabSelected}); @override _SelectMovieDramaTabState createState() => _SelectMovieDramaTabState(); @@ -15,6 +18,11 @@ class _SelectMovieDramaTabState extends State void initState() { super.initState(); tabController = TabController(length: 2, vsync: this); + tabController.addListener(() { + if (!tabController.indexIsChanging) { + widget.onTabSelected(tabController.index); + } + }); } @override @@ -34,14 +42,22 @@ class _SelectMovieDramaTabState extends State indicator: const BoxDecoration( border: Border( bottom: BorderSide( - color: Colors.orange, + color: AppColors.mainOrangeColor, width: 4.0, ), ), ), - indicatorSize: TabBarIndicatorSize.tab, // 이 속성으로 인디케이터 크기를 탭 크기에 맞춤 + indicatorSize: TabBarIndicatorSize.tab, labelColor: Colors.black, unselectedLabelColor: Colors.grey, + labelStyle: const TextStyle( + fontSize: 18.0, // 텍스트 크기 + fontWeight: FontWeight.bold, // 텍스트 굵기 + ), + unselectedLabelStyle: const TextStyle( + fontSize: 16.0, // 선택되지 않은 탭의 텍스트 크기 + fontWeight: FontWeight.normal, // 선택되지 않은 탭의 텍스트 굵기 + ), ); } } diff --git a/frontend/lib/widgets/selectable_circle.dart b/frontend/lib/widgets/selectable_circle.dart index da05bf2..43d3272 100644 --- a/frontend/lib/widgets/selectable_circle.dart +++ b/frontend/lib/widgets/selectable_circle.dart @@ -1,10 +1,15 @@ import 'package:flutter/material.dart'; class SelectableCircle extends StatefulWidget { - // final String label; + final String contentTitle; + final String contentImage; + final VoidCallback onTap; // 추가된 onTap 콜백 const SelectableCircle({ Key? key, + required this.contentTitle, + required this.contentImage, + required this.onTap, // onTap 콜백을 받아옴 }) : super(key: key); @override @@ -23,15 +28,19 @@ class _SelectableCircleState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: _toggleSelection, + onTap: () { + _toggleSelection(); + widget.onTap(); // 탭 시 onTap 콜백 호출 + }, child: Column( children: [ Stack( alignment: Alignment.center, children: [ - // 회색 원 + // 이미지가 들어간 원 CircleAvatar( radius: 35, + backgroundImage: NetworkImage(widget.contentImage), backgroundColor: Colors.grey.shade300, ), // 주황색 테두리가 그려진 원 @@ -49,31 +58,17 @@ class _SelectableCircleState extends State { ], ), const SizedBox(height: 8), - Text("영화", - style: TextStyle(color: isSelected ? Colors.black : Colors.grey)), + Text( + widget.contentTitle, + style: TextStyle( + color: isSelected ? Colors.black : Colors.grey, + overflow: TextOverflow.ellipsis, + ), + maxLines: 1, + textAlign: TextAlign.center, + ), ], ), ); } } - -class CircleGrid extends StatelessWidget { - const CircleGrid({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return GridView.builder( - padding: const EdgeInsets.all(16.0), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, // 한 줄에 3개의 항목 - crossAxisSpacing: 10.0, - mainAxisSpacing: 10.0, - childAspectRatio: 1, // 정사각형 비율 - ), - itemCount: 9, // 예시를 위해 9개의 아이템을 표시 - itemBuilder: (context, index) { - return const SelectableCircle(); - }, - ); - } -} diff --git a/frontend/macos/Flutter/Flutter-Debug.xcconfig b/frontend/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/frontend/macos/Flutter/Flutter-Debug.xcconfig +++ b/frontend/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/macos/Flutter/Flutter-Release.xcconfig b/frontend/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/frontend/macos/Flutter/Flutter-Release.xcconfig +++ b/frontend/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e777c67 100644 --- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 685f5a9..3453d6d 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -25,6 +25,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + camera: + dependency: "direct main" + description: + name: camera + sha256: "26ff41045772153f222ffffecba711a206f670f5834d40ebf5eed3811692f167" + url: "https://pub.dev" + source: hosted + version: "0.11.0+2" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "8bd9cab67551642eb33ceb33ece7acc0890014fc90ddfae637c7e2b683657e65" + url: "https://pub.dev" + source: hosted + version: "0.6.7+2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "7c28969a975a7eb2349bc2cb2dfe3ad218a33dba9968ecfb181ce08c87486655" + url: "https://pub.dev" + source: hosted + version: "0.9.17+3" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 + url: "https://pub.dev" + source: hosted + version: "2.8.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + url: "https://pub.dev" + source: hosted + version: "0.3.5" characters: dependency: transitive description: @@ -49,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -65,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" flutter: dependency: "direct main" description: flutter @@ -78,6 +134,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + url: "https://pub.dev" + source: hosted + version: "2.0.21" flutter_svg: dependency: "direct main" description: @@ -91,6 +155,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: transitive description: @@ -139,6 +208,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + url: "https://pub.dev" + source: hosted + version: "2.4.0" matcher: dependency: transitive description: @@ -179,6 +256,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + url: "https://pub.dev" + source: hosted + version: "2.2.10" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -187,6 +312,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + rename: + dependency: "direct main" + description: + name: rename + sha256: "6ef5daf4b11130e71d93630cfb70725e5a35b19039739cfcd2b272c834ba25fe" + url: "https://pub.dev" + source: hosted + version: "3.0.2" sky_engine: dependency: transitive description: flutter @@ -216,6 +365,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -296,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: @@ -306,4 +471,4 @@ packages: version: "6.5.0" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index bd3481c..02b6e00 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -36,6 +36,9 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 flutter_svg: ^2.0.10+1 + camera: ^0.11.0+2 + rename: ^3.0.2 + path_provider: ^2.1.4 dev_dependencies: flutter_test: @@ -56,6 +59,17 @@ flutter: assets: - assets/icons/menu.svg - assets/icons/logo.png + - assets/icons/main_duckcheol.svg + - assets/icons/main_background.svg + - assets/icons/home.svg + - assets/icons/search.svg + - assets/icons/camera.svg + - assets/icons/mypage.svg + - assets/icons/location.svg + - assets/icons/back.svg + - assets/icons/bookmark.svg + - assets/images/frame1.png + - assets/images/frame2.png fonts: - family: Pretendard fonts: