diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..0ee24b4
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,7 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.3.0
+ hooks:
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
diff --git a/README.md b/README.md
index aadd767..08b4ab3 100644
--- a/README.md
+++ b/README.md
@@ -7,14 +7,15 @@
### 프로젝트 사용 기술
-* flutter
-* dart
-* flutter_bloc: 상태관리
-* autoRouter: navigation
-* get_it: di
-* dio: api
-* freezed: data generation
-* derry - script manager
+* Flutter, Dart
+* State Manegement : flutter_bloc
+* Navigation: auto_route
+* DI : get_it
+* REST API: dio
+* Data class: freezed
+* Script manager: derry
+* Resource manager: flutter_gen
+* Lint: flutter_lint
### Architecture
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 365de27..f18e0e4 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -3,4 +3,5 @@ include: package:flutter_lints/flutter.yaml
linter:
rules:
- - eol_at_end_of_file
+ - eol_at_end_of_file: true
+ - require_trailing_commas: true
diff --git a/android/app/build.gradle b/android/app/build.gradle
index c9b18ef..deca3bc 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -6,7 +6,7 @@ plugins {
}
android {
- namespace = "com.example.withu_app"
+ namespace = "com.android.withu"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -21,7 +21,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId = "com.example.withu_app"
+ applicationId = "com.android.withu"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
diff --git a/android/app/src/main/kotlin/com/example/withu_app/MainActivity.kt b/android/app/src/main/kotlin/com/example/withu_app/MainActivity.kt
index 8a826eb..356b852 100644
--- a/android/app/src/main/kotlin/com/example/withu_app/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/example/withu_app/MainActivity.kt
@@ -1,4 +1,4 @@
-package com.example.withu_app
+package com.android.withu
import io.flutter.embedding.android.FlutterActivity
diff --git a/assets/color/colors.xml b/assets/color/colors.xml
new file mode 100644
index 0000000..c0807f1
--- /dev/null
+++ b/assets/color/colors.xml
@@ -0,0 +1,8 @@
+
+ #142d00
+ #496433
+ #8a9680
+ #e6ede1
+ #F6BE2C
+ #ffffff
+
diff --git a/assets/translations/ko.json b/assets/translations/ko.json
new file mode 100644
index 0000000..e4830e8
--- /dev/null
+++ b/assets/translations/ko.json
@@ -0,0 +1,11 @@
+{
+ "appName": "위드유",
+ "temporarySave": "임시저장",
+ "inProgress": "진행",
+ "closed": "마감",
+ "photography": "촬영",
+ "catering": "케이터링",
+ "foodStyling": "푸드스타일링",
+ "florist": "플라워리스트",
+ "numberOfRecruits": "모집인원"
+}
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..d97f17e
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
new file mode 100644
index 0000000..c043b1f
--- /dev/null
+++ b/ios/Podfile.lock
@@ -0,0 +1,23 @@
+PODS:
+ - Flutter (1.0.0)
+ - shared_preferences_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ shared_preferences_foundation:
+ :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+
+SPEC CHECKSUMS:
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
+ shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
+
+PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
+
+COCOAPODS: 1.15.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index cd84c44..1cf6d4d 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -14,6 +14,8 @@
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 */; };
+ DBC749BBCEE1813469E9EBE9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54FC5508885571A5F027815E /* Pods_Runner.framework */; };
+ FC19169D40D3B30B1C8285AB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12ED764B4D86D43F555C363 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -40,11 +42,15 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 0D82AA4422652D8004F2A305 /* 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 = ""; };
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 = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 54FC5508885571A5F027815E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 5B89B99F570AD57467A5B205 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ 64CD9938BCF89D57A5460C0C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.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 = ""; };
@@ -55,13 +61,26 @@
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 = ""; };
+ A8B58E44B1EB758AA8A2DCCD /* 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 = ""; };
+ CD45469538CEE843A45C33C2 /* 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 = ""; };
+ E12ED764B4D86D43F555C363 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F93BE9EC760304A94A11FACA /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 93639A99A847B6F3892EE7F7 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FC19169D40D3B30B1C8285AB /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ DBC749BBCEE1813469E9EBE9 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -76,6 +95,15 @@
path = RunnerTests;
sourceTree = "";
};
+ 5940FA6FAF3401039AC3618C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 54FC5508885571A5F027815E /* Pods_Runner.framework */,
+ E12ED764B4D86D43F555C363 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -94,6 +122,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
+ B592DA797F244265F115B786 /* Pods */,
+ 5940FA6FAF3401039AC3618C /* Frameworks */,
);
sourceTree = "";
};
@@ -121,6 +151,20 @@
path = Runner;
sourceTree = "";
};
+ B592DA797F244265F115B786 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ CD45469538CEE843A45C33C2 /* Pods-Runner.debug.xcconfig */,
+ A8B58E44B1EB758AA8A2DCCD /* Pods-Runner.release.xcconfig */,
+ 0D82AA4422652D8004F2A305 /* Pods-Runner.profile.xcconfig */,
+ 64CD9938BCF89D57A5460C0C /* Pods-RunnerTests.debug.xcconfig */,
+ F93BE9EC760304A94A11FACA /* Pods-RunnerTests.release.xcconfig */,
+ 5B89B99F570AD57467A5B205 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -128,8 +172,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
+ BAC011BA047135336964E80D /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
+ 93639A99A847B6F3892EE7F7 /* Frameworks */,
);
buildRules = (
);
@@ -145,12 +191,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ DF2CA63EBF8244753EE19552 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ E235F37719081D02864E5419 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -253,6 +301,67 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ BAC011BA047135336964E80D /* [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-RunnerTests-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;
+ };
+ DF2CA63EBF8244753EE19552 /* [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;
+ };
+ E235F37719081D02864E5419 /* [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;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -369,7 +478,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.withuApp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.ios.withu;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -379,6 +488,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 64CD9938BCF89D57A5460C0C /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -396,6 +506,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = F93BE9EC760304A94A11FACA /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -411,6 +522,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReference = 5B89B99F570AD57467A5B205 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -549,7 +661,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.withuApp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.ios.withu;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -572,7 +684,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.withuApp;
+ PRODUCT_BUNDLE_IDENTIFIER = com.ios.withu;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
index 89c2725..b5b843a 100644
--- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -2,4 +2,4 @@
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
-You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 6d8c73a..39eac26 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -2,6 +2,8 @@
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
@@ -24,6 +26,8 @@
$(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
+ UIApplicationSupportsIndirectInputEvents
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -41,9 +45,11 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
+ CFBundleLocalizations
+
+ en
+ ko
+
+
diff --git a/lib/core/config/config.dart b/lib/core/config/config.dart
new file mode 100644
index 0000000..c9e7d5c
--- /dev/null
+++ b/lib/core/config/config.dart
@@ -0,0 +1 @@
+export 'env/env.dart';
diff --git a/lib/core/config/env/dev.dart b/lib/core/config/env/dev.dart
new file mode 100644
index 0000000..898c09c
--- /dev/null
+++ b/lib/core/config/env/dev.dart
@@ -0,0 +1,4 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/main.dart';
+
+void main() => run(environment: EnvironmentType.dev);
diff --git a/lib/core/config/env/env.dart b/lib/core/config/env/env.dart
new file mode 100644
index 0000000..1706e63
--- /dev/null
+++ b/lib/core/config/env/env.dart
@@ -0,0 +1,2 @@
+export 'environment.dart';
+export 'environment_type.dart';
diff --git a/lib/core/config/env/environment.dart b/lib/core/config/env/environment.dart
new file mode 100644
index 0000000..01a8117
--- /dev/null
+++ b/lib/core/config/env/environment.dart
@@ -0,0 +1,10 @@
+import 'package:withu_app/core/core.dart';
+
+class Environment {
+ Environment._();
+
+ static late final EnvironmentType env;
+
+ /// Prod 환경 여부
+ static bool get isProd => env == EnvironmentType.prod;
+}
diff --git a/lib/core/config/env/environment_type.dart b/lib/core/config/env/environment_type.dart
new file mode 100644
index 0000000..402b6f2
--- /dev/null
+++ b/lib/core/config/env/environment_type.dart
@@ -0,0 +1 @@
+enum EnvironmentType { prod, dev }
diff --git a/lib/core/config/env/prod.dart b/lib/core/config/env/prod.dart
new file mode 100644
index 0000000..7fad4e0
--- /dev/null
+++ b/lib/core/config/env/prod.dart
@@ -0,0 +1,4 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/main.dart';
+
+void main() => run(environment: EnvironmentType.prod);
diff --git a/lib/core/core.dart b/lib/core/core.dart
index a1a5d85..e74f355 100644
--- a/lib/core/core.dart
+++ b/lib/core/core.dart
@@ -1,4 +1,5 @@
-export 'widgets/widgets.dart';
export 'network/network.dart';
export 'router/router.dart';
export 'utils/utils.dart';
+export 'types/type.dart';
+export 'config/config.dart';
diff --git a/lib/core/network/api.dart b/lib/core/network/api.dart
new file mode 100644
index 0000000..71920be
--- /dev/null
+++ b/lib/core/network/api.dart
@@ -0,0 +1,17 @@
+import 'package:dio/dio.dart';
+import 'package:pretty_dio_logger/pretty_dio_logger.dart';
+
+class API {
+ final String url = 'https://example.com';
+
+ final _dio = Dio(BaseOptions())
+ ..interceptors.add(
+ PrettyDioLogger(
+ requestBody: true,
+ responseBody: true,
+ // enabled: kDebugMode,
+ ),
+ );
+
+ Dio get dio => _dio;
+}
diff --git a/lib/core/network/mock_api.dart b/lib/core/network/mock_api.dart
new file mode 100644
index 0000000..ebe0c8c
--- /dev/null
+++ b/lib/core/network/mock_api.dart
@@ -0,0 +1,9 @@
+import 'package:http_mock_adapter/http_mock_adapter.dart';
+
+import 'api.dart';
+
+mixin MockAPI on API {
+ late final DioAdapter _dioAdapter = DioAdapter(dio: dio);
+
+ DioAdapter get dioAdapter => _dioAdapter;
+}
diff --git a/lib/core/network/network.dart b/lib/core/network/network.dart
index e69de29..5cd88cf 100644
--- a/lib/core/network/network.dart
+++ b/lib/core/network/network.dart
@@ -0,0 +1,2 @@
+export 'api.dart';
+export 'mock_api.dart';
diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart
index e69de29..5af27c9 100644
--- a/lib/core/router/router.dart
+++ b/lib/core/router/router.dart
@@ -0,0 +1,21 @@
+import 'package:auto_route/auto_route.dart';
+import 'package:withu_app/core/router/router.gr.dart';
+
+@AutoRouterConfig()
+class AppRouter extends RootStackRouter {
+ @override
+ RouteType get defaultRouteType => const RouteType.material();
+
+ @override
+ List get routes => [
+ AutoRoute(
+ page: SplashRoute.page,
+ path: '/',
+ initial: true,
+ ),
+ AutoRoute(
+ page: JobPostingsRoute.page,
+ path: '/job-postings',
+ ),
+ ];
+}
diff --git a/lib/core/router/router.gr.dart b/lib/core/router/router.gr.dart
new file mode 100644
index 0000000..edd8fe5
--- /dev/null
+++ b/lib/core/router/router.gr.dart
@@ -0,0 +1,53 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// **************************************************************************
+// AutoRouterGenerator
+// **************************************************************************
+
+// ignore_for_file: type=lint
+// coverage:ignore-file
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'package:auto_route/auto_route.dart' as _i3;
+import 'package:withu_app/feature/job_posting/presentation/pages/job_postings_page.dart'
+ as _i1;
+import 'package:withu_app/feature/splash/presentation/pages/splash_page.dart'
+ as _i2;
+
+/// generated route for
+/// [_i1.JobPostingsPage]
+class JobPostingsRoute extends _i3.PageRouteInfo {
+ const JobPostingsRoute({List<_i3.PageRouteInfo>? children})
+ : super(
+ JobPostingsRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'JobPostingsRoute';
+
+ static _i3.PageInfo page = _i3.PageInfo(
+ name,
+ builder: (data) {
+ return const _i1.JobPostingsPage();
+ },
+ );
+}
+
+/// generated route for
+/// [_i2.SplashPage]
+class SplashRoute extends _i3.PageRouteInfo {
+ const SplashRoute({List<_i3.PageRouteInfo>? children})
+ : super(
+ SplashRoute.name,
+ initialChildren: children,
+ );
+
+ static const String name = 'SplashRoute';
+
+ static _i3.PageInfo page = _i3.PageInfo(
+ name,
+ builder: (data) {
+ return const _i2.SplashPage();
+ },
+ );
+}
diff --git a/lib/core/types/job_category_type.dart b/lib/core/types/job_category_type.dart
new file mode 100644
index 0000000..93d9490
--- /dev/null
+++ b/lib/core/types/job_category_type.dart
@@ -0,0 +1,21 @@
+import 'package:withu_app/core/core.dart';
+
+/// 직업 종류
+enum JobCategoryType with L10nKeyProvider {
+ /// 촬영
+ photography(l10nKey: 'photography'),
+
+ /// 케이터링
+ catering(l10nKey: 'catering'),
+
+ /// 푸드스타일링
+ foodStyling(l10nKey: 'foodStyling'),
+
+ /// 플라워리스트
+ florist(l10nKey: 'florist');
+
+ @override
+ final String l10nKey;
+
+ const JobCategoryType({required this.l10nKey});
+}
diff --git a/lib/core/types/job_posting_status_type.dart b/lib/core/types/job_posting_status_type.dart
new file mode 100644
index 0000000..a4a71b4
--- /dev/null
+++ b/lib/core/types/job_posting_status_type.dart
@@ -0,0 +1,18 @@
+// 공고 상태 타입
+import 'package:withu_app/core/core.dart';
+
+enum JobPostingStatusType with L10nKeyProvider {
+ /// 임시저장
+ temporary(l10nKey: 'temporarySave'),
+
+ /// 진행
+ inProgress(l10nKey: 'inProgress'),
+
+ /// 마감
+ closed(l10nKey: 'closed');
+
+ @override
+ final String l10nKey;
+
+ const JobPostingStatusType({required this.l10nKey});
+}
diff --git a/lib/core/types/type.dart b/lib/core/types/type.dart
new file mode 100644
index 0000000..fc86c34
--- /dev/null
+++ b/lib/core/types/type.dart
@@ -0,0 +1,2 @@
+export 'job_category_type.dart';
+export 'job_posting_status_type.dart';
diff --git a/lib/core/utils/extensions/date_ex.dart b/lib/core/utils/extensions/date_ex.dart
new file mode 100644
index 0000000..9b4bc39
--- /dev/null
+++ b/lib/core/utils/extensions/date_ex.dart
@@ -0,0 +1,11 @@
+
+import 'package:intl/intl.dart';
+
+extension DateTimeEx on DateTime {
+
+ /// 포맷팅한 날짜 얻기
+ String format(String format) {
+ final DateFormat formatter = DateFormat(format);
+ return formatter.format(this);
+ }
+}
diff --git a/lib/core/utils/extensions/extensions.dart b/lib/core/utils/extensions/extensions.dart
new file mode 100644
index 0000000..0a0b1c2
--- /dev/null
+++ b/lib/core/utils/extensions/extensions.dart
@@ -0,0 +1,2 @@
+export 'date_ex.dart';
+export 'theme_extension.dart';
diff --git a/lib/core/utils/extensions/theme_extension.dart b/lib/core/utils/extensions/theme_extension.dart
new file mode 100644
index 0000000..1202208
--- /dev/null
+++ b/lib/core/utils/extensions/theme_extension.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+
+extension BulidContextTextThemeEx on BuildContext {
+ TextTheme get textTheme => Theme.of(this).textTheme;
+}
+
+extension TextThemeEx on TextTheme {
+ TextStyle? get bodyLargeBold => bodyLarge?.copyWith(
+ fontWeight: FontWeight.w700,
+ );
+
+ TextStyle? get bodyMediumBold => bodyMedium?.copyWith(
+ fontWeight: FontWeight.w700,
+ );
+
+ TextStyle? get bodySmallBold => bodySmall?.copyWith(
+ fontWeight: FontWeight.w700,
+ );
+}
diff --git a/lib/core/utils/injections.dart b/lib/core/utils/injections.dart
index 94dbb79..356f6c5 100644
--- a/lib/core/utils/injections.dart
+++ b/lib/core/utils/injections.dart
@@ -1,7 +1,10 @@
import 'package:get_it/get_it.dart';
+import 'package:withu_app/feature/job_posting/init_injections.dart';
+import 'package:withu_app/feature/splash/splash.dart';
-final sl = GetIt.instance;
+final getIt = GetIt.instance;
Future initInjections() async {
-
+ initSplashInjections();
+ initJobPostingInjections();
}
diff --git a/lib/core/utils/logger/logger.dart b/lib/core/utils/logger/logger.dart
new file mode 100644
index 0000000..dfd0aad
--- /dev/null
+++ b/lib/core/utils/logger/logger.dart
@@ -0,0 +1,18 @@
+import 'package:logger/logger.dart';
+
+var logger = Logger(
+ printer: PrettyPrinter(
+ methodCount: 2,
+ // Number of method calls to be displayed
+ errorMethodCount: 8,
+ // Number of method calls if stacktrace is provided
+ lineLength: 120,
+ // Width of the output
+ colors: true,
+ // Colorful log messages
+ printEmojis: true,
+ // Print an emoji for each log message
+ // Should each log print contain a timestamp
+ dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
+ ),
+);
diff --git a/lib/core/utils/mixins/l10n_key_provider.dart b/lib/core/utils/mixins/l10n_key_provider.dart
new file mode 100644
index 0000000..8485e31
--- /dev/null
+++ b/lib/core/utils/mixins/l10n_key_provider.dart
@@ -0,0 +1,12 @@
+import 'package:easy_localization/easy_localization.dart' as easy;
+
+// Enum 타입들에 Localization 제고
+mixin L10nKeyProvider {
+
+ /// Localization Key
+ String get l10nKey;
+}
+
+extension L10nKeyProviderExt on L10nKeyProvider {
+ String get tr => easy.tr(l10nKey);
+}
diff --git a/lib/core/utils/mixins/mixins.dart b/lib/core/utils/mixins/mixins.dart
new file mode 100644
index 0000000..b271d84
--- /dev/null
+++ b/lib/core/utils/mixins/mixins.dart
@@ -0,0 +1 @@
+export 'l10n_key_provider.dart';
diff --git a/lib/core/utils/resource/resource.dart b/lib/core/utils/resource/resource.dart
new file mode 100644
index 0000000..fcd43ac
--- /dev/null
+++ b/lib/core/utils/resource/resource.dart
@@ -0,0 +1 @@
+export 'string_res.dart';
diff --git a/lib/core/utils/resource/string_res.dart b/lib/core/utils/resource/string_res.dart
new file mode 100644
index 0000000..cc67dac
--- /dev/null
+++ b/lib/core/utils/resource/string_res.dart
@@ -0,0 +1,18 @@
+import 'package:easy_localization/easy_localization.dart' as easy;
+
+enum StringRes {
+ appName,
+ temporarySave,
+ inProgress,
+ closed,
+ photography,
+ catering,
+ foodStyling,
+ florist,
+ numberOfRecruits,
+}
+
+extension StringResEx on StringRes {
+ /// localization
+ String get tr => easy.tr(name);
+}
diff --git a/lib/core/utils/theme/custom_theme.dart b/lib/core/utils/theme/custom_theme.dart
new file mode 100644
index 0000000..3ee4303
--- /dev/null
+++ b/lib/core/utils/theme/custom_theme.dart
@@ -0,0 +1,60 @@
+import 'package:flutter/material.dart';
+import 'package:withu_app/gen/colors.gen.dart';
+
+class CustomTheme {
+ const CustomTheme();
+
+ static ThemeData get theme {
+ return ThemeData(
+ primaryColor: ColorName.primary,
+ textTheme: textTheme,
+ );
+ }
+
+ static TextTheme get textTheme {
+ return const TextTheme(
+ headlineLarge: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.w700,
+ color: ColorName.primary,
+ height: 38.4 / 24,
+ letterSpacing: -0.5,
+ ),
+ headlineMedium: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w700,
+ color: ColorName.primary,
+ height: 32 / 20,
+ letterSpacing: -0.5,
+ ),
+ headlineSmall: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w700,
+ color: ColorName.primary,
+ height: 28.8 / 18,
+ letterSpacing: -0.5,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: ColorName.primary,
+ height: 25.6 / 16,
+ letterSpacing: -0.5,
+ ),
+ bodyMedium: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w400,
+ color: ColorName.primary,
+ height: 22.4 / 14,
+ letterSpacing: -0.5,
+ ),
+ bodySmall: TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w400,
+ color: ColorName.primary,
+ height: 19.2 / 12,
+ letterSpacing: -0.5,
+ ),
+ );
+ }
+}
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index cce6a3b..a3f8795 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -1 +1,6 @@
export 'injections.dart';
+export 'logger/logger.dart';
+export 'extensions/extensions.dart';
+export 'theme/custom_theme.dart';
+export 'resource/resource.dart';
+export 'mixins/mixins.dart';
diff --git a/lib/core/widgets/widgets.dart b/lib/core/widgets/widgets.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/data/data.dart b/lib/data/data.dart
deleted file mode 100644
index 93816dd..0000000
--- a/lib/data/data.dart
+++ /dev/null
@@ -1,2 +0,0 @@
-export 'repositories/repositories.dart';
-export 'data_sources/data_sources.dart';
diff --git a/lib/data/data_sources/data_sources.dart b/lib/data/data_sources/data_sources.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/data/repositories/repositories.dart b/lib/data/repositories/repositories.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/domain/entities/entities.dart b/lib/domain/entities/entities.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/domain/repositories/repositories.dart b/lib/domain/repositories/repositories.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/domain/usecases/usecases.dart b/lib/domain/usecases/usecases.dart
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/feature/feature.dart b/lib/feature/feature.dart
new file mode 100644
index 0000000..b5ad8ac
--- /dev/null
+++ b/lib/feature/feature.dart
@@ -0,0 +1,2 @@
+export 'splash/splash.dart';
+export 'job_posting/job_posting.dart';
diff --git a/lib/feature/job_posting/data/data.dart b/lib/feature/job_posting/data/data.dart
new file mode 100644
index 0000000..b516411
--- /dev/null
+++ b/lib/feature/job_posting/data/data.dart
@@ -0,0 +1,2 @@
+export 'data_sources/data_sources.dart';
+export 'repositories/repository_impl.dart';
diff --git a/lib/feature/job_posting/data/data_sources/data_sources.dart b/lib/feature/job_posting/data/data_sources/data_sources.dart
new file mode 100644
index 0000000..91bacf0
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/data_sources.dart
@@ -0,0 +1,3 @@
+export 'dto/dto.dart';
+export 'remote/remote.dart';
+export 'mock/mock.dart';
diff --git a/lib/feature/job_posting/data/data_sources/dto/dto.dart b/lib/feature/job_posting/data/data_sources/dto/dto.dart
new file mode 100644
index 0000000..92e45ac
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/dto.dart
@@ -0,0 +1,2 @@
+export 'job_posting_model.dart';
+export 'job_postings_model.dart';
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_posting_model.dart b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.dart
new file mode 100644
index 0000000..f8c3329
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.dart
@@ -0,0 +1,23 @@
+import 'package:withu_app/core/core.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'job_posting_model.freezed.dart';
+
+part 'job_posting_model.g.dart';
+
+@freezed
+class JobPostingModel with _$JobPostingModel {
+ const factory JobPostingModel({
+ required String id, // Id
+ required String title, // 공고명
+ required JobCategoryType category, // 직업 카테고리
+ required JobPostingStatusType status, // 상태값
+ required DateTime startDate, // 근무 시작 날짜
+ required DateTime? endDate, // 근무 종료 날짜
+ required int currentMemberCount, // 현재 모집 인원
+ required int maxMemberCount, // 최대 모집 인원
+ }) = _JobPostingModel;
+
+ factory JobPostingModel.fromJson(Map json) =>
+ _$JobPostingModelFromJson(json);
+}
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_posting_model.freezed.dart b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.freezed.dart
new file mode 100644
index 0000000..339f986
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.freezed.dart
@@ -0,0 +1,321 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'job_posting_model.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+JobPostingModel _$JobPostingModelFromJson(Map json) {
+ return _JobPostingModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$JobPostingModel {
+ String get id => throw _privateConstructorUsedError; // Id
+ String get title => throw _privateConstructorUsedError; // 공고명
+ JobCategoryType get category => throw _privateConstructorUsedError; // 직업 카테고리
+ JobPostingStatusType get status => throw _privateConstructorUsedError; // 상태값
+ DateTime get startDate => throw _privateConstructorUsedError; // 근무 시작 날짜
+ DateTime? get endDate => throw _privateConstructorUsedError; // 근무 종료 날짜
+ int get currentMemberCount => throw _privateConstructorUsedError; // 현재 모집 인원
+ int get maxMemberCount => throw _privateConstructorUsedError;
+
+ /// Serializes this JobPostingModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of JobPostingModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $JobPostingModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $JobPostingModelCopyWith<$Res> {
+ factory $JobPostingModelCopyWith(
+ JobPostingModel value, $Res Function(JobPostingModel) then) =
+ _$JobPostingModelCopyWithImpl<$Res, JobPostingModel>;
+ @useResult
+ $Res call(
+ {String id,
+ String title,
+ JobCategoryType category,
+ JobPostingStatusType status,
+ DateTime startDate,
+ DateTime? endDate,
+ int currentMemberCount,
+ int maxMemberCount});
+}
+
+/// @nodoc
+class _$JobPostingModelCopyWithImpl<$Res, $Val extends JobPostingModel>
+ implements $JobPostingModelCopyWith<$Res> {
+ _$JobPostingModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of JobPostingModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? title = null,
+ Object? category = null,
+ Object? status = null,
+ Object? startDate = null,
+ Object? endDate = freezed,
+ Object? currentMemberCount = null,
+ Object? maxMemberCount = null,
+ }) {
+ return _then(_value.copyWith(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ title: null == title
+ ? _value.title
+ : title // ignore: cast_nullable_to_non_nullable
+ as String,
+ category: null == category
+ ? _value.category
+ : category // ignore: cast_nullable_to_non_nullable
+ as JobCategoryType,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as JobPostingStatusType,
+ startDate: null == startDate
+ ? _value.startDate
+ : startDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ endDate: freezed == endDate
+ ? _value.endDate
+ : endDate // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ currentMemberCount: null == currentMemberCount
+ ? _value.currentMemberCount
+ : currentMemberCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ maxMemberCount: null == maxMemberCount
+ ? _value.maxMemberCount
+ : maxMemberCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$JobPostingModelImplCopyWith<$Res>
+ implements $JobPostingModelCopyWith<$Res> {
+ factory _$$JobPostingModelImplCopyWith(_$JobPostingModelImpl value,
+ $Res Function(_$JobPostingModelImpl) then) =
+ __$$JobPostingModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String id,
+ String title,
+ JobCategoryType category,
+ JobPostingStatusType status,
+ DateTime startDate,
+ DateTime? endDate,
+ int currentMemberCount,
+ int maxMemberCount});
+}
+
+/// @nodoc
+class __$$JobPostingModelImplCopyWithImpl<$Res>
+ extends _$JobPostingModelCopyWithImpl<$Res, _$JobPostingModelImpl>
+ implements _$$JobPostingModelImplCopyWith<$Res> {
+ __$$JobPostingModelImplCopyWithImpl(
+ _$JobPostingModelImpl _value, $Res Function(_$JobPostingModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of JobPostingModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? title = null,
+ Object? category = null,
+ Object? status = null,
+ Object? startDate = null,
+ Object? endDate = freezed,
+ Object? currentMemberCount = null,
+ Object? maxMemberCount = null,
+ }) {
+ return _then(_$JobPostingModelImpl(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ title: null == title
+ ? _value.title
+ : title // ignore: cast_nullable_to_non_nullable
+ as String,
+ category: null == category
+ ? _value.category
+ : category // ignore: cast_nullable_to_non_nullable
+ as JobCategoryType,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as JobPostingStatusType,
+ startDate: null == startDate
+ ? _value.startDate
+ : startDate // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ endDate: freezed == endDate
+ ? _value.endDate
+ : endDate // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ currentMemberCount: null == currentMemberCount
+ ? _value.currentMemberCount
+ : currentMemberCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ maxMemberCount: null == maxMemberCount
+ ? _value.maxMemberCount
+ : maxMemberCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$JobPostingModelImpl implements _JobPostingModel {
+ const _$JobPostingModelImpl(
+ {required this.id,
+ required this.title,
+ required this.category,
+ required this.status,
+ required this.startDate,
+ required this.endDate,
+ required this.currentMemberCount,
+ required this.maxMemberCount});
+
+ factory _$JobPostingModelImpl.fromJson(Map json) =>
+ _$$JobPostingModelImplFromJson(json);
+
+ @override
+ final String id;
+// Id
+ @override
+ final String title;
+// 공고명
+ @override
+ final JobCategoryType category;
+// 직업 카테고리
+ @override
+ final JobPostingStatusType status;
+// 상태값
+ @override
+ final DateTime startDate;
+// 근무 시작 날짜
+ @override
+ final DateTime? endDate;
+// 근무 종료 날짜
+ @override
+ final int currentMemberCount;
+// 현재 모집 인원
+ @override
+ final int maxMemberCount;
+
+ @override
+ String toString() {
+ return 'JobPostingModel(id: $id, title: $title, category: $category, status: $status, startDate: $startDate, endDate: $endDate, currentMemberCount: $currentMemberCount, maxMemberCount: $maxMemberCount)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$JobPostingModelImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.title, title) || other.title == title) &&
+ (identical(other.category, category) ||
+ other.category == category) &&
+ (identical(other.status, status) || other.status == status) &&
+ (identical(other.startDate, startDate) ||
+ other.startDate == startDate) &&
+ (identical(other.endDate, endDate) || other.endDate == endDate) &&
+ (identical(other.currentMemberCount, currentMemberCount) ||
+ other.currentMemberCount == currentMemberCount) &&
+ (identical(other.maxMemberCount, maxMemberCount) ||
+ other.maxMemberCount == maxMemberCount));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, id, title, category, status,
+ startDate, endDate, currentMemberCount, maxMemberCount);
+
+ /// Create a copy of JobPostingModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$JobPostingModelImplCopyWith<_$JobPostingModelImpl> get copyWith =>
+ __$$JobPostingModelImplCopyWithImpl<_$JobPostingModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$JobPostingModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _JobPostingModel implements JobPostingModel {
+ const factory _JobPostingModel(
+ {required final String id,
+ required final String title,
+ required final JobCategoryType category,
+ required final JobPostingStatusType status,
+ required final DateTime startDate,
+ required final DateTime? endDate,
+ required final int currentMemberCount,
+ required final int maxMemberCount}) = _$JobPostingModelImpl;
+
+ factory _JobPostingModel.fromJson(Map json) =
+ _$JobPostingModelImpl.fromJson;
+
+ @override
+ String get id; // Id
+ @override
+ String get title; // 공고명
+ @override
+ JobCategoryType get category; // 직업 카테고리
+ @override
+ JobPostingStatusType get status; // 상태값
+ @override
+ DateTime get startDate; // 근무 시작 날짜
+ @override
+ DateTime? get endDate; // 근무 종료 날짜
+ @override
+ int get currentMemberCount; // 현재 모집 인원
+ @override
+ int get maxMemberCount;
+
+ /// Create a copy of JobPostingModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$JobPostingModelImplCopyWith<_$JobPostingModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_posting_model.g.dart b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.g.dart
new file mode 100644
index 0000000..691906d
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_posting_model.g.dart
@@ -0,0 +1,48 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'job_posting_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$JobPostingModelImpl _$$JobPostingModelImplFromJson(
+ Map json) =>
+ _$JobPostingModelImpl(
+ id: json['id'] as String,
+ title: json['title'] as String,
+ category: $enumDecode(_$JobCategoryTypeEnumMap, json['category']),
+ status: $enumDecode(_$JobPostingStatusTypeEnumMap, json['status']),
+ startDate: DateTime.parse(json['startDate'] as String),
+ endDate: json['endDate'] == null
+ ? null
+ : DateTime.parse(json['endDate'] as String),
+ currentMemberCount: (json['currentMemberCount'] as num).toInt(),
+ maxMemberCount: (json['maxMemberCount'] as num).toInt(),
+ );
+
+Map _$$JobPostingModelImplToJson(
+ _$JobPostingModelImpl instance) =>
+ {
+ 'id': instance.id,
+ 'title': instance.title,
+ 'category': _$JobCategoryTypeEnumMap[instance.category]!,
+ 'status': _$JobPostingStatusTypeEnumMap[instance.status]!,
+ 'startDate': instance.startDate.toIso8601String(),
+ 'endDate': instance.endDate?.toIso8601String(),
+ 'currentMemberCount': instance.currentMemberCount,
+ 'maxMemberCount': instance.maxMemberCount,
+ };
+
+const _$JobCategoryTypeEnumMap = {
+ JobCategoryType.photography: 'photography',
+ JobCategoryType.catering: 'catering',
+ JobCategoryType.foodStyling: 'foodStyling',
+ JobCategoryType.florist: 'florist',
+};
+
+const _$JobPostingStatusTypeEnumMap = {
+ JobPostingStatusType.temporary: 'temporary',
+ JobPostingStatusType.inProgress: 'inProgress',
+ JobPostingStatusType.closed: 'closed',
+};
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_postings_model.dart b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.dart
new file mode 100644
index 0000000..b4d3eed
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.dart
@@ -0,0 +1,16 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'job_posting_model.dart';
+
+part 'job_postings_model.freezed.dart';
+
+part 'job_postings_model.g.dart';
+
+@freezed
+class JobPostingsModel with _$JobPostingsModel {
+ const factory JobPostingsModel({
+ required List? contents,
+ }) = _JobPostingsModel;
+
+ factory JobPostingsModel.fromJson(Map json) =>
+ _$JobPostingsModelFromJson(json);
+}
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_postings_model.freezed.dart b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.freezed.dart
new file mode 100644
index 0000000..442562b
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.freezed.dart
@@ -0,0 +1,176 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'job_postings_model.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+JobPostingsModel _$JobPostingsModelFromJson(Map json) {
+ return _JobPostingsModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$JobPostingsModel {
+ List? get contents => throw _privateConstructorUsedError;
+
+ /// Serializes this JobPostingsModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of JobPostingsModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $JobPostingsModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $JobPostingsModelCopyWith<$Res> {
+ factory $JobPostingsModelCopyWith(
+ JobPostingsModel value, $Res Function(JobPostingsModel) then) =
+ _$JobPostingsModelCopyWithImpl<$Res, JobPostingsModel>;
+ @useResult
+ $Res call({List? contents});
+}
+
+/// @nodoc
+class _$JobPostingsModelCopyWithImpl<$Res, $Val extends JobPostingsModel>
+ implements $JobPostingsModelCopyWith<$Res> {
+ _$JobPostingsModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of JobPostingsModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? contents = freezed,
+ }) {
+ return _then(_value.copyWith(
+ contents: freezed == contents
+ ? _value.contents
+ : contents // ignore: cast_nullable_to_non_nullable
+ as List?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$JobPostingsModelImplCopyWith<$Res>
+ implements $JobPostingsModelCopyWith<$Res> {
+ factory _$$JobPostingsModelImplCopyWith(_$JobPostingsModelImpl value,
+ $Res Function(_$JobPostingsModelImpl) then) =
+ __$$JobPostingsModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({List? contents});
+}
+
+/// @nodoc
+class __$$JobPostingsModelImplCopyWithImpl<$Res>
+ extends _$JobPostingsModelCopyWithImpl<$Res, _$JobPostingsModelImpl>
+ implements _$$JobPostingsModelImplCopyWith<$Res> {
+ __$$JobPostingsModelImplCopyWithImpl(_$JobPostingsModelImpl _value,
+ $Res Function(_$JobPostingsModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of JobPostingsModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? contents = freezed,
+ }) {
+ return _then(_$JobPostingsModelImpl(
+ contents: freezed == contents
+ ? _value._contents
+ : contents // ignore: cast_nullable_to_non_nullable
+ as List?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$JobPostingsModelImpl implements _JobPostingsModel {
+ const _$JobPostingsModelImpl({required final List? contents})
+ : _contents = contents;
+
+ factory _$JobPostingsModelImpl.fromJson(Map json) =>
+ _$$JobPostingsModelImplFromJson(json);
+
+ final List? _contents;
+ @override
+ List? get contents {
+ final value = _contents;
+ if (value == null) return null;
+ if (_contents is EqualUnmodifiableListView) return _contents;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(value);
+ }
+
+ @override
+ String toString() {
+ return 'JobPostingsModel(contents: $contents)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$JobPostingsModelImpl &&
+ const DeepCollectionEquality().equals(other._contents, _contents));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, const DeepCollectionEquality().hash(_contents));
+
+ /// Create a copy of JobPostingsModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$JobPostingsModelImplCopyWith<_$JobPostingsModelImpl> get copyWith =>
+ __$$JobPostingsModelImplCopyWithImpl<_$JobPostingsModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$JobPostingsModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _JobPostingsModel implements JobPostingsModel {
+ const factory _JobPostingsModel(
+ {required final List? contents}) =
+ _$JobPostingsModelImpl;
+
+ factory _JobPostingsModel.fromJson(Map json) =
+ _$JobPostingsModelImpl.fromJson;
+
+ @override
+ List? get contents;
+
+ /// Create a copy of JobPostingsModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$JobPostingsModelImplCopyWith<_$JobPostingsModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/feature/job_posting/data/data_sources/dto/job_postings_model.g.dart b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.g.dart
new file mode 100644
index 0000000..dd080a8
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/dto/job_postings_model.g.dart
@@ -0,0 +1,21 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'job_postings_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$JobPostingsModelImpl _$$JobPostingsModelImplFromJson(
+ Map json) =>
+ _$JobPostingsModelImpl(
+ contents: (json['contents'] as List?)
+ ?.map((e) => JobPostingModel.fromJson(e as Map))
+ .toList(),
+ );
+
+Map _$$JobPostingsModelImplToJson(
+ _$JobPostingsModelImpl instance) =>
+ {
+ 'contents': instance.contents,
+ };
diff --git a/lib/feature/job_posting/data/data_sources/mock/job_posting_mock_api.dart b/lib/feature/job_posting/data/data_sources/mock/job_posting_mock_api.dart
new file mode 100644
index 0000000..b73018f
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/mock/job_posting_mock_api.dart
@@ -0,0 +1,46 @@
+import 'dart:async';
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+
+class JobPostingMockApi extends JobPostingApi with MockAPI {
+ @override
+ FutureOr> fetchList({
+ required JobPostingStatusType status,
+ required int page,
+ }) async {
+ try {
+ final list = List.generate(
+ 30,
+ (int index) => JobPostingModel(
+ id: '$index',
+ title: '공고명 #${index + (page * 30)}',
+ status: status,
+ category: JobCategoryType.values[index % 4],
+ startDate: DateTime.now(),
+ endDate: DateTime.now(),
+ currentMemberCount: index % 3,
+ maxMemberCount: 3,
+ ),
+ );
+
+ dioAdapter.onGet(
+ url,
+ (server) => server.reply(
+ 200,
+ JobPostingsModel(contents: list),
+ delay: const Duration(seconds: 1),
+ ),
+ );
+
+ final response = await dio.get(url);
+
+ if (response.statusCode == 200) {
+ return JobPostingsModel.fromJson(response.data).contents ?? [];
+ }
+ return [];
+ } catch (e) {
+ logger.e(e);
+ return [];
+ }
+ }
+}
diff --git a/lib/feature/job_posting/data/data_sources/mock/mock.dart b/lib/feature/job_posting/data/data_sources/mock/mock.dart
new file mode 100644
index 0000000..46b8188
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/mock/mock.dart
@@ -0,0 +1 @@
+export 'job_posting_mock_api.dart';
diff --git a/lib/feature/job_posting/data/data_sources/remote/job_posting_api.dart b/lib/feature/job_posting/data/data_sources/remote/job_posting_api.dart
new file mode 100644
index 0000000..9217b74
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/remote/job_posting_api.dart
@@ -0,0 +1,10 @@
+import 'dart:async';
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+
+abstract class JobPostingApi extends API {
+ FutureOr> fetchList({
+ required JobPostingStatusType status,
+ required int page,
+ });
+}
diff --git a/lib/feature/job_posting/data/data_sources/remote/job_posting_api_impl.dart b/lib/feature/job_posting/data/data_sources/remote/job_posting_api_impl.dart
new file mode 100644
index 0000000..fcac558
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/remote/job_posting_api_impl.dart
@@ -0,0 +1,13 @@
+import 'dart:async';
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+
+class JobPostingApiImpl extends JobPostingApi {
+ @override
+ FutureOr> fetchList({
+ required JobPostingStatusType status,
+ required int page,
+ }) async {
+ return [];
+ }
+}
diff --git a/lib/feature/job_posting/data/data_sources/remote/remote.dart b/lib/feature/job_posting/data/data_sources/remote/remote.dart
new file mode 100644
index 0000000..d0ac639
--- /dev/null
+++ b/lib/feature/job_posting/data/data_sources/remote/remote.dart
@@ -0,0 +1,2 @@
+export 'job_posting_api.dart';
+export 'job_posting_api_impl.dart';
diff --git a/lib/feature/job_posting/data/repositories/repository_impl.dart b/lib/feature/job_posting/data/repositories/repository_impl.dart
new file mode 100644
index 0000000..5af521c
--- /dev/null
+++ b/lib/feature/job_posting/data/repositories/repository_impl.dart
@@ -0,0 +1,24 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+import 'package:withu_app/feature/job_posting/domain/domain.dart';
+
+class JobPostingRepositoryImpl implements JobPostingRepository {
+ final JobPostingApi api;
+
+ JobPostingRepositoryImpl({
+ required this.api,
+ });
+
+ @override
+ Future?> searchJobPostings({
+ required JobPostingStatusType status,
+ required int page,
+ }) async {
+ try {
+ return await api.fetchList(status: status, page: page);
+ } catch (e) {
+ logger.e(e);
+ return null;
+ }
+ }
+}
diff --git a/lib/domain/domain.dart b/lib/feature/job_posting/domain/domain.dart
similarity index 100%
rename from lib/domain/domain.dart
rename to lib/feature/job_posting/domain/domain.dart
diff --git a/lib/feature/job_posting/domain/entities/entities.dart b/lib/feature/job_posting/domain/entities/entities.dart
new file mode 100644
index 0000000..17116e6
--- /dev/null
+++ b/lib/feature/job_posting/domain/entities/entities.dart
@@ -0,0 +1 @@
+export 'job_posting_entity.dart';
diff --git a/lib/feature/job_posting/domain/entities/job_posting_entity.dart b/lib/feature/job_posting/domain/entities/job_posting_entity.dart
new file mode 100644
index 0000000..3fdf3ce
--- /dev/null
+++ b/lib/feature/job_posting/domain/entities/job_posting_entity.dart
@@ -0,0 +1,63 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+
+class JobPostingEntity {
+ /// id
+ final String id;
+
+ /// 공고명
+ final String title;
+
+ /// 카테고리
+ final JobCategoryType category;
+
+ /// 시작날짜
+ final DateTime startDate;
+
+ /// 종료날짜
+ final DateTime? endDate;
+
+ /// 상태
+ final JobPostingStatusType status;
+
+ /// 현재 지원한 인원 수
+ final int currentMemberCount;
+
+ /// 최대 인원 수
+ final int maxMemberCount;
+
+ JobPostingEntity({
+ required this.id,
+ required this.title,
+ required this.category,
+ required this.startDate,
+ required this.endDate,
+ required this.status,
+ required this.currentMemberCount,
+ required this.maxMemberCount,
+ });
+
+ factory JobPostingEntity.fromModel(JobPostingModel model) {
+ final JobPostingModel(
+ :id,
+ :title,
+ :category,
+ :startDate,
+ :endDate,
+ :status,
+ :currentMemberCount,
+ :maxMemberCount,
+ ) = model;
+
+ return JobPostingEntity(
+ id: id,
+ title: title,
+ category: category,
+ startDate: startDate,
+ endDate: endDate,
+ status: status,
+ currentMemberCount: currentMemberCount,
+ maxMemberCount: maxMemberCount,
+ );
+ }
+}
diff --git a/lib/feature/job_posting/domain/repositories/repositories.dart b/lib/feature/job_posting/domain/repositories/repositories.dart
new file mode 100644
index 0000000..4ad408a
--- /dev/null
+++ b/lib/feature/job_posting/domain/repositories/repositories.dart
@@ -0,0 +1,10 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+
+abstract class JobPostingRepository {
+ /// 공고 목록 조회
+ Future?> searchJobPostings({
+ required JobPostingStatusType status,
+ required int page,
+ });
+}
diff --git a/lib/feature/job_posting/domain/usecases/usecases.dart b/lib/feature/job_posting/domain/usecases/usecases.dart
new file mode 100644
index 0000000..46ee97e
--- /dev/null
+++ b/lib/feature/job_posting/domain/usecases/usecases.dart
@@ -0,0 +1,36 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+import 'package:withu_app/feature/job_posting/domain/domain.dart';
+
+abstract class JobPostingUseCase {
+ Future?> searchJobPostings({
+ required JobPostingStatusType status,
+ required int page,
+ });
+}
+
+class JobPostingUseCaseImpl implements JobPostingUseCase {
+ final JobPostingRepository repository;
+
+ JobPostingUseCaseImpl({
+ required this.repository,
+ });
+
+ @override
+ Future> searchJobPostings({
+ required JobPostingStatusType status,
+ required int page,
+ }) async {
+ try {
+ final List? result = await repository.searchJobPostings(
+ status: status,
+ page: page,
+ );
+
+ return result?.map(JobPostingEntity.fromModel).toList() ?? [];
+ } catch (e) {
+ logger.e(e);
+ return [];
+ }
+ }
+}
diff --git a/lib/feature/job_posting/init_injections.dart b/lib/feature/job_posting/init_injections.dart
new file mode 100644
index 0000000..b435c75
--- /dev/null
+++ b/lib/feature/job_posting/init_injections.dart
@@ -0,0 +1,28 @@
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/data/data.dart';
+import 'package:withu_app/feature/job_posting/domain/domain.dart';
+import 'package:withu_app/feature/job_posting/job_posting.dart';
+
+initJobPostingInjections() {
+ getIt.registerSingleton(
+ Environment.isProd ? JobPostingApiImpl() : JobPostingMockApi(),
+ );
+ getIt.registerSingleton(
+ JobPostingRepositoryImpl(api: getIt()),
+ );
+ getIt.registerSingleton(
+ JobPostingUseCaseImpl(repository: getIt()),
+ );
+ getIt.registerFactory(
+ () => JobPostingsTemporaryBloc(useCase: getIt()),
+ );
+ getIt.registerFactory(
+ () => JobPostingsInProgressBloc(useCase: getIt()),
+ );
+ getIt.registerFactory(
+ () => JobPostingsClosedBloc(useCase: getIt()),
+ );
+ getIt.registerFactory(
+ () => JobPostingsTabBloc(),
+ );
+}
diff --git a/lib/feature/job_posting/job_posting.dart b/lib/feature/job_posting/job_posting.dart
new file mode 100644
index 0000000..28451b8
--- /dev/null
+++ b/lib/feature/job_posting/job_posting.dart
@@ -0,0 +1,3 @@
+export 'init_injections.dart';
+
+export 'presentation/presentation.dart';
diff --git a/lib/feature/job_posting/presentation/blocs/blocs.dart b/lib/feature/job_posting/presentation/blocs/blocs.dart
new file mode 100644
index 0000000..f5c8106
--- /dev/null
+++ b/lib/feature/job_posting/presentation/blocs/blocs.dart
@@ -0,0 +1,2 @@
+export 'job_postings/job_postings_bloc.dart';
+export 'job_postings_tab/job_postings_tab.dart';
diff --git a/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_bloc.dart b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_bloc.dart
new file mode 100644
index 0000000..7574206
--- /dev/null
+++ b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_bloc.dart
@@ -0,0 +1,54 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:withu_app/core/core.dart';
+import 'package:withu_app/feature/job_posting/domain/domain.dart';
+
+part 'job_postings_state.dart';
+
+part 'job_postings_event.dart';
+
+class JobPostingsBloc extends Bloc {
+ final JobPostingUseCase useCase;
+
+ JobPostingsBloc({
+ required this.useCase,
+ }) : super(const JobPostingState()) {
+ on(_onGettingListEvent);
+ }
+
+ _onGettingListEvent(
+ OnGettingListEvent event,
+ Emitter emit,
+ ) async {
+ emit(state.copyWith(status: JobPostingsStatus.loading));
+
+ final List entities = await useCase.searchJobPostings(
+ status: event.type,
+ page: event.page,
+ ) ??
+ [];
+
+ emit(
+ state.copyWith(
+ status: JobPostingsStatus.success,
+ isLast: false,
+ list: entities,
+ ),
+ );
+ }
+}
+
+/// 임시저장 Bloc
+class JobPostingsTemporaryBloc extends JobPostingsBloc {
+ JobPostingsTemporaryBloc({required super.useCase});
+}
+
+/// 진행 Bloc
+class JobPostingsInProgressBloc extends JobPostingsBloc {
+ JobPostingsInProgressBloc({required super.useCase});
+}
+
+/// 마감 Bloc
+class JobPostingsClosedBloc extends JobPostingsBloc {
+ JobPostingsClosedBloc({required super.useCase});
+}
diff --git a/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_event.dart b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_event.dart
new file mode 100644
index 0000000..b12f10c
--- /dev/null
+++ b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_event.dart
@@ -0,0 +1,15 @@
+part of 'job_postings_bloc.dart';
+
+sealed class JobPostingsEvent {}
+
+/// 리스트 조회 이벤트
+class OnGettingListEvent extends JobPostingsEvent {
+ final JobPostingStatusType type;
+
+ final int page;
+
+ OnGettingListEvent({
+ required this.type,
+ required this.page,
+ });
+}
diff --git a/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_state.dart b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_state.dart
new file mode 100644
index 0000000..29ff1b7
--- /dev/null
+++ b/lib/feature/job_posting/presentation/blocs/job_postings/job_postings_state.dart
@@ -0,0 +1,35 @@
+part of 'job_postings_bloc.dart';
+
+enum JobPostingsStatus { initial, loading, success, failure }
+
+class JobPostingState extends Equatable {
+ /// 상태값
+ final JobPostingsStatus status;
+
+ /// 공고 목록
+ final List list;
+
+ /// 마지막 페이지 여부
+ final bool isLast;
+
+ const JobPostingState({
+ this.status = JobPostingsStatus.initial,
+ this.list = const [],
+ this.isLast = true,
+ });
+
+ JobPostingState copyWith({
+ JobPostingsStatus? status,
+ List? list,
+ bool? isLast,
+ }) {
+ return JobPostingState(
+ status: status ?? this.status,
+ list: list ?? this.list,
+ isLast: isLast ?? this.isLast,
+ );
+ }
+
+ @override
+ List