bradheo65
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 쥬스메이커 > 프로젝트 기간 2022.04.25 ~ 2022.05.13 </br> 팀원: bonf, bradheo65, ZZBAE</br> 리뷰어: GREEN --- ## 개발환경 및 라이브러리 [![swift](https://img.shields.io/badge/swift-5.6-orange)]() [![xcode](https://img.shields.io/badge/Xcode-13.3-blue)]() --- ## 주차 별 학습 [STEP-01](#Step-01) [STEP-02](#Step-02) [STEP-03](#Step-03) ## PR > Step01: https://github.com/yagom-academy/ios-juice-maker/pull/223 > Step02: https://github.com/yagom-academy/ios-juice-maker/pull/234 > Step03: https://github.com/yagom-academy/ios-juice-maker/pull/242 --- ## Step 01 > 04.25 (월) - 순서도, UML, GroundRolue 작성 > 04.26 (화) - 열거형 CaseIterable 프로토콜 사용, 에러 처리 사용 > 04.27 (수) - Step1 PR 작성 > 04.28 (목) - reNaming, 리뷰 토대로 수정 --- #### 키워드 > #CaseIterable #defaultError #try-catch --- #### 순서도 ![unknown](https://user-images.githubusercontent.com/98302604/165425608-3a918bc5-32e5-4f12-b551-302fe8529ff9.png) --- #### UML ![](https://i.imgur.com/EQEdVL3.png) --- #### 구현 내용 > 각 과일별 재고 초기값 수량 선정, 쥬스 선택 시 과일별 재고에서 해당 쥬스를 만드는데 필요한 재료와 수량만큼 소모되는 기능, 과일 재고가 부족할 시 에러 처리를 해주는 기능. --- **파일 : Fruit** - enum Fruit = 과일 종류를 관리하는 타입 ```swift enum Fruit: CaseIterable { case strawberry case banana case pineapple case kiwi case mango } //열거형을 이용해 다섯가지 과일 나열 ``` --- **파일 : FruitStore** - class FruitStore = 저장고에 남은 과일 개수를 관리해주는 타입 - func chooseRecipe: 선택된 과일 쥬스에 대한 레시피 확인 후 에러 처리 해주는 메서드 - private func checkFruitStock: 과일 재고에 대해 확인하는 메서드 - private func useStock: 과일 재고에 있는 것으로 쥬스 재료를 소모하는 메서드 ```swift class FruitStore { var stocks: [Fruit: Int] = [:] init(defaultFruitStock: Int) { Fruit.allCases.forEach { stocks[$0] = defaultFruitStock } } func chooseRecipe(order: Juice) throws { for fruit in order.recipeOfJuice.keys { try checkFruitStock(juice: order, fruits: fruit) } } private func checkFruitStock(juice: Juice, fruits: Fruit) throws { guard let stockFruit = stocks[fruits], let needFruitAmount = juice.recipeOfJuice[fruits], stockFruit >= needFruitAmount else { throw StockError.outOfStock } useStock(juice: juice, fruits: fruits) } private func useStock(juice: Juice, fruits: Fruit) { if let stockFruit = stocks[fruits], let requiredFruit = juice.recipeOfJuice[fruits] { stocks.updateValue(stockFruit - requiredFruit, forKey: fruits) } } } ``` --- **파일 : Juice** - enum Juice: 쥬스 메뉴를 관리하는 타입 - var recipeOfJuice: 쥬스 제조 시 필요한 과일을 딕셔너리로 관리 ``` swift enum Juice: CaseIterable { case strawberry case banana case strawberryBanana case mango case mangoKiwi case kiwi case pineapple //열거형을 이용해 7가지 쥬스 종류 나열 var recipeOfJuice: [Fruit: Int] { switch self { case .strawberry: return [.strawberry: 16] case .banana: return [.banana: 2] case .strawberryBanana: return [.strawberry: 10, .banana: 1] case .mango: return [.mango: 3] case .mangoKiwi: return [.mango: 2, .kiwi: 1] case .kiwi: return [.kiwi: 3] case .pineapple: return [.pineapple: 2] } } } //enum은 class와 struct처럼 method와 property를 선언할 수 있다. switch-case문을 이용해서 모든 과일에 대한 값(재료 개수)을 빠짐없이 설정할 수 있다. ``` --- **파일 : JuiceMaker** - struct JuiceMaker = 주스를 만들어주는 타입 - fruitStore: FruitStore에 저장되는 과일 개수의 초기값을 설정해준다. - func makeJuice: 쥬스를 만드는 함수를 실행시키고, 오류 발생 시 처리해주는 함수 ``` swift struct JuiceMaker { let fruitStore = FruitStore(defaultFruitStock: 10) func makeJuice(of juice: Juice){ do { try fruitStore.chooseRecipe(order: juice) } catch StockError.outOfStock { print("재고부족") } catch { print(error) } } } ``` --- **파일 : Error** - enum StockError: 재고 에러를 관리하는 타입 ```swift enum StockError: Error { case outOfStock } //과일에 재료가 부족할 때 에러! ``` --- #### 고민한 점/해결한 점 1) 열거형 프로토콜 >- **고민한점**: 과일 재고의 초기값을 설정하기 위해 처음에 딕셔너리 형태로 하드코딩을 진행보았지만 다시 생각해보니 이미 열거형을 사용해서 초기값을 넣어준다면 유지보수하기도 편하기 때문에 어떻게 구현을 하면 될지 고민해보았습니다. > >- **해결한점**: Fruits라는 열거형에 CaseIterable이라는 프로토콜을 설정해주어서 딕셔너리 형태로 사용할 수 있도록 설정해 주었습니다. ```swift= enum Fruits: CaseIterable { case strawberry case banana case pineapple case kiwi case mango } ``` 2) for문과 forEach의 사용성 > >- **고민한점**: for문과 forEach 중 어떤 것을 사용해서 각 과일 별로 초기 값을 넣어줄지 고민했습니다. > >- **해결한점**: 서로의 차이점은 for문인 경우 지정된 요소 수 만큼 반복되고 forEach인 경우 저장된 요소 수 만큼 반복되어 전체에 하나씩 넣어 줄 수 있어 forEach를 사용해서 작성해 보았습니다. ```swift= private var stocks: [Fruits: Int] = [:] private let stock = 10 init() { Fruits.allCases.forEach { stocks[$0] = stock } } ``` 3) do-catch 디폴트 오류 > >- **고민한점**: do-catch 문을 이용하여 오류 처리를 하는 과정에서 "call can throw, but it is not marked with 'try' and the error is not handled" 문구의 오류가 발생하였습니다. > >- **해결한점**: do-catch 오류의 디폴트값을 설정해서 해결할 수 있었습니다. ```swift= do { try fruitStore.chooseRecipe(order: juice) } catch StockError.outOfError { print("재고부족") } catch { // 디폴트 오류를 설정 print(error) } ``` --- ## Step 02 > 04.29 (금) - 버튼 이벤트 함수 구상, FruitStore에 선언해 둔 함수 JuiceMaker로 옮기는 작업 > 05.02 (월) - Step1 복습 및 Step2에 필요한 내용 공부 > 05.03 (화) - 버튼 이벤트 함수 구현 --- #### 이번 스텝 수행 중 핵심 경험 체크 - [x] 내비게이션 바 및 바 버튼 아이템의 활용 - [x] 얼럿 컨트롤러 활용 - [x] Modality의 활용 --- #### 구현해야하는 기능 - ‘재고수정’ 버튼을 터치하면 ‘재고 추가’ 화면으로 이동합니다 - 각 주문 버튼 터치 시 * 쥬스 재료의 재고가 있는 경우 : 쥬스 제조 후 “*** 쥬스 나왔습니다! 맛있게 드세요!” 얼럿 표시 * 쥬스 재료의 재고가 없는 경우 : “재료가 모자라요. 재고를 수정할까요?” 얼럿 표시 - ‘예’ 선택시 재고수정화면으로 이동 - ‘아니오’ 선택시 얼럿 닫기 * 과일쥬스를 제조하여 과일의 재고가 변경되면 화면의 적절한 요소에 변경사항을 반영합니다 #### 구현내용 1. 각 과일 별 재고 라벨 생성 2. 쥬스 생성 시 과일 재고에 따른 alert 이벤트 생성 3. 쥬스 생성 후 변경된 재고 수량 반영 4. 과일 재고 부족 시 alert 이벤트에서 재고 ViewController로 이동 기능 생성 5. 쥬스 만드는 알람 시 한글로 보여지기 위해 Juice열거형에 name 연산 프로퍼티 추가 6. + Step01 리뷰 반영 --- #### 고민한 점/해결한 점 1. alert 이벤트 >- 고민한 점: alert 이벤트 생성 후 ViewController로 이동 가능 기능 구현을 어떻게 해야할지..</br> >- 해결한 점: addAction의 UIAlertAction에서 handler에서 코드 입력 부분에 세그웨이 연결하는 방법 중 하나인 performSegue()를 사용해서 구현해보았습니다. withIdentifier인 부분은 View디렉토리 main에서 원하는 ViewController의 Identifier를 입력해주어 해결했습니다. ```swift eventFail.addAction(UIAlertAction(title: "예", style: .default, handler: {_ in self.performSegue(withIdentifier: "moveToFruitStock", sender: nil)})) ``` 2. do-catch문의 위치 선정 > - 고민한 점: 처음 do-catch 문이 juiceMaker 클래스 안에 위치해 있었기에 alert 메시지를 어떻게 구현해야 할지 고민하였습니다. > - 해결한 점: do-catch 문을 viewController에서 구현해주며 alert 메시지를 오류처리에서 구현할 수 있게 되었습니다. 3. 쥬스 생성 알람 한글로 변경 > - 고민한 점 : step01에서 생성하였던 Juice의 이름들이 그대로 나오는 것보다, 초기화면에 나와있는 한글로 변경하기 위해 고민하였습니다. > - 해결한 점 : Juice 열거형에 한글 이름을 넣은 name 연산 프로퍼티를 추가하여, 초기화면에서 쥬스 생성에 성공할 때 뜨는 얼럿에 한글로 쥬스 이름이 나오게끔 구현할 수 있었습니다. ```swift var name: String { switch self { case .strawberry: return "딸기" case .banana: return "바나나" case .kiwi: return "키위" case .mango: return "망고" case .pineapple: return "파인애플" case .strawberryBanana: return "딸바" case .mangoKiwi: return "망키" } } ``` --- ## Step 03 > 05.09 (월) - 싱글톤 사용으로 각 ViewController으로 데이터 전달 > 05.10 (화) - AutoLayout 설정, Step03 PR작성 > 05.11 (수) - 휴무 > 05.12 (목) - PR리뷰 확인 후 수정 및 IBOutCollection 사용 --- #### 이번 스텝 수행 중 핵심 경험 체크 - [x] 내비게이션 바 및 바 버튼 아이템의 활용 - [x] Stepper 활용 - [x] Modality의 활용 - [x] 화면 사이의 데이터 공유 - [x] 오토레이아웃 시작하기 --- #### 구현해야하는 기능 ![](https://i.imgur.com/pTRDA22.png) - 화면 제목 ‘재고 추가’ 및 ‘닫기’ 버튼 구현 - 닫기를 터치하면 이전화면으로 복귀 - 화면 진입시 과일의 현재 재고 수량 표시 - -, + 를 통한 재고 수정 - iPhone 12 외에 다른 시뮬레이터에서도 UI가 정상적으로 보일 수 있도록 오토레이아웃 적용 - 오토레이아웃 정복하기 참고(야곰닷넷 로그인 후 사용/유료로 보이면 야곰에게 DM주세요) #### 구현내용 1. 싱글톤 패턴 이용하여 데이터 전달 2. Stepper 값 변경 3. @IBOutlet Collection 이용하여 표현하기 4. autoLayout 설정 5. 은닉화, 캡슐화 --- #### UML (5/12 활동학습 내용추가) **1. Sequence Diagram** <img width="914" alt="스크린샷 2022-05-12 오전 10 34 40" src="https://user-images.githubusercontent.com/98302604/167982091-ae9c6161-14bd-4497-bfd7-52755fb5772a.png"> **2. Class Diagram** <img width="864" alt="스크린샷 2022-05-12 오전 11 45 34" src="https://user-images.githubusercontent.com/98302604/167982055-c6bf9b77-04c6-47ee-8133-f89c6ea58b17.png"> --- #### 고민한 점/해결한 점 1. 첫 Stepper 기능 구현 시 value에 값을 연결하고 동작해 본 결과 계속 기존 stock에 있던 값을 불러와서 계산을 했었는데, viewdidLoad에서 초기값을 선언해주어서 해결해주었습니다. 2. Stepper 기능 실행 할 때, + 를 누르면 과일재고 초기값인 10에서 더해주는게 아니라 0으로 되돌아가서 하나씩 추가가 되는 문제가 있었습니다. 그래서 Label에 초기값을 설정해준 것 처럼 Stepper에도 value값을 넣어주어 stepper 작동 시 기존값에서 수량이 늘어나고 줄어들 수 있게끔 해주었습니다. ```swift func setStockAndStepper() { if let strawberry = fruitStore.stocks[.strawberry], let banana = fruitStore.stocks[.banana], let kiwi = fruitStore.stocks[.kiwi], let mango = fruitStore.stocks[.mango], let pineapple = fruitStore.stocks[.pineapple] { strawberryLabel.text = "\(strawberry)" bananaLabel.text = "\(banana)" pineappleLabel.text = "\(pineapple)" kiwiLabel.text = "\(kiwi)" mangoLabel.text = "\(mango)" strawberryStepper.value = Double(strawberry) bananaStepper.value = Double(banana) pineappleStepper.value = Double(pineapple) kiwiStepper.value = Double(kiwi) mangoStepper.value = Double(mango) } ``` 3. show의 경우 네비게이션이 없을 경우 모달로 표현되는 것을 알게 되었습니다. 근데 present의 경우 Presentation을 automatic으로 할 경우 값이 넘어가지 않고 FullScreen을 해야지만 값이 넘어가게 됩니다. FullScreen을 해야만 JuiceMakerViewController가 DisAppear 되는 것을 확인하여 해결하였습니다. --- ## 그라운드 룰 ``` ## 그라운드 룰 ### 👨‍👦‍👦 협업 방식 코드 작성 시 제약사항 준수 git commit 시 Karma Style 준수 짝 프로그래밍으로 코드 작성 시 부담 없이 작성 후 토의 순서도(전반적인 흐름)와 UML(세부적인 내용) 작성 의견이 있을 경우 자신 있게 말하기 - 자기의 코드가 아니라 우리의 코드 !! ### 📱 연락이 어려운 시간 업무시간: 10:00 ~ 19:00 bonf : 12:00 ~ 13:30, 18:00 ~ 19:30 Brad : 12:00 ~ 13:30, 18:00 ~ 19:30 ZZBAE: 12:00 ~ 13:30, 18:00 ~ 19:30 급한일이 있을 경우 디엠 보낸다. ### ☎️ 소통 방법 기본적으로 디스코드를 사용 ### 💻 코드 컨벤션 Swift Statements 형식 준수 ### 🪵 브랜치 이름규칙 develop - 최종 코드 → develop에서 야곰 레포로 pr 보내고, 머지되면 main으로 이동 각 step 별로 브런치 생성 (ex. step01, step02, step03) ### ⌨️ 커밋 규칙 1. [타입] : 제목 제목은 최대 50 글자까지만 입력 2. 본문은 위에 작성 본문은 한 줄에 최대 72 글자까지만 입력 3. 꼬릿말은 아래에 작성: ex) #이슈 번호 - --- COMMIT END --- > [타입] : 리스트 > feat : 기능 (새로운 기능) fix : 버그 (버그 수정) refact : 리팩토링 style : 스타일 (코드 형식, 세미콜론 추가: 비즈니스 로직에 변경 없음) docs : 문서 (문서 추가, 수정, 삭제) test : 테스트 (테스트 코드 추가, 수정, 삭제: 비즈니스 로직에 변경 없음) chore : 기타 변경사항 (빌드 스크립트 수정 등) --- > 제목 첫 글자를 대문자로 제목은 명령문으로 제목 끝에 마침표(.) 금지 제목과 본문을 한 줄 띄워 분리하기 본문은 "어떻게" 보다 "무엇을", "왜"를 설명한다. 본문에 여러줄의 메시지를 작성할 땐 "-"로 구분 ------------------

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully