├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── osj
│ │ │ │ └── lotura
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── launcher_icon.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── applogo.jpeg
├── applogo_unselected.jpeg
├── dry_image.jpeg
├── fonts
│ └── Lotura.ttf
└── laundry_image.jpeg
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── Runner-Bridging-Header.h
│ └── Runner.entitlements
├── lib
├── data
│ ├── apply
│ │ ├── data_source
│ │ │ └── remote
│ │ │ │ └── remote_apply_data_source.dart
│ │ ├── dto
│ │ │ ├── request
│ │ │ │ ├── apply_cancel_request.dart
│ │ │ │ ├── apply_request.dart
│ │ │ │ ├── get_apply_list_request.dart
│ │ │ │ └── send_fcm_info_request.dart
│ │ │ └── response
│ │ │ │ └── apply_response.dart
│ │ └── repository
│ │ │ └── apply_repository_impl.dart
│ ├── laundry
│ │ ├── data_source
│ │ │ ├── local
│ │ │ │ └── local_laundry_data_source.dart
│ │ │ └── remote
│ │ │ │ └── remote_laundry_data_source.dart
│ │ ├── dto
│ │ │ └── response
│ │ │ │ └── laundry_response.dart
│ │ └── repository
│ │ │ └── laundry_repository_impl.dart
│ └── notice
│ │ ├── data_source
│ │ ├── local
│ │ │ └── local_notice_data_source.dart
│ │ └── remote
│ │ │ └── remote_notice_data_source.dart
│ │ ├── dto
│ │ └── response
│ │ │ └── notice_response.dart
│ │ └── repository
│ │ └── notice_repository_impl.dart
├── di
│ └── di.dart
├── domain
│ ├── apply
│ │ ├── entity
│ │ │ └── apply_entity.dart
│ │ ├── repository
│ │ │ └── apply_repository.dart
│ │ └── use_case
│ │ │ ├── apply_cancel_use_case.dart
│ │ │ ├── get_apply_list_use_case.dart
│ │ │ └── send_fcm_info_use_case.dart
│ ├── laundry
│ │ ├── entity
│ │ │ └── laundry_entity.dart
│ │ ├── repository
│ │ │ └── laundry_repository.dart
│ │ └── use_case
│ │ │ ├── get_all_laundry_list_use_case.dart
│ │ │ ├── get_laundry_room_index_use_case.dart
│ │ │ ├── get_laundry_status_use_case.dart
│ │ │ └── update_laundry_room_index_use_case.dart
│ └── notice
│ │ ├── entity
│ │ └── notice_entity.dart
│ │ ├── repository
│ │ └── notice_repository.dart
│ │ └── use_case
│ │ ├── get_last_notice_id_use_case.dart
│ │ ├── get_notice_use_case.dart
│ │ └── update_last_notice_id_use_case.dart
├── init
│ └── fcm_init.dart
├── main.dart
└── presentation
│ ├── app_update_page
│ └── ui
│ │ └── app_update_page.dart
│ ├── apply_page
│ ├── bloc
│ │ ├── apply_bloc.dart
│ │ ├── apply_event.dart
│ │ ├── apply_model.dart
│ │ └── apply_state.dart
│ └── ui
│ │ ├── view
│ │ └── apply_page.dart
│ │ └── widget
│ │ └── machine_card.dart
│ ├── laundry_room_page
│ ├── bloc
│ │ ├── laundry_bloc.dart
│ │ ├── laundry_event.dart
│ │ ├── laundry_model.dart
│ │ └── laundry_state.dart
│ └── ui
│ │ ├── view
│ │ └── laundry_room_page.dart
│ │ └── widget
│ │ └── machine_button.dart
│ ├── notice_page
│ ├── bloc
│ │ ├── notice_bloc.dart
│ │ ├── notice_event.dart
│ │ ├── notice_model.dart
│ │ └── notice_state.dart
│ └── ui
│ │ ├── view
│ │ └── notice_page.dart
│ │ └── widget
│ │ └── notice_list_tile.dart
│ ├── setting_page
│ ├── bloc
│ │ ├── laundry_room_model.dart
│ │ ├── room_bloc.dart
│ │ ├── room_event.dart
│ │ └── room_state.dart
│ └── ui
│ │ ├── view
│ │ └── setting_page.dart
│ │ └── widget
│ │ └── setting_page_bottom_sheet.dart
│ ├── splash_page
│ └── ui
│ │ └── view
│ │ └── splash_page.dart
│ └── utils
│ ├── bottom_navi.dart
│ ├── lotura_colors.dart
│ ├── lotura_icons.dart
│ ├── machine_widget.dart
│ ├── osj_bottom_sheet.dart
│ ├── osj_icon_button.dart
│ ├── osj_image_button.dart
│ ├── osj_text_button.dart
│ └── vibrating_widget.dart
├── pubspec.lock
├── pubspec.yaml
└── test
└── widget_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 | android/app/google-services.json
35 | ios/Runner/GoogleService-Info.plist
36 | ios/firebase_app_id_file.json
37 | lib/firebase_options.dart
38 | /lib/secret.dart
39 |
40 | # Symbolication related
41 | app.*.symbols
42 |
43 | # Obfuscation related
44 | app.*.map.json
45 |
46 | # Android Studio will place build artifacts here
47 | /android/app/debug
48 | /android/app/profile
49 | /android/app/release
50 | /android/app/key.jks
51 | /android/app/key.properties
52 | /android/app/proguard-rules.pro
53 | /android/app/upload_certificate.pem
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
8 | channel: beta
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
17 | base_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
18 | - platform: android
19 | create_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
20 | base_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
21 | - platform: ios
22 | create_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
23 | base_revision: c29b09b8782b39b1678bc9c861f40902086fb7c8
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Lotura-Flutter 레포지토리 커밋 규칙입니다.
2 |
3 | - feat: 새로운 기능을 구현 및 추가한 경우
4 | - fix: 버그를 수정한 경우
5 | - docs: readme파일과 같이 문서를 수정한 경우
6 | - style: 코드 포맷 변경, 파일명 변경, 세미콜론 누락 또는 변수명이 바뀌었거나 오타를 수정한 경우
7 | - refactor: 코드를 리팩토링한 경우
8 | - test: 테스트 추가, 테스트 코드 리팩터링 (프로덕션 코드에는 변경이 없어야 함)
9 | - chore: 빌드 테스크 업데이트, 패키지 매니저 등의 환경설정
10 | - move: 폴더 또는 파일 위치를 이동할 경우
11 | - remove: 폴더 또는 파일을 삭제할 경우
12 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '27'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '3.7.1'
22 | }
23 |
24 | def keystoreProperties = new Properties()
25 | def keystorePropertiesFile = rootProject.file('app/key.properties')
26 | if (keystorePropertiesFile.exists()) {
27 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
28 | }
29 |
30 | apply plugin: 'com.android.application'
31 | // START: FlutterFire Configuration
32 | apply plugin: 'com.google.gms.google-services'
33 | // END: FlutterFire Configuration
34 | apply plugin: 'kotlin-android'
35 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
36 |
37 | android {
38 | compileSdkVersion flutter.compileSdkVersion
39 | ndkVersion flutter.ndkVersion
40 |
41 | compileOptions {
42 | sourceCompatibility JavaVersion.VERSION_1_8
43 | targetCompatibility JavaVersion.VERSION_1_8
44 | }
45 |
46 | kotlinOptions {
47 | jvmTarget = '1.8'
48 | }
49 |
50 | sourceSets {
51 | main.java.srcDirs += 'src/main/kotlin'
52 | }
53 |
54 | defaultConfig {
55 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
56 | applicationId "com.osj.lotura"
57 | // You can update the following values to match your application needs.
58 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
59 | minSdkVersion 23
60 | targetSdkVersion flutter.targetSdkVersion
61 | versionCode flutterVersionCode.toInteger()
62 | versionName flutterVersionName
63 | }
64 |
65 | signingConfigs {
66 | release {
67 | keyAlias keystoreProperties['keyAlias']
68 | keyPassword keystoreProperties['keyPassword']
69 | storeFile file(keystoreProperties['storeFile'])
70 | storePassword keystoreProperties['storePassword']
71 | }
72 | }
73 | buildTypes {
74 | release {
75 | signingConfig signingConfigs.release
76 |
77 | minifyEnabled true
78 |
79 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
80 | }
81 | }
82 | }
83 |
84 | flutter {
85 | source '../..'
86 | }
87 |
88 | dependencies {
89 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
90 | }
91 |
92 | //3.16 이상부터 바뀌는걸 ^권장^ 하는 옵션
93 | //plugins {
94 | // id("com.android.application")
95 | // id("kotlin-android")
96 | // id("dev.flutter.flutter-gradle-plugin")
97 | // id("com.google.gms.google-services")
98 | //}
99 | //
100 | //def localProperties = new Properties()
101 | //def localPropertiesFile = rootProject.file('local.properties')
102 | //if (localPropertiesFile.exists()) {
103 | // localPropertiesFile.withReader('UTF-8') { reader ->
104 | // localProperties.load(reader)
105 | // }
106 | //}
107 | //
108 | //def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
109 | //if (flutterVersionCode == null) {
110 | // flutterVersionCode = '19'
111 | //}
112 | //
113 | //def flutterVersionName = localProperties.getProperty('flutter.versionName')
114 | //if (flutterVersionName == null) {
115 | // flutterVersionName = '3.3.6'
116 | //}
117 | //
118 | //android {
119 | // namespace "com.osj.lotura"
120 | // compileSdk flutter.compileSdkVersion
121 | // ndkVersion flutter.ndkVersion
122 | //
123 | // compileOptions {
124 | // sourceCompatibility JavaVersion.VERSION_1_8
125 | // targetCompatibility JavaVersion.VERSION_1_8
126 | // }
127 | //
128 | // kotlinOptions {
129 | // jvmTarget = '1.8'
130 | // }
131 | //
132 | // sourceSets {
133 | // main.java.srcDirs += 'src/main/kotlin'
134 | // }
135 | //
136 | // defaultConfig {
137 | // // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
138 | // applicationId "com.osj.lotura"
139 | // // You can update the following values to match your application needs.
140 | // // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
141 | // minSdkVersion 23
142 | // targetSdkVersion flutter.targetSdkVersion
143 | // versionCode flutterVersionCode.toInteger()
144 | // versionName flutterVersionName
145 | // }
146 | //
147 | // buildTypes {
148 | // release {
149 | // // TODO: Add your own signing config for the release build.
150 | // // Signing with the debug keys for now, so `flutter run --release` works.
151 | // signingConfig signingConfigs.debug
152 | // }
153 | // }
154 | //}
155 | //
156 | //flutter {
157 | // source '../..'
158 | //}
159 | //
160 | //dependencies {
161 | // implementation platform('com.google.firebase:firebase-bom:32.7.4')
162 | //}
163 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
38 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/osj/lotura/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.osj.lotura
2 |
3 | import android.app.PendingIntent
4 | import android.content.Intent
5 | import android.content.IntentFilter
6 | import android.nfc.NdefMessage
7 | import android.nfc.NdefRecord
8 | import android.nfc.NfcAdapter
9 | import android.os.Bundle
10 | import io.flutter.embedding.android.FlutterActivity
11 | import io.flutter.embedding.engine.FlutterEngine
12 | import io.flutter.plugin.common.MethodChannel
13 |
14 | class MainActivity : FlutterActivity() {
15 | private var nfcAdapter: NfcAdapter? = null
16 | private var returnData = -1
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | nfcAdapter = NfcAdapter.getDefaultAdapter(this)
20 | if (nfcAdapter != null) {
21 | if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED) {
22 | val data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
23 | val message = data?.get(0) as NdefMessage
24 | val record = message.records[0] as NdefRecord
25 | val byteArr = record.payload
26 |
27 | returnData = String(byteArr).toInt()
28 | }
29 | }
30 | }
31 |
32 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
33 | super.configureFlutterEngine(flutterEngine)
34 | MethodChannel(
35 | flutterEngine.dartExecutor.binaryMessenger,
36 | "com.osj.lotura/nfc_info"
37 | ).setMethodCallHandler { call, result ->
38 | when (call.method) {
39 | "getNFCInfo" -> {
40 | result.success("{\"index\" : $returnData}")
41 | returnData = -1
42 | }
43 |
44 | "nfcIsAvailable" -> {
45 | result.success(nfcAdapter?.isEnabled)
46 | }
47 | }
48 | }
49 | }
50 |
51 | override fun onNewIntent(intent: Intent) {
52 | super.onNewIntent(intent)
53 | if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED) {
54 | val data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
55 | val message = data?.get(0) as NdefMessage
56 | val record = message.records[0] as NdefRecord
57 | val byteArr = record.payload
58 |
59 | returnData = String(byteArr).toInt()
60 | }
61 | }
62 |
63 | override fun onResume() {
64 | super.onResume()
65 | enableNfcForegroundDispatch()
66 | }
67 |
68 | override fun onPause() {
69 | super.onPause()
70 | disableNfcForegroundDispatch()
71 | }
72 |
73 | private fun enableNfcForegroundDispatch() {
74 | val pendingIntent = PendingIntent.getActivity(
75 | this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
76 | PendingIntent.FLAG_IMMUTABLE
77 | )
78 | val intentFilters = arrayOf(
79 | IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
80 | )
81 | nfcAdapter?.enableForegroundDispatch(this, pendingIntent, intentFilters, null)
82 | }
83 |
84 | private fun disableNfcForegroundDispatch() {
85 | nfcAdapter?.disableForegroundDispatch(this)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.4.2'
10 | // START: FlutterFire Configuration
11 | classpath 'com.google.gms:google-services:4.3.14'
12 | // END: FlutterFire Configuration
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | rootProject.buildDir = '../build'
25 | subprojects {
26 | project.buildDir = "${rootProject.buildDir}/${project.name}"
27 | }
28 | subprojects {
29 | project.evaluationDependsOn(':app')
30 | }
31 |
32 | tasks.register("clean", Delete) {
33 | delete rootProject.buildDir
34 | }
35 |
36 | //3.16 이상부터 바뀌는걸 ^권장^ 하는 옵션
37 | //allprojects {
38 | // repositories {
39 | // google()
40 | // mavenCentral()
41 | // }
42 | //}
43 | //
44 | //rootProject.buildDir = '../build'
45 | //subprojects {
46 | // project.buildDir = "${rootProject.buildDir}/${project.name}"
47 | //}
48 | //subprojects {
49 | // project.evaluationDependsOn(':app')
50 | //}
51 | //
52 | //tasks.register("clean", Delete) {
53 | // delete rootProject.buildDir
54 | //}
55 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 | //3.16 이상부터 바뀌는걸 ^권장^ 하는 옵션
13 | //pluginManagement {
14 | // def flutterSdkPath = {
15 | // def properties = new Properties()
16 | // file("local.properties").withInputStream { properties.load(it) }
17 | // def flutterSdkPath = properties.getProperty("flutter.sdk")
18 | // assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
19 | // return flutterSdkPath
20 | // }
21 | // settings.ext.flutterSdkPath = flutterSdkPath()
22 | //
23 | // includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
24 | //
25 | // repositories {
26 | // google()
27 | // mavenCentral()
28 | // gradlePluginPortal()
29 | // }
30 | //}
31 | //
32 | //plugins {
33 | // id "dev.flutter.flutter-plugin-loader" version "1.0.0"
34 | // id "com.android.application" version "7.3.0" apply false
35 | // id "org.jetbrains.kotlin.android" version "1.7.10" apply false
36 | // id "com.google.gms.google-services" version "4.3.14" apply false
37 | //}
38 | //
39 | //include ":app"
40 |
--------------------------------------------------------------------------------
/assets/applogo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/assets/applogo.jpeg
--------------------------------------------------------------------------------
/assets/applogo_unselected.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/assets/applogo_unselected.jpeg
--------------------------------------------------------------------------------
/assets/dry_image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/assets/dry_image.jpeg
--------------------------------------------------------------------------------
/assets/fonts/Lotura.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/assets/fonts/Lotura.ttf
--------------------------------------------------------------------------------
/assets/laundry_image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/assets/laundry_image.jpeg
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | # target 'RunnerTests' do
36 | # inherit! :search_paths
37 | # end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Firebase/CoreOnly (10.18.0):
3 | - FirebaseCore (= 10.18.0)
4 | - Firebase/Messaging (10.18.0):
5 | - Firebase/CoreOnly
6 | - FirebaseMessaging (~> 10.18.0)
7 | - firebase_core (2.24.2):
8 | - Firebase/CoreOnly (= 10.18.0)
9 | - Flutter
10 | - firebase_messaging (14.7.10):
11 | - Firebase/Messaging (= 10.18.0)
12 | - firebase_core
13 | - Flutter
14 | - FirebaseCore (10.18.0):
15 | - FirebaseCoreInternal (~> 10.0)
16 | - GoogleUtilities/Environment (~> 7.12)
17 | - GoogleUtilities/Logger (~> 7.12)
18 | - FirebaseCoreInternal (10.19.0):
19 | - "GoogleUtilities/NSData+zlib (~> 7.8)"
20 | - FirebaseInstallations (10.19.0):
21 | - FirebaseCore (~> 10.0)
22 | - GoogleUtilities/Environment (~> 7.8)
23 | - GoogleUtilities/UserDefaults (~> 7.8)
24 | - PromisesObjC (~> 2.1)
25 | - FirebaseMessaging (10.18.0):
26 | - FirebaseCore (~> 10.0)
27 | - FirebaseInstallations (~> 10.0)
28 | - GoogleDataTransport (~> 9.2)
29 | - GoogleUtilities/AppDelegateSwizzler (~> 7.8)
30 | - GoogleUtilities/Environment (~> 7.8)
31 | - GoogleUtilities/Reachability (~> 7.8)
32 | - GoogleUtilities/UserDefaults (~> 7.8)
33 | - nanopb (< 2.30910.0, >= 2.30908.0)
34 | - Flutter (1.0.0)
35 | - flutter_local_notifications (0.0.1):
36 | - Flutter
37 | - GoogleDataTransport (9.3.0):
38 | - GoogleUtilities/Environment (~> 7.7)
39 | - nanopb (< 2.30910.0, >= 2.30908.0)
40 | - PromisesObjC (< 3.0, >= 1.2)
41 | - GoogleUtilities/AppDelegateSwizzler (7.12.0):
42 | - GoogleUtilities/Environment
43 | - GoogleUtilities/Logger
44 | - GoogleUtilities/Network
45 | - GoogleUtilities/Environment (7.12.0):
46 | - PromisesObjC (< 3.0, >= 1.2)
47 | - GoogleUtilities/Logger (7.12.0):
48 | - GoogleUtilities/Environment
49 | - GoogleUtilities/Network (7.12.0):
50 | - GoogleUtilities/Logger
51 | - "GoogleUtilities/NSData+zlib"
52 | - GoogleUtilities/Reachability
53 | - "GoogleUtilities/NSData+zlib (7.12.0)"
54 | - GoogleUtilities/Reachability (7.12.0):
55 | - GoogleUtilities/Logger
56 | - GoogleUtilities/UserDefaults (7.12.0):
57 | - GoogleUtilities/Logger
58 | - nanopb (2.30909.1):
59 | - nanopb/decode (= 2.30909.1)
60 | - nanopb/encode (= 2.30909.1)
61 | - nanopb/decode (2.30909.1)
62 | - nanopb/encode (2.30909.1)
63 | - package_info_plus (0.4.5):
64 | - Flutter
65 | - path_provider_foundation (0.0.1):
66 | - Flutter
67 | - FlutterMacOS
68 | - PromisesObjC (2.3.1)
69 | - url_launcher_ios (0.0.1):
70 | - Flutter
71 |
72 | DEPENDENCIES:
73 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
74 | - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
75 | - Flutter (from `Flutter`)
76 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
77 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
78 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
79 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
80 |
81 | SPEC REPOS:
82 | trunk:
83 | - Firebase
84 | - FirebaseCore
85 | - FirebaseCoreInternal
86 | - FirebaseInstallations
87 | - FirebaseMessaging
88 | - GoogleDataTransport
89 | - GoogleUtilities
90 | - nanopb
91 | - PromisesObjC
92 |
93 | EXTERNAL SOURCES:
94 | firebase_core:
95 | :path: ".symlinks/plugins/firebase_core/ios"
96 | firebase_messaging:
97 | :path: ".symlinks/plugins/firebase_messaging/ios"
98 | Flutter:
99 | :path: Flutter
100 | flutter_local_notifications:
101 | :path: ".symlinks/plugins/flutter_local_notifications/ios"
102 | package_info_plus:
103 | :path: ".symlinks/plugins/package_info_plus/ios"
104 | path_provider_foundation:
105 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
106 | url_launcher_ios:
107 | :path: ".symlinks/plugins/url_launcher_ios/ios"
108 |
109 | SPEC CHECKSUMS:
110 | Firebase: 414ad272f8d02dfbf12662a9d43f4bba9bec2a06
111 | firebase_core: 0af4a2b24f62071f9bf283691c0ee41556dcb3f5
112 | firebase_messaging: 90e8a6db84b6e1e876cebce4f30f01dc495e7014
113 | FirebaseCore: 2322423314d92f946219c8791674d2f3345b598f
114 | FirebaseCoreInternal: b444828ea7cfd594fca83046b95db98a2be4f290
115 | FirebaseInstallations: 033d199474164db20c8350736842a94fe717b960
116 | FirebaseMessaging: 9bc34a98d2e0237e1b121915120d4d48ddcf301e
117 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
118 | flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
119 | GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe
120 | GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34
121 | nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
122 | package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
123 | path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
124 | PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
125 | url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
126 |
127 | PODFILE CHECKSUM: 3765b805c03121794c6d132546df9366f592bc4d
128 |
129 | COCOAPODS: 1.15.2
130 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 | import Firebase
4 |
5 | @available(iOS 13.0.0, *)
6 | @UIApplicationMain
7 | @objc class AppDelegate: FlutterAppDelegate {
8 | private var channel: FlutterMethodChannel?
9 | private var nfcData = -1
10 | private var isAppRunning = false
11 |
12 | override func application(
13 | _ application: UIApplication,
14 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
15 | ) -> Bool {
16 | FirebaseApp.configure()
17 | let controller = self.window.rootViewController as! FlutterViewController
18 | channel = FlutterMethodChannel.init(name: "com.osj.lotura/nfc_info", binaryMessenger: controller as! FlutterBinaryMessenger)
19 | channel?.setMethodCallHandler { (call, result) in
20 | if(call.method == "getNFCInfo") {
21 | result("{\"index\" : \(self.nfcData)}")
22 | }
23 | }
24 | GeneratedPluginRegistrant.register(with: self)
25 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
26 | }
27 |
28 | override func application(
29 | _ app: UIApplication,
30 | open url: URL,
31 | options: [UIApplication.OpenURLOptionsKey : Any] = [:]
32 | ) -> Bool {
33 | let contents = url.absoluteString.components(separatedBy: "=")
34 | self.nfcData = NSString(string : contents[1]).integerValue
35 | return true
36 | }
37 |
38 | override func applicationWillEnterForeground(_ application: UIApplication) {
39 | if self.isAppRunning == true {
40 | self.nfcData = -1
41 | } else {
42 | self.isAppRunning = true
43 | }
44 | }
45 |
46 | override func applicationWillResignActive(_ application: UIApplication) {
47 | if self.isAppRunning == true {
48 | self.nfcData = -1
49 | } else {
50 | self.isAppRunning = true
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-20x20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Icon-App-20x20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Icon-App-29x29@1x.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "Icon-App-29x29@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "Icon-App-29x29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "Icon-App-40x40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Icon-App-40x40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "Icon-App-60x60@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "Icon-App-60x60@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "Icon-App-20x20@1x.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "Icon-App-20x20@2x.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "filename" : "Icon-App-29x29@1x.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "Icon-App-29x29@2x.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "29x29"
80 | },
81 | {
82 | "filename" : "Icon-App-40x40@1x.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "Icon-App-40x40@2x.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "40x40"
92 | },
93 | {
94 | "filename" : "Icon-App-76x76@1x.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "Icon-App-76x76@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "76x76"
104 | },
105 | {
106 | "filename" : "Icon-App-83.5x83.5@2x.png",
107 | "idiom" : "ipad",
108 | "scale" : "2x",
109 | "size" : "83.5x83.5"
110 | },
111 | {
112 | "filename" : "Icon-App-1024x1024@1x.png",
113 | "idiom" : "ios-marketing",
114 | "scale" : "1x",
115 | "size" : "1024x1024"
116 | }
117 | ],
118 | "info" : {
119 | "author" : "xcode",
120 | "version" : 1
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/team-osj/Loutra-Flutter/d9e3cdb62591bef4534ba18e50f9485fa40a9aa1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FLTEnableImpeller
6 |
7 | UIApplicationSceneManifest
8 |
9 | UIApplicationSupportsMultipleScenes
10 |
11 | UISceneConfigurations
12 |
13 |
14 | FirebaseAppDelegateProxyEnabled
15 |
16 | NFCReaderUsageDescription
17 | NFC 태그를 이용해 알림 신청을 하기 위한 권한 요청
18 | CADisableMinimumFrameDurationOnPhone
19 |
20 | CFBundleDevelopmentRegion
21 | $(DEVELOPMENT_LANGUAGE)
22 | CFBundleDisplayName
23 | Lotura
24 | CFBundleExecutable
25 | $(EXECUTABLE_NAME)
26 | CFBundleIdentifier
27 | $(PRODUCT_BUNDLE_IDENTIFIER)
28 | CFBundleInfoDictionaryVersion
29 | 6.0
30 | CFBundleName
31 | lotura
32 | CFBundlePackageType
33 | APPL
34 | CFBundleShortVersionString
35 | $(FLUTTER_BUILD_NAME)
36 | CFBundleSignature
37 | ????
38 | CFBundleURLTypes
39 |
40 |
41 | CFBundleTypeRole
42 | Editor
43 | CFBundleURLSchemes
44 |
45 | lotura
46 |
47 |
48 |
49 | CFBundleVersion
50 | $(FLUTTER_BUILD_NUMBER)
51 | LSRequiresIPhoneOS
52 |
53 | UIApplicationSupportsIndirectInputEvents
54 |
55 | UIBackgroundModes
56 |
57 | fetch
58 | remote-notification
59 |
60 | UILaunchStoryboardName
61 | LaunchScreen
62 | UIMainStoryboardFile
63 | Main
64 | UISupportedInterfaceOrientations
65 |
66 | UIInterfaceOrientationPortrait
67 | UIInterfaceOrientationLandscapeLeft
68 | UIInterfaceOrientationLandscapeRight
69 |
70 | UISupportedInterfaceOrientations~ipad
71 |
72 | UIInterfaceOrientationPortrait
73 | UIInterfaceOrientationPortraitUpsideDown
74 | UIInterfaceOrientationLandscapeLeft
75 | UIInterfaceOrientationLandscapeRight
76 |
77 | UIViewControllerBasedStatusBarAppearance
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.developer.nfc.readersession.formats
8 |
9 | NDEF
10 | TAG
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/data/apply/data_source/remote/remote_apply_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:firebase_messaging/firebase_messaging.dart';
5 | import 'package:http/http.dart' as http;
6 | import 'package:lotura/data/apply/dto/request/apply_cancel_request.dart';
7 | import 'package:lotura/data/apply/dto/request/send_fcm_info_request.dart';
8 | import 'package:lotura/data/apply/dto/response/apply_response.dart';
9 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
10 | import 'package:lotura/secret.dart';
11 |
12 | class RemoteApplyDataSource {
13 | Future _getToken() async =>
14 | await FirebaseMessaging.instance.getToken() ?? "whatThe";
15 |
16 | Future> getApplyList() async {
17 | final response = await http.post(Uri.parse("$baseurl/push_list"),
18 | body: {"token": await _getToken()});
19 | if (response.statusCode != 200) throw Exception(response.body);
20 | return (jsonDecode(response.body) as List)
21 | .map((i) => ApplyResponse.fromJson(i).toEntity())
22 | .toList();
23 | }
24 |
25 | Future sendFCMInfo(
26 | {required SendFCMInfoRequest sendFCMInfoRequest}) async {
27 | sendFCMInfoRequest.token = await _getToken();
28 | final response = await http.post(Uri.parse("$baseurl/push_request"),
29 | headers: {"Content-Type": "application/json"},
30 | body: json.encode(sendFCMInfoRequest.toJson()));
31 | if (response.statusCode != 200 && response.statusCode != 304) {
32 | throw Exception(response.body);
33 | }
34 | }
35 |
36 | Future applyCancel(
37 | {required ApplyCancelRequest applyCancelRequest}) async {
38 | applyCancelRequest.token = await _getToken();
39 | final response = await http.post(Uri.parse("$baseurl/push_cancel"),
40 | headers: {"Content-Type": "application/json"},
41 | body: json.encode(applyCancelRequest.toJson()));
42 | if (response.statusCode != 200) throw Exception(response.body);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/data/apply/dto/request/apply_cancel_request.dart:
--------------------------------------------------------------------------------
1 | class ApplyCancelRequest {
2 | String? token;
3 | int deviceId;
4 |
5 | ApplyCancelRequest({this.token, required this.deviceId});
6 |
7 | Map toJson() {
8 | final Map data = {};
9 | data['token'] = token;
10 | data['device_id'] = deviceId;
11 | return data;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/data/apply/dto/request/apply_request.dart:
--------------------------------------------------------------------------------
1 | class ApplyRequest {
2 | String token;
3 |
4 | ApplyRequest({required this.token});
5 |
6 | Map toJson() {
7 | final Map data = {};
8 | data['token'] = token;
9 | return data;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/data/apply/dto/request/get_apply_list_request.dart:
--------------------------------------------------------------------------------
1 | class GetApplyListRequest {
2 | String? token;
3 |
4 | GetApplyListRequest({this.token});
5 |
6 | Map toJson() {
7 | final Map data = {};
8 | data['token'] = token;
9 | return data;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/data/apply/dto/request/send_fcm_info_request.dart:
--------------------------------------------------------------------------------
1 | class SendFCMInfoRequest {
2 | String? token;
3 | int deviceId;
4 | int expectState;
5 |
6 | SendFCMInfoRequest(
7 | {this.token, required this.deviceId, required this.expectState});
8 |
9 | Map toJson() {
10 | final Map data = {};
11 | data['token'] = token;
12 | data['device_id'] = deviceId;
13 | data['expect_state'] = expectState;
14 | return data;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/data/apply/dto/response/apply_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
2 | import 'package:lotura/main.dart';
3 |
4 | class ApplyResponse {
5 | final int deviceId;
6 | final String deviceType;
7 |
8 | ApplyResponse({required this.deviceId, required this.deviceType});
9 |
10 | factory ApplyResponse.fromJson(Map json) {
11 | return ApplyResponse(
12 | deviceId: json['device_id'],
13 | deviceType: json['device_type'],
14 | );
15 | }
16 |
17 | ApplyEntity toEntity() => ApplyEntity(
18 | deviceId: deviceId,
19 | deviceType: deviceType == "WASH" ? DeviceType.wash : DeviceType.dry);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/data/apply/repository/apply_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:lotura/data/apply/data_source/remote/remote_apply_data_source.dart';
4 | import 'package:lotura/data/apply/dto/request/apply_cancel_request.dart';
5 | import 'package:lotura/data/apply/dto/request/send_fcm_info_request.dart';
6 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
7 | import 'package:lotura/domain/apply/repository/apply_repository.dart';
8 |
9 | class ApplyRepositoryImpl implements ApplyRepository {
10 | final RemoteApplyDataSource _remoteApplyDataSource;
11 |
12 | ApplyRepositoryImpl({required RemoteApplyDataSource remoteApplyDataSource})
13 | : _remoteApplyDataSource = remoteApplyDataSource;
14 |
15 | @override
16 | Future> getApplyList() =>
17 | _remoteApplyDataSource.getApplyList();
18 |
19 | @override
20 | Future sendFCMInfo({required SendFCMInfoRequest sendFCMInfoRequest}) =>
21 | _remoteApplyDataSource.sendFCMInfo(
22 | sendFCMInfoRequest: sendFCMInfoRequest);
23 |
24 | @override
25 | Future applyCancel({required ApplyCancelRequest applyCancelRequest}) =>
26 | _remoteApplyDataSource.applyCancel(
27 | applyCancelRequest: applyCancelRequest);
28 | }
29 |
--------------------------------------------------------------------------------
/lib/data/laundry/data_source/local/local_laundry_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'package:hive/hive.dart';
2 |
3 | class LocalLaundryDataSource {
4 | final Box _box;
5 |
6 | const LocalLaundryDataSource({required Box localDatabase})
7 | : _box = localDatabase;
8 |
9 | Future setValue({required String key, required int value}) =>
10 | _box.put(key, value);
11 |
12 | int? getValue({required String key}) => _box.get(key);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/data/laundry/data_source/remote/remote_laundry_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:http/http.dart' as http;
5 | import 'package:lotura/data/laundry/dto/response/laundry_response.dart';
6 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
7 | import 'package:lotura/secret.dart';
8 | import 'package:web_socket_channel/web_socket_channel.dart';
9 |
10 | class RemoteLaundryDataSource {
11 | final StreamController _streamController;
12 |
13 | RemoteLaundryDataSource(
14 | {required StreamController streamController})
15 | : _streamController = streamController;
16 |
17 | Stream get laundryList =>
18 | _streamController.stream.asBroadcastStream();
19 |
20 | void webSocketInit() async {
21 | final channel = WebSocketChannel.connect(Uri.parse(webSocketUrl));
22 | await channel.ready;
23 | channel.stream.listen((data) {
24 | _streamController.sink.add(
25 | LaundryResponse.fromJson(jsonDecode(data) as Map)
26 | .toEntity());
27 | });
28 | }
29 |
30 | Future> getAllLaundryList() async {
31 | final response = await http.get(Uri.parse("$baseurl/device_list_boy"));
32 | if (response.statusCode != 200) {
33 | throw Exception(response.body);
34 | }
35 | return (jsonDecode(response.body) as List)
36 | .map((i) => LaundryResponse.fromJson(i).toEntity())
37 | .toList();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/data/laundry/dto/response/laundry_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
2 | import 'package:lotura/main.dart';
3 |
4 | class LaundryResponse {
5 | final int id;
6 | final int state;
7 | final String deviceType;
8 |
9 | const LaundryResponse({
10 | required this.id,
11 | required this.state,
12 | required this.deviceType,
13 | });
14 |
15 | factory LaundryResponse.fromJson(Map json) {
16 | return LaundryResponse(
17 | id: json['id'],
18 | state: json['state'],
19 | deviceType: json['device_type'],
20 | );
21 | }
22 |
23 | LaundryEntity toEntity() {
24 | return LaundryEntity(
25 | id: id,
26 | state: CurrentState.values.elementAt(state),
27 | deviceType: deviceType == "WASH"
28 | ? DeviceType.wash
29 | : deviceType == "DRY"
30 | ? DeviceType.dry
31 | : DeviceType.empty,
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/data/laundry/repository/laundry_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:lotura/data/laundry/data_source/local/local_laundry_data_source.dart';
4 | import 'package:lotura/data/laundry/data_source/remote/remote_laundry_data_source.dart';
5 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
6 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
7 |
8 | class LaundryRepositoryImpl implements LaundryRepository {
9 | final LocalLaundryDataSource _localLaundryDataSource;
10 | final RemoteLaundryDataSource _remoteLaundryDataSource;
11 |
12 | LaundryRepositoryImpl(
13 | {required LocalLaundryDataSource localLaundryDataSource,
14 | required RemoteLaundryDataSource remoteLaundryDataSource})
15 | : _localLaundryDataSource = localLaundryDataSource,
16 | _remoteLaundryDataSource = remoteLaundryDataSource;
17 |
18 | @override
19 | Stream get laundryList =>
20 | _remoteLaundryDataSource.laundryList.asBroadcastStream();
21 |
22 | @override
23 | void webSocketInit() => _remoteLaundryDataSource.webSocketInit();
24 |
25 | @override
26 | int? getValue({required String key}) =>
27 | _localLaundryDataSource.getValue(key: key);
28 |
29 | @override
30 | Future setValue({required String key, required int value}) =>
31 | _localLaundryDataSource.setValue(key: key, value: value);
32 |
33 | @override
34 | Future> getAllLaundryList() =>
35 | _remoteLaundryDataSource.getAllLaundryList();
36 | }
37 |
--------------------------------------------------------------------------------
/lib/data/notice/data_source/local/local_notice_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'package:hive/hive.dart';
2 |
3 | class LocalNoticeDataSource {
4 | final Box _box;
5 |
6 | const LocalNoticeDataSource({required Box box}) : _box = box;
7 |
8 | Future setValue({required String key, required int value}) =>
9 | _box.put(key, value);
10 |
11 | int? getValue({required String key}) => _box.get(key);
12 | }
13 |
--------------------------------------------------------------------------------
/lib/data/notice/data_source/remote/remote_notice_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:http/http.dart' as http;
5 | import 'package:lotura/data/notice/dto/response/notice_response.dart';
6 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
7 | import 'package:lotura/secret.dart';
8 |
9 | class RemoteNoticeDataSource {
10 | Future> getNotice() async {
11 | final response = await http.get(Uri.parse("$baseurl/notice"));
12 | if (response.statusCode != 200) {
13 | throw Exception(response.body);
14 | }
15 | return (jsonDecode(utf8.decode(response.bodyBytes)) as List)
16 | .map((i) => NoticeResponse.fromJson(i).toEntity())
17 | .toList();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/data/notice/dto/response/notice_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
2 |
3 | class NoticeResponse {
4 | final int id;
5 | final String title;
6 | final String contents;
7 | final String date;
8 |
9 | NoticeResponse({
10 | required this.id,
11 | required this.title,
12 | required this.contents,
13 | required this.date,
14 | });
15 |
16 | factory NoticeResponse.fromJson(Map json) {
17 | return NoticeResponse(
18 | id: json['id'],
19 | title: json['title'],
20 | contents: json['contents'],
21 | date: json['date'],
22 | );
23 | }
24 |
25 | NoticeEntity toEntity() {
26 | return NoticeEntity(
27 | noticeId: id,
28 | title: title,
29 | contents: contents,
30 | date: date,
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/data/notice/repository/notice_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/data/notice/data_source/local/local_notice_data_source.dart';
2 | import 'package:lotura/data/notice/data_source/remote/remote_notice_data_source.dart';
3 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
4 | import 'package:lotura/domain/notice/repository/notice_repository.dart';
5 |
6 | class NoticeRepositoryImpl implements NoticeRepository {
7 | final RemoteNoticeDataSource _remoteNoticeDataSource;
8 | final LocalNoticeDataSource _localNoticeDataSource;
9 |
10 | const NoticeRepositoryImpl({
11 | required RemoteNoticeDataSource remoteNoticeDataSource,
12 | required LocalNoticeDataSource localNoticeDataSource,
13 | }) : _remoteNoticeDataSource = remoteNoticeDataSource,
14 | _localNoticeDataSource = localNoticeDataSource;
15 |
16 | @override
17 | Future> getNotice() => _remoteNoticeDataSource.getNotice();
18 |
19 | @override
20 | int? getLastNoticeId({required String key}) =>
21 | _localNoticeDataSource.getValue(key: key);
22 |
23 | @override
24 | Future setLastNoticeId({required String key, required int value}) =>
25 | _localNoticeDataSource.setValue(key: key, value: value);
26 | }
27 |
--------------------------------------------------------------------------------
/lib/di/di.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:hive/hive.dart';
5 | import 'package:lotura/data/apply/data_source/remote/remote_apply_data_source.dart';
6 | import 'package:lotura/data/apply/repository/apply_repository_impl.dart';
7 | import 'package:lotura/data/laundry/data_source/local/local_laundry_data_source.dart';
8 | import 'package:lotura/data/laundry/data_source/remote/remote_laundry_data_source.dart';
9 | import 'package:lotura/data/laundry/repository/laundry_repository_impl.dart';
10 | import 'package:lotura/data/notice/data_source/local/local_notice_data_source.dart';
11 | import 'package:lotura/data/notice/data_source/remote/remote_notice_data_source.dart';
12 | import 'package:lotura/data/notice/repository/notice_repository_impl.dart';
13 | import 'package:lotura/domain/apply/repository/apply_repository.dart';
14 | import 'package:lotura/domain/apply/use_case/apply_cancel_use_case.dart';
15 | import 'package:lotura/domain/apply/use_case/get_apply_list_use_case.dart';
16 | import 'package:lotura/domain/apply/use_case/send_fcm_info_use_case.dart';
17 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
18 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
19 | import 'package:lotura/domain/laundry/use_case/get_all_laundry_list_use_case.dart';
20 | import 'package:lotura/domain/laundry/use_case/get_laundry_room_index_use_case.dart';
21 | import 'package:lotura/domain/laundry/use_case/get_laundry_status_use_case.dart';
22 | import 'package:lotura/domain/laundry/use_case/update_laundry_room_index_use_case.dart';
23 | import 'package:lotura/domain/notice/repository/notice_repository.dart';
24 | import 'package:lotura/domain/notice/use_case/get_last_notice_id_use_case.dart';
25 | import 'package:lotura/domain/notice/use_case/get_notice_use_case.dart';
26 | import 'package:lotura/domain/notice/use_case/update_last_notice_id_use_case.dart';
27 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
28 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_bloc.dart';
29 | import 'package:lotura/presentation/notice_page/bloc/notice_bloc.dart';
30 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
31 |
32 | Future> di() async {
33 | final box = await Hive.openBox("Lotura");
34 |
35 | LocalLaundryDataSource localLaundryDataSource =
36 | LocalLaundryDataSource(localDatabase: box);
37 |
38 | LocalNoticeDataSource localNoticeDataSource = LocalNoticeDataSource(box: box);
39 |
40 | RemoteLaundryDataSource remoteLaundryDataSource = RemoteLaundryDataSource(
41 | streamController: StreamController.broadcast());
42 |
43 | RemoteApplyDataSource remoteApplyDataSource = RemoteApplyDataSource();
44 |
45 | RemoteNoticeDataSource remoteNoticeDataSource = RemoteNoticeDataSource();
46 |
47 | LaundryRepository laundryRepository = LaundryRepositoryImpl(
48 | localLaundryDataSource: localLaundryDataSource,
49 | remoteLaundryDataSource: remoteLaundryDataSource);
50 |
51 | ApplyRepository applyRepository =
52 | ApplyRepositoryImpl(remoteApplyDataSource: remoteApplyDataSource);
53 |
54 | NoticeRepository noticeRepository = NoticeRepositoryImpl(
55 | remoteNoticeDataSource: remoteNoticeDataSource,
56 | localNoticeDataSource: localNoticeDataSource);
57 |
58 | GetLaundryStatusUseCase getLaundryStatusUseCase =
59 | GetLaundryStatusUseCase(laundryRepository: laundryRepository);
60 |
61 | GetApplyListUseCase getApplyListUseCase =
62 | GetApplyListUseCase(applyRepository: applyRepository);
63 |
64 | SendFCMInfoUseCase sendFCMInfoUseCase =
65 | SendFCMInfoUseCase(applyRepository: applyRepository);
66 |
67 | ApplyCancelUseCase applyCancelUseCase =
68 | ApplyCancelUseCase(applyRepository: applyRepository);
69 |
70 | GetLaundryRoomIndexUseCase getLaundryRoomIndexUseCase =
71 | GetLaundryRoomIndexUseCase(laundryRepository: laundryRepository);
72 |
73 | GetAllLaundryListUseCase getAllLaundryListUseCase =
74 | GetAllLaundryListUseCase(laundryRepository: laundryRepository);
75 |
76 | UpdateLaundryRoomIndexUseCase updateLaundryRoomIndexUseCase =
77 | UpdateLaundryRoomIndexUseCase(laundryRepository: laundryRepository);
78 |
79 | GetNoticeUseCase getNoticeUseCase =
80 | GetNoticeUseCase(noticeRepository: noticeRepository);
81 |
82 | GetLastNoticeIdUseCase getLastNoticeIdUseCase =
83 | GetLastNoticeIdUseCase(noticeRepository: noticeRepository);
84 |
85 | UpdateLastNoticeIdUseCase updateLastNoticeIdUseCase =
86 | UpdateLastNoticeIdUseCase(noticeRepository: noticeRepository);
87 |
88 | return [
89 | BlocProvider(
90 | create: (context) => ApplyBloc(
91 | getApplyListUseCase: getApplyListUseCase,
92 | applyCancelUseCase: applyCancelUseCase,
93 | sendFCMInfoUseCase: sendFCMInfoUseCase)),
94 | BlocProvider(
95 | create: (context) => LaundryBloc(
96 | getLaundryStatusUseCase: getLaundryStatusUseCase,
97 | getAllLaundryListUseCase: getAllLaundryListUseCase)),
98 | BlocProvider(
99 | create: (context) => RoomBloc(
100 | getLaundryRoomIndexUseCase: getLaundryRoomIndexUseCase,
101 | updateLaundryRoomIndexUseCase: updateLaundryRoomIndexUseCase),
102 | ),
103 | BlocProvider(
104 | create: (context) => NoticeBloc(
105 | getNoticeUseCase: getNoticeUseCase,
106 | getLastNoticeIdUseCase: getLastNoticeIdUseCase,
107 | updateLastNoticeIdUseCase: updateLastNoticeIdUseCase,
108 | ),
109 | ),
110 | ];
111 | }
112 |
--------------------------------------------------------------------------------
/lib/domain/apply/entity/apply_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/main.dart';
2 |
3 | class ApplyEntity {
4 | final int deviceId;
5 | final DeviceType deviceType;
6 |
7 | const ApplyEntity({
8 | required this.deviceId,
9 | required this.deviceType,
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/lib/domain/apply/repository/apply_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:lotura/data/apply/dto/request/apply_cancel_request.dart';
4 | import 'package:lotura/data/apply/dto/request/send_fcm_info_request.dart';
5 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
6 |
7 | abstract class ApplyRepository {
8 | Future> getApplyList();
9 |
10 | Future sendFCMInfo({required SendFCMInfoRequest sendFCMInfoRequest});
11 |
12 | Future applyCancel({required ApplyCancelRequest applyCancelRequest});
13 | }
14 |
--------------------------------------------------------------------------------
/lib/domain/apply/use_case/apply_cancel_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/data/apply/dto/request/apply_cancel_request.dart';
2 | import 'package:lotura/domain/apply/repository/apply_repository.dart';
3 |
4 | class ApplyCancelUseCase {
5 | final ApplyRepository _applyRepository;
6 |
7 | ApplyCancelUseCase({required ApplyRepository applyRepository})
8 | : _applyRepository = applyRepository;
9 |
10 | Future execute({required ApplyCancelRequest applyCancelRequest}) =>
11 | _applyRepository.applyCancel(applyCancelRequest: applyCancelRequest);
12 | }
13 |
--------------------------------------------------------------------------------
/lib/domain/apply/use_case/get_apply_list_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
2 | import 'package:lotura/domain/apply/repository/apply_repository.dart';
3 |
4 | class GetApplyListUseCase {
5 | final ApplyRepository _applyRepository;
6 |
7 | GetApplyListUseCase({required ApplyRepository applyRepository})
8 | : _applyRepository = applyRepository;
9 |
10 | Future> execute() => _applyRepository.getApplyList();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/domain/apply/use_case/send_fcm_info_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/data/apply/dto/request/send_fcm_info_request.dart';
2 | import 'package:lotura/domain/apply/repository/apply_repository.dart';
3 |
4 | class SendFCMInfoUseCase {
5 | final ApplyRepository _applyRepository;
6 |
7 | SendFCMInfoUseCase({required ApplyRepository applyRepository})
8 | : _applyRepository = applyRepository;
9 |
10 | Future execute({required SendFCMInfoRequest sendFCMInfoRequest}) =>
11 | _applyRepository.sendFCMInfo(sendFCMInfoRequest: sendFCMInfoRequest);
12 | }
13 |
--------------------------------------------------------------------------------
/lib/domain/laundry/entity/laundry_entity.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/main.dart';
2 |
3 | class LaundryEntity {
4 | final int id;
5 | final CurrentState state;
6 | final DeviceType deviceType;
7 |
8 | const LaundryEntity({
9 | required this.id,
10 | required this.state,
11 | required this.deviceType,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/lib/domain/laundry/repository/laundry_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
4 |
5 | abstract class LaundryRepository {
6 | Stream get laundryList;
7 |
8 | void webSocketInit();
9 |
10 | Future> getAllLaundryList();
11 |
12 | Future setValue({required String key, required int value});
13 |
14 | int? getValue({required String key});
15 | }
16 |
--------------------------------------------------------------------------------
/lib/domain/laundry/use_case/get_all_laundry_list_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
2 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
3 |
4 | class GetAllLaundryListUseCase {
5 | final LaundryRepository _laundryRepository;
6 |
7 | GetAllLaundryListUseCase({required LaundryRepository laundryRepository})
8 | : _laundryRepository = laundryRepository;
9 |
10 | Future> execute() =>
11 | _laundryRepository.getAllLaundryList();
12 | }
13 |
--------------------------------------------------------------------------------
/lib/domain/laundry/use_case/get_laundry_room_index_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
2 |
3 | class GetLaundryRoomIndexUseCase {
4 | final LaundryRepository _laundryRepository;
5 |
6 | const GetLaundryRoomIndexUseCase(
7 | {required LaundryRepository laundryRepository})
8 | : _laundryRepository = laundryRepository;
9 |
10 | int get execute => _laundryRepository.getValue(key: "laundryRoomIndex") ?? 0;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/domain/laundry/use_case/get_laundry_status_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
2 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
3 |
4 | class GetLaundryStatusUseCase {
5 | final LaundryRepository _laundryRepository;
6 |
7 | GetLaundryStatusUseCase({required LaundryRepository laundryRepository})
8 | : _laundryRepository = laundryRepository;
9 |
10 | Stream get laundryList => _laundryRepository.laundryList;
11 |
12 | void execute() {
13 | _laundryRepository.webSocketInit();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/domain/laundry/use_case/update_laundry_room_index_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/repository/laundry_repository.dart';
2 |
3 | class UpdateLaundryRoomIndexUseCase {
4 | final LaundryRepository _laundryRepository;
5 |
6 | const UpdateLaundryRoomIndexUseCase(
7 | {required LaundryRepository laundryRepository})
8 | : _laundryRepository = laundryRepository;
9 |
10 | Future execute({required int value}) =>
11 | _laundryRepository.setValue(key: 'laundryRoomIndex', value: value);
12 | }
13 |
--------------------------------------------------------------------------------
/lib/domain/notice/entity/notice_entity.dart:
--------------------------------------------------------------------------------
1 | class NoticeEntity {
2 | final int noticeId;
3 | final String title;
4 | final String contents;
5 | final String date;
6 |
7 | const NoticeEntity({
8 | required this.noticeId,
9 | required this.title,
10 | required this.contents,
11 | required this.date,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/lib/domain/notice/repository/notice_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
4 |
5 | abstract class NoticeRepository {
6 | Future> getNotice();
7 |
8 | Future setLastNoticeId({required String key, required int value});
9 |
10 | int? getLastNoticeId({required String key});
11 | }
12 |
--------------------------------------------------------------------------------
/lib/domain/notice/use_case/get_last_notice_id_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
2 | import 'package:lotura/domain/notice/repository/notice_repository.dart';
3 |
4 | class GetLastNoticeIdUseCase {
5 | final NoticeRepository _noticeRepository;
6 |
7 | const GetLastNoticeIdUseCase({required NoticeRepository noticeRepository})
8 | : _noticeRepository = noticeRepository;
9 |
10 | bool execute({required List noticeList}) {
11 | int lastNoticeId =
12 | _noticeRepository.getLastNoticeId(key: "lastNoticeId") ?? -1;
13 | int newNoticeId = noticeList.isEmpty ? -1 : noticeList.first.noticeId;
14 | return lastNoticeId < newNoticeId;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/domain/notice/use_case/get_notice_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
2 | import 'package:lotura/domain/notice/repository/notice_repository.dart';
3 |
4 | class GetNoticeUseCase {
5 | final NoticeRepository _noticeRepository;
6 |
7 | GetNoticeUseCase({required NoticeRepository noticeRepository})
8 | : _noticeRepository = noticeRepository;
9 |
10 | Future> execute() => _noticeRepository.getNotice();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/domain/notice/use_case/update_last_notice_id_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
2 | import 'package:lotura/domain/notice/repository/notice_repository.dart';
3 |
4 | class UpdateLastNoticeIdUseCase {
5 | final NoticeRepository _noticeRepository;
6 |
7 | UpdateLastNoticeIdUseCase({required NoticeRepository noticeRepository})
8 | : _noticeRepository = noticeRepository;
9 |
10 | Future execute({required List noticeList}) =>
11 | _noticeRepository.setLastNoticeId(
12 | key: "lastNoticeId",
13 | value: noticeList.isEmpty ? -1 : noticeList.first.noticeId);
14 | }
15 |
--------------------------------------------------------------------------------
/lib/init/fcm_init.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_core/firebase_core.dart';
2 | import 'package:firebase_messaging/firebase_messaging.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_bloc/flutter_bloc.dart';
5 | import 'package:flutter_local_notifications/flutter_local_notifications.dart';
6 | import 'package:lotura/data/apply/dto/request/get_apply_list_request.dart';
7 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
8 | import 'package:lotura/presentation/apply_page/bloc/apply_event.dart';
9 |
10 | void fcmInit(BuildContext context) async {
11 | await FirebaseMessaging.instance.requestPermission(
12 | alert: true,
13 | announcement: true,
14 | badge: true,
15 | carPlay: true,
16 | criticalAlert: true,
17 | provisional: true,
18 | sound: true);
19 | await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
20 | alert: true, badge: true, sound: true);
21 | const AndroidNotificationChannel channel = AndroidNotificationChannel(
22 | 'high_importance_channel',
23 | 'High Importance Notifications',
24 | description: 'This channel is used for important notifications.',
25 | importance: Importance.max,
26 | );
27 | final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
28 | FlutterLocalNotificationsPlugin();
29 | await flutterLocalNotificationsPlugin
30 | .resolvePlatformSpecificImplementation<
31 | AndroidFlutterLocalNotificationsPlugin>()
32 | ?.createNotificationChannel(channel);
33 | await flutterLocalNotificationsPlugin.initialize(
34 | const InitializationSettings(
35 | android: AndroidInitializationSettings('@mipmap/launcher_icon'),
36 | iOS: DarwinInitializationSettings(),
37 | ),
38 | );
39 | FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
40 | FirebaseMessaging.onMessage.listen(
41 | (RemoteMessage message) {
42 | BlocProvider.of(context)
43 | .add(GetApplyListEvent(getApplyListRequest: GetApplyListRequest()));
44 | RemoteNotification? notification = message.notification;
45 | AndroidNotification? android = message.notification?.android;
46 | if (notification != null && android != null) {
47 | flutterLocalNotificationsPlugin.show(
48 | 0,
49 | notification.title,
50 | notification.body,
51 | NotificationDetails(
52 | android: AndroidNotificationDetails(
53 | channel.id,
54 | channel.name,
55 | channelDescription: channel.description,
56 | ),
57 | ),
58 | );
59 | }
60 | },
61 | );
62 | }
63 |
64 | @pragma('vm:entry-point')
65 | Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
66 | await Firebase.initializeApp();
67 | }
68 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:firebase_core/firebase_core.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter_bloc/flutter_bloc.dart';
7 | import 'package:flutter_screenutil/flutter_screenutil.dart';
8 | import 'package:hive_flutter/hive_flutter.dart';
9 | import 'package:lotura/di/di.dart';
10 | import 'package:lotura/firebase_options.dart';
11 | import 'package:lotura/init/fcm_init.dart';
12 | import 'package:lotura/presentation/splash_page/ui/view/splash_page.dart';
13 | import 'package:lotura/presentation/utils/lotura_colors.dart';
14 | import 'package:lotura/presentation/utils/lotura_icons.dart';
15 |
16 | void main() async {
17 | WidgetsFlutterBinding.ensureInitialized();
18 | await Firebase.initializeApp(
19 | options: DefaultFirebaseOptions.currentPlatform,
20 | );
21 | SystemChrome.setSystemUIOverlayStyle(
22 | const SystemUiOverlayStyle(statusBarColor: LoturaColors.gray100),
23 | );
24 | await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
25 | await Hive.initFlutter();
26 | const platformMsg = MethodChannel('com.osj.lotura/nfc_info');
27 | final data = await platformMsg.invokeMethod("getNFCInfo");
28 | runApp(MyApp(
29 | blocList: await di(),
30 | nfcTagData: (jsonDecode(data)['index'] as int),
31 | ));
32 | }
33 |
34 | class MyApp extends StatelessWidget {
35 | const MyApp({super.key, required this.blocList, required this.nfcTagData});
36 |
37 | final List blocList;
38 | final int nfcTagData;
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return MultiBlocProvider(
43 | providers: blocList,
44 | child: ScreenUtilInit(
45 | designSize: const Size(430, 932),
46 | builder: (context, child) {
47 | fcmInit(context);
48 | return MaterialApp(
49 | theme: ThemeData(
50 | splashColor: Colors.transparent,
51 | highlightColor: Colors.transparent,
52 | ),
53 | debugShowCheckedModeBanner: false,
54 | home: SplashPage(nfcTagData: nfcTagData),
55 | );
56 | },
57 | ),
58 | );
59 | }
60 | }
61 |
62 | enum CurrentState {
63 | working(
64 | icon: LoturaIcons.working,
65 | color: LoturaColors.primary50,
66 | deepColor: LoturaColors.primary700,
67 | deviceIconColor: LoturaColors.primary400,
68 | text: "작동중"),
69 | available(
70 | icon: LoturaIcons.checkCircle,
71 | color: LoturaColors.green50,
72 | deepColor: LoturaColors.green700,
73 | deviceIconColor: LoturaColors.green400,
74 | text: "사용 가능"),
75 | disconnected(
76 | icon: LoturaIcons.disconnected,
77 | color: LoturaColors.white,
78 | deepColor: LoturaColors.black,
79 | deviceIconColor: LoturaColors.gray400,
80 | text: "연결 끊김"),
81 | breakdown(
82 | icon: LoturaIcons.cancelCircle,
83 | color: LoturaColors.red50,
84 | deepColor: LoturaColors.red700,
85 | deviceIconColor: LoturaColors.red400,
86 | text: "고장");
87 |
88 | bool get isWorking => this == CurrentState.working;
89 | bool get isNotWorking => this != CurrentState.working;
90 |
91 | bool get isAvailable => this == CurrentState.available;
92 |
93 | bool get isDisconnected => this == CurrentState.disconnected;
94 |
95 | bool get isBreakdown => this == CurrentState.breakdown;
96 |
97 | final IconData icon;
98 | final Color color, deepColor, deviceIconColor;
99 | final String text;
100 |
101 | const CurrentState({
102 | required this.icon,
103 | required this.color,
104 | required this.deepColor,
105 | required this.deviceIconColor,
106 | required this.text,
107 | });
108 | }
109 |
110 | enum DeviceType {
111 | wash(
112 | text: "세탁기",
113 | icon: LoturaIcons.laundry,
114 | imagePath: "assets/laundry_image.jpeg",
115 | ),
116 | dry(
117 | text: "건조기",
118 | icon: LoturaIcons.dry,
119 | imagePath: "assets/dry_image.jpeg",
120 | ),
121 | empty(
122 | text: "",
123 | icon: Icons.abc,
124 | imagePath: "",
125 | );
126 |
127 | bool get isWash => this == DeviceType.wash;
128 |
129 | bool get isDry => this == DeviceType.dry;
130 |
131 | bool get isEmpty => this == DeviceType.empty;
132 |
133 | final String text, imagePath;
134 | final IconData icon;
135 |
136 | const DeviceType({
137 | required this.text,
138 | required this.icon,
139 | required this.imagePath,
140 | });
141 | }
142 |
143 | enum Gender {
144 | boy(text: "남자 세탁실"),
145 | girl(text: "여자 세탁실");
146 |
147 | bool get isBoy => this == Gender.boy;
148 |
149 | bool get isGirl => this == Gender.girl;
150 |
151 | const Gender({required this.text});
152 |
153 | final String text;
154 | }
155 |
156 | enum RoomLocation {
157 | schoolSide(roomName: "남자 학교측 세탁실"),
158 | dormitorySide(roomName: "남자 기숙사측 세탁실"),
159 | schoolGirlSide(roomName: "여자 기숙사측 세탁실");
160 |
161 | bool get isSchoolSide => this == RoomLocation.schoolSide;
162 |
163 | bool get isDormitorySide => this == RoomLocation.dormitorySide;
164 |
165 | bool get isSchoolGirlSide => this == RoomLocation.schoolGirlSide;
166 |
167 | bool get isNotSchoolGirlSide => this != RoomLocation.schoolGirlSide;
168 |
169 | const RoomLocation({required this.roomName});
170 |
171 | final String roomName;
172 | }
173 |
174 | enum LaundryRoomLayer {
175 | first(icon: Icons.looks_one_outlined),
176 | second(icon: Icons.looks_two_outlined);
177 |
178 | bool get isFirst => this == LaundryRoomLayer.first;
179 |
180 | bool get isSecond => this == LaundryRoomLayer.second;
181 |
182 | const LaundryRoomLayer({required this.icon});
183 |
184 | final IconData icon;
185 | }
186 |
--------------------------------------------------------------------------------
/lib/presentation/app_update_page/ui/app_update_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_screenutil/flutter_screenutil.dart';
6 | import 'package:lotura/presentation/utils/lotura_colors.dart';
7 | import 'package:lotura/presentation/utils/osj_text_button.dart';
8 | import 'package:url_launcher/url_launcher.dart';
9 |
10 | class AppUpdatePage extends StatelessWidget {
11 | const AppUpdatePage({super.key});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return ScreenUtilInit(
16 | designSize: const Size(430, 932),
17 | builder: (context, child) => MaterialApp(
18 | debugShowCheckedModeBanner: false,
19 | theme: ThemeData(
20 | splashColor: Colors.transparent,
21 | highlightColor: Colors.transparent,
22 | ),
23 | home: Scaffold(
24 | backgroundColor: Colors.grey[600],
25 | body: Center(
26 | child: Dialog(
27 | backgroundColor: LoturaColors.white,
28 | surfaceTintColor: LoturaColors.white,
29 | child: Container(
30 | padding: EdgeInsets.all(30.0.r),
31 | height: 270.0.h,
32 | child: Column(
33 | crossAxisAlignment: CrossAxisAlignment.start,
34 | children: [
35 | Text("업데이트 안내",
36 | style: TextStyle(
37 | fontWeight: FontWeight.bold, fontSize: 24.0.sp)),
38 | SizedBox(height: 10.0.h),
39 | const Expanded(
40 | child: Text(
41 | "Lotura의 새로운 버전이 출시되었어요!\n지금 바로 업데이트 하러 가기 ⬇️⬇️",
42 | style: TextStyle(fontWeight: FontWeight.w500),
43 | ),
44 | ),
45 | Row(
46 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
47 | children: [
48 | OSJTextButton(
49 | function: () {
50 | if (Platform.isAndroid) {
51 | SystemNavigator.pop();
52 | } else {
53 | exit(0);
54 | }
55 | },
56 | width: 130.0.w,
57 | height: 60.0.h,
58 | fontSize: 18.0.sp,
59 | color: LoturaColors.gray100,
60 | fontColor: LoturaColors.black,
61 | text: "닫기",
62 | radius: 8.0.r,
63 | ),
64 | OSJTextButton(
65 | function: () async => await launchUrl(
66 | Uri.parse(Platform.isAndroid
67 | ? "https://play.google.com/store/apps/details?id=com.osj.lotura"
68 | : "https://apps.apple.com/us/app/lotura/id6448836740"),
69 | mode: LaunchMode.externalApplication),
70 | width: 130.0.w,
71 | height: 60.0.h,
72 | fontSize: 18.0.sp,
73 | color: LoturaColors.primary700,
74 | fontColor: LoturaColors.white,
75 | text: "업데이트",
76 | radius: 8.0.r,
77 | ),
78 | ],
79 | ),
80 | ],
81 | ),
82 | ),
83 | ),
84 | ),
85 | ),
86 | ),
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/bloc/apply_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:lotura/data/apply/dto/request/apply_cancel_request.dart';
3 | import 'package:lotura/data/apply/dto/request/send_fcm_info_request.dart';
4 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
5 | import 'package:lotura/domain/apply/use_case/apply_cancel_use_case.dart';
6 | import 'package:lotura/domain/apply/use_case/get_apply_list_use_case.dart';
7 | import 'package:lotura/domain/apply/use_case/send_fcm_info_use_case.dart';
8 | import 'package:lotura/presentation/apply_page/bloc/apply_event.dart';
9 | import 'package:lotura/presentation/apply_page/bloc/apply_model.dart';
10 | import 'package:lotura/presentation/apply_page/bloc/apply_state.dart';
11 |
12 | class ApplyBloc extends Bloc> {
13 | final GetApplyListUseCase _getApplyListUseCase;
14 | final SendFCMInfoUseCase _sendFCMInfoUseCase;
15 | final ApplyCancelUseCase _applyCancelUseCase;
16 |
17 | ApplyBloc(
18 | {required GetApplyListUseCase getApplyListUseCase,
19 | required SendFCMInfoUseCase sendFCMInfoUseCase,
20 | required ApplyCancelUseCase applyCancelUseCase})
21 | : _getApplyListUseCase = getApplyListUseCase,
22 | _sendFCMInfoUseCase = sendFCMInfoUseCase,
23 | _applyCancelUseCase = applyCancelUseCase,
24 | super(Empty()) {
25 | on(_getApplyListEventHandler);
26 | on(_sendFCMEventHandler);
27 | on(_applyCancelEventHandler);
28 | }
29 |
30 | void _getApplyListEventHandler(
31 | GetApplyListEvent event, Emitter> emit) async {
32 | try {
33 | emit(Loading());
34 | final applyList = await _getApplyListUseCase.execute();
35 | final applyModel = ApplyModel(applyList: applyList);
36 | emit((Loaded(data: applyModel)));
37 | } catch (e) {
38 | emit(Error(errorMessage: e));
39 | }
40 | }
41 |
42 | void _sendFCMEventHandler(
43 | SendFCMEvent event, Emitter> emit) async {
44 | try {
45 | bool isDeviceInApplyList =
46 | state.value.applyList.any((e) => e.deviceId == event.deviceId);
47 | switch (isDeviceInApplyList) {
48 | case true:
49 | break;
50 | case false:
51 | {
52 | List applyList = state.value.applyList;
53 | emit(Loading());
54 | await _sendFCMInfoUseCase.execute(
55 | sendFCMInfoRequest: SendFCMInfoRequest(
56 | deviceId: event.deviceId, expectState: 1));
57 | applyList.add(ApplyEntity(
58 | deviceId: event.deviceId, deviceType: event.deviceType));
59 | applyList.sort((a, b) => a.deviceId.compareTo(b.deviceId));
60 | final applyModel = ApplyModel(applyList: applyList);
61 | emit(Loaded(data: applyModel));
62 | break;
63 | }
64 | }
65 | } catch (e) {
66 | emit(Error(errorMessage: e));
67 | }
68 | }
69 |
70 | void _applyCancelEventHandler(
71 | ApplyCancelEvent event, Emitter> emit) async {
72 | try {
73 | List newApplyList = state.value.applyList;
74 | emit(Loading());
75 | await _applyCancelUseCase.execute(
76 | applyCancelRequest: ApplyCancelRequest(deviceId: event.deviceId));
77 | newApplyList.removeWhere((e) => e.deviceId == event.deviceId);
78 | final applyModel = ApplyModel(applyList: newApplyList);
79 | emit(Loaded(data: applyModel));
80 | } catch (e) {
81 | emit(Error(errorMessage: e));
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/bloc/apply_event.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/data/apply/dto/request/get_apply_list_request.dart';
2 | import 'package:lotura/main.dart';
3 |
4 | abstract class ApplyEvent {}
5 |
6 | class GetApplyListEvent extends ApplyEvent {
7 | final GetApplyListRequest getApplyListRequest;
8 |
9 | GetApplyListEvent({required this.getApplyListRequest});
10 | }
11 |
12 | class ApplyCancelEvent extends ApplyEvent {
13 | final int deviceId;
14 |
15 | ApplyCancelEvent({
16 | required this.deviceId,
17 | });
18 | }
19 |
20 | class SendFCMEvent extends ApplyEvent {
21 | final int deviceId;
22 | final DeviceType deviceType;
23 |
24 | SendFCMEvent({
25 | required this.deviceId,
26 | required this.deviceType,
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/bloc/apply_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/apply/entity/apply_entity.dart';
2 |
3 | class ApplyModel {
4 | final List applyList;
5 |
6 | ApplyModel({
7 | required this.applyList,
8 | });
9 |
10 | ApplyModel copyWith({List? applyList}) {
11 | return ApplyModel(applyList: applyList ?? this.applyList);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/bloc/apply_state.dart:
--------------------------------------------------------------------------------
1 | enum ApplyStateEnum { empty, loading, error, loaded }
2 |
3 | sealed class ApplyState {
4 | ApplyState({required this.applyStateEnum, this.error, this.valueOrNull});
5 |
6 | T? valueOrNull;
7 | Object? error;
8 | ApplyStateEnum applyStateEnum;
9 |
10 | T get value => valueOrNull!;
11 | }
12 |
13 | class Empty extends ApplyState {
14 | Empty() : super(applyStateEnum: ApplyStateEnum.empty);
15 | }
16 |
17 | class Loading extends ApplyState {
18 | Loading() : super(applyStateEnum: ApplyStateEnum.loading);
19 | }
20 |
21 | class Error extends ApplyState {
22 | final Object errorMessage;
23 |
24 | Error({required this.errorMessage})
25 | : super(applyStateEnum: ApplyStateEnum.error, error: errorMessage);
26 | }
27 |
28 | class Loaded extends ApplyState {
29 | final T data;
30 |
31 | Loaded({required this.data})
32 | : super(applyStateEnum: ApplyStateEnum.loaded, valueOrNull: data);
33 | }
34 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/ui/view/apply_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart' as s;
4 | import 'package:lotura/main.dart';
5 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
6 | import 'package:lotura/presentation/apply_page/bloc/apply_model.dart';
7 | import 'package:lotura/presentation/apply_page/bloc/apply_state.dart';
8 | import 'package:lotura/presentation/apply_page/ui/widget/machine_card.dart';
9 | import 'package:lotura/presentation/notice_page/bloc/notice_bloc.dart';
10 | import 'package:lotura/presentation/notice_page/bloc/notice_event.dart';
11 | import 'package:lotura/presentation/notice_page/bloc/notice_model.dart';
12 | import 'package:lotura/presentation/notice_page/bloc/notice_state.dart' as n;
13 | import 'package:lotura/presentation/notice_page/ui/view/notice_page.dart';
14 | import 'package:lotura/presentation/setting_page/ui/view/setting_page.dart';
15 | import 'package:lotura/presentation/utils/lotura_colors.dart';
16 | import 'package:lotura/presentation/utils/lotura_icons.dart';
17 |
18 | class ApplyPage extends StatelessWidget {
19 | ApplyPage({super.key});
20 |
21 | final TextStyle bigStyle = TextStyle(
22 | fontSize: 32.0.sp,
23 | color: LoturaColors.black,
24 | fontWeight: FontWeight.bold,
25 | );
26 |
27 | final TextStyle smallStyle = TextStyle(
28 | fontSize: 18.0.sp,
29 | color: LoturaColors.gray500,
30 | );
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return Scaffold(
35 | backgroundColor: LoturaColors.gray100,
36 | appBar: AppBar(
37 | backgroundColor: LoturaColors.gray100,
38 | elevation: 0.0,
39 | leadingWidth: MediaQuery.of(context).size.width,
40 | title: Padding(
41 | padding: EdgeInsets.only(left: 10.0.r),
42 | child: Row(
43 | children: [
44 | Image.asset(
45 | "assets/applogo.jpeg",
46 | width: 24.0.w,
47 | height: 24.0.h,
48 | ),
49 | SizedBox(width: 8.0.w),
50 | Text(
51 | "OSJ",
52 | style: TextStyle(
53 | fontSize: 20.0.sp,
54 | color: LoturaColors.primary700,
55 | fontWeight: FontWeight.bold),
56 | ),
57 | ],
58 | ),
59 | ),
60 | actions: [
61 | IconButton(
62 | onPressed: () {
63 | context.read().add(UpdateLastNoticeIdEvent());
64 | Navigator.push(context,
65 | MaterialPageRoute(builder: (context) => const NoticePage()));
66 | },
67 | icon: BlocBuilder>(
68 | builder: (context, state) => state.value.isNewNotice
69 | ? Stack(
70 | alignment: Alignment.topRight,
71 | children: [
72 | Icon(
73 | LoturaIcons.notice,
74 | color: LoturaColors.black,
75 | size: 24.0.r,
76 | ),
77 | Container(
78 | width: 10.0.r,
79 | height: 10.0.r,
80 | decoration: const BoxDecoration(
81 | color: Colors.blue,
82 | shape: BoxShape.circle,
83 | ),
84 | ),
85 | ],
86 | )
87 | : Icon(
88 | LoturaIcons.notice,
89 | color: LoturaColors.black,
90 | size: 24.0.r,
91 | ),
92 | ),
93 | ),
94 | IconButton(
95 | onPressed: () => Navigator.push(context,
96 | MaterialPageRoute(builder: (context) => const SettingPage())),
97 | icon: Icon(
98 | Icons.settings,
99 | color: LoturaColors.black,
100 | size: 28.0.r,
101 | )),
102 | SizedBox(width: 24.0.w),
103 | ],
104 | ),
105 | body: Padding(
106 | padding: EdgeInsets.only(left: 24.0.w, right: 24.0.w, top: 36.0.h),
107 | child: Column(
108 | crossAxisAlignment: CrossAxisAlignment.start,
109 | children: [
110 | FittedBox(
111 | fit: BoxFit.scaleDown,
112 | child: Column(
113 | crossAxisAlignment: CrossAxisAlignment.start,
114 | children: [
115 | Text("알림 설정한\n세탁기와 건조기", style: bigStyle),
116 | SizedBox(height: 24.0.h),
117 | Text("알림을 설정하여 세탁기와 건조기를\n누구보다 빠르게 사용해보세요.",
118 | style: smallStyle),
119 | ],
120 | ),
121 | ),
122 | SizedBox(height: 20.0.h),
123 | Expanded(
124 | child: BlocBuilder>(
125 | builder: (context, state) {
126 | return switch (state) {
127 | Empty() => const Center(child: Text("비어있음")),
128 | Loading() =>
129 | const Center(child: CircularProgressIndicator()),
130 | Error() => const Center(child: Text("인터넷 연결을 확인해주세요")),
131 | Loaded() => ScrollConfiguration(
132 | behavior:
133 | const ScrollBehavior().copyWith(overscroll: false),
134 | child: ListView.builder(
135 | itemCount: state.value.applyList.length.isEven
136 | ? state.value.applyList.length ~/ 2
137 | : state.value.applyList.length ~/ 2 + 1,
138 | itemBuilder: (context, index) {
139 | return Column(
140 | children: [
141 | Row(
142 | mainAxisAlignment:
143 | MainAxisAlignment.spaceAround,
144 | children: [
145 | MachineCard(
146 | deviceId: state.value
147 | .applyList[index * 2].deviceId,
148 | isEnableNotification: false,
149 | deviceType: state.value
150 | .applyList[index * 2].deviceType,
151 | state: CurrentState.working),
152 | index * 2 + 1 < state.value.applyList.length
153 | ? MachineCard(
154 | deviceId: state
155 | .value
156 | .applyList[index * 2 + 1]
157 | .deviceId,
158 | isEnableNotification: false,
159 | deviceType: state
160 | .value
161 | .applyList[index * 2 + 1]
162 | .deviceType,
163 | state: CurrentState.working)
164 | : const MachineCard(
165 | //리팩토링 꼭 하기
166 | deviceId: -1,
167 | isEnableNotification: false,
168 | deviceType: DeviceType.empty,
169 | state: CurrentState.working)
170 | ],
171 | ),
172 | SizedBox(height: 20.0.r),
173 | ],
174 | );
175 | },
176 | ),
177 | ),
178 | };
179 | },
180 | ),
181 | ),
182 | ],
183 | ),
184 | ),
185 | );
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/lib/presentation/apply_page/ui/widget/machine_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:lotura/presentation/utils/machine_widget.dart';
4 |
5 | class MachineCard extends MachineWidget {
6 | const MachineCard({
7 | super.key,
8 | required super.deviceId,
9 | required super.isEnableNotification,
10 | required super.deviceType,
11 | required super.state,
12 | });
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return deviceType.isEmpty
17 | ? Container(
18 | width: 170.0.r,
19 | padding: EdgeInsets.only(top: 10.0.r),
20 | decoration: BoxDecoration(
21 | borderRadius: BorderRadius.circular(16.0),
22 | color: Colors.transparent,
23 | ),
24 | child: Column(
25 | mainAxisSize: MainAxisSize.min,
26 | crossAxisAlignment: CrossAxisAlignment.center,
27 | children: [
28 | Image.asset(
29 | "assets/dry_image.jpeg",
30 | width: 100.0.r,
31 | height: 100.0.r,
32 | color: Colors.transparent,
33 | ),
34 | Text(
35 | "12번 건조기",
36 | textScaler: TextScaler.noScaling,
37 | style: TextStyle(
38 | fontSize: 20.0.sp,
39 | fontWeight: FontWeight.w500,
40 | color: Colors.transparent),
41 | ),
42 | Container(
43 | padding: EdgeInsets.all(8.0.r),
44 | child: Text(
45 | "고장",
46 | textAlign: TextAlign.center,
47 | textScaler: TextScaler.noScaling,
48 | style: TextStyle(
49 | color: Colors.transparent,
50 | fontSize: 20.0.sp,
51 | ),
52 | ),
53 | ),
54 | ],
55 | ),
56 | )
57 | : GestureDetector(
58 | onTap: () => showModalOSJBottomSheet(context: context),
59 | child: Container(
60 | width: 170.0.r,
61 | padding: EdgeInsets.only(top: 10.0.r),
62 | decoration: BoxDecoration(
63 | borderRadius: BorderRadius.circular(16.0),
64 | color: Colors.white,
65 | ),
66 | child: Column(
67 | mainAxisSize: MainAxisSize.min,
68 | crossAxisAlignment: CrossAxisAlignment.center,
69 | children: [
70 | Image.asset(
71 | deviceType.imagePath,
72 | width: 100.0.r,
73 | height: 100.0.r,
74 | ),
75 | Text(
76 | "$deviceId번 ${deviceType.text}",
77 | textScaler: TextScaler.noScaling,
78 | style: TextStyle(
79 | fontSize: 20.0.sp,
80 | fontWeight: FontWeight.w500,
81 | ),
82 | ),
83 | Container(
84 | padding: EdgeInsets.all(8.0.r),
85 | child: Text(
86 | state.text,
87 | textAlign: TextAlign.center,
88 | textScaler: TextScaler.noScaling,
89 | style: TextStyle(
90 | color: state.deepColor,
91 | fontSize: 20.0.sp,
92 | ),
93 | ),
94 | ),
95 | ],
96 | ),
97 | ),
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/bloc/laundry_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
3 | import 'package:lotura/domain/laundry/use_case/get_all_laundry_list_use_case.dart';
4 | import 'package:lotura/domain/laundry/use_case/get_laundry_status_use_case.dart';
5 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_event.dart';
6 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_model.dart';
7 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_state.dart';
8 |
9 | class LaundryBloc extends Bloc> {
10 | final GetLaundryStatusUseCase _getLaundryStatusUseCase;
11 | final GetAllLaundryListUseCase _getAllLaundryListEventUseCase;
12 |
13 | LaundryBloc(
14 | {required GetLaundryStatusUseCase getLaundryStatusUseCase,
15 | required GetAllLaundryListUseCase getAllLaundryListUseCase})
16 | : _getLaundryStatusUseCase = getLaundryStatusUseCase,
17 | _getAllLaundryListEventUseCase = getAllLaundryListUseCase,
18 | super(Empty()) {
19 | on(_getLaundryEventHandler);
20 | on(_getAllLaundryListEventHandler);
21 | }
22 |
23 | void _getLaundryEventHandler(
24 | GetLaundryEvent event, Emitter> emit) async {
25 | try {
26 | _getLaundryStatusUseCase.execute();
27 | await for (var data in _getLaundryStatusUseCase.laundryList) {
28 | final newLaundryModel = LaundryModel(
29 | laundryList: state.value.laundryList
30 | .map((e) => e.id == data.id
31 | ? LaundryEntity(
32 | id: e.id,
33 | state: data.state,
34 | deviceType: data.deviceType)
35 | : e)
36 | .toList());
37 | emit(Loaded(data: newLaundryModel));
38 | }
39 | } catch (e) {
40 | emit(Error(error: e));
41 | }
42 | }
43 |
44 | void _getAllLaundryListEventHandler(GetAllLaundryListEvent event,
45 | Emitter> emit) async {
46 | try {
47 | emit(Loading());
48 | final newState = await _getAllLaundryListEventUseCase.execute();
49 | final newLaundryModel = LaundryModel(laundryList: newState);
50 | emit(Loaded(data: newLaundryModel));
51 | } catch (e) {
52 | emit(Error(error: e));
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/bloc/laundry_event.dart:
--------------------------------------------------------------------------------
1 | abstract class LaundryEvent {}
2 |
3 | class GetLaundryEvent extends LaundryEvent {}
4 |
5 | class GetAllLaundryListEvent extends LaundryEvent {}
6 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/bloc/laundry_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
2 |
3 | class LaundryModel {
4 | final List laundryList;
5 |
6 | const LaundryModel({
7 | required this.laundryList,
8 | });
9 |
10 | LaundryModel copyWith({List? laundryList}) {
11 | return LaundryModel(
12 | laundryList: laundryList ?? this.laundryList,
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/bloc/laundry_state.dart:
--------------------------------------------------------------------------------
1 | enum LaundryStateEnum { empty, loading, error, loaded }
2 |
3 | sealed class LaundryState {
4 | LaundryState({required this.laundryState, this.error, this.valueOrNull});
5 |
6 | T? valueOrNull;
7 | Object? error;
8 | LaundryStateEnum laundryState;
9 |
10 | T get value => valueOrNull!;
11 | }
12 |
13 | class Empty extends LaundryState {
14 | Empty() : super(laundryState: LaundryStateEnum.empty);
15 | }
16 |
17 | class Loading extends LaundryState {
18 | Loading() : super(laundryState: LaundryStateEnum.loading);
19 | }
20 |
21 | class Error extends LaundryState {
22 | final Object error;
23 |
24 | Error({required this.error})
25 | : super(laundryState: LaundryStateEnum.error, error: error);
26 | }
27 |
28 | class Loaded extends LaundryState {
29 | final T data;
30 |
31 | Loaded({required this.data})
32 | : super(laundryState: LaundryStateEnum.loaded, valueOrNull: data);
33 | }
34 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/ui/view/laundry_room_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart' as s;
4 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
5 | import 'package:lotura/main.dart';
6 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_bloc.dart';
7 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_model.dart';
8 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_state.dart';
9 | import 'package:lotura/presentation/laundry_room_page/ui/widget/machine_button.dart';
10 | import 'package:lotura/presentation/notice_page/bloc/notice_bloc.dart';
11 | import 'package:lotura/presentation/notice_page/bloc/notice_event.dart';
12 | import 'package:lotura/presentation/notice_page/bloc/notice_model.dart';
13 | import 'package:lotura/presentation/notice_page/bloc/notice_state.dart' as n;
14 | import 'package:lotura/presentation/notice_page/ui/view/notice_page.dart';
15 | import 'package:lotura/presentation/setting_page/bloc/laundry_room_model.dart';
16 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
17 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
18 | import 'package:lotura/presentation/setting_page/bloc/room_state.dart';
19 | import 'package:lotura/presentation/setting_page/ui/view/setting_page.dart';
20 | import 'package:lotura/presentation/utils/lotura_colors.dart';
21 | import 'package:lotura/presentation/utils/lotura_icons.dart';
22 | import 'package:lotura/presentation/utils/osj_bottom_sheet.dart';
23 | import 'package:lotura/presentation/utils/osj_text_button.dart';
24 |
25 | class LaundryRoomPage extends StatelessWidget {
26 | LaundryRoomPage({super.key, required this.nfcTagData});
27 |
28 | final int nfcTagData;
29 |
30 | final Map place = {
31 | 0: "남자 학교측 세탁실 1층",
32 | 1: "남자 학교측 세탁실 2층",
33 | 2: "남자 기숙사측 세탁실 1층",
34 | 3: "남자 기숙사측 세탁실 2층",
35 | 4: "여자 세탁실 1층",
36 | };
37 |
38 | final Map placeIndex = {
39 | 0: 0,
40 | 1: 16,
41 | 2: 31,
42 | 3: 47,
43 | 4: 63,
44 | };
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return BlocBuilder>(
49 | builder: (context, roomBlocState) {
50 | return switch (roomBlocState) {
51 | Initial() => const Center(child: CircularProgressIndicator()),
52 | Changed() => Scaffold(
53 | backgroundColor: LoturaColors.gray100,
54 | appBar: AppBar(
55 | backgroundColor: LoturaColors.gray100,
56 | elevation: 0.0,
57 | leadingWidth: MediaQuery.of(context).size.width,
58 | title: Padding(
59 | padding: EdgeInsets.only(left: 10.r),
60 | child: Text(
61 | roomBlocState.value.roomLocation.roomName,
62 | style: TextStyle(
63 | color: LoturaColors.black,
64 | fontSize: 22.0.sp,
65 | fontWeight: FontWeight.w600,
66 | ),
67 | ),
68 | ),
69 | actions: [
70 | IconButton(
71 | onPressed: () {
72 | context.read().add(UpdateLastNoticeIdEvent());
73 | Navigator.push(
74 | context,
75 | MaterialPageRoute(
76 | builder: (context) => const NoticePage()));
77 | },
78 | icon: BlocBuilder>(
79 | builder: (context, state) => state.value.isNewNotice
80 | ? Stack(
81 | alignment: Alignment.topRight,
82 | children: [
83 | Icon(
84 | LoturaIcons.notice,
85 | color: LoturaColors.black,
86 | size: 24.0.r,
87 | ),
88 | Container(
89 | width: 10.0.r,
90 | height: 10.0.r,
91 | decoration: const BoxDecoration(
92 | color: Colors.blue,
93 | shape: BoxShape.circle,
94 | ),
95 | ),
96 | ],
97 | )
98 | : Icon(
99 | LoturaIcons.notice,
100 | color: LoturaColors.black,
101 | size: 24.0.r,
102 | ),
103 | ),
104 | ),
105 | IconButton(
106 | onPressed: () => Navigator.push(
107 | context,
108 | MaterialPageRoute(
109 | builder: (context) => const SettingPage())),
110 | icon: Icon(
111 | Icons.settings,
112 | color: LoturaColors.black,
113 | size: 28.0.r,
114 | ),
115 | ),
116 | SizedBox(width: 24.0.w),
117 | ],
118 | ),
119 | body: Padding(
120 | padding: EdgeInsets.only(
121 | left: 24.0.w,
122 | right: 24.0.w,
123 | ),
124 | child: Column(
125 | crossAxisAlignment: CrossAxisAlignment.start,
126 | children: [
127 | SingleChildScrollView(
128 | scrollDirection: Axis.horizontal,
129 | padding: EdgeInsets.symmetric(vertical: 12.0.h),
130 | child: roomBlocState.data.roomLocation.isNotSchoolGirlSide
131 | ? Row(
132 | children: [
133 | OSJTextButton(
134 | function: () => context.read().add(
135 | ModifyRoomIndexEvent(
136 | roomLocation:
137 | RoomLocation.schoolSide)),
138 | fontSize: 18.0.sp,
139 | color: roomBlocState
140 | .value.roomLocation.isSchoolSide
141 | ? LoturaColors.white
142 | : LoturaColors.gray100,
143 | fontColor: roomBlocState
144 | .value.roomLocation.isSchoolSide
145 | ? LoturaColors.primary700
146 | : LoturaColors.gray300,
147 | text: "남자 학교측",
148 | padding:
149 | EdgeInsets.symmetric(horizontal: 5.0.r),
150 | radius: 8.0,
151 | ),
152 | SizedBox(width: 8.0.w),
153 | OSJTextButton(
154 | function: () => context.read().add(
155 | ModifyRoomIndexEvent(
156 | roomLocation:
157 | RoomLocation.dormitorySide)),
158 | fontSize: 18.0.sp,
159 | color: roomBlocState
160 | .value.roomLocation.isDormitorySide
161 | ? LoturaColors.white
162 | : LoturaColors.gray100,
163 | fontColor: roomBlocState
164 | .value.roomLocation.isDormitorySide
165 | ? LoturaColors.primary700
166 | : LoturaColors.gray300,
167 | text: "남자 기숙사측",
168 | padding:
169 | EdgeInsets.symmetric(horizontal: 5.0.r),
170 | radius: 8.0,
171 | ),
172 | ],
173 | )
174 | : OSJTextButton(
175 | function: () => context.read().add(
176 | ModifyRoomIndexEvent(
177 | roomLocation:
178 | RoomLocation.schoolGirlSide)),
179 | fontSize: 18.0.sp,
180 | color: LoturaColors.white,
181 | fontColor: LoturaColors.primary700,
182 | text: "여자 기숙사측",
183 | padding: EdgeInsets.symmetric(horizontal: 5.0.r),
184 | radius: 8.0,
185 | ),
186 | ),
187 | LaundryRoomLayerFilter(
188 | laundryRoomLayer: roomBlocState.value.laundryRoomLayer,
189 | currentRoomLocation: roomBlocState.data.roomLocation,
190 | ),
191 | Expanded(
192 | child:
193 | BlocBuilder>(
194 | builder: (context, state) {
195 | return switch (state) {
196 | Empty() => const Center(child: Text("비어있음")),
197 | Loading() =>
198 | const Center(child: CircularProgressIndicator()),
199 | Error() =>
200 | const Center(child: Text("인터넷 연결을 확인해주세요")),
201 | Loaded() => roomBlocState
202 | .data.roomLocation.isNotSchoolGirlSide
203 | ? LaundryList(
204 | list: state.data.laundryList,
205 | laundryRoomModel: roomBlocState.value,
206 | nfcData: nfcTagData,
207 | )
208 | : const Center(child: Text("여자 세탁실은 준비중~")),
209 | };
210 | },
211 | ),
212 | ),
213 | ],
214 | ),
215 | ),
216 | ),
217 | };
218 | },
219 | );
220 | }
221 | }
222 |
223 | class LaundryRoomLayerFilter extends StatelessWidget {
224 | const LaundryRoomLayerFilter({
225 | super.key,
226 | required this.laundryRoomLayer,
227 | required this.currentRoomLocation,
228 | });
229 |
230 | final LaundryRoomLayer laundryRoomLayer;
231 | final RoomLocation currentRoomLocation;
232 |
233 | @override
234 | Widget build(BuildContext context) {
235 | return Row(
236 | mainAxisAlignment: MainAxisAlignment.end,
237 | children: [
238 | TextButton(
239 | onPressed: () => context.read().add(
240 | ModifyLaundryRoomLayerEvent(
241 | laundryRoomLayer: LaundryRoomLayer.first)),
242 | child: Text(
243 | "1층",
244 | style: TextStyle(
245 | fontSize: 18.0.sp,
246 | color: laundryRoomLayer.isFirst
247 | ? LoturaColors.black
248 | : LoturaColors.gray300,
249 | ),
250 | ),
251 | ),
252 | if (currentRoomLocation.isNotSchoolGirlSide)
253 | TextButton(
254 | onPressed: () => context.read().add(
255 | ModifyLaundryRoomLayerEvent(
256 | laundryRoomLayer: LaundryRoomLayer.second)),
257 | child: Text(
258 | "2층",
259 | style: TextStyle(
260 | fontSize: 18.0.sp,
261 | color: laundryRoomLayer.isSecond
262 | ? LoturaColors.black
263 | : LoturaColors.gray300,
264 | ),
265 | ),
266 | ),
267 | ],
268 | );
269 | }
270 | }
271 |
272 | class LaundryList extends StatelessWidget {
273 | LaundryList({
274 | super.key,
275 | required this.list,
276 | required this.laundryRoomModel,
277 | required this.nfcData,
278 | });
279 |
280 | final List list;
281 | final LaundryRoomModel laundryRoomModel;
282 | final int nfcData;
283 |
284 | final Map placeIndex = {0: 0, 1: 16, 2: 31, 3: 47, 4: 63};
285 |
286 | @override
287 | Widget build(BuildContext context) {
288 | WidgetsBinding.instance.addPostFrameCallback(
289 | (_) {
290 | if (nfcData != -1 && laundryRoomModel.isNFCShowBottomSheet == false) {
291 | if (laundryRoomModel.showingBottomSheet == true) {
292 | Navigator.of(context).pop();
293 | }
294 | context.read().add(ShowBottomSheetEvent());
295 | context.read().add(ShowingBottomSheetEvent());
296 | showModalBottomSheet(
297 | context: context,
298 | shape: RoundedRectangleBorder(
299 | borderRadius: BorderRadius.vertical(
300 | top: Radius.circular(25.r),
301 | ),
302 | ),
303 | builder: (context) => OSJBottomSheet(
304 | deviceId: nfcData,
305 | isEnableNotification: true,
306 | state: list[nfcData - 1].state,
307 | machine: list[nfcData - 1].deviceType,
308 | ),
309 | );
310 | }
311 | },
312 | );
313 | return ScrollConfiguration(
314 | behavior: const ScrollBehavior().copyWith(overscroll: false),
315 | child: ListView.builder(
316 | itemCount: 8,
317 | // laundryRoomModel.roomLocation == RoomLocation.womanRoom ? 10 : 8,
318 | itemBuilder: (context, index) {
319 | return Column(
320 | children: [
321 | Row(
322 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
323 | children: [
324 | MachineButton(
325 | laundryEntity: laundryRoomModel.laundryRoomLayer.isFirst
326 | ? list[
327 | placeIndex[laundryRoomModel.roomLocation.index]! +
328 | index]
329 | : list[
330 | placeIndex[laundryRoomModel.roomLocation.index]! +
331 | index +
332 | 32],
333 | isEnableNotification: true,
334 | ),
335 | const Icon(LoturaIcons.triangleUp, color: Colors.grey),
336 | MachineButton(
337 | laundryEntity: laundryRoomModel.laundryRoomLayer.isFirst
338 | ? list[
339 | placeIndex[laundryRoomModel.roomLocation.index]! +
340 | index +
341 | 8]
342 | : list[
343 | placeIndex[laundryRoomModel.roomLocation.index]! +
344 | index +
345 | 40],
346 | isEnableNotification: true,
347 | ),
348 | ],
349 | ),
350 | SizedBox(height: 10.0.h),
351 | ],
352 | );
353 | },
354 | ),
355 | );
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/lib/presentation/laundry_room_page/ui/widget/machine_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 | import 'package:lotura/domain/laundry/entity/laundry_entity.dart';
4 | import 'package:lotura/presentation/utils/lotura_colors.dart';
5 | import 'package:lotura/presentation/utils/lotura_icons.dart';
6 | import 'package:lotura/presentation/utils/machine_widget.dart';
7 |
8 | class MachineButton extends MachineWidget {
9 | MachineButton({
10 | super.key,
11 | required this.laundryEntity,
12 | required super.isEnableNotification,
13 | }) : super(
14 | deviceId: laundryEntity.id,
15 | deviceType: laundryEntity.deviceType,
16 | state: laundryEntity.state,
17 | );
18 |
19 | final LaundryEntity laundryEntity;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return laundryEntity.deviceType.isEmpty
24 | ? Container(
25 | padding: EdgeInsets.all(12.0.r),
26 | constraints: BoxConstraints(
27 | minWidth: 150.0.w,
28 | maxWidth: 185.0.w,
29 | ),
30 | decoration: BoxDecoration(
31 | color: Colors.transparent,
32 | borderRadius: BorderRadius.circular(16.0),
33 | border: Border.all(color: Colors.transparent)),
34 | child: Row(
35 | mainAxisSize: MainAxisSize.min,
36 | crossAxisAlignment: CrossAxisAlignment.start,
37 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
38 | children: [
39 | Container(
40 | width: 32.0.r,
41 | height: 32.0.r,
42 | decoration: BoxDecoration(
43 | shape: BoxShape.circle,
44 | color: Colors.transparent,
45 | border: Border.all(color: Colors.transparent, width: 2),
46 | ),
47 | child: Icon(LoturaIcons.dry,
48 | size: 20.0.r, color: Colors.transparent),
49 | ),
50 | Column(
51 | crossAxisAlignment: CrossAxisAlignment.end,
52 | children: [
53 | Text("12번",
54 | style: TextStyle(
55 | fontSize: 16.0.sp, color: Colors.transparent)),
56 | Row(
57 | mainAxisAlignment: MainAxisAlignment.end,
58 | children: [
59 | Text("세탁기",
60 | style: TextStyle(
61 | fontSize: 15.0.sp, color: Colors.transparent)),
62 | ],
63 | ),
64 | ],
65 | ),
66 | ],
67 | ),
68 | )
69 | : GestureDetector(
70 | onTap: () => showModalOSJBottomSheet(context: context),
71 | child: Container(
72 | padding: EdgeInsets.all(12.0.r),
73 | constraints: BoxConstraints(
74 | minWidth: 150.0.w,
75 | maxWidth: 185.0.w,
76 | ),
77 | decoration: BoxDecoration(
78 | color: laundryEntity.state.color,
79 | borderRadius: BorderRadius.circular(16.0),
80 | border: Border.all(color: LoturaColors.gray200)),
81 | child: Row(
82 | mainAxisSize: MainAxisSize.min,
83 | crossAxisAlignment: CrossAxisAlignment.start,
84 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
85 | children: [
86 | Container(
87 | width: 32.0.r,
88 | height: 32.0.r,
89 | decoration: BoxDecoration(
90 | shape: BoxShape.circle,
91 | color: laundryEntity.state.color,
92 | border: Border.all(
93 | color: laundryEntity.state.deviceIconColor, width: 2),
94 | ),
95 | child: Icon(laundryEntity.deviceType.icon,
96 | size: 20.0.r,
97 | color: laundryEntity.state.deviceIconColor),
98 | ),
99 | Column(
100 | crossAxisAlignment: CrossAxisAlignment.end,
101 | children: [
102 | Text("${laundryEntity.id}번",
103 | style: TextStyle(
104 | fontSize: 15.0.sp, fontWeight: FontWeight.w500)),
105 | Row(
106 | mainAxisAlignment: MainAxisAlignment.end,
107 | children: [
108 | Text(laundryEntity.deviceType.text,
109 | style: TextStyle(fontSize: 14.0.sp)),
110 | ],
111 | ),
112 | ],
113 | ),
114 | ],
115 | ),
116 | ),
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/bloc/notice_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
3 | import 'package:lotura/domain/notice/use_case/get_last_notice_id_use_case.dart';
4 | import 'package:lotura/domain/notice/use_case/get_notice_use_case.dart';
5 | import 'package:lotura/domain/notice/use_case/update_last_notice_id_use_case.dart';
6 | import 'package:lotura/presentation/notice_page/bloc/notice_event.dart';
7 | import 'package:lotura/presentation/notice_page/bloc/notice_model.dart';
8 | import 'package:lotura/presentation/notice_page/bloc/notice_state.dart';
9 |
10 | class NoticeBloc extends Bloc> {
11 | final GetNoticeUseCase _getNoticeUseCase;
12 | final GetLastNoticeIdUseCase _getLastNoticeIdUseCase;
13 | final UpdateLastNoticeIdUseCase _updateLastNoticeIdUseCase;
14 |
15 | NoticeBloc(
16 | {required GetNoticeUseCase getNoticeUseCase,
17 | required GetLastNoticeIdUseCase getLastNoticeIdUseCase,
18 | required UpdateLastNoticeIdUseCase updateLastNoticeIdUseCase})
19 | : _getNoticeUseCase = getNoticeUseCase,
20 | _getLastNoticeIdUseCase = getLastNoticeIdUseCase,
21 | _updateLastNoticeIdUseCase = updateLastNoticeIdUseCase,
22 | super(Empty(
23 | data: const NoticeModel(
24 | noticeList: [],
25 | isNewNotice: false,
26 | ))) {
27 | on(_getNoticeEventHandler);
28 | on(_updateLastNoticeIdEventHandler);
29 | }
30 |
31 | void _getNoticeEventHandler(
32 | GetNoticeEvent event, Emitter> emit) async {
33 | try {
34 | emit(Loading());
35 | final noticeList = await _getNoticeUseCase.execute();
36 | final isNewNotice =
37 | _getLastNoticeIdUseCase.execute(noticeList: noticeList);
38 | final newNoticeModel =
39 | NoticeModel(noticeList: noticeList, isNewNotice: isNewNotice);
40 | emit(Loaded(data: newNoticeModel));
41 | } catch (e) {
42 | emit(Error(error: e));
43 | }
44 | }
45 |
46 | void _updateLastNoticeIdEventHandler(UpdateLastNoticeIdEvent event,
47 | Emitter> emit) async {
48 | try {
49 | await _updateLastNoticeIdUseCase.execute(
50 | noticeList: state.value.noticeList);
51 | emit(Loaded(data: state.value.copyWith(isNewNotice: false)));
52 | } catch (e) {
53 | emit(Error(error: e));
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/bloc/notice_event.dart:
--------------------------------------------------------------------------------
1 | abstract class NoticeEvent {}
2 |
3 | class GetNoticeEvent extends NoticeEvent {}
4 |
5 | class UpdateLastNoticeIdEvent extends NoticeEvent {}
6 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/bloc/notice_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
2 |
3 | class NoticeModel {
4 | final List noticeList;
5 | final bool isNewNotice;
6 |
7 | const NoticeModel({
8 | required this.noticeList,
9 | required this.isNewNotice,
10 | });
11 |
12 | NoticeModel copyWith({List? noticeList, bool? isNewNotice}) {
13 | return NoticeModel(
14 | noticeList: noticeList ?? this.noticeList,
15 | isNewNotice: isNewNotice ?? this.isNewNotice,
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/bloc/notice_state.dart:
--------------------------------------------------------------------------------
1 | enum NoticeStateEnum { empty, loading, error, loaded }
2 |
3 | sealed class NoticeState {
4 | NoticeState({required this.noticeState, this.error, this.valueOrNull});
5 |
6 | T? valueOrNull;
7 | Object? error;
8 | NoticeStateEnum noticeState;
9 |
10 | T get value => valueOrNull!;
11 | }
12 |
13 | class Empty extends NoticeState {
14 | final T data;
15 |
16 | Empty({required this.data}) : super(noticeState: NoticeStateEnum.empty);
17 | }
18 |
19 | class Loading extends NoticeState {
20 | Loading() : super(noticeState: NoticeStateEnum.loading);
21 | }
22 |
23 | class Error extends NoticeState {
24 | final Object error;
25 |
26 | Error({required this.error})
27 | : super(noticeState: NoticeStateEnum.error, error: error);
28 | }
29 |
30 | class Loaded extends NoticeState {
31 | final T data;
32 |
33 | Loaded({required this.data})
34 | : super(noticeState: NoticeStateEnum.loaded, valueOrNull: data);
35 | }
36 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/ui/view/notice_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:lotura/presentation/notice_page/bloc/notice_bloc.dart';
5 | import 'package:lotura/presentation/notice_page/bloc/notice_model.dart';
6 | import 'package:lotura/presentation/notice_page/bloc/notice_state.dart';
7 | import 'package:lotura/presentation/notice_page/ui/widget/notice_list_tile.dart';
8 | import 'package:lotura/presentation/utils/lotura_colors.dart';
9 |
10 | class NoticePage extends StatelessWidget {
11 | const NoticePage({super.key});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | backgroundColor: LoturaColors.gray100,
17 | appBar: AppBar(
18 | backgroundColor: LoturaColors.gray100,
19 | elevation: 0.0,
20 | toolbarHeight: 90.0.r,
21 | leading: IconButton(
22 | padding: EdgeInsets.only(left: 24.0.r),
23 | onPressed: () => Navigator.pop(context),
24 | icon: Icon(
25 | Icons.keyboard_arrow_left,
26 | color: LoturaColors.black,
27 | size: 30.0.r,
28 | ),
29 | ),
30 | title: Text(
31 | "공지",
32 | style: TextStyle(
33 | fontSize: 24.0.sp,
34 | color: LoturaColors.black,
35 | fontWeight: FontWeight.bold,
36 | ),
37 | ),
38 | ),
39 | body: Padding(
40 | padding: EdgeInsets.symmetric(horizontal: 24.0.r),
41 | child: BlocBuilder>(
42 | builder: (context, state) => switch (state) {
43 | Empty() => const Center(child: Text("비어있음")),
44 | Loading() => const Center(child: CircularProgressIndicator()),
45 | Error() => const Center(child: Text("인터넷 연결을 확인해주세요")),
46 | Loaded(data: final data) => SizedBox(
47 | width: MediaQuery.of(context).size.width,
48 | height: MediaQuery.of(context).size.height,
49 | child: ListView.builder(
50 | padding: EdgeInsets.only(top: 12.0.r),
51 | itemCount: data.noticeList.length,
52 | itemBuilder: (context, index) {
53 | return NoticeListTile(noticeEntity: data.noticeList[index]);
54 | },
55 | ),
56 | ),
57 | },
58 | ),
59 | ),
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/presentation/notice_page/ui/widget/notice_list_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_markdown/flutter_markdown.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:lotura/domain/notice/entity/notice_entity.dart';
5 | import 'package:lotura/presentation/utils/lotura_colors.dart';
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | class NoticeListTile extends StatefulWidget {
9 | final NoticeEntity noticeEntity;
10 |
11 | const NoticeListTile({super.key, required this.noticeEntity});
12 |
13 | @override
14 | State createState() => _NoticeListTileState();
15 | }
16 |
17 | class _NoticeListTileState extends State {
18 | bool _tap = false;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return GestureDetector(
23 | onTap: () => setState(() => _tap = !_tap),
24 | child: Container(
25 | margin: EdgeInsets.symmetric(vertical: 7.5.h),
26 | padding: EdgeInsets.all(20.0.r),
27 | decoration: const BoxDecoration(
28 | color: LoturaColors.white,
29 | borderRadius: BorderRadius.all(Radius.circular(12))),
30 | child: Column(
31 | crossAxisAlignment: CrossAxisAlignment.start,
32 | children: [
33 | Row(
34 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
35 | children: [
36 | Expanded(
37 | child: Column(
38 | crossAxisAlignment: CrossAxisAlignment.start,
39 | children: [
40 | Text(widget.noticeEntity.title,
41 | maxLines: 4, style: TextStyle(fontSize: 16.0.sp)),
42 | Text(widget.noticeEntity.date,
43 | maxLines: 2, style: TextStyle(fontSize: 10.0.sp)),
44 | ],
45 | ),
46 | ),
47 | Icon(
48 | _tap ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
49 | ],
50 | ),
51 | _tap
52 | ? Markdown(
53 | padding: EdgeInsets.only(top: 10.0.r),
54 | shrinkWrap: true,
55 | physics: const NeverScrollableScrollPhysics(),
56 | data: widget.noticeEntity.contents,
57 | onTapLink: (text, href, title) async {
58 | await launchUrl(Uri.parse(href!),
59 | mode: LaunchMode.externalApplication);
60 | },
61 | styleSheet: MarkdownStyleSheet(
62 | a: const TextStyle(color: Colors.blue),
63 | ),
64 | )
65 | : const SizedBox.shrink(),
66 | ],
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/bloc/laundry_room_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/main.dart';
2 |
3 | class LaundryRoomModel {
4 | final RoomLocation roomLocation;
5 | final LaundryRoomLayer laundryRoomLayer;
6 | final bool isClick, isNFCShowBottomSheet, showingBottomSheet;
7 |
8 | const LaundryRoomModel({
9 | required this.roomLocation,
10 | required this.laundryRoomLayer,
11 | required this.isClick,
12 | required this.isNFCShowBottomSheet,
13 | required this.showingBottomSheet,
14 | });
15 |
16 | LaundryRoomModel copyWith(
17 | {RoomLocation? roomLocation,
18 | LaundryRoomLayer? laundryRoomLayer,
19 | bool? isClick,
20 | bool? isNFCShowBottomSheet,
21 | bool? showingBottomSheet}) {
22 | return LaundryRoomModel(
23 | roomLocation: roomLocation ?? this.roomLocation,
24 | laundryRoomLayer: laundryRoomLayer ?? this.laundryRoomLayer,
25 | isClick: isClick ?? this.isClick,
26 | isNFCShowBottomSheet: isNFCShowBottomSheet ?? this.isNFCShowBottomSheet,
27 | showingBottomSheet: showingBottomSheet ?? this.showingBottomSheet);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/bloc/room_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_bloc/flutter_bloc.dart';
2 | import 'package:lotura/domain/laundry/use_case/get_laundry_room_index_use_case.dart';
3 | import 'package:lotura/domain/laundry/use_case/update_laundry_room_index_use_case.dart';
4 | import 'package:lotura/main.dart';
5 | import 'package:lotura/presentation/setting_page/bloc/laundry_room_model.dart';
6 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
7 | import 'package:lotura/presentation/setting_page/bloc/room_state.dart';
8 |
9 | class RoomBloc extends Bloc> {
10 | final GetLaundryRoomIndexUseCase _getLaundryRoomIndexUseCase;
11 | final UpdateLaundryRoomIndexUseCase _updateLaundryRoomIndexUseCase;
12 |
13 | RoomBloc(
14 | {required GetLaundryRoomIndexUseCase getLaundryRoomIndexUseCase,
15 | required UpdateLaundryRoomIndexUseCase updateLaundryRoomIndexUseCase})
16 | : _getLaundryRoomIndexUseCase = getLaundryRoomIndexUseCase,
17 | _updateLaundryRoomIndexUseCase = updateLaundryRoomIndexUseCase,
18 | super(Initial(
19 | data: const LaundryRoomModel(
20 | roomLocation: RoomLocation.schoolSide,
21 | laundryRoomLayer: LaundryRoomLayer.first,
22 | isClick: false,
23 | isNFCShowBottomSheet: false,
24 | showingBottomSheet: false))) {
25 | on(_updateRoomIndexEventHandler);
26 | on(_getRoomIndexEventHandler);
27 | on(_modifyRoomIndexEventHandler);
28 | on(_modifyPlaceIconIndexEventHandler);
29 | on(_showBottomSheetEventHandler);
30 | on(_initialShowBottomSheetEventHandler);
31 | on(_showingBottomSheetEventHandler);
32 | on(_closingBottomSheetEventHandler);
33 | }
34 |
35 | void _updateRoomIndexEventHandler(
36 | UpdateRoomIndexEvent event, Emitter> emit) {
37 | emit(Changed(data: state.value.copyWith(roomLocation: event.roomLocation)));
38 | _updateLaundryRoomIndexUseCase.execute(value: event.roomLocation.index);
39 | }
40 |
41 | void _getRoomIndexEventHandler(
42 | GetRoomIndexEvent event, Emitter> emit) {
43 | emit(Changed(
44 | data: state.value.copyWith(
45 | roomLocation: RoomLocation.values
46 | .elementAt(_getLaundryRoomIndexUseCase.execute))));
47 | }
48 |
49 | void _modifyRoomIndexEventHandler(
50 | ModifyRoomIndexEvent event, Emitter> emit) {
51 | emit(Changed(data: state.value.copyWith(roomLocation: event.roomLocation)));
52 | }
53 |
54 | void _modifyPlaceIconIndexEventHandler(ModifyLaundryRoomLayerEvent event,
55 | Emitter> emit) {
56 | emit(Changed(
57 | data: state.value.copyWith(laundryRoomLayer: event.laundryRoomLayer)));
58 | }
59 |
60 | void _showBottomSheetEventHandler(
61 | ShowBottomSheetEvent event, Emitter> emit) {
62 | emit(Changed(data: state.value.copyWith(isNFCShowBottomSheet: true)));
63 | }
64 |
65 | void _initialShowBottomSheetEventHandler(InitialShowBottomSheetEvent event,
66 | Emitter> emit) {
67 | emit(Changed(data: state.value.copyWith(isNFCShowBottomSheet: false)));
68 | }
69 |
70 | void _showingBottomSheetEventHandler(ShowingBottomSheetEvent event,
71 | Emitter> emit) {
72 | emit(Changed(data: state.value.copyWith(showingBottomSheet: true)));
73 | }
74 |
75 | void _closingBottomSheetEventHandler(ClosingBottomSheetEvent event,
76 | Emitter> emit) {
77 | emit(Changed(data: state.value.copyWith(showingBottomSheet: false)));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/bloc/room_event.dart:
--------------------------------------------------------------------------------
1 | import 'package:lotura/main.dart';
2 |
3 | abstract class RoomEvent {}
4 |
5 | class GetRoomIndexEvent extends RoomEvent {}
6 |
7 | class UpdateRoomIndexEvent extends RoomEvent {
8 | final RoomLocation roomLocation;
9 |
10 | UpdateRoomIndexEvent({required this.roomLocation});
11 | }
12 |
13 | class ModifyRoomIndexEvent extends RoomEvent {
14 | final RoomLocation roomLocation;
15 |
16 | ModifyRoomIndexEvent({required this.roomLocation});
17 | }
18 |
19 | class ModifyLaundryRoomLayerEvent extends RoomEvent {
20 | final LaundryRoomLayer laundryRoomLayer;
21 |
22 | ModifyLaundryRoomLayerEvent({required this.laundryRoomLayer});
23 | }
24 |
25 | class ShowBottomSheetEvent extends RoomEvent {}
26 |
27 | class InitialShowBottomSheetEvent extends RoomEvent {}
28 |
29 | class ShowingBottomSheetEvent extends RoomEvent {}
30 |
31 | class ClosingBottomSheetEvent extends RoomEvent {}
32 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/bloc/room_state.dart:
--------------------------------------------------------------------------------
1 | enum RoomStateEnum { initial, changed }
2 |
3 | sealed class RoomState {
4 | RoomState({required this.roomState, required this.value, this.error});
5 |
6 | T value;
7 | Object? error;
8 | RoomStateEnum roomState;
9 | }
10 |
11 | class Initial extends RoomState {
12 | final T data;
13 |
14 | Initial({required this.data})
15 | : super(roomState: RoomStateEnum.initial, value: data);
16 | }
17 |
18 | class Changed extends RoomState {
19 | final T data;
20 |
21 | Changed({required this.data})
22 | : super(roomState: RoomStateEnum.changed, value: data);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/ui/view/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
5 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
6 | import 'package:lotura/presentation/setting_page/ui/widget/setting_page_bottom_sheet.dart';
7 | import 'package:lotura/presentation/utils/lotura_colors.dart';
8 | import 'package:url_launcher/url_launcher.dart';
9 |
10 | class SettingPage extends StatefulWidget {
11 | const SettingPage({super.key});
12 |
13 | @override
14 | State createState() => _SettingPageState();
15 | }
16 |
17 | class _SettingPageState extends State {
18 | @override
19 | void initState() {
20 | super.initState();
21 | context.read().add(GetRoomIndexEvent());
22 | }
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return Scaffold(
27 | backgroundColor: LoturaColors.gray100,
28 | appBar: AppBar(
29 | backgroundColor: LoturaColors.gray100,
30 | elevation: 0.0,
31 | toolbarHeight: 90.0.r,
32 | leading: IconButton(
33 | padding: EdgeInsets.only(left: 24.0.r),
34 | onPressed: () => Navigator.pop(context),
35 | icon: Icon(
36 | Icons.keyboard_arrow_left,
37 | color: LoturaColors.black,
38 | size: 30.0.r,
39 | ),
40 | ),
41 | title: Text(
42 | "설정",
43 | style: TextStyle(
44 | fontSize: 24.0.sp,
45 | color: LoturaColors.black,
46 | fontWeight: FontWeight.bold,
47 | ),
48 | ),
49 | ),
50 | body: Padding(
51 | padding: EdgeInsets.symmetric(
52 | horizontal: 24.0.r,
53 | vertical: 12.0.r,
54 | ),
55 | child: Column(
56 | children: [
57 | GestureDetector(
58 | onTap: () => showModalBottomSheet(
59 | context: context,
60 | shape: RoundedRectangleBorder(
61 | borderRadius: BorderRadius.vertical(
62 | top: Radius.circular(24.r),
63 | ),
64 | ),
65 | backgroundColor: LoturaColors.white,
66 | builder: (context) {
67 | context.read().add(GetRoomIndexEvent());
68 | return const SettingPageBottomSheet();
69 | }),
70 | behavior: HitTestBehavior.translucent,
71 | child: Row(
72 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
73 | children: [
74 | Flexible(
75 | child: Text(
76 | "메인 세탁실 설정",
77 | style: TextStyle(fontSize: 18.0.sp),
78 | overflow: TextOverflow.ellipsis,
79 | ),
80 | ),
81 | Icon(
82 | Icons.keyboard_arrow_right,
83 | color: LoturaColors.gray300,
84 | size: 30.0.r,
85 | ),
86 | ],
87 | ),
88 | ),
89 | SizedBox(height: 30.0.r),
90 | GestureDetector(
91 | onTap: () async => await launchUrl(
92 | Uri.parse('https://open.kakao.com/o/sHjnH1Se'),
93 | mode: LaunchMode.externalApplication),
94 | behavior: HitTestBehavior.translucent,
95 | child: Row(
96 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
97 | children: [
98 | Text(
99 | "문의하기",
100 | style: TextStyle(fontSize: 18.0.sp),
101 | ),
102 | Icon(
103 | Icons.keyboard_arrow_right,
104 | color: LoturaColors.gray300,
105 | size: 30.0.r,
106 | ),
107 | ],
108 | ),
109 | ),
110 | ],
111 | ),
112 | ),
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/presentation/setting_page/ui/widget/setting_page_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart';
4 | import 'package:lotura/main.dart';
5 | import 'package:lotura/presentation/setting_page/bloc/laundry_room_model.dart';
6 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
7 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
8 | import 'package:lotura/presentation/setting_page/bloc/room_state.dart';
9 | import 'package:lotura/presentation/utils/lotura_colors.dart';
10 |
11 | class SettingPageBottomSheet extends StatelessWidget {
12 | const SettingPageBottomSheet({super.key});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return BlocBuilder>(
17 | builder: (context, state) {
18 | return switch (state) {
19 | Initial() => const SizedBox.shrink(),
20 | Changed() => Container(
21 | padding: EdgeInsets.all(24.0.r),
22 | decoration: const BoxDecoration(
23 | color: LoturaColors.white,
24 | borderRadius: BorderRadius.only(
25 | topLeft: Radius.circular(20.0),
26 | topRight: Radius.circular(20.0)),
27 | ),
28 | child: Wrap(
29 | children: [
30 | FittedBox(
31 | fit: BoxFit.scaleDown,
32 | child: Text(
33 | "메인 세탁실 설정",
34 | style: TextStyle(
35 | color: LoturaColors.black,
36 | fontWeight: FontWeight.w600,
37 | fontSize: 30.0.sp),
38 | ),
39 | ),
40 | SizedBox(height: 4.0.h),
41 | FittedBox(
42 | fit: BoxFit.scaleDown,
43 | child: Text(
44 | "세탁실 탭에서 처음에 보여질 세탁실을 선택해보세요.",
45 | style: TextStyle(
46 | fontSize: 25.0.sp, color: LoturaColors.black),
47 | ),
48 | ),
49 | SizedBox(height: 30.0.r),
50 | CheckButton(
51 | gender: Gender.boy,
52 | roomLocation: RoomLocation.schoolSide,
53 | currentRoomLocation: state.data.roomLocation,
54 | ),
55 | CheckButton(
56 | gender: Gender.girl,
57 | roomLocation: RoomLocation.schoolGirlSide,
58 | currentRoomLocation: state.data.roomLocation,
59 | ),
60 | ],
61 | ),
62 | ),
63 | };
64 | },
65 | );
66 | }
67 | }
68 |
69 | class CheckButton extends StatelessWidget {
70 | const CheckButton({
71 | super.key,
72 | required this.gender,
73 | required this.roomLocation,
74 | required this.currentRoomLocation,
75 | });
76 |
77 | final Gender gender;
78 | final RoomLocation roomLocation, currentRoomLocation;
79 |
80 | @override
81 | Widget build(BuildContext context) {
82 | return GestureDetector(
83 | onTap: () => context
84 | .read()
85 | .add(UpdateRoomIndexEvent(roomLocation: roomLocation)),
86 | child: Container(
87 | decoration: BoxDecoration(
88 | color: currentRoomLocation == roomLocation
89 | ? LoturaColors.gray100
90 | : LoturaColors.white,
91 | borderRadius: BorderRadius.circular(8.0.r),
92 | ),
93 | padding: EdgeInsets.all(12.0.r),
94 | child: Row(
95 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
96 | children: [
97 | Flexible(
98 | child: Text(
99 | gender.text,
100 | overflow: TextOverflow.ellipsis,
101 | style: TextStyle(
102 | fontSize: 16.0.sp,
103 | color: LoturaColors.black,
104 | ),
105 | ),
106 | ),
107 | Icon(
108 | Icons.check,
109 | size: 24.0.r,
110 | color: currentRoomLocation == roomLocation
111 | ? LoturaColors.black
112 | : LoturaColors.white,
113 | ),
114 | ],
115 | ),
116 | ),
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/lib/presentation/splash_page/ui/view/splash_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 | import 'dart:io';
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_bloc/flutter_bloc.dart';
7 | import 'package:flutter_screenutil/flutter_screenutil.dart';
8 | import 'package:http/http.dart' as http;
9 | import 'package:lotura/data/apply/dto/request/get_apply_list_request.dart';
10 | import 'package:lotura/presentation/app_update_page/ui/app_update_page.dart';
11 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
12 | import 'package:lotura/presentation/apply_page/bloc/apply_event.dart';
13 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_bloc.dart';
14 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_event.dart';
15 | import 'package:lotura/presentation/notice_page/bloc/notice_bloc.dart';
16 | import 'package:lotura/presentation/notice_page/bloc/notice_event.dart';
17 | import 'package:lotura/presentation/utils/bottom_navi.dart';
18 | import 'package:lotura/presentation/utils/lotura_colors.dart';
19 | import 'package:lotura/secret.dart';
20 | import 'package:package_info_plus/package_info_plus.dart';
21 |
22 | class SplashPage extends StatefulWidget {
23 | const SplashPage({super.key, required this.nfcTagData});
24 |
25 | final int nfcTagData;
26 |
27 | @override
28 | State createState() => _SplashPageState();
29 | }
30 |
31 | class _SplashPageState extends State {
32 | Future checkAppVersion() async {
33 | PackageInfo packageInfo = await PackageInfo.fromPlatform();
34 | String appVersion = packageInfo.version;
35 |
36 | final res = await http.get(Uri.parse(
37 | "$baseurl/app_ver_${Platform.isAndroid ? "android" : "ios"}"));
38 |
39 | final newestAppVersion = jsonDecode(res.body)['version'];
40 | final storeState = jsonDecode(res.body)['store_status'];
41 |
42 | if (newestAppVersion != appVersion && storeState == "1") {
43 | Future.delayed(Duration.zero).then((value) =>
44 | Navigator.pushAndRemoveUntil(
45 | context,
46 | MaterialPageRoute(builder: (context) => const AppUpdatePage()),
47 | (route) => false));
48 | } else {
49 | Future.delayed(const Duration(seconds: 1)).then(
50 | (value) {
51 | Navigator.pushAndRemoveUntil(
52 | context,
53 | MaterialPageRoute(
54 | builder: (context) => BottomNavi(nfcTagData: widget.nfcTagData),
55 | ),
56 | (route) => false);
57 | },
58 | );
59 | }
60 | }
61 |
62 | @override
63 | void initState() {
64 | super.initState();
65 | checkAppVersion();
66 | context.read().add(GetNoticeEvent());
67 | context.read().add(GetAllLaundryListEvent());
68 | context
69 | .read()
70 | .add(GetApplyListEvent(getApplyListRequest: GetApplyListRequest()));
71 | context.read().add(GetLaundryEvent());
72 | }
73 |
74 | @override
75 | Widget build(BuildContext context) {
76 | return Scaffold(
77 | backgroundColor: LoturaColors.gray100,
78 | body: Center(
79 | child: Column(
80 | mainAxisAlignment: MainAxisAlignment.center,
81 | children: [
82 | Image.asset(
83 | "assets/applogo.jpeg",
84 | width: 100.0.r,
85 | height: 100.0.r,
86 | ),
87 | SizedBox(height: 12.0.h),
88 | const Text(
89 | 'OSJ',
90 | style: TextStyle(
91 | color: LoturaColors.primary700,
92 | fontSize: 40,
93 | fontWeight: FontWeight.w700,
94 | ),
95 | ),
96 | ],
97 | ),
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/presentation/utils/bottom_navi.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter_bloc/flutter_bloc.dart';
7 | import 'package:flutter_screenutil/flutter_screenutil.dart';
8 | import 'package:hive_flutter/hive_flutter.dart';
9 | import 'package:lotura/data/apply/dto/request/get_apply_list_request.dart';
10 | import 'package:lotura/main.dart';
11 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
12 | import 'package:lotura/presentation/apply_page/bloc/apply_event.dart';
13 | import 'package:lotura/presentation/apply_page/ui/view/apply_page.dart';
14 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_bloc.dart';
15 | import 'package:lotura/presentation/laundry_room_page/bloc/laundry_event.dart';
16 | import 'package:lotura/presentation/laundry_room_page/ui/view/laundry_room_page.dart';
17 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
18 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
19 | import 'package:lotura/presentation/utils/lotura_colors.dart';
20 | import 'package:lotura/presentation/utils/osj_icon_button.dart';
21 | import 'package:lotura/presentation/utils/osj_image_button.dart';
22 |
23 | class BottomNavi extends StatefulWidget {
24 | BottomNavi({super.key, required this.nfcTagData});
25 |
26 | int nfcTagData;
27 |
28 | @override
29 | State createState() => _BottomNaviState();
30 | }
31 |
32 | class _BottomNaviState extends State
33 | with SingleTickerProviderStateMixin, WidgetsBindingObserver {
34 | late TabController controller;
35 | final platformMsg = const MethodChannel('com.osj.lotura/nfc_info');
36 |
37 | int selectedIndex = 1;
38 | bool isChange = false;
39 |
40 | @override
41 | void initState() {
42 | super.initState();
43 | WidgetsBinding.instance.addObserver(this);
44 | if (Platform.isAndroid) {
45 | const MethodChannel('com.osj.lotura/nfc_info')
46 | .invokeMethod('nfcIsAvailable')
47 | .then(
48 | (value) {
49 | if (value.toString() == "false") {
50 | WidgetsBinding.instance.addPostFrameCallback(
51 | (_) {
52 | Hive.openBox("Lotura").then(
53 | (value) {
54 | if (value.get('다시 보지 않기') == null) {
55 | showDialog(
56 | context: context,
57 | builder: (context) => Center(
58 | child: AlertDialog(
59 | title: Text(
60 | "NFC가 비활성화 되어있습니다.",
61 | style: TextStyle(fontSize: 20.0.sp),
62 | ),
63 | content: Text(
64 | "Lotura 서비스 이용을 위해\n스마트폰의 NFC를 설정해주세요.",
65 | style: TextStyle(fontSize: 18.0.sp),
66 | ),
67 | shape: RoundedRectangleBorder(
68 | borderRadius: BorderRadius.circular(10.0),
69 | ),
70 | actionsAlignment: MainAxisAlignment.spaceEvenly,
71 | actions: [
72 | MaterialButton(
73 | onPressed: () {
74 | value.put('다시 보지 않기', 1);
75 | Navigator.of(context).pop();
76 | },
77 | child: Text("다시 보지 않기",
78 | style: TextStyle(fontSize: 16.0.sp))),
79 | MaterialButton(
80 | onPressed: () => Navigator.of(context).pop(),
81 | child: Text("닫기",
82 | style: TextStyle(fontSize: 16.0.sp))),
83 | ],
84 | ),
85 | ),
86 | );
87 | }
88 | },
89 | );
90 | },
91 | );
92 | }
93 | },
94 | );
95 | }
96 | if (widget.nfcTagData != -1) {
97 | int d = widget.nfcTagData;
98 | if (d <= 16) {
99 | context
100 | .read()
101 | .add(ModifyRoomIndexEvent(roomLocation: RoomLocation.schoolSide));
102 | context.read().add(ModifyLaundryRoomLayerEvent(
103 | laundryRoomLayer: LaundryRoomLayer.first));
104 | } else if (d <= 32) {
105 | context.read().add(
106 | ModifyRoomIndexEvent(roomLocation: RoomLocation.dormitorySide));
107 | context.read().add(ModifyLaundryRoomLayerEvent(
108 | laundryRoomLayer: LaundryRoomLayer.first));
109 | } else if (d <= 48) {
110 | context
111 | .read()
112 | .add(ModifyRoomIndexEvent(roomLocation: RoomLocation.schoolSide));
113 | context.read().add(ModifyLaundryRoomLayerEvent(
114 | laundryRoomLayer: LaundryRoomLayer.second));
115 | } else if (d <= 64) {
116 | context.read().add(
117 | ModifyRoomIndexEvent(roomLocation: RoomLocation.dormitorySide));
118 | context.read().add(ModifyLaundryRoomLayerEvent(
119 | laundryRoomLayer: LaundryRoomLayer.second));
120 | }
121 | } else {
122 | context.read().add(GetRoomIndexEvent());
123 | }
124 | controller = TabController(length: 2, vsync: this)
125 | ..index = 1
126 | ..animation?.addListener(() {
127 | if (controller.offset >= 0.5 &&
128 | controller.offset < 1.0 &&
129 | isChange == false) {
130 | setState(() {
131 | isChange = true;
132 | selectedIndex = 1;
133 | });
134 | }
135 | if (controller.offset < 0.5 &&
136 | controller.offset > 0.0 &&
137 | isChange == true) {
138 | setState(() {
139 | isChange = false;
140 | selectedIndex = 0;
141 | });
142 | }
143 | if (controller.offset >= -0.5 &&
144 | controller.offset < 0.0 &&
145 | isChange == false) {
146 | setState(() {
147 | isChange = true;
148 | selectedIndex = 1;
149 | });
150 | }
151 | if (controller.offset < -0.5 &&
152 | controller.offset > -1.0 &&
153 | isChange == true) {
154 | setState(() {
155 | isChange = false;
156 | selectedIndex = 0;
157 | });
158 | }
159 | if (controller.offset == 1.0 || controller.offset == 0.0) {
160 | setState(() {
161 | isChange = false;
162 | });
163 | }
164 | });
165 | }
166 |
167 | @override
168 | void didChangeAppLifecycleState(AppLifecycleState state) {
169 | super.didChangeAppLifecycleState(state);
170 | switch (state) {
171 | case AppLifecycleState.resumed:
172 | platformMsg.invokeMethod("getNFCInfo").then((value) {
173 | widget.nfcTagData = (jsonDecode(value)['index'] as int);
174 | if (widget.nfcTagData != -1) {
175 | selectedIndex = 1;
176 | controller.index = 1;
177 | int d = widget.nfcTagData;
178 | if (d <= 16) {
179 | context.read().add(
180 | ModifyRoomIndexEvent(roomLocation: RoomLocation.schoolSide));
181 | context.read().add(ModifyLaundryRoomLayerEvent(
182 | laundryRoomLayer: LaundryRoomLayer.first));
183 | } else if (d <= 32) {
184 | context.read().add(ModifyRoomIndexEvent(
185 | roomLocation: RoomLocation.dormitorySide));
186 | context.read().add(ModifyLaundryRoomLayerEvent(
187 | laundryRoomLayer: LaundryRoomLayer.first));
188 | } else if (d <= 48) {
189 | context.read().add(
190 | ModifyRoomIndexEvent(roomLocation: RoomLocation.schoolSide));
191 | context.read().add(ModifyLaundryRoomLayerEvent(
192 | laundryRoomLayer: LaundryRoomLayer.second));
193 | } else if (d <= 64) {
194 | context.read().add(ModifyRoomIndexEvent(
195 | roomLocation: RoomLocation.dormitorySide));
196 | context.read().add(ModifyLaundryRoomLayerEvent(
197 | laundryRoomLayer: LaundryRoomLayer.second));
198 | }
199 | BlocProvider.of(context)
200 | .add(InitialShowBottomSheetEvent());
201 | setState(() {});
202 | }
203 | });
204 | BlocProvider.of(context).add(GetAllLaundryListEvent());
205 | BlocProvider.of(context).add(GetLaundryEvent());
206 | BlocProvider.of(context)
207 | .add(GetApplyListEvent(getApplyListRequest: GetApplyListRequest()));
208 | default:
209 | }
210 | }
211 |
212 | @override
213 | void dispose() {
214 | super.dispose();
215 | controller.dispose();
216 | WidgetsBinding.instance.removeObserver(this);
217 | }
218 |
219 | @override
220 | Widget build(BuildContext context) {
221 | return Scaffold(
222 | backgroundColor: LoturaColors.gray100,
223 | body: TabBarView(
224 | controller: controller,
225 | children: [
226 | ApplyPage(),
227 | LaundryRoomPage(nfcTagData: widget.nfcTagData),
228 | ],
229 | ),
230 | bottomNavigationBar: TabBar(
231 | padding: EdgeInsets.only(top: 10.0.h, bottom: 30.0.h),
232 | dividerColor: Colors.transparent,
233 | controller: controller,
234 | indicatorColor: Colors.transparent,
235 | tabs: [
236 | OSJIconButton(
237 | width: 185.0.w,
238 | height: 48.0.h,
239 | iconSize: 24.0.r,
240 | color: selectedIndex == 0
241 | ? LoturaColors.white
242 | : LoturaColors.gray100,
243 | iconColor: selectedIndex == 0
244 | ? LoturaColors.primary700
245 | : LoturaColors.gray300,
246 | iconData: Icons.home),
247 | OSJImageButton(
248 | width: 185.0.w,
249 | height: 48.0.h,
250 | color:
251 | selectedIndex == 1 ? LoturaColors.white : LoturaColors.gray100,
252 | imagePath: selectedIndex == 1
253 | ? "assets/applogo.jpeg"
254 | : "assets/applogo_unselected.jpeg",
255 | ),
256 | ],
257 | ),
258 | );
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/lib/presentation/utils/lotura_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoturaColors {
4 | LoturaColors._();
5 |
6 | static const Color customGreen = Color(0xFF7DB45A);
7 | static const Color customRed = Color(0xFFDA6156);
8 |
9 | static const Color primary900 = Color(0xff164ED4);
10 | static const Color primary700 = Color(0xff3C70EC);
11 | static const Color primary500 = Color(0xff7599ED);
12 | static const Color primary400 = Color(0xff758AC3);
13 | static const Color primary300 = Color(0xff98B1ED);
14 | static const Color primary100 = Color(0xffEBF1FF);
15 | static const Color primary50 = Color(0xffFAFBFF);
16 |
17 | static const Color green700 = Color(0xff25BD1D);
18 | static const Color green400 = Color(0xff6ACC64);
19 | static const Color green100 = Color(0xffECFFEB);
20 | static const Color green50 = Color(0xffF8FFF8);
21 |
22 | static const Color red700 = Color(0xffD91F1F);
23 | static const Color red400 = Color(0xffD96B6B);
24 | static const Color red100 = Color(0xffFFEBEB);
25 | static const Color red50 = Color(0xffFFFAFA);
26 |
27 | static const Color black = Color(0xff000000);
28 | static const Color gray900 = Color(0xff1D1F22);
29 | static const Color gray700 = Color(0xff2E3135);
30 | static const Color gray500 = Color(0xff676C74);
31 | static const Color gray400 = Color(0xffB2B2B2);
32 | static const Color gray300 = Color(0xffADB3BD);
33 | static const Color gray200 = Color(0xffE7E7E7);
34 | static const Color gray100 = Color(0xffF0F3F6);
35 | static const Color white = Color(0xffFFFFFF);
36 | }
37 |
--------------------------------------------------------------------------------
/lib/presentation/utils/lotura_icons.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class LoturaIcons {
4 | LoturaIcons._();
5 |
6 | static const _kFontFam = 'LoturaIcons';
7 | static const String? _kFontPkg = null;
8 |
9 | static const IconData checkCircle =
10 | IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
11 | static const IconData disconnected =
12 | IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
13 | static const IconData dry =
14 | IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
15 | static const IconData grid =
16 | IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
17 | static const IconData list =
18 | IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
19 | static const IconData laundry =
20 | IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
21 | static const IconData working =
22 | IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
23 | static const IconData cancelCircle =
24 | IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
25 | static const IconData notice =
26 | IconData(0xf0f3, fontFamily: _kFontFam, fontPackage: _kFontPkg);
27 | static const IconData triangleUp =
28 | IconData(0xf311, fontFamily: _kFontFam, fontPackage: _kFontPkg);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/presentation/utils/machine_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart' as m;
3 | import 'package:lotura/main.dart';
4 | import 'package:lotura/presentation/utils/osj_bottom_sheet.dart';
5 |
6 | abstract class MachineWidget extends StatelessWidget {
7 | const MachineWidget({
8 | super.key,
9 | required this.deviceId,
10 | required this.isEnableNotification,
11 | required this.state,
12 | required this.deviceType,
13 | });
14 |
15 | final int deviceId;
16 | final DeviceType deviceType;
17 | final CurrentState state;
18 | final bool isEnableNotification;
19 |
20 | void showModalOSJBottomSheet({required BuildContext context}) =>
21 | showModalBottomSheet(
22 | context: context,
23 | shape: RoundedRectangleBorder(
24 | borderRadius: BorderRadius.vertical(
25 | top: Radius.circular(25.r),
26 | ),
27 | ),
28 | builder: (context) => OSJBottomSheet(
29 | deviceId: deviceId,
30 | isEnableNotification: isEnableNotification,
31 | state: state,
32 | machine: deviceType,
33 | ),
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/lib/presentation/utils/osj_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:flutter_screenutil/flutter_screenutil.dart' as s;
4 | import 'package:lotura/main.dart';
5 | import 'package:lotura/presentation/apply_page/bloc/apply_bloc.dart';
6 | import 'package:lotura/presentation/apply_page/bloc/apply_event.dart';
7 | import 'package:lotura/presentation/setting_page/bloc/room_bloc.dart';
8 | import 'package:lotura/presentation/setting_page/bloc/room_event.dart';
9 | import 'package:lotura/presentation/utils/lotura_colors.dart';
10 | import 'package:lotura/presentation/utils/osj_text_button.dart';
11 |
12 | class OSJBottomSheet extends StatefulWidget {
13 | const OSJBottomSheet({
14 | super.key,
15 | required this.deviceId,
16 | required this.isEnableNotification,
17 | required this.state,
18 | required this.machine,
19 | });
20 |
21 | final int deviceId;
22 | final bool isEnableNotification;
23 | final CurrentState state;
24 | final DeviceType machine;
25 |
26 | @override
27 | State createState() => _OSJBottomSheetState();
28 | }
29 |
30 | class _OSJBottomSheetState extends State {
31 | String text(bool isEnableNotification, CurrentState state) {
32 | if (isEnableNotification) {
33 | switch (state) {
34 | case CurrentState.working:
35 | return "${widget.deviceId}번 ${widget.machine.text}를\n알림 설정 하실건가요?";
36 | case CurrentState.available:
37 | return "${widget.deviceId}번 ${widget.machine.text}는\n현재 사용 가능한 상태에요.";
38 | case CurrentState.disconnected:
39 | return "${widget.deviceId}번 ${widget.machine.text}의 연결이 끊겨서\n상태를 확인할 수 없어요.";
40 | case CurrentState.breakdown:
41 | return "${widget.deviceId}번 ${widget.machine.text}는\n고장으로 인해 사용이 불가능해요.";
42 | }
43 | } else {
44 | return "${widget.deviceId}번 ${widget.machine.text}의\n알림 설정을 해제하실건가요?";
45 | }
46 | }
47 |
48 | @override
49 | void initState() {
50 | super.initState();
51 | context.read().add(ShowingBottomSheetEvent());
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return PopScope(
57 | onPopInvoked: (_) async {
58 | context.read().add(ClosingBottomSheetEvent());
59 | return Future(() => true);
60 | },
61 | child: Container(
62 | padding: EdgeInsets.symmetric(vertical: 32.0.r, horizontal: 24.0.r),
63 | decoration: const BoxDecoration(
64 | color: LoturaColors.white,
65 | borderRadius: BorderRadius.only(
66 | topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
67 | ),
68 | child: Wrap(
69 | children: [
70 | Padding(
71 | padding: EdgeInsets.only(bottom: 24.0.r),
72 | child: FittedBox(
73 | fit: BoxFit.scaleDown,
74 | child: Column(
75 | crossAxisAlignment: CrossAxisAlignment.start,
76 | children: [
77 | if (widget.state.isNotWorking)
78 | Icon(
79 | widget.state.icon,
80 | size: 24.0.r,
81 | color: widget.state.isAvailable
82 | ? LoturaColors.green700
83 | : widget.state.isDisconnected
84 | ? LoturaColors.black
85 | : LoturaColors.red700,
86 | ),
87 | SizedBox(height: 10.0.r),
88 | Text(
89 | text(widget.isEnableNotification, widget.state),
90 | style: TextStyle(
91 | color: Colors.black,
92 | fontSize: 22.0.sp,
93 | fontWeight: FontWeight.w600,
94 | ),
95 | ),
96 | ],
97 | ),
98 | ),
99 | ),
100 | widget.state.isWorking
101 | ? Row(
102 | children: [
103 | Expanded(
104 | child: OSJTextButton(
105 | function: () {
106 | Navigator.of(context).pop();
107 | context
108 | .read()
109 | .add(ClosingBottomSheetEvent());
110 | },
111 | fontSize: 14.0.sp,
112 | color: LoturaColors.gray100,
113 | fontColor: LoturaColors.black,
114 | padding: EdgeInsets.symmetric(vertical: 15.0.r),
115 | text: "취소"),
116 | ),
117 | SizedBox(width: 16.0.r),
118 | Expanded(
119 | child: OSJTextButton(
120 | function: () {
121 | widget.isEnableNotification
122 | ? context.read().add(SendFCMEvent(
123 | deviceId: widget.deviceId,
124 | deviceType: widget.machine))
125 | : context
126 | .read()
127 | .add(ApplyCancelEvent(
128 | deviceId: widget.deviceId,
129 | ));
130 | context
131 | .read()
132 | .add(ClosingBottomSheetEvent());
133 | Navigator.pop(context);
134 | },
135 | fontSize: 14.0.sp,
136 | color: LoturaColors.primary700,
137 | fontColor: LoturaColors.white,
138 | padding: EdgeInsets.symmetric(vertical: 15.0.r),
139 | text: widget.isEnableNotification
140 | ? "알림 설정"
141 | : "알림 해제"),
142 | ),
143 | ],
144 | )
145 | : OSJTextButton(
146 | function: () {
147 | context.read().add(ClosingBottomSheetEvent());
148 | Navigator.of(context).pop();
149 | },
150 | fontSize: 16.0.sp,
151 | color: LoturaColors.gray100,
152 | fontColor: LoturaColors.black,
153 | padding: EdgeInsets.symmetric(vertical: 15.0.r),
154 | text: "확인"),
155 | ],
156 | ),
157 | ),
158 | );
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/lib/presentation/utils/osj_icon_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class OSJIconButton extends StatelessWidget {
4 | const OSJIconButton(
5 | {super.key,
6 | required this.width,
7 | required this.height,
8 | required this.iconSize,
9 | required this.color,
10 | required this.iconColor,
11 | required this.iconData,
12 | this.function});
13 |
14 | final double width, height, iconSize;
15 | final Color color, iconColor;
16 | final IconData iconData;
17 | final Function? function;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return GestureDetector(
22 | onTap: function != null ? () => function : null,
23 | child: Container(
24 | width: width,
25 | height: height,
26 | decoration: BoxDecoration(
27 | color: color,
28 | borderRadius: BorderRadius.circular(16.0),
29 | ),
30 | child: Center(
31 | child: Icon(iconData, size: iconSize, color: iconColor),
32 | ),
33 | ),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/presentation/utils/osj_image_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class OSJImageButton extends StatelessWidget {
5 | const OSJImageButton({
6 | super.key,
7 | required this.width,
8 | required this.height,
9 | required this.color,
10 | required this.imagePath,
11 | this.function,
12 | this.imageWidth,
13 | this.imageHeight,
14 | });
15 |
16 | final double width, height;
17 | final double? imageWidth, imageHeight;
18 | final Color color;
19 | final String imagePath;
20 | final Function? function;
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return GestureDetector(
25 | onTap: function != null ? () => function : null,
26 | child: Container(
27 | width: width,
28 | height: height,
29 | decoration: BoxDecoration(
30 | color: color,
31 | borderRadius: BorderRadius.circular(16.0),
32 | ),
33 | child: Center(
34 | child: Image.asset(
35 | imagePath,
36 | width: imageWidth ?? 24.0.r,
37 | height: imageHeight ?? 24.0.r,
38 | ),
39 | ),
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/presentation/utils/osj_text_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class OSJTextButton extends StatelessWidget {
5 | const OSJTextButton(
6 | {super.key,
7 | required this.fontSize,
8 | required this.color,
9 | required this.fontColor,
10 | required this.text,
11 | this.width,
12 | this.height,
13 | this.function,
14 | this.padding = EdgeInsets.zero,
15 | this.radius = 16.0});
16 |
17 | final double? width, height;
18 | final double fontSize, radius;
19 | final Color color, fontColor;
20 | final String text;
21 | final VoidCallback? function;
22 | final EdgeInsets padding;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return GestureDetector(
27 | onTap: function,
28 | child: Container(
29 | width: width,
30 | height: height,
31 | padding: padding,
32 | decoration: BoxDecoration(
33 | color: color,
34 | borderRadius: BorderRadius.circular(radius),
35 | ),
36 | child: Center(
37 | child: Text(
38 | text,
39 | style: TextStyle(fontSize: fontSize, color: fontColor),
40 | textAlign: TextAlign.center,
41 | ),
42 | ),
43 | ),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/presentation/utils/vibrating_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_screenutil/flutter_screenutil.dart';
3 |
4 | class VibratingWidget extends StatefulWidget {
5 | const VibratingWidget({super.key, required this.child});
6 |
7 | final Widget child;
8 |
9 | @override
10 | State createState() => _VibratingWidgetState();
11 | }
12 |
13 | class _VibratingWidgetState extends State
14 | with SingleTickerProviderStateMixin {
15 | late AnimationController _animationController;
16 | late Animation _animation;
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 | _animationController = AnimationController(
22 | duration: const Duration(milliseconds: 100), vsync: this);
23 |
24 | _animation = Tween(begin: 0.0, end: 4.0.r).animate(_animationController);
25 |
26 | _animationController.repeat();
27 | }
28 |
29 | @override
30 | void dispose() {
31 | _animationController.dispose();
32 | super.dispose();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return AnimatedBuilder(
38 | animation: _animation,
39 | builder: (context, child) => Transform.translate(
40 | offset: Offset(_animation.value - 2.0.r, 0),
41 | child: widget.child,
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: lotura
2 | description: A new Flutter project.
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 3.7.1+27
20 |
21 | environment:
22 | sdk: '>=3.1.3 <4.0.0'
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 |
31 | flutter_icons:
32 | android: "launcher_icon"
33 | ios: true
34 | image_path: "assets/applogo.jpeg"
35 |
36 | dependencies:
37 | flutter:
38 | sdk: flutter
39 |
40 |
41 | # The following adds the Cupertino Icons font to your application.
42 | # Use with the CupertinoIcons class for iOS style icons.
43 | cupertino_icons: ^1.0.2
44 | flutter_screenutil: ^5.6.0
45 | firebase_messaging: ^14.1.4
46 | flutter_local_notifications: ^16.3.0
47 | url_launcher: ^6.1.7
48 | flutter_launcher_icons: ^0.13.1
49 | flutter_bloc: ^8.1.3
50 | equatable: ^2.0.5
51 | hive: ^2.2.3
52 | hive_flutter: ^1.1.0
53 | http: ^1.1.0
54 | web_socket_channel: ^2.4.0
55 | package_info_plus: ^4.2.0
56 | flutter_markdown: ^0.7.1
57 |
58 |
59 | dev_dependencies:
60 | flutter_test:
61 | sdk: flutter
62 |
63 | # The "flutter_lints" package below contains a set of recommended lints to
64 | # encourage good coding practices. The lint set provided by the package is
65 | # activated in the `analysis_options.yaml` file located at the root of your
66 | # package. See that file for information about deactivating specific lint
67 | # rules and activating additional ones.
68 | flutter_lints: ^3.0.1
69 |
70 | # For information on the generic Dart part of this file, see the
71 | # following page: https://dart.dev/tools/pub/pubspec
72 |
73 | # The following section is specific to Flutter packages.
74 | flutter:
75 | assets:
76 | - assets/
77 | fonts:
78 | - family: LoturaIcons
79 | fonts:
80 | - asset: assets/fonts/Lotura.ttf
81 |
82 |
83 | # The following line ensures that the Material Icons font is
84 | # included with your application, so that you can use the icons in
85 | # the material Icons class.
86 | uses-material-design: true
87 |
88 | # To add assets to your application, add an assets section, like this:
89 |
90 | # An image asset can refer to one or more resolution-specific "variants", see
91 | # https://flutter.dev/assets-and-images/#resolution-aware
92 |
93 | # For details regarding adding assets from package dependencies, see
94 | # https://flutter.dev/assets-and-images/#from-packages
95 |
96 | # To add custom fonts to your application, add a fonts section here,
97 | # in this "flutter" section. Each entry in this list should have a
98 | # "family" key with the font family name, and a "fonts" key with a
99 | # list giving the asset and other descriptors for the font. For
100 | # example:
101 | # For details regarding fonts from package dependencies,
102 | # see https://flutter.dev/custom-fonts/#from-packages
103 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:lotura/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------