diff --git a/README.md b/README.md deleted file mode 100644 index 84fcdbb..0000000 --- a/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# TIL - -Today I Learned - -오늘 학습한 내용을 정리하는 것 - - -## 샘플 -- https://github.com/namjunemy/TIL -- https://github.com/milooy/TIL -- https://github.com/YutoMizutani/til diff --git a/dart_240611.md b/dart_240611.md index f768402..e68fd44 100644 --- a/dart_240611.md +++ b/dart_240611.md @@ -6,7 +6,11 @@ - abstract 클래스 - 사물의 어떤 큰 특징들을 보고 만드는 편 +- 상속을 통해 기능을 재사용하고자 할 때 사용한다 +- 필드와 메서드를 포함 할 수 있고, 상속받은 클래스들이 일부 메서드를 구현하도록 강제한다 - abstract class 만 사용하자 +- 'is - a' 관계를 나타낼 때 사용한다. 'Medic' 은 'Unit' 이다 : OK! + (but Unit은 Medic이다: NO! 유닛이 메딕만 있는것이 아니다) - 추상 클래스에서는 {}을 넣지 않고 메서드를 쓴다 (); 요렇게 끝내면 됨 - 추상 클래스는 인스턴스화 금지(안된다잉) - 추상 클래스는 메서도가 없어도 괜찮다(group만을 만드려고 그러는 경우가 있다) @@ -17,9 +21,13 @@ abstract 클래스 interface - 다중 구현이 가능하다 -- 인터페이스 안에서는 프로퍼티 정의 해놓으면(getter/setter) 상속 받은데서 getter/setter 오버라이드를 해야되는데 번거롭다 +- 클래스가 구현 해야 할 메서드의 목록을 제공한다 +- 'can-do' 관계를 나타낼 때 사용한다. 'Medic'은 'Healable' 능력을 가지고(할 수)있다 +- 인터페이스 안에서는 프로퍼티 정의 해놓으면(getter/setter) +- 상속 받은데서 getter/setter 오버라이드를 해야되는데 번거롭다 - 그래서 주로 메서드를 정의 해놓는 편이 나을것이다 - 인터페이스 안에는 다 미정의 되어 있는 기능 - 보통 독립적인 기능들이 모여있다(사람이라면 말하기,걷기등..) -- abstract interface class 만 사용하자(implements 만 사용하자 공식문서에는 그렇게 되어있다구!!) -- \ No newline at end of file +- 인터페이스는 메서드 시그니처만을 정의한다 +- abstract interface class 만 사용하자 +- (implements 만 사용하자 공식문서에는 그렇게 되어있다구!!) diff --git a/dart_240612.md b/dart_240612.md index 5d527dc..698820a 100644 --- a/dart_240612.md +++ b/dart_240612.md @@ -2,6 +2,163 @@ - 다형성을 알고 나면 개발이 즐거워진다(제발) - 다형성 - 공통메소드를 통합 - 공통성은 없는데 메소드(기능)만 동일하게 가지고 있으면 interface에서 구현한다 +- +- +- 스타크래프트 게임을 예로 들어 크게 +- **유닛(Unit)**과 **건물(Structure)**로 분류할 수 있습니다. 또한 테란(Terran), 저그(Zerg), 프로토스(Protoss) 종족으로도 나눌 수 있으며, +- 테란의 경우 **바이오닉(Bionic)**과 **기계 유닛(Mechanic)**으로도 나눌 수 있습니다. + +유닛과 건물의 공통 기능과 특정 기능 +유닛과 건물은 공통 기능과 특정 기능을 가지고 있습니다. 공통 기능은 각각의 클래스에서 별도로 정의하기보다는, +상위 클래스에서 정의해 놓고 하위 클래스에서 필요할 때 메서드를 재정의(@override)하여 사용하는 것이 더 효율적입니다. +예를 들어, 테란의 메딕 유닛은 바이오닉(Bionic)이고 유닛(Unit)이며 테란 종족(Terran)입니다. +메딕은 다른 유닛과 공통적인 기능과 특정 기능을 모두 가지고 있습니다. + +공통 기능과 특정 기능의 예 +메딕의 공통 기능으로는 이동(move), 멈춤(hold), 체력(HP), 마력(MP) 등이 있으며, +특정 기능으로는 회복(Healable) 기능이 있습니다. +공통적인 기능은 특정 유닛에 국한되지 않기 때문에 인터페이스에서 선언하고, +이를 통해 각 클래스에서 필요할 때 구현하는 것이 효율적입니다. + + +메서드 오버라이딩(Method Overriding): + +상위 클래스나 인터페이스에서 정의한 메서드를 하위 클래스에서 재정의하여, +동일한 이름의 메서드가 클래스마다 다르게 동작할 수 있게 합니다. +예를 들어, Unit 클래스의 move() 메서드는 Medic와 Marine에서 서로 다른 방식으로 동작할 수 있습니다. + + +인터페이스 구현(Interface Implementation): + +여러 클래스가 같은 인터페이스를 구현하여 동일한 메서드를 각각 다르게 정의할 수 있습니다. +예를 들어, Healable 인터페이스의 heal() 메서드는 Medic와 Protoss High Templar에서 각각 다르게 구현될 수 있습니다. + + +객체의 동적 바인딩(Dynamic Binding): + +실행 시간에 객체의 실제 타입에 따라 호출되는 메서드가 결정됩니다. +예를 들어, Unit 타입의 변수에 Medic 객체를 할당하면, Unit 타입을 통해 Medic의 move() 메서드를 호출할 수 있습니다. + + +다형성을 통한 유연성: + +다형성을 통해 코드의 유연성을 높일 수 있으며, 이는 새로운 클래스나 기능을 추가할 때 기존 코드를 수정하지 않고도 기능을 확장할 수 있게 합니다. +예를 들어, 새로운 유닛을 추가할 때 Unit 인터페이스를 구현하기만 하면 기존 코드에서 이 새로운 유닛을 사용할 수 있습니다. + +- 추상 클래스와 인터페이스의 차이점 +* 추상 클래스의 특징 +추상 클래스는 상속을 통해 기본 기능을 정의하고 하위 클래스에서 이를 구체적으로 구현하게 합니다. +추상 클래스를 상속(extends)받으면, 상위 클래스의 모든 메서드를 재정의(@override)하거나 기본적으로 제공된 기능을 사용하게 됩니다. +상속받은 하위 클래스에서 특정 기능만 필요할 때도 불필요한 기능까지 포함해야 할 수 있습니다. +* 인터페이스의 특징 +인터페이스는 클래스가 구현해야 할 메서드의 목록을 정의합니다. 인터페이스는 특정 기능을 제공하는 데 초점을 맞춥니다. +인터페이스를 구현(implements)하면 필요한 기능만 선택적으로 구현할 수 있어 유연성을 높일 수 있습니다. +다중 인터페이스를 통해 여러 기능을 조합하여 사용할 수 있으며, 이는 상속의 단점을 보완합니다. +왜 인터페이스가 더 적합한가? +추상 클래스를 사용하면 상위 클래스의 모든 기능을 상속받아야 하므로, 하위 클래스에서 필요하지 않은 기능까지 포함될 수 있습니다. +반면, 인터페이스는 필요한 기능만을 선언하고 이를 선택적으로 구현할 수 있기 때문에 유연성과 확장성을 더욱 높일 수 있습니다. + +따라서, 추상 클래스를 사용하는 것보다 인터페이스를 선언하여 필요한 기능만 선택적으로 구현하는 것이 더 적합합니다. +인터페이스는 다양한 클래스가 동일한 기능을 구현할 수 있게 하여 코드의 재사용성을 높이고, 시스템을 보다 유연하게 설계할 수 있게 합니다. + + +```dart +// 추상 클래스: 기본 기능 정의 +abstract class Unit { + int hp; + int mp; + + void move(); + void hold(); +} + +// 인터페이스: 특정 능력 정의 +abstract interface class Healable { + void heal(); +} + +abstract interface class Attackable { + void attack(); +} + +// 인터페이스: 종족 정의 +abstract interface class Terran { + void color(); +} + +// 유닛: 메딕 +class Medic implements Unit, Healable, Terran { + @override + int hp = 60; + + @override + int mp = 100; + + @override + void move() { + print("Medic is moving"); + } + + @override + void hold() { + print("Medic is holding position"); + } + + @override + void heal() { + print("Medic is healing"); + } + + @override + void color() { + print("Medic's color is blue"); + } +} + +// 유닛: 마린 +class Marine implements Unit, Attackable, Terran { + @override + int hp = 40; + + @override + int mp = 0; + + @override + void move() { + print("Marine is moving"); + } + + @override + void hold() { + print("Marine is holding position"); + } + + @override + void color() { + print("Marine's color is blue"); + } + + // 유닛을 제어하는 함수 + void commandUnit(Unit unit) { + unit.move(); + unit.hold(); + } + + void main() { + Unit medic = Medic(); + Unit marine = Marine(); + + commandUnit(medic); // Medic is moving + commandUnit(marine); // Marine is moving + } +} + +``` + + + 이건 다른 유닛들도 implements 할 수 있다는 말이 된다 + 그리고 메딕만의 특정기능은 class Medic 내에서 구현 할 수 있으며 + - 인스턴스 생성할때 공통 기능때문에 같은 interface로 묶을 수 있다 - 추상적인 선언 = new 상세정의 - 여러 인터페이스를 쉼표(,)로 받을 수 있다 diff --git a/dart_240618.md b/dart_240618.md index 8ba6259..6fedc35 100644 --- a/dart_240618.md +++ b/dart_240618.md @@ -137,8 +137,8 @@ Employee 타입 leader가 필드에 있었네?;; } ``` -이렇게 되어 있고, Department.fromJson 메서드는 이 JSON 데어티를 가져와서 -Employee.fromJson 메서드에 전달하여 Employee 객체로 역직렬화 되는것이다. +이렇게 되어 있고, Department.fromJson 메서드는 이 JSON 데이터를 가져와서 +Employee.fromJson 생성자에 전달하여 Employee 객체로 역직렬화 되는것이다. 테스트코드 형태를 equals 로 비교하면 띄어쓰기라던지 있으면 비교가 잘 안되니까 Map 형태로 바꿔서 비교하는것이 더 나을거 같다 \ No newline at end of file diff --git a/dart_240626.md b/dart_240626.md new file mode 100644 index 0000000..716d731 --- /dev/null +++ b/dart_240626.md @@ -0,0 +1,44 @@ +## Model class +- 클래스 별도 기능을 가지지 않는다 +- 데이터 소스를 앱에서 필요한 형태로 바꿔주는 역할 +- 모델 객체 클래스의 속성에 대한 데이터를 조회할 수 있는 클래스 + +## 권장 폴더 구조 +- data + - data_source + - model + - repository + +## subString, subList, take + - 셋팅 해놓은 숫자 이하로 값이 들어오면 에러발생 한다 + - take는 안전하게 뒤에 세팅한 숫자만큼 가져올 수 있다 +## final + - 매개변수에도 final을 붙일 수 있지만, 병적이라 하지말자 + +## List.unmodifiable + - repository에서 return 보낼대 캡슐화를 보낼때 List는 unmodifiable로 내보내면 + 수정불가 + +## dynamic + - 다이나믹을 리턴할때 한번 더 map을 이용해서 리턴하는것이 안전하다 + +## 블랙박스 테스트 + - 사용자관점에서 입력값과 출력값이 예상대로 나오는지에 대해서만 테스트 + - 내용이나, 로직등을 테스트 하지 않는다 + - 경계값 분석(-1, 0, 1) , 상태전이 + +## 단위 테스트 (Unit Test) + - +## unit8list + - 바이트데이터의 array + +## class + - 클래스 내에서 클래스를 생성하지 말자(다른 클래스는 생성자로 받으면 된다) + - 한 클래스는 한개의 책임만 가지자 + - +## Test Double + - 의존성을 가장 낮추고 테스트가 가능하게 만드는 것 + - stub, spy등 모든것은 mock으로 볼 수 있다(2007년 마이크로소프트) + +## interface +- 목 객체를 diff --git a/dart_240628.md b/dart_240628.md new file mode 100644 index 0000000..8e631a1 --- /dev/null +++ b/dart_240628.md @@ -0,0 +1,64 @@ +data_source: 데이터 소스와 직접적으로 상호작용하는 로직을 포함합니다. + 예를 들어, API 호출이나 데이터베이스 쿼리 등이 이에 해당합니다. + +dto (Data Transfer Object): 네트워크 요청이나 응답에서 사용되는 객체를 정의합니다. + 주로 API로부터 받은 데이터의 구조를 정의하는 데 사용됩니다. + +mapper: 한 형태의 객체를 다른 형태로 변환하는 로직을 담당합니다. + 예를 들어, dto를 앱 내부에서 사용하는 model 객체로 변환하는 기능을 포함할 수 있습니다. + +model: 애플리케이션 내부에서 사용하는 데이터 모델을 정의합니다. + 이는 비즈니스 로직에 사용되는 주요 데이터 구조입니다. + +repository: 데이터 소스와 애플리케이션 사이의 중간자 역할을 합니다. + 데이터를 가져오고, 캐싱하고, 변환하는 등의 작업을 수행합니다. + +```dart +enum ContentType { article, image, video, unknown } + +abstract class Content { + int? id; + String? title; + String? content; + String? url; + String? caption; + DateTime? createdAt; + ContentType type; + + Content({ + this.id, + this.title, + this.content, + this.url, + this.caption, + this.createdAt, + required this.type, + }); + + factory Content.fromJson(Map json) { + switch (json['type']) { + case 'article': + return ArticleContent.fromJson(json); + case 'image': + return ImageContent.fromJson(json); + case 'video': + return VideoContent.fromJson(json); + default: + return UnknownContent.fromJson(json); + } + } +} + +``` + +위 코드는 어느 폴더에 있는게 가장 어울릴까? + +enum은 애플리케이션의 다양한 부분에서 사용될 수 있으며, 그 위치는 enum이 어떤 목적으로 사용되는지에 따라 달라집니다. 예를 들어: +API 응답에서 특정 값을 나타내기 위해 사용되는 enum은 dto 폴더에 위치하는 것이 적절할 수 있습니다. +애플리케이션 내부의 비즈니스 로직에서 사용되는 enum은 model 폴더에 더 적합할 수 있습니다. +데이터 소스의 종류를 구분하는 enum은 data_source 폴더에 속할 수 있습니다. + +json to dart 라이브러리 사용시, +json_serializable을 사용하면, json_serializable은 json 데이터를 dart 객체로 변환하는 데 사용되는 코드를 생성합니다. +이를 통해 json 데이터를 dart 객체로 변환하고, dart 객체를 json 데이터로 변환하는 코드를 쉽게 작성할 수 있습니다. + diff --git a/dart_240703.md b/dart_240703.md new file mode 100644 index 0000000..a117628 --- /dev/null +++ b/dart_240703.md @@ -0,0 +1,14 @@ +* dto를 사용하지 않을때 모델에서 사용하는 방법중에 @jsonKey(name: xxx) 가 있다. +* 이건 들어오는 이름 대신에 내가 임의로 사용하는 이름으로 변경해서 쓰겠다 할때 사용한다. + +jsonSeializable과 equtable을 함께 사용한다 + +URL 고수준 : 개발자가 사용하기 쉬운 수준 +바이너리 데이터 : 2진 데이터 + +소켓 : 이쪽을 통해서 통신을 한다 +TCP/IP : IP 기준으로 통신한다 + +cookie 같은건 모바일에선 sharedPreference 등으로 대체 + +base64? \ No newline at end of file diff --git a/dart_240704.md b/dart_240704.md new file mode 100644 index 0000000..643515b --- /dev/null +++ b/dart_240704.md @@ -0,0 +1,117 @@ +* 서버에서 받을 때 쿼리파라미터로 받아오면 필터링해서 들어온다 + 용도에 따라서 받아올지를 골라서 사용해야 한다 (id는 쿼리파라미터로 잘 쓰지는 않는다) +* DTO는 copywith 가 없으니 final을 사용하지 않아도 된다 있다면 붙여도 되고! +* Uint8List 만 받을 수 있으면 파일로 다운해도 부담이 없다 +* pubspec.lock 파일은 버전관리를 안해도 된다 + 전력적으로 버전관리를 해야해서 하는 경우는 있지만, 크게 관리 안해도 된다 +* ### 주석에 자세한 설명을 하면 관리에 있어서 훨씬 편하다 + 테스트를 여기에 넣어서 나중에 테스트코드를 그대로 사용하기도 한다 +* example 이란 이름보단 내용과 관련된 이름으로 하는게 더 의미 있다 +* 같이 하나만 받는 경우는 값이 없을 경우(터지는 경우)를 생각해서 처리를 하는 것이 필요하다 + + +Result 패턴 +* 기본 : 예외는 try-catch +* Result 패턴 : 성공 or 실패 처리에 유용한 패턴 +* .success + .error +* result는 상속이나 구현을 제한한다 + - 향상성 enum 객체처럼 사용 but 동등성 비교는 안된다 + +result 클래스는 범용적으로 쓰려고 만드는 거라서 이름을 특수하게 짓지 않아도 된다 +result.when / reslut.map 은 쓰지말고 switch로 변환하도록 하자 - lagacy 코드를 줄이기 위해서 +if문에 예외가 1개인 경우에는 else를 사용하지 않아도 된다 + - 향상성 enum 객체처럼 사용 but 동등성 비교는 안된다 +* 버그를 줄이는 목적 : 에러 발생시 오류 패턴 방지 (프로그램이 종료되거나 하면 안된다) + + +enum에 설정해 놓은 대로 에러를 처리하지 않고 +extention을 이용해서 에러를 각 언어에 맞게 처리를 하는것이 더 좋다 + +1. Enum을 사용한 에러 처리 + 기존 방식으로 에러를 enum으로 정의하면, 에러 유형을 고정된 집합으로 관리할 수 있습니다. + 그러나 이는 유연성이 부족하고 각 에러에 대한 구체적인 처리가 어렵습니다. 예를 들어: + +```dart +enum ErrorType { + networkError, + validationError, + unknownError, +} + +// 에러 처리 +void handleError(ErrorType error) { + switch (error) { + case ErrorType.networkError: + print("Network error occurred"); + break; + case ErrorType.validationError: + print("Validation error occurred"); + break; + case ErrorType.unknownError: + print("An unknown error occurred"); + break; + } +} +``` +2. Extension을 사용한 에러 처리 + Extension을 사용하면 enum의 각 값에 대해 메서드를 정의할 수 있어서, + 더 구체적이고 유연하게 에러를 처리할 수 있습니다. + 또한, 각 에러 유형에 대해 맞춤형 메시지나 행동을 추가할 수 있습니다. 예를 들어: + +```dart + +enum ErrorType { + networkError, + validationError, + unknownError, +} + +// ErrorType에 대한 Extension 정의 +extension ErrorTypeExtension on ErrorType { + String get errorMessage { + switch (this) { + case ErrorType.networkError: + return "Network error occurred. Please check your internet connection."; + case ErrorType.validationError: + return "Validation error occurred. Please check the input values."; + case ErrorType.unknownError: + return "An unknown error occurred. Please try again later."; + } + } + + void handle() { +// 각 에러 유형에 맞는 처리를 정의 + print(errorMessage); +// 추가적으로, 각 에러에 대한 로그 저장, 사용자 알림 등도 가능 + } +} + +// 에러 처리 +void handleError(ErrorType error) { + error.handle(); +} +``` +요약 +Enum을 사용한 에러 처리는 단순하지만 유연성이 떨어짐. +Extension을 사용한 에러 처리는 각 에러 유형에 대해 구체적인 메시지와 동작을 정의할 수 있어서 더 유연하고 관리하기 쉽다. +이 방식은 특히 다양한 에러 처리 시나리오가 필요한 경우에 유용합니다. 각 에러에 대한 처리 로직을 enum 정의에 포함시키지 않고, +확장 메서드로 정의하여 코드의 가독성과 유지 보수성을 높일 수 있습니다. + + + +freezed + - json serializable + equatable + copywith + toString + hashCode + == + fromJson + toJson + - 종합 세트 같은 느낌이다..! + + - 터미널에서 실행법 + dart pub add freezed_annotation + dart pub add dev:build_runner + dart pub add dev:freezed +# if using freezed to generate fromJson/toJson, also add: + dart pub add json_annotation + dart pub add dev:json_serializable + +* List 같은 경우에는 초기값을 넣어줘야해서 @Default([])를 맨 앞에 넣어줘야한다 +* 안드로이드 help - find - watch : build 계속 리빌드 하기 싫을때 해놓으면 자동으로 반영된다 +* fromjson, tojson 같은 경우에는 json_serializable을 사용하면 된다 \ No newline at end of file diff --git a/dart_240705.md b/dart_240705.md new file mode 100644 index 0000000..f83e735 --- /dev/null +++ b/dart_240705.md @@ -0,0 +1,218 @@ +typedef : 기존 타입을 새로운 이름으로 재정의하여 코드의 가독성을 높이고 +재사용성을 개선하는 데 사용됩니다. + +```dart + typedef PhotoResultError = Error, PhotoRepositoryError>; + +abstract class PhotoRepository { + Future fetchPhotos(); +} +``` + +이 코드는 PhotoRepository 클래스에서 fetchPhotos 메서드가 +PhotoResultError 타입의 Future를 반환하도록 정의되어 있습니다. +PhotoResultError는 List와 PhotoRepositoryError를 사용하는 Error 타입의 별칭입니다. + +만약 static만 있는 클래스라면 인스턴스화 할 필요가 없으므로 +의미를 더 명확하게 하기 위해 abstract 를 붙이는 것이 더 낫다. +코드의 가독성을 높이고 유지보수성을 높이는데 기여한다. +abstract를 붙이고 인스턴화를 시도하면 컴파일 에러가 발생한다. + +```dart +abstract class UtilityClass { + // Static method + static void utilityMethod() { + print('This is a utility method.'); + } +} + +void main() { + // UtilityClass instance = UtilityClass(); // This line would cause a compile-time error. + UtilityClass.utilityMethod(); // Correct usage +} + +``` + +```dart +void main() { + int maxInt = 9223372036854775807; // 2^63 - 1 + int overflowInt = maxInt + 1; + + print("Max Int: $maxInt"); + print("Overflow Int: $overflowInt"); // Should output: -9223372036854775808 +}------------------------ +-- +-- +-- +-- +- + +Max +Int: 9223372036854775807 +Overflow +Int: - +9223372036854775808 +``` + +이 범위에서 최대값에 1을 더하면 메모리에서 비트가 넘치면서, +2진수로 표현할 때 첫 번째 비트가 최솟값을 나타내는 비트로 바뀌게 됩니다. 따라서, +9223372036854775807 + 1 은 −9223372036854775808가 됩니다. 이 현상을 오버플로우라고 합니다. + +key 같은 경우는 직접 올리지 말자 / 로컬 파일로 관리하면 좋다 + +- 플러터에서는 flutter_dotenv 패키지를 이용해서 하면 편하다 + +재귀 호출 - 자기 자신을 계속 호출해서 메모리에 쌓이다가 +return 을 받으면 한번에 다 돌려줘서 터지는? 경우가 있어서 주의해야 한다 + +꼬리 재귀 - 재귀 호출을 하면서 계속해서 메모리에 쌓이는 것을 방지하기 위해서 +return을 받으면서 계속해서 돌려주는 방식으로 메모리를 적게 사용할 수 있다 +dart는 꼬리 재귀를 지원하지 않는다 + +좋은 코드를 위해 의식해야 하는 6가지 코드 원칙 + +- DRY : Don't Repeat Yourself (중복을 피하라) + 1. 중복 코드를 제거하자(메소드로 분리하자) + +- PIE : Program to an Intetly, and Expressively (명확하고 표현력 있게 프로그래밍 하라) + 1. 코드를 읽는 사람이 이해하기 쉽게 작성 + 2. 쉬운 이름을 사용하자 + 3. 컨벤션을 따르자 + 4. 매직 넘버에 이름을 붙이자(숫자를 직접 사용하지 말고 이름을 붙여서 사용) + +- SRP : Single Responsibility Principle (단일 책임 원칙) + 1. 클래스는 하나의 책임만 가져야 한다 (단일 책임 원칙) + 2. 에러 수정 시 다른 클래스에 영향을 주지 않도록 한다, 그 클래스만 수정하면 되게 하자 + 3. 클래스 분리가 너무 많아도 오히려 관리가 어려울 수 있다 (적당히) + +- OCP : Open/Closed Principle (개방-폐쇄 원칙) + 1. 확장에는 열려있고 변경에는 닫혀 있어야 한다 (개방-폐쇄 원칙) + 2. 새로운 기능이 추가되어도 기존 코드를 수정하지 않고 확장할 수 있게 하자 + 3. 인터페이스를 사용하면 쉽게 확장이 가능하다 + 4. 상속을 사용하면 확장이 어렵다 + 5. String 상속 금지이므로 확장이 어렵다 + 6. Iterable, Comparator 등을 사용하면 확장이 쉽다 + +- SDP : Stable Dependencies Principle (안정된 의존성 원칙) + 1. 안정된 클래스는 변경되지 않아야 한다 + 2. 안정된 클래스는 다른 클래스에 의존하면 안된다 + 3. 안정된 클래스는 인터페이스에 의존해야 한다 + 4. 안정된 클래스는 구체 클래스에 의존하면 안된다(생성자가 변하거나 할 수 있으니까) + +- ADP : Acyclic Dependencies Principle (비순환 의존성 원칙) + 1. 의존성 비순환 원칙은 순환 의존성을 피해야 한다 + 2. 순환 의존성은 코드를 이해하기 어렵게 만든다 + 3. 순환 의존성은 테스트하기 어렵게 만든다 + 4. 순환 의존성은 코드를 변경하기 어렵게 만든다 + 5. 순환 의존성은 코드를 확장하기 어렵게 만든다 + 6. 순환 의존성은 코드를 재사용하기 어렵게 만든다 + +설계원칙 : SOLID + +- 단일 책임 원칙(Single Responsibility Principle) +- 개방-폐쇄 원칙(Open-Closed Principle) +- 리스코프 치환 원칙(Liskov Substitution Principle) +- 인터페이스 분리 원칙(Interface Segregation Principle) +- 의존 역전 원칙(Dependency Inversion Principle) + +상속 = 확장 / 변경 = 수정 + +1. 단일책임원칙 : 클래스는 하나의 책임만 가져야 한다 - 외부 객체는 생성자로 주입받아라 + (Slime, goblin, Character 클래스를 분리하자) +2. 개방폐쇄원칙 : 확장에는 열려있고 변경에는 닫혀 있어야 한다 - (Slime, goblin 각각 공격하지 말고 Character로 공격하게 하자) + 인터페이스를 파라미터로 받으면 수정하지 않아도 기능 확장(적용)이 가능하다 +3. 리스코프치환원칙 : 자식클래스는 부모클래스에서 가능한 행위를 수행할 수 있어야 한다 (is - a 원칙) + (Slime, goblin이 Character를 상속받아서 공격을 할 수 있게 하자) +4. 인터페이스분리원칙 : 클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안된다 + (인터페이스 1개에 모든 기능을 넣지 말고, 기능별로 나눠서 + 사용하자[attackble, moveable, flyable, runable, swimable, jumpable]) +5. 의존관계 역전원칙 : 추상화에 의존해야 하며 구체화에 의존하면 안된다 + (메딕 캐릭터가 특정 객체를 파라미터로 받지 말고 인터페이스를 받아서 사용하게 하자[Healable interface 구현체를 받도록 하는것]) + +디자인 패턴 + +- software design pattern : 소프트웨어 설계시 특정 문맥에서 공통적으로 자주 발생하는 문제에 대해 재사용 가능한 해결책 +- 디자인 패턴의 장점 : 코드의 재사용성, 코드의 유지보수성, 코드의 가독성, 유연성, 확장성 +- 디자인 패턴의 종류 : 생성 패턴, 구조 패턴, 행위 패턴 + - 생성 패턴 : 객체 생성에 관련된 패턴(싱글턴 패턴, 팩토리 패턴, 빌더 패턴 등등) + 1. 싱글턴 패턴 : 객체를 하나만 생성하고, 이후에 생성된 객체를 리턴하는 패턴(1개의 인스턴스가 공유된다) + 캐시나 공유데이터, 처리의 효율화에 등에 사용하는 패턴* + +*** 싱글턴 패턴에서 팩토리 생성자가 필요한 이유? + +* 단일 인스턴스 보장: + +팩토리 생성자는 객체의 생성과정을 완전히 제어할 수 있습니다. 팩토리 생성자를 사용하면 클래스의 기존 인스턴스가 반환되도록 할 수 있습니다. +이는 새 인스턴스를 생성하지 않고, 이미 존재하는 인스턴스를 반환하여 메모리 사용을 최적화하고 상태의 일관성을 유지합니다. + +* 지연 초기화(lazy initialization): + +팩토리 생성자는 클래스가 처음 요청될 때까지 인스턴스를 생성하지 않는 지연 초기화를 지원할 수 있습니다. +이렇게 하면 불필요한 메모리 소비를 방지하고 성능을 개선할 수 있습니다. + +* 상속과 다형성 지원: + +팩토리 생성자를 사용하면 상속된 클래스에서도 싱글턴 패턴을 구현할 수 있습니다. +이를 통해 다형성을 지원하는 동시에 싱글턴 인스턴스를 관리할 수 있습니다. + +* 의존성 주입 및 초기화 논리 포함 가능: + +팩토리 생성자는 인스턴스를 반환할 때 특정 초기화 로직을 포함하거나 의존성을 주입할 수 있는 유연성을 제공합니다. +다트에서 팩토리 생성자를 사용한 싱글턴 패턴 예제 + + ```dart + + +class Singleton { +// Singleton 인스턴스를 저장할 정적 변수 + static final Singleton _instance = Singleton._internal(); + +// 프라이빗 생성자 (internal 사용은 명시적이진 않지만, 관례적으로 내부 생성자를 의미) + Singleton._internal(); + +// 팩토리 생성자 + factory Singleton() { + return _instance; + } + +// Singleton 클래스의 다른 메서드들 + void doSomething() { + print("Singleton instance is working!"); + } +} + +void main() { + Singleton s1 = Singleton(); + Singleton s2 = Singleton(); + + print(s1 == s2); // true + s1.doSomething(); +} + + + ``` + +정적 변수 _instance: 싱글턴 인스턴스를 저장하는 정적 변수를 선언합니다. 이 변수는 클래스 수준에서 단 하나만 존재합니다. +프라이빗 생성자 Singleton._internal(): 외부에서 직접 호출할 수 없는 프라이빗 생성자를 정의하여, 클래스의 인스턴스가 직접 생성되지 않도록 합니다. +팩토리 생성자: factory 키워드를 사용하여 팩토리 생성자를 정의합니다. 이 생성자는 인스턴스가 요청될 때마다 기존의 _instance를 반환합니다. +메서드 doSomething: 싱글턴 클래스가 수행할 수 있는 예제 메서드를 정의했습니다. +메인 함수: 두 개의 Singleton 인스턴스를 요청했을 때, 동일한 인스턴스임을 확인할 수 있습니다. print(s1 == s2)는 true를 출력합니다. + + 2. 팩토리 패턴 : 객체를 생성하는 인터페이스를 정의하고, 이를 구현한 클래스에서 객체를 생성하는 패턴 + +- 구조 패턴 : 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴 (데커레이터 패턴, 퍼사드 패턴 등등) + 1. 데커레이터 패턴 : 객체에 추가적인 요소를 동적으로 추가하는 패턴 + 2. 퍼사드 패턴 : 복잡한 서브시스템을 간단하게 제공하는 인터페이스를 제공하는 패턴(Repository 패턴) + 3. 이터레이터 패턴 : 반복 구조 객체를 통해 요소를 순차적으로 접근하는 패턴 +- 행위 패턴 : 객체나 클래스 사이의 알고리즘과 책임 분배에 관련된 패턴 (Strategy Pattern(전략패턴), 이터레이터 패턴, 옵서버 패턴 등등) + 1. 전략 패턴 : 객체의 상태 변화를 관찰하고, 상태가 변경될 때 자동으로 알림을 받아서 업데이트할 수 있도록 하는 디자인 패턴입니다. + 이 패턴은 일대다(one-to-many) 의존성을 정의합니다. + 2. 옵서버 패턴 : 옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 + 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. + 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다. 발행/구독 모델로 알려져 있기도 하다. (예: 콜백함수) + 3. 이터레이터 패턴 : 컬렉션(예: 리스트, 맵, 집합 등) 요소를 하나씩 순차적으로 접근하는 방법을 제공하는 디자인 패턴입니다. + 이 패턴을 사용하면 컬렉션의 내부 구조를 노출하지 않고도 그 요소들을 탐색할 수 있습니다. + - Iterable과 Iterator의 구조 + - Iterable: 컬렉션을 반복할 수 있는 인터페이스로, iterator라는 getter를 제공합니다. 이 getter는 Iterator를 반환합니다. + - Iterator: Iterable 컬렉션의 요소를 탐색하기 위한 인터페이스로, moveNext() 메서드를 통해 다음 요소로 이동하고, current를 통해 현재 요소에 + 접근할 수 있습니다. \ No newline at end of file diff --git a/dart_240710.md b/dart_240710.md new file mode 100644 index 0000000..4b74ae0 --- /dev/null +++ b/dart_240710.md @@ -0,0 +1,4 @@ +- 만약 static, factory를 사용하려면 abstract class를 사용하고 + abstract class를 implements 하는 방식으로 사용하자(이상해 하지마세요) + +- id는 int로 가져가는게 맞나? (String으로 가져가는게 더 좋을 수도 있음) \ No newline at end of file diff --git a/flutter_240717.md b/flutter_240717.md new file mode 100644 index 0000000..fe1b8cb --- /dev/null +++ b/flutter_240717.md @@ -0,0 +1,29 @@ +navigator.of(context).push +- context가 없으면 이동이 안된다(datasource, repository같은 데는 이동이 안돼 + 근데, getX를 하면 이동이 된다) + +데이터돌려받기 - todolist 같은것으로 예시를 보면 + 리스트 페이지 다음화면에서 새로 할일을 쓰고 추가해서 + 앞 화면으로 돌아갔을때 새로 할일이 추가되있어야 하는 경우 + +context.push(Uri(paht : '/', queryParameters: {'age': 10})) + - 10에 해당하는 위치에 dynamic이 아니라 String으로 보내야한다 + - 받는 쪽에서 String으로 되어있다 + - 객체하나 보낼때는 extra로 보내는게 더 편함 + +WEBP 가 PNG보다 훨씬 용량이 작다 + +기기들이 크기가 다양하니 컴포넌트를 높이 정도만 주고, +가로는 double.infinity정도를 주는게 유지보수가 용이하다 + +안드로이드 스튜디오 뉴 터미널에서는 build runner build가 호환이 안된다 + +viewmodel의 액션은 모두다 void 그래서 fetct가 어울림 +getter로만 나간다 +나머지는 다 void로 한쪽으로만 간다 +상태도 뷰모델에 넣는다 + +리액티브 프로그래밍 - 상태가변경되면 자동으로 반영 + 리스너블빌더(listenableBuilder) + + diff --git a/flutter_240724.md b/flutter_240724.md new file mode 100644 index 0000000..5bcc554 --- /dev/null +++ b/flutter_240724.md @@ -0,0 +1,19 @@ + +context.read - 1회성 +context.watch - 반복하는UI 재사용이 가능 +## onPressed 내에서 .watch로 접근하면 에러. read로 사용해야한다 +## consumer로 감싸면 하단에 context.read로 사용해야함 + +그래서 위에 viewModel = xxx 하고 +하단에서 viewModel.xx 로 메서드 호출 + + + +initState - build - microtask +빌드가 한번이라도 호출이 되야 context로 접근이 되면 가져올수 있다 + - Future.microtask() 이용 + +-> viewModel에 생성자에 특정 메서드를 실행하려면 넣어놓으면 된다 +(시작하자마자 바로 실행) + +firebase diff --git a/network_240712.md b/network_240712.md new file mode 100644 index 0000000..f5468c8 --- /dev/null +++ b/network_240712.md @@ -0,0 +1,13 @@ +서브넷이 같으면 네트워크 크기가 같다 +서브넷이 같으면 ip 개수 정해준다 + +iplv4는 전세계인이 사용하기엔 부족하다. 그걸 확장하는게 사설ip +사설ip는 공인ip로 변환해줘야한다 +공인ip는 인터넷에 연결되어있는 ip 공인ip는 유료다 +ap : access point에 따라서 ip는 다를 수 있다 + +curl ifconfig.me ip확인 +wsl을 설치해라 +windows : ipconfig + +mac : netstat -rn | grep defalut | grep en0 \ No newline at end of file diff --git a/project/bmi_calculator/lib/main/main_screen.dart b/project/bmi_calculator/lib/main/main_screen.dart new file mode 100644 index 0000000..1c5f398 --- /dev/null +++ b/project/bmi_calculator/lib/main/main_screen.dart @@ -0,0 +1,120 @@ +import 'package:bmi_calculator/result/result_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MainScreen extends StatefulWidget { + const MainScreen({super.key}); + + @override + State createState() => _MainScreenState(); +} + +class _MainScreenState extends State { + final _formKey = GlobalKey(); + final _heightController = TextEditingController(); + final _weightController = TextEditingController(); + + @override + void dispose() { + _heightController.dispose(); + _weightController.dispose(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + + load(); + } + + Future save() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setDouble('height', double.tryParse(_heightController.text) ?? 0); + await prefs.setDouble('weight', double.tryParse(_weightController.text) ?? 0); + } + + Future load() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final double? height = prefs.getDouble('height'); + final double? weight = prefs.getDouble('weight'); + + if (height != null && weight != null) { + _heightController.text = '$height'; + _weightController.text = '$weight'; + print('키 : $height, 몸무게 : $weight'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('비만도계산기'), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextFormField( + controller: _weightController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: '몸무게', + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || value.isEmpty) { + return '몸무게를 입력해주세요'; + } + + return null; + }, + onChanged: (value) => save(), + ), + const SizedBox(height: 8), + TextFormField( + controller: _heightController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: '키', + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || value.isEmpty) { + return '키를 입력해주세요'; + } + + return null; + }, + onChanged: (value) => save(), + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () { + if (_formKey.currentState?.validate() == false) { + return; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ResultScreen( + height: double.parse(_heightController.text), + weight: double.parse(_weightController.text), + ), + ), + ); + }, + child: const Text('결과'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/project/bmi_calculator/lib/result/result_screen.dart b/project/bmi_calculator/lib/result/result_screen.dart new file mode 100644 index 0000000..aafe70f --- /dev/null +++ b/project/bmi_calculator/lib/result/result_screen.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +class ResultScreen extends StatelessWidget { + final double height; + final double weight; + + const ResultScreen({ + required this.height, + required this.weight, + super.key, + }); + + String _calcBmi(double bmi) { + String result = '저체중'; + if (bmi >= 35) { + result = '고도 비만'; + } else if (bmi >= 30) { + result = '2단계 비만'; + } else if (bmi >= 25) { + result = '1단계 비만'; + } else if (bmi >= 23) { + result = '과체중'; + } else if (bmi >= 18.5) { + result = '정상'; + } + return result; + } + + Widget _buildIcon(double bmi) { + Icon icon = const Icon( + Icons.sentiment_dissatisfied, + color: Colors.green, + size: 100, + ); + if (bmi >= 23) { + icon = const Icon( + Icons.sentiment_very_dissatisfied, + color: Colors.green, + size: 100, + ); + } else if (bmi >= 18.5) { + icon = const Icon( + Icons.sentiment_satisfied, + color: Colors.green, + size: 100, + ); + } + return icon; + } + + @override + Widget build(BuildContext context) { + final double bmi = weight / ((height / 100.0) * (height / 100.0)); + return Scaffold( + appBar: AppBar( + title: const Text('결과'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + _calcBmi(bmi), + style: const TextStyle( + fontSize: 36, + ), + ), + _buildIcon(bmi), + ], + ), + ), + ); + } +} diff --git a/project/flutter_240702.md b/project/flutter_240702.md new file mode 100644 index 0000000..dc4014d --- /dev/null +++ b/project/flutter_240702.md @@ -0,0 +1,58 @@ +stateless widget에서 +gesturedetector, inkwell 같은 걸로 활용하지 말자 +- 재사용성이 떨어지고, 화면 이동은 되고, + +=> .call 메서드를 통해서 이벤트를 돌려주자 + +rule!! +1. 데이터가 바꿨어? 생성자를 통해서 데이터를 입력 +2. 이벤트가 발생했어? .call 메서드를 통해서 이벤트를 돌려줘 + +stateless에서 상태를 가지게 되면, + +test 코드 +1. MaterialApp - Scaffold - body - widget 까지는 필요하다 +2. widget을 테스트할때는 widget을 테스트하는게 아니라, widget을 통해서 이벤트를 돌려주는 것을 테스트해야 한다 + +build내에는 로직 호출, 통신 http 같은 코드를 사용하면 안된다 = 매번 로딩을 하기때문에 안됨 +* appbar에서 텍스트를 중심에 놓으려면 center 위젯이 아닌 centerTitle을 사용하면 된다 +* TextField내 onSubmitted는 사용자가 입력하고 제출할때 호출, onChanged는 사용자가 입력이 변경될때마다 호출 +* +* 근데 이 아랫줄은 생각을 좀 해봐야됨 +* final repository = PixabayImageItemRepository();는 변수 선언과 동시에 객체를 생성 및 초기화합니다. + final PixabayRepository _pixabayRepository;는 변수 선언만 하고 초기화는 나중에 이루어집니다. +* 또한 언더스코어(_)로 시작하여 프라이빗 변수임을 나타냅니다. +* changeNotifierProvider는 의존성 주입, 상태관리지만 상태관리에만 집중을 하면 좋을거 같고 +* 의존성 주입은 GetIt으로 하는 것을 추천? 한다 + +Stream +* changeNotifierProvider를 안쓰면 Stream으로 상태관리 가능 +* 데이터를 UI로 자동변환이 되는데, 필요없는 경우에는 Stream을 안써도 된다 +* .listen으로 구독하면 해지를 꼭 해지! +* cold Stream - 구독을 하면 그때부터 데이터를 보냄(하나의 리스너로만 제약) +* - 일반적으로 한 화면에 하나의 데이터를 사용하는 경우 +* +* hot Stream- 계속 데이터를 보내고 있다가 구독하면 보냄 지나간건 보내지 않음(여러 리스너를 허용 .broadcase 생성자 사용) +* viewModel에 여러 화면을 공유한다면 Hot Stream으로 연결해야할 거 같음 + +* 다이얼로그 가 떴다가 사라질때 1회성 이벤트 +* sink - 데이터가 줄줄줄 흐른다(싱크대의 싱크) +* 구독은 initState 밖에 안됨(1회성) +- Future.microtask로 잠깐 시간을 벌어준다 +* 만들일이 있을 때 - yield + +DI +* 인젝터블 패키지를 이용해서 의존성 주입을 편하게 하는 패키지 +* 어노테이션을 이용해서 @Singleton(as: NoteRepository) 이라고 쓰고 빌드하면 필요로 하는곳에 싱글턴으로 들어간다 +* Future 로 사용할 때가 있는데 시간이 오래걸리는 코드들에 필요할때가 있고, 기본은 void로 실행하면 된다 +* interface면 대문자 @Singleton 하고<여기에 인터페이스 넣고> +* viewModel이면 팩토리기에 @injectable만 하면 된다 +* viewModel에는 레파짓토리,데이터소스 만 생성자로 받자 +* 모듈에서 전부다 lazysingleton 으로 하는게 속편함 +* diSetup에서 getIt 모듈 3벌을 만들어서, 메서드만 교체해서 해도 된다(prod,dev,test) + +고급 상태 관리 기술 + + +* git +* 체리픽 \ No newline at end of file diff --git a/project/flutter_240731.md b/project/flutter_240731.md new file mode 100644 index 0000000..49c799f --- /dev/null +++ b/project/flutter_240731.md @@ -0,0 +1,4 @@ +- get it + +* changeNotifierProvider 내 create에는 제네릭을 생략하면 에러가 날 수도 있는데 안나면 생략 가능 +* \ No newline at end of file diff --git a/project/learn_flutter_togerther/asset/images/faceBookIconButton.png b/project/learn_flutter_togerther/asset/images/faceBookIconButton.png new file mode 100644 index 0000000..6895df8 Binary files /dev/null and b/project/learn_flutter_togerther/asset/images/faceBookIconButton.png differ diff --git a/project/learn_flutter_togerther/asset/images/googleIconButton.png b/project/learn_flutter_togerther/asset/images/googleIconButton.png new file mode 100644 index 0000000..c510290 Binary files /dev/null and b/project/learn_flutter_togerther/asset/images/googleIconButton.png differ diff --git a/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/component/creator_profile_item.dart b/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/component/creator_profile_item.dart new file mode 100644 index 0000000..414787a --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/component/creator_profile_item.dart @@ -0,0 +1,8 @@ +class CreatorProfileItem extends StatelessWidget { + const CreatorProfileItem({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/main_screen.dart b/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/main_screen.dart new file mode 100644 index 0000000..2269303 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/creatorprofile/presentation/main_screen.dart @@ -0,0 +1,20 @@ +import 'package:flutter/cupertino.dart'; + +class CreatorProfile extends StatelessWidget { + const CreatorProfile({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + child: Row( + children: [ + + ], + ), + ) + ], + ); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.dart b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.dart new file mode 100644 index 0000000..d2632e2 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'ingredient.freezed.dart'; + +part 'ingredient.g.dart'; + +@freezed +class Ingredient with _$Ingredient { + const factory Ingredient({ + required String name, + required String imageUrl, + required String weight, + }) = _Ingredient; + + factory Ingredient.fromJson(Map json) => _$IngredientFromJson(json); +} \ No newline at end of file diff --git a/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.freezed.dart b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.freezed.dart new file mode 100644 index 0000000..2ede3ce --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.freezed.dart @@ -0,0 +1,188 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'ingredient.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Ingredient _$IngredientFromJson(Map json) { + return _Ingredient.fromJson(json); +} + +/// @nodoc +mixin _$Ingredient { + String get name => throw _privateConstructorUsedError; + String get imageUrl => throw _privateConstructorUsedError; + String get weight => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IngredientCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IngredientCopyWith<$Res> { + factory $IngredientCopyWith( + Ingredient value, $Res Function(Ingredient) then) = + _$IngredientCopyWithImpl<$Res, Ingredient>; + @useResult + $Res call({String name, String imageUrl, String weight}); +} + +/// @nodoc +class _$IngredientCopyWithImpl<$Res, $Val extends Ingredient> + implements $IngredientCopyWith<$Res> { + _$IngredientCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? imageUrl = null, + Object? weight = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + weight: null == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IngredientImplCopyWith<$Res> + implements $IngredientCopyWith<$Res> { + factory _$$IngredientImplCopyWith( + _$IngredientImpl value, $Res Function(_$IngredientImpl) then) = + __$$IngredientImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String imageUrl, String weight}); +} + +/// @nodoc +class __$$IngredientImplCopyWithImpl<$Res> + extends _$IngredientCopyWithImpl<$Res, _$IngredientImpl> + implements _$$IngredientImplCopyWith<$Res> { + __$$IngredientImplCopyWithImpl( + _$IngredientImpl _value, $Res Function(_$IngredientImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? imageUrl = null, + Object? weight = null, + }) { + return _then(_$IngredientImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + weight: null == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IngredientImpl implements _Ingredient { + const _$IngredientImpl( + {required this.name, required this.imageUrl, required this.weight}); + + factory _$IngredientImpl.fromJson(Map json) => + _$$IngredientImplFromJson(json); + + @override + final String name; + @override + final String imageUrl; + @override + final String weight; + + @override + String toString() { + return 'Ingredient(name: $name, imageUrl: $imageUrl, weight: $weight)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IngredientImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && + (identical(other.weight, weight) || other.weight == weight)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, name, imageUrl, weight); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IngredientImplCopyWith<_$IngredientImpl> get copyWith => + __$$IngredientImplCopyWithImpl<_$IngredientImpl>(this, _$identity); + + @override + Map toJson() { + return _$$IngredientImplToJson( + this, + ); + } +} + +abstract class _Ingredient implements Ingredient { + const factory _Ingredient( + {required final String name, + required final String imageUrl, + required final String weight}) = _$IngredientImpl; + + factory _Ingredient.fromJson(Map json) = + _$IngredientImpl.fromJson; + + @override + String get name; + @override + String get imageUrl; + @override + String get weight; + @override + @JsonKey(ignore: true) + _$$IngredientImplCopyWith<_$IngredientImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.g.dart b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.g.dart new file mode 100644 index 0000000..0ff2eec --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/model/ingredient.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ingredient.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IngredientImpl _$$IngredientImplFromJson(Map json) => + _$IngredientImpl( + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + weight: json['weight'] as String, + ); + +Map _$$IngredientImplToJson(_$IngredientImpl instance) => + { + 'name': instance.name, + 'imageUrl': instance.imageUrl, + 'weight': instance.weight, + }; diff --git a/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/component/Ingredient_item.dart b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/component/Ingredient_item.dart new file mode 100644 index 0000000..8a40848 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/component/Ingredient_item.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import '../../model/ingredient.dart'; + +class IngredientItem extends StatelessWidget { + final Ingredient ingredient; + + const IngredientItem({ + super.key, + required this.ingredient, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), + child: Container( + width: double.infinity, + height: 100, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey[350], + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Image.network( + ingredient.imageUrl, + ), + ), + const SizedBox( + width: 25, + ), + Text( + style: const TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + ), + ingredient.name, + ), + const Spacer(), + Text( + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.normal, + color: Colors.black26), + ingredient.weight, + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/main_screen.dart b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/main_screen.dart new file mode 100644 index 0000000..ee95058 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/ingridentItem/presentation/main_screen.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import '../model/ingredient.dart'; +import 'component/Ingredient_item.dart'; + +class MainScreen extends StatefulWidget { + const MainScreen({super.key}); + + + @override + State createState() => _MainScreenState(); +} +class _MainScreenState extends State { + final ingredient = const Ingredient( + name: 'Apple', + imageUrl: + 'https://media.istockphoto.com/id/184276818/ko/%EC%82%AC%EC%A7%84/%EB%A0%88%EB%93%9C-%EC%82%AC%EA%B3%BC%EB%82%98%EB%AC%B4.jpg?s=2048x2048&w=is&k=20&c=ha7OqiGpi8QruIPKcU6rix1-KN_fm210KTHjTFRb4Xk=', + weight: '500g', + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Column( + children: [ + const Text('여기는 mainScreen'), + IngredientItem( + ingredient: ingredient, + ), + ], + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/landmark_card/landmark.dart b/project/learn_flutter_togerther/lib/01_widget/landmark_card/landmark.dart new file mode 100644 index 0000000..e192758 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/landmark_card/landmark.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class LandmarkCard extends StatelessWidget { + const LandmarkCard({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Image.network('https://picsum.photos/250?image=9'), + Text(''), + Row( + children: [ + + ], + ), + ], + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/component/recipe_card_item.dart b/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/component/recipe_card_item.dart new file mode 100644 index 0000000..23b87a8 --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/component/recipe_card_item.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class RecipeCardItem extends StatelessWidget { + const RecipeCardItem({super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Column( + children: [ + Stack( + children: [ + // ClipRRect( + // borderRadius: BorderRadius.circular(15), + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Container( + width: double.infinity, + height: 200, + color: Colors.grey, + ), + ), + Positioned( + top: 10, + right: 100, + child: Container( + width: 50, + height: 50, + color: Colors.black26, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/main_screen_recipe.dart b/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/main_screen_recipe.dart new file mode 100644 index 0000000..9c483cb --- /dev/null +++ b/project/learn_flutter_togerther/lib/01_widget/recipeCardWidget/presentation/main_screen_recipe.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class MainScreenRecipe extends StatelessWidget { + const MainScreenRecipe({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text('Main Screen Recipe'), + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.dart b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.dart new file mode 100644 index 0000000..a753144 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.dart @@ -0,0 +1,17 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'food_recipe.freezed.dart'; + +part 'food_recipe.g.dart'; + +@freezed +class FoodRecipe with _$FoodRecipe { + const factory FoodRecipe({ + required String title, + required String email, + required String password, + + }) = _FoodRecipe; + + factory FoodRecipe.fromJson(Map json) => _$FoodRecipeFromJson(json); +} \ No newline at end of file diff --git a/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.freezed.dart b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.freezed.dart new file mode 100644 index 0000000..2c65b3d --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.freezed.dart @@ -0,0 +1,188 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'food_recipe.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +FoodRecipe _$FoodRecipeFromJson(Map json) { + return _FoodRecipe.fromJson(json); +} + +/// @nodoc +mixin _$FoodRecipe { + String get title => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + String get password => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $FoodRecipeCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FoodRecipeCopyWith<$Res> { + factory $FoodRecipeCopyWith( + FoodRecipe value, $Res Function(FoodRecipe) then) = + _$FoodRecipeCopyWithImpl<$Res, FoodRecipe>; + @useResult + $Res call({String title, String email, String password}); +} + +/// @nodoc +class _$FoodRecipeCopyWithImpl<$Res, $Val extends FoodRecipe> + implements $FoodRecipeCopyWith<$Res> { + _$FoodRecipeCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? title = null, + Object? email = null, + Object? password = null, + }) { + return _then(_value.copyWith( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FoodRecipeImplCopyWith<$Res> + implements $FoodRecipeCopyWith<$Res> { + factory _$$FoodRecipeImplCopyWith( + _$FoodRecipeImpl value, $Res Function(_$FoodRecipeImpl) then) = + __$$FoodRecipeImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String title, String email, String password}); +} + +/// @nodoc +class __$$FoodRecipeImplCopyWithImpl<$Res> + extends _$FoodRecipeCopyWithImpl<$Res, _$FoodRecipeImpl> + implements _$$FoodRecipeImplCopyWith<$Res> { + __$$FoodRecipeImplCopyWithImpl( + _$FoodRecipeImpl _value, $Res Function(_$FoodRecipeImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? title = null, + Object? email = null, + Object? password = null, + }) { + return _then(_$FoodRecipeImpl( + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$FoodRecipeImpl implements _FoodRecipe { + const _$FoodRecipeImpl( + {required this.title, required this.email, required this.password}); + + factory _$FoodRecipeImpl.fromJson(Map json) => + _$$FoodRecipeImplFromJson(json); + + @override + final String title; + @override + final String email; + @override + final String password; + + @override + String toString() { + return 'FoodRecipe(title: $title, email: $email, password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FoodRecipeImpl && + (identical(other.title, title) || other.title == title) && + (identical(other.email, email) || other.email == email) && + (identical(other.password, password) || + other.password == password)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, title, email, password); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FoodRecipeImplCopyWith<_$FoodRecipeImpl> get copyWith => + __$$FoodRecipeImplCopyWithImpl<_$FoodRecipeImpl>(this, _$identity); + + @override + Map toJson() { + return _$$FoodRecipeImplToJson( + this, + ); + } +} + +abstract class _FoodRecipe implements FoodRecipe { + const factory _FoodRecipe( + {required final String title, + required final String email, + required final String password}) = _$FoodRecipeImpl; + + factory _FoodRecipe.fromJson(Map json) = + _$FoodRecipeImpl.fromJson; + + @override + String get title; + @override + String get email; + @override + String get password; + @override + @JsonKey(ignore: true) + _$$FoodRecipeImplCopyWith<_$FoodRecipeImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.g.dart b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.g.dart new file mode 100644 index 0000000..e2b8d55 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/model/food_recipe.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'food_recipe.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$FoodRecipeImpl _$$FoodRecipeImplFromJson(Map json) => + _$FoodRecipeImpl( + title: json['title'] as String, + email: json['email'] as String, + password: json['password'] as String, + ); + +Map _$$FoodRecipeImplToJson(_$FoodRecipeImpl instance) => + { + 'title': instance.title, + 'email': instance.email, + 'password': instance.password, + }; diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/component/big_button.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/big_button.dart new file mode 100644 index 0000000..08326a1 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/big_button.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/color_styles.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/text_styles.dart'; + +class BigButton extends StatefulWidget { + final void Function() onTap; + + //data + final String title; + + const BigButton({ + super.key, + required this.title, + required this.onTap, + }); + + @override + State createState() => _BigButtonState(); +} + +class _BigButtonState extends State { + //ui + bool _isPressed = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + onTapDown: (_) { + setState(() { + _isPressed = true; + }); + }, + onTapUp: (_) { + setState(() { + _isPressed = false; + }); + }, + child: Container( + width: double.infinity, + height: 70, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: _isPressed ? ColorStyles.gray4 : ColorStyles.primary100, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Text( + widget.title, + style: TextStyles.mediumTextBold.copyWith( + color: ColorStyles.white, + ), + ), + ), + SizedBox( + width: 40, + ), + Icon( + Icons.arrow_forward, + color: ColorStyles.white, + ), + ], + ), + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/component/input_text.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/input_text.dart new file mode 100644 index 0000000..14c7a66 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/input_text.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/color_styles.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/text_styles.dart'; + +class InputTextField extends StatefulWidget { + //data + final String inputTitle; + final String hintText; + + const InputTextField({ + super.key, + required this.inputTitle, + required this.hintText, + }); + + @override + State createState() => _InputTextFieldState(); +} + +class _InputTextFieldState extends State { + @override + Widget build(BuildContext context) { + return Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.inputTitle, + style: TextStyles.smallTextRegular, + ), + const SizedBox( + height: 10, + ), + TextField( + decoration: InputDecoration( + hintText: widget.hintText, + hintStyle: TextStyles.smallerTextRegular.copyWith( + color: ColorStyles.gray4, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Colors.grey, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: const BorderSide( + color: Colors.grey, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sign_up/sign_up_screen.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sign_up/sign_up_screen.dart new file mode 100644 index 0000000..ba249e5 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sign_up/sign_up_screen.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/component/big_button.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/component/sns_login_items.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/component/input_text.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/color_styles.dart'; +import 'package:learn_flutter_togerther/02_material_design/presentation/ui/text_styles.dart'; + +class SignUpScreen extends StatelessWidget { + const SignUpScreen({super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 60, + ), + Text( + 'Hello,', + style: TextStyles.headerTextBold, + ), + Text('Welcome Back!', style: TextStyles.headerTextRegular), + const SizedBox( + height: 40, + ), + Container( + child: InputTextField( + inputTitle: 'Email', + hintText: 'Enter Email', + ), + ), + const SizedBox( + height: 40, + ), + const SizedBox( + height: 10, + ), + Container( + child: InputTextField( + inputTitle: 'Enter Password', + hintText: 'Enter password', + )), + const SizedBox( + height: 20, + ), + Text( + 'Forgot Password?', + style: TextStyles.smallerTextRegular.copyWith( + color: ColorStyles.secondary100, + ), + ), + const SizedBox( + height: 20, + ), + BigButton(title: 'Sign In', onTap: () {}), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 3.0, + color: Colors.grey[300], + ), + const Text( + ' Or Sign in With ', + style: TextStyle( + color: ColorStyles.gray4, + ), + ), + Container( + height: 3.0, + color: Colors.grey[300], + ), + ], + ), + const SizedBox( + height: 20, + ), + const SnsLoginItems(), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '''Don't have an account?''', + style: TextStyles.smallerTextRegular.copyWith( + color: ColorStyles.primary100, + ), + ), + SizedBox( + width: 10, + ), + Text( + 'Sign up', + style: TextStyles.smallerTextRegular.copyWith( + color: ColorStyles.primary100, + ), + ), + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sns_login_items.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sns_login_items.dart new file mode 100644 index 0000000..a4cedef --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/component/sns_login_items.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class SnsLoginItems extends StatelessWidget { + const SnsLoginItems({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: Image.asset( + 'asset/images/googleIconButton.png', + ), + width: 50, + height: 50, + ), + Container( + child: Image.asset( + 'asset/images/faceBookIconButton.png', + ), + width: 50, + height: 50, + ), + ], + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/main_food_recipe.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/main_food_recipe.dart new file mode 100644 index 0000000..cca4444 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/main_food_recipe.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +import 'component/sign_up/sign_up_screen.dart'; + +class MainFoodRecipe extends StatelessWidget { + const MainFoodRecipe({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: SignUpScreen(), + ), + ); + } +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/color_styles.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/color_styles.dart new file mode 100644 index 0000000..05c2ab2 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/color_styles.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +abstract class ColorStyles { + static const Color black = Color(0xFF000000); + static const Color gray1 = Color(0xFF484848); + static const Color gray2 = Color(0xFF797979); + static const Color gray3 = Color(0xFFA9A9A9); + static const Color gray4 = Color(0xFFD9D9D9); + static const Color white = Color(0xFFFFFFFF); + + static const Color primary100 = Color(0xFF129575); + static const Color primary80 = Color(0xFF71B1A1); + static const Color primary60 = Color(0xFFAFD3CA); + static const Color primary40 = Color(0xFFDBEBE7); + static const Color primary20 = Color(0xFFF6FAF9); + + static const Color secondary100 = Color(0xFFFF9C00); + static const Color secondary80 = Color(0xFFFFA61A); + static const Color secondary60 = Color(0xFFFFBA4D); + static const Color secondary40 = Color(0xFFFFCE80); + static const Color secondary20 = Color(0xFFFFE1B3); + + static const Color error = Color(0xFFE94A59); + static const Color warning = Color(0xFF995E00); + + static const Color success = Color(0xFF31B057); + static const Color success10 = Color(0xFFEAF7EE); + + static const Color rating = Color(0xFFFFAD30); +} diff --git a/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/text_styles.dart b/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/text_styles.dart new file mode 100644 index 0000000..f850f95 --- /dev/null +++ b/project/learn_flutter_togerther/lib/02_material_design/presentation/ui/text_styles.dart @@ -0,0 +1,117 @@ + + +import 'package:flutter/material.dart'; + +abstract class TextStyles { + static TextStyle tittleTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 50, + fontWeight: FontWeight.bold, + height: 75 / 50, + letterSpacing: 0, + ); + + static TextStyle headerTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 30, + fontWeight: FontWeight.bold, + height: 45 / 30, + letterSpacing: 0, + ); + + static TextStyle largeTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 20, + fontWeight: FontWeight.bold, + height: 30 / 20, + letterSpacing: 0, + ); + + static TextStyle mediumTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 18, + fontWeight: FontWeight.bold, + height: 27 / 18, + letterSpacing: 0, + ); + + static TextStyle normalTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 16, + fontWeight: FontWeight.bold, + height: 24 / 16, + letterSpacing: 0, + ); + + static TextStyle smallTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + fontWeight: FontWeight.bold, + height: 21 / 14, + letterSpacing: 0, + ); + + static TextStyle smallerTextBold = TextStyle( + fontFamily: 'Poppins', + fontSize: 11, + fontWeight: FontWeight.bold, + height: 17 / 11, + letterSpacing: 0, + ); + + static TextStyle tittleTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 50, + fontWeight: FontWeight.normal, + height: 75 / 50, + letterSpacing: 0, + ); + + static TextStyle headerTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 30, + fontWeight: FontWeight.normal, + height: 45 / 30, + letterSpacing: 0, + ); + + static TextStyle largeTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 20, + fontWeight: FontWeight.normal, + height: 30 / 20, + letterSpacing: 0, + ); + + static TextStyle mediumTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 18, + fontWeight: FontWeight.normal, + height: 27 / 18, + letterSpacing: 0, + ); + + static TextStyle normalTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 16, + fontWeight: FontWeight.normal, + height: 24 / 16, + letterSpacing: 0, + ); + + static TextStyle smallTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + fontWeight: FontWeight.normal, + height: 21 / 14, + letterSpacing: 0, + ); + + static TextStyle smallerTextRegular = TextStyle( + fontFamily: 'Poppins', + fontSize: 11, + fontWeight: FontWeight.normal, + height: 17 / 11, + letterSpacing: 0, + ); +} diff --git a/project/learn_flutter_togerther/lib/flutter_learn_01.md b/project/learn_flutter_togerther/lib/flutter_learn_01.md new file mode 100644 index 0000000..29ceb00 --- /dev/null +++ b/project/learn_flutter_togerther/lib/flutter_learn_01.md @@ -0,0 +1,15 @@ + stateless widget에서 +gesturedetector, inkwell 같은 걸로 활용하지 말자 + - 재사용성이 떨어지고, 화면 이동은 되고, + +=> .call 메서드를 통해서 이벤트를 돌려주자 + +rule!! +1. 데이터가 바꿨어? 생성자를 통해서 데이터를 입력 +2. 이벤트가 발생했어? .call 메서드를 통해서 이벤트를 돌려줘 + +stateless에서 상태를 가지게 되면, + +test 코드 +1. MaterialApp - Scaffold - body - widget 까지는 필요하다 +2. widget을 테스트할때는 widget을 테스트하는게 아니라, widget을 통해서 이벤트를 돌려주는 것을 테스트해야 한다 \ No newline at end of file diff --git a/project/stop_watch/lib/stop_watch_screen.dart b/project/stop_watch/lib/stop_watch_screen.dart new file mode 100644 index 0000000..95a45db --- /dev/null +++ b/project/stop_watch/lib/stop_watch_screen.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class StopWatchScreen extends StatefulWidget { + const StopWatchScreen({super.key}); + + @override + State createState() => _StopWatchScreenState(); +} + +class _StopWatchScreenState extends State { + Timer? _timer; + + int _time = 0; + bool _isRunning = false; + + List _lapTimes = []; + + void _clickButton() { + _isRunning = !_isRunning; + + if (_isRunning) { + _start(); + } else { + _pause(); + } + } + + void _start() { + _timer = Timer.periodic(Duration(milliseconds: 10), (timer) { + setState(() { + _time++; + }); + }); + } + + void _pause() { + _timer?.cancel(); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + int sec = _time ~/ 100; + String hundredth = '${_time % 100}'.padLeft(2,'0'); + + return Scaffold( + appBar: AppBar( + title: const Text('stop watch'), + ), + body: Column( + children: [ + const SizedBox( + height: 30, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '$sec', + style: TextStyle(fontSize: 50), + ), + Text('$hundredth'), + ], + ), + SizedBox( + width: 100, + height: 200, + child: ListView( + children: [ + Text('111'), + Text('111'), + Text('111'), + Text('111'), + Text('111'), + Text('111'), + Text('111'), + Text('111'), + Text('111'), + ], + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FloatingActionButton( + backgroundColor: Colors.orange, + onPressed: () {}, + child: const Icon(Icons.refresh), + ), + FloatingActionButton( + onPressed: () { + setState(() { + _clickButton(); + }); + }, + child: _isRunning + ? const Icon(Icons.pause) + : const Icon(Icons.play_arrow), + ), + FloatingActionButton( + backgroundColor: Colors.green, + onPressed: () {}, + child: const Icon(Icons.add), + ), + ], + ), + const SizedBox( + height: 30, + ), + ], + ), + ); + } +} diff --git a/shortCut.md b/shortCut.md index c68c663..1c8b79e 100644 --- a/shortCut.md +++ b/shortCut.md @@ -4,4 +4,16 @@ - Ctrl + h = 상속관계를 나타냄 - alt + j = 다중문자 선택 - android - help - find action - Regex 클릭하고 - //.*\n (모든 주석과 Enter까지 선택) - Replace All 하면 삭제 -- \ No newline at end of file + +- 에뮬레이터 실행 중이 아닌데 실행이 되고 있다면 .lock 파일을 지우면 실행되는것 같다 +- /Android/sdk/platform-tools/adb 를 환경변수에 등록하면 adb devices 하면 에뮬레이터 목록을 볼 수 있다. + + +- flutter create . : 현재 프로젝트에 플러터 프로젝트를 만든다 +- 예전버전이라서 지금과 호환이 안되면 + .idea을 삭제하고 / + 그래도 안되면 pubspec.yaml에 dart 버전을 현재 버전으로 바꿔주고 + Pub outdated : 현재버전과 yaml에 있는 버전을 비교해서 보여준다 + (flutter pub upgrade major-version) + pusbspec.lock : 현재 라이브러리의 실제 버전을 기술 해놓았다 + 에뮬레이터에 이미 설치되어 있다면 삭제를 하고 다시 설치해보자- \ No newline at end of file diff --git "a/\354\260\275\354\227\205.md" "b/\354\260\275\354\227\205.md" new file mode 100644 index 0000000..0baadb3 --- /dev/null +++ "b/\354\260\275\354\227\205.md" @@ -0,0 +1,62 @@ + +## 투자유치 +the vc +- https://thevc.kr/ +- 6개월 뒤에 지원받음 +- 심사역 투자팀 투심위 서류처리(2개월) +- 투자자 독소조항 잘 검토 모르면 네이버 같은 창업관련 자원을 받아서 할 수도 있다 +- 데모데이 : 여러 회사를 투자사로 7분동안 설명을 해서 네트워크를 만들 수 있다 +- 법인 : 정관 + +- 초기 창업 패키지 + +7/25 +- 컴포넌트는 생성자로 데이터를 받아라 +- 상태관리 파일 위치는 같은 화면, 뷰모델이 들어가있는 폴더에 위치하면 된다 + +- flutter pub add firebase-core +- git ignore에 설정 +- google-Services +- GoogleService-info.plist +- firebase 홈페이지에서도 찾을 수 있다 초기화, 플러그인등.. + +7/26 +클린아키텍쳐 +데이터소스 별도 클래스 -> 여러 레파짓토리에서 쓴다 +뷰모델이 화면에 쓰이는 로직이 너무 많다 +유즈케이스에다 만들고 뷰모델은 가져오기만 하는식으로 하자 +폴더구조 - 목적에 맞는 파일들까리 모은데(view, viewModel, ui, uiState) +로버트 C.마틴 - 클린 아키텍쳐 - 소프트웨어 구조와 설계원칙 + +Repository +- data layer에 구현체 +- domain layer에 추상인터페이스 + +- 테스트가 용이하다 - 테스트를 부위별로 할 수 있다 +- domain - model - repository + +get user user case내 result를 사용(viewModel에 바로 접근) +- 데이터만 잘 들어오면 성공이야? +- 데이터는 ok, 화면에서(비즈니스 로직 분기 - 리절트로 성공/실패를 할 수도 있다) + +call() +- _getUserUserCase.call() 생략가능 +- 대신 call이 많으면 알기가 어려움 +- call함수 재정의 - execute()로 대신 사용 가능하다 +- core 폴더는 공통기능/공통관리 + +특강 +- os는 왜 필요할까? +- 연결되어있는 하드웨어를 쓸 수가 없다 +- 자원관리 대상 - 하드웨어 +- cloud 왜 필요할까? +- 편리함, 비용적으로 효율적으로 사용할 수 있으니깡 + +- Iaas = 건물에 물, 전기 있는 상황 +- PaaS = + 가구등 필요한 것들이 있는 상황 +- Saas = 소프트웨어적인(선생님, 학생등을 ) 부분이 있는 상황 +- IDE = Saas서비스(모든 어플리케이션), 웹페이지 +- 어플라이언스 +- EC2 인스턴스 +- 도메인서버-오리진-오리진서버?로 간다 +- \ No newline at end of file