Blog

  • fixed_point

    fixed_point Numerics Library

    Build Status Build status

    Notice: This library is deprecated. Please consider switching to CNL, the successor to fixed_point for many new features and improvements.

    Description

    The fixed_point library provides a header-only C++11 API for approximating real numbers using binary fixed-point arithmetic. It forms the reference implementation of a standard library proposal presented in paper, P0037 and is developed as part of study groups, SG14 and SG6.

    Download

    The library is hosted on GitHub:

    $ git clone https://github.com/johnmcfarlane/fixed_point.git

    The API is exposed through headers in the include directory. Add this to your system header list and include, e.g.:

    #include <sg14/fixed_point>

    Tests and Benchmarks

    Linux

    Tested on Travis (Ubuntu 14.04) using GCC 5.4 and Clang 3.5 and Debian GNU/Linux 8.3 using GCC 5.4 and Clang 3.5.0.

    Requires:

    Optional:

    • Boost – facilitates multiprecision support
    • Doxygen – generates documentation in the doc/gh-pages directory
    • pandoc – generates proposal papers

    For a list of configuration options:

    $ cmake -LH

    To build everything:

    $ cmake -DCMAKE_BUILD_TYPE=Release
    $ make

    To disable exception handling (incompatible with Boost 1.55 or lower), add -DEXCEPTIONS=OFF to the cmake command:

    $ cmake -DCMAKE_BUILD_TYPE=Release -DEXCEPTIONS=OFF
    $ make

    To run tests:

    $ cmake -DCMAKE_BUILD_TYPE=Release
    $ make fp_test
    $ ./fp_test

    To run benchmarks:

    $ cmake -DCMAKE_BUILD_TYPE=Release
    $ make fp_benchmark
    $ ./fp_benchmark

    To profile benchmarks:

    1. Build with frame pointers included:

      $ cmake -DCMAKE_BUILD_TYPE=Release -DPROFILE=ON
      $ make fp_benchmark
      $ ./fp_benchmark
    2. then run:

      $ perf record -g ./fp_benchmark
      $ perf report -g 'graph,0.5,caller'

    To install:

    $ cmake -DCMAKE_BUILD_TYPE=Release
    $ make
    $ sudo make install

    Windows

    Tested on AppVeyor and on Windows 10 Professional with CMake 3.8.0. Requires:

    • MSBuild 15.0 (VS 2017)
    • CMake 3.8.0

    To build vs/Release/fp_test.exe and vs/Release/fp_benchmark.exe:

    cmake -G "Visual Studio 15 2017" .
    MSBuild.exe /m fixed_point.sln /p:Configuration=Release
    

    For 64-bit builds, append Win64 to the -G option above:

    cmake -G "Visual Studio 15 2017 Win64" .
    

    Note that fp_benchmark is unlikely to produce valid results due to missing escape and clobber functions.

    Cleaning

    To clean the project files:

    git clean -Xdff .
    

    (Use with caution!)

    Further Reading

    Contact Information

    All feedback greatly appreciated.

    Visit original content creator repository https://github.com/johnmcfarlane/fixed_point
  • blackat

    Blackat – End-to-End Encryption Messaging System

    Blackat Logo

    Blackat is a secure messaging application built using React Native, MongoDB, Socket.io, and Material 3. The app provides end-to-end encryption using the Signal Protocol, ensuring that your messages and calls are private and secure. Blackat is designed for the Android platform.

    Features

    • Secure messaging: Blackat uses the Signal Protocol to encrypt your messages, ensuring end-to-end encryption and protecting your privacy.
      • Text Messaging: Send and receive text messages securely within the app. Communicate with your contacts using end-to-end encryption to protect the privacy of your conversations.
      • Image Sharing: Share images securely with your contacts. Blackat allows you to send and receive images while ensuring the confidentiality and integrity of the shared content.
      • Sticker Labels: Express yourself using sticker labels in your messages. Blackat offers a collection of fun and expressive sticker labels that you can use to add a personal touch to your conversations.
    • Offline messaging: Blackat allows you to send and receive messages even when you are offline. Messages will be delivered as soon as you regain connectivity.
    • Profile Creation: Users can create their personal profiles by providing relevant information such as their name and profile picture. This allows other users to identify and connect with them.
    • Personal information is only shared with established contacts and exchanged through an end-to-end encrypted channel.

    Installation

    Clone the repository:

    git clone https://github.com/thiensgith/blackat.git
    

    Server deployment:

    1. Install the required dependencies:
    cd server && npm install
    
    1. Create .env file. Ex: .env.example file
    MONGODB_SERVER=<YOUR MONGODB DATABASE URI>
    
    1. Start server in debug mode
    npm run dev
    

    Android Installtion:

    Follow these steps to install and run Blackat on your Android device:

    1. Install the required dependencies:
    cd client && npm install
    
    1. Create .env file. Ex: .env.example file
    SOCKET_SERVER=<YOUR SERVER URI>
    
    1. Connect your Android device and enable USB debugging.
    2. Run the app on your device:
    npm run android
    

    Technologies Used

    • React Native: A JavaScript framework for building cross-platform mobile applications.
    • MongoDB: A NoSQL database used for storing user data securely.
    • Socket.io: A library for real-time, bidirectional communication between clients and servers.
    • Material 3: A design system developed by Google, providing beautiful and consistent UI components.

    License

    This project is licensed under the MIT License.


    We hope you enjoy using Blackat! Feel free to reach out to our support team if you have any questions or feedback.

    Visit original content creator repository https://github.com/yuntian3008/blackat
  • travelers_hub_back-end

    Visit original content creator repository
    https://github.com/SelmaNdapanda/travelers_hub_back-end

  • meaning_iOS


    미라클 모닝으로 시작하는 당신의 의미있는 아침, meaning iOS


    🌱 서비스 소개

    프로젝트 진행기간 : 2020년 12월 26일 ~ 2021년 01월 15일

    모든 것은 바뀔 수 있고 나 역시 무언가를 바꿀 수 있습니다.

    기상 시간이 달라진다면, 당신도 변할 수 있습니다.


    ‘내’가 눈 뜨는 시간이 아닌, ‘해’가 뜨는 시간부터 하루를 시작하는 미라클 모닝.

    미닝을 통해 미라클 모닝에 도전하며 당신만의 의미있는 아침을 만들어 나가보세요.

    일찍 일어나는 습관으로 하루를 길게 보내면, 성장의 발판을 마련할 수 있습니다.

    미닝과 함께 체계적인 계획을 세우고 이를 규칙적으로 실천하면서 성취감을 얻어보세요.

    성장지향적인 그룹원들과 목표를 공유한다면 우리는 함께, 더 멀리 갈 수 있습니다.


    SETTING


    🐕 개발 환경

    Swift 4 Xcode swift iOS COCOAPODS


    ➕ 사용 라이브러리

    Moya Alamofire Kingfisher lottie-ios


    📏 Coding Convention

    meaning Coding Convention


    🐱 How We Use Git

    How We Use Git


    🗂 Foldering

    💻 meaning
      🗂 Global
       🗂 Extension
        📑 Fonts+Extension.swift
       🗂 Model
        📑 GenericResponse.swift
       🗂 Service
        📑NetworkResult.swift
      🗂 Screen
       🗂 Home
        🗂 Cell
         📑 CardListCell.swift
        🗂 Storyboard
         📑 Home.storyboard
        🗂 ViewController
         📑 HomeVC.swift
       🗂 Login
        🗂 Storyboard
         📑 Login.storyboard
        🗂 ViewController
         📑 LoginVC.swift
      🗂 Support
       🗂 Font
       🗂 Assets.xcassets
       📑 LaunchScreen.storyboard
       📑 AppDelegate.swift
       📑 SceneDelegate.swift
       📑 Info.plist
      🗂 meaning.xcodeproj

    📱 Screen 단위

    • TapBar : 커스텀탭바
    • Login : 스플래쉬
    • Login : 로그인
    • Onboarding : 닉네임 및 기상시간 입력
    • Home : 홈, 캘린더 화면
    • Camera : 타임스탬프
    • Mission : 미션카드
    • MyPage : 마이페이지
    • GroupList : 그룹탭(그룹 목록 + 그룹 생성)
    • GroupFeed : 그룹SNS(그룹 글 목록 + 글 자세히보기 + 그룹 설정)

    Service

    🗓 WORKFLOW


    👷 실행화면

    – 기준 iPhone : 아이폰se2, 아이폰12mini, 아이폰12Pro
    – 테스트 계정 : 아이디 – iOS@meaning.com / 비밀번호 – iosmeaning


    📱 Splash, Login 화면


    화면

    Splash


    스플래시 화면을 lottie-ios 를 통해 적용하였습니다.
    한 Loop 가 재생되고 로그인 화면으로 넘어갑니다.
    splash 에서는 토큰의 세션이 만료되었는지에 대해 서버통신을 통해 확인합니다.
    만료가 되었거나 토큰이 없다면 로그인 화면으로, 유효한 토큰을 소유한 유저라면 바로 홈화면으로 넘어갑니다.

    Login


    더 나은 레이아웃을 위해 로그인 버튼을 누르면 애니메이션과 함께 아이디, 비밀번호 란이 보여집니다.
    아이디 혹은 비밀번호를 적지 않았거나 옳지 않은 값이 들어간다면 빨간 경고 글씨가 띄워집니다.

    상세 화면

    로그인 진입 화면 로그인 화면 값 오류 화면

    📱 온보딩 화면


    화면

    OnBoarding


    로그인 화면 이후, 닉네임과 기상시간을 입력하지 않았던 유저에게 온보딩 화면이 나타납니다.
    목표 기상 시간의 경우에는 pickerview 를 통해 구현하였습니다.

    상세 화면

    진입 화면 닉네임 입력 화면
    기상시간 화면 기상시간 입력 후 화면

    📱 홈 – 캘린더 – 미션 화면


    화면

    Home – Calendar – Mission


    로그인이 완료되면 홈화면으로 이어집니다.
    홈화면은 캘린더와 미션으로 이어지는 구간입니다.
    미션은 좌우 슬라이드가 되도록 구현하였으며, 캘린더는 상단 날짜 버튼을 누르면 넘어가도록 되어있습니다.
    캘린더는 해당 달에 유저가 미라클 모닝을 성공한 날들을 별로 표시합니다.
    아래 커스텀 탭바의 카메라 버튼은 기상미션 수행 외에 인증 카메라를 사용할 수 있게 해줍니다.


    상세 화면

    홈 화면 캘린더 화면

    📱 홈-캘린더-미션완료 후 화면


    화면

    After Mission Completed


    미션이 완료되면 순차적으로 해당 미션이 완료되어 홈에서 확인할 수 있습니다.
    한번 완료된 미션은 다시 할 수 없습니다.

    상세 화면

    미션 완료 화면 미션 완료 후 별이 채워진 캘린더

    📱 타임스탬프 화면


    화면

    TimeStamp


    미닝 앱의 메인 기능이라고 할 수 있는 ‘타임스탬프’ 기능입니다.
    카메라를 키게 하여 카메라 위에 현재 시간이 나와있는 뷰를 올려 화면을 캡쳐한 후 저장하는 기능입니다.

    상세 화면

    타임스탬프 화면

    📱 4개 미션 화면


    화면

    Missions


    기본 기상인증 미션은 네가지가 주어집니다.
    첫번째는 타임카메라를 통해 자신의 아침을 인증하고, 그룹에 속해있다면 그룹에 인증사진을 올리고 그룹에 속해있지 않다면 개인피드에 인증사진을 업로드하는 것으로 미션을 수행합니다.
    두번째로 자극을 줄 수 있는 격언을 읽는 미션을 수행합니다.
    세번째로 하루 회고일기를 작성합니다.
    마지막으로 짧은 독서록을 쓰는 미션을 수행합니다.

    상세 화면

    하루다짐 미션 화면 회고일기 미션 화면 회고일기 작성 화면
    짧은독서 미션 화면 짧은독서 작성 화면
    타임스탬프 미션 화면 타임스탬프 작성 화면

    📱 마이피드 – 마이피드 상세보기 화면


    화면

    MyFeed


    자신의 기상미션에서 찍은 사진들이 업로드 되는 개인공간입니다.
    피드로 확인이 가능하고, 자신이 몇번째 미라클 모닝을 했는지 그리고 각 피드의 상세 페이지도 확인할 수 있습니다.

    상세 화면

    마이피드 화면 마이피드 상세보기 화면

    📱 그룹 목록 화면


    화면

    Group List


    다양한 그룹을 구경할 수 있는 목록 창 입니다.
    좌우 collectionview 로 확인할 수 있으며, 또 그 아래로는 테이블뷰로도 정보가 제공됩니다.

    상세 화면

    그룹 목록 화면 참가 그룹이 없을 때

    📱 그룹 상세보기-가입 화면


    화면

    Joining Clubs


    참가하고 싶은 그룹을 누르면 상세 설명을 확인할 수 있습니다.
    그 이후에 참가 버튼을 누르면 참가가 완료되고, 그룹 홈에서 자신이 참가하는 그룹을 확인할 수 있습니다.
    그리고 더이상 홈에서 자신이 참가하고 있는 그룹이 보이지 않게됩니다.

    상세 화면

    그룹 상세보기 참가버튼 누른 후 참가 후 그룹 목록
    자신이 참가한 그룹
    더이상 보이지 않음

    📱 그룹생성 화면


    화면

    Create Group


    자신이 속해있는 그룹이 없을 때,
    그룹에 참여해도 되지만, 그룹을 직접 생성해도 됩니다.
    그룹을 만들고 그룹의 피드를 확인하면 게시물이 없다는 블랭크 뷰가 나오게 됩니다.

    상세 화면

    그룹 생성 화면 그룹 생성 내용 작성 화면 생성 완료 화면

    📱 그룹 피드-피드 상세보기-그룹 설정 화면


    화면

    Group Feed


    그룹 피드는 자신이 참가하고 있는 그룹사람들의 인증 사진들을 확인하는 공간입니다.
    피드에서 상세보기로 이동도 가능하며, 그룹을 설정하는 페이지에서 그룹에 대한 상세 내용을 확인할 수 있습니다.

    상세 화면

    그룹 피드 비었을 때 화면 그룹 피드 내용 화면
    설정 화면 그룹 피드 상세보기 화면

    🛠 기능명세서

    우선순위 기능명 설명 구현여부 담당자
    P1 스플래쉬 앱 실행시 스플래쉬가 보여진다. 🟣 선민승
    P1 로그인 로그인을 하여 미닝 앱을 사용한다. 🟣 선민승
    P1 온보딩(닉네임) 사용자가 원하는 닉네임을 입력한다. 🟣 김민희
    P1 온보딩(기상시간) 오전 5시부터 오전 8시 사이의 목표 기상시간을 설정한다. 🟣 김민희
    P1 온보딩(환영글) 사용자를 환영하며, 홈으로 연결된다. 🟣 김민희
    P1 커스텀 탭바 가운데 카메라 버튼을 원형으로 탭바를 커스텀한다. 탭바 아이템을 클릭하여, 해당 뷰로 이동한다. 🟣 박세은
    P1 카메라 (타임스탬프) 현재 시간이 즉각 반영되어 이미지와 함께 촬영이 되며, 갤러리에 저장된다. 🟣 김민희
    P1 미션을 좌우 슬라이드가 되도록하며, 상단 날짜를 클릭하면 캘린더로 넘어간다.
    미션을 완료하면, 미션 완료 텍스트가 보여지는 카드로 변한다.
    미션을 순차적으로 수행하지 않을 경우, 이전 먼저 해달라는 토스트 알림을 보여준다.
    🟣 김민희
    P1 캘린더 메인 홈에서 상단 날짜를 누르면 캘린더가 보인다.
    미션 완료 시 해당일의 별이 채워진다.
    🟡 김민희
    P1 피드 업로드 (사진 업로드) 사진을 마이 피드와 가입된 그룹 피드에 업로드 한다. 🟡 김민희, 선민승
    P2 미션카드(오늘 하루 다짐) 모닝미라클과 관련된 글귀를 매일 중복을 피하면서 보여준다. 🟡 선민승
    P2 미션카드(자기회고/일기) 200자 이내로 자기회고를 할 수 있는 텍스트필드가 있다. 🟡 선민승
    P2 미션카드(책 한줄평) 책을 읽고 200자 이내로 감상평이나 한줄평을 남길 수 있는 텍스트가 있다. 🟡 선민승
    P2 마이피드 그동안 내가 올린 미라클 모닝 인증샷을 세로 스크롤로 내려 볼 수 있고, 나의 달성 횟수를 보여준다. 🟡 선민승, 김민희
    P2 그룹 목록 내가 가입한 그룹, 다른 그룹들을 살펴볼 수 있다. 🟡 박세은, 김민희
    P2 그룹 상세보기 그룹 목록에서 그룹을 클릭하면 그룹이름, 그룹 정보, 인원수 및 참가인원을 확인할 수 있다. 🟡 박세은
    P2 그룹 생성 그룹을 직접 만들어서 그룹을 관리할 수 있다. 이미 내 그룹이 있거나, 이미 있는 이름일 경우 생성이 불가하다. 🟡 박세은
    P2 그룹 참여 그룹 참여하기 버튼을 눌렀을 때
    1) 가입한 그룹이 없는 경우, 가입이 완료된다.
    2) 가입한 그룹이 있는 경우, 이미 가입된 그룹이 있다는 팝업이 보인다.
    🟡 박세은
    P2 그룹 피드 그동안 그룹 멤버들이 올린 미라클 모닝 인증샷을 세로 스크롤로 내려 볼 수 있고, 얼마나 많은 그룹원들이 참여하고 있는지를 보여준다.
    그룹에 글이 올라오지 않은 경우, 게시물이 없다는 멘트와 함께 [홈으로 돌아가기] 버튼을 보여준다.
    🟡 김민희
    P3 인증글 상세보기 그룹에서 다른 사람의 인증글을 클릭하면 인증글을 볼 수 있다. 🟢 김민희
    P3 그룹 설정 그룹 정보 및 그룹원 정보를 보여준다. 🟢 박세은

    🎉 새롭게 도전해본 기능

    Meaning iOS 팀은 끝없는 도전을 두려워하지 않습니다. 이번 프로젝트에서 각자 해보지 않았던 새로운 기술들을 도전하고 공부하는 시간을 가져보았습니다.

    👀 민희
    1. Moya가 Moya?

    Moya 프레임 워크 이용하기

    • 추상화 네트워킹 라이브러리
    • URLSession과 Alamofire를 한번 더 감싼 API
    • moya가 제시하는 기본 구현 방식의 문제점은?
    1. 새로운 앱을 쓰기 힘들게 만든다.
    2. 앱을 유지하기 어렵게 만든다.
    3. unit 테스트를 하기 어렵게 만든다.
    
    • 그럼 moya는 뭐가 더 좋을까요?
    • moya는 열거형(enum)을 사용하여 네트워크 요청 방식을 type-safe한 방식으로 캡슐화 하는데 초첨을 맞춘 프레임워크
    • moya는 자체적인 네트워크 수행은 X, Alamofire의 네트워킹 서비스를 사용하고, 추상화 하기 위한 기능들을 제공한다. → 결론 : Alamofire 직접 사용X, Alamofire를 기반으로 하고 있는 Moya를 거쳐 사용 O!

    😳 Moya 그래서 어떻게 사용해요?

    1. pod 에 설치하기 → Moya를 설치하면 자동으로 Alamofire도 설치되는 형태입니다.
    1. 서버 통신에 필요한 API를 enum을 이용해 case별로 추상화합니다.

      • case 별로 나눠서 추상화 함으로써 한눈에 api 별 통신에 필요한 type을 볼 수 있고, 수정하기 편리합니다.
      import Foundation
      import Moya
      
      enum APITarget {
          // case 별로 api를 나눠줍니다
          case onboard(token: String, nickName: String, wakeUpTime: String) // 온보드
          case timestamp(token: String, dateTime: String, timeStampContents: String, image: UIImage) // 타임스탬프 작성
          case groupEdit(token: String, groupid: Int) // 그룹 설정
      }
      
      // MARK: TargetType Protocol 구현
      
      extension APITarget: TargetType {
          var baseURL: URL {
              // baseURL - 서버의 도메인
              return URL(string: "[서버 도메인]")!
          }
          
          var path: String {
              // path - 서버의 도메인 뒤에 추가 될 경로
              switch self {
              case .onboard:
                  return "/user/onboard"
              case .timestamp:
                  return "/timestamp"
              case .groupEdit(_, let groupid):
                  return "/group/\(groupid)/edit"
              }
          }
          
          var method: Moya.Method {
              // method - 통신 method (get, post, put, delete ...)
              switch self {
              case .timestamp:
                  return .post
              case .onboard:
                  return .put
              case .groupEdit:
                  return .get
              }
          }
          
          var sampleData: Data {
              // sampleDAta - 테스트용 Mock Data
              return Data()
          }
          
          var task: Task {
              // task - 리퀘스트에 사용되는 파라미터 설정
              switch self {
      
              case .onboard( _, let nickName, let wakeUpTime):
      		// 파라미터 존재시
                  return .requestParameters(parameters: ["nickName" : nickName, "wakeUpTime": wakeUpTime], encoding: JSONEncoding.default)
                  
              case .timestamp(_, let dateTime, let timeStampContents, let image):
      		// multipart/form-data 사용시
                  let dateTimeData = MultipartFormData(provider: .data(dateTime.data(using: .utf8)!), name: "dateTime")
                  let timeStampContentsData = MultipartFormData(provider: .data(timeStampContents.data(using: .utf8)!), name: "timeStampContents")
                  let imageData = MultipartFormData(provider: .data(image.jpegData(compressionQuality: 1.0)!), name: "image", fileName: "jpeg", mimeType: "image/jpeg")
                  let multipartData = [dateTimeData, timeStampContentsData, imageData]
                  return .uploadMultipart(multipartData)
              
              case .groupEdit:
                  // 파라미터가 존재하지 않을 시
      		return .requestPlain
              }
          }
          
          var validationType: Moya.ValidationType {
              // validationType - 허용할 response의 타입
              return .successAndRedirectCodes
      	// successAndRedirectCodes - Array(200..<400)
          }
          
          var headers: [String : String]? {
              // headers - HTTP header
              switch self {
      
              case .onboard(let token, _, _), .groupEdit(let token, _):
                  return ["Content-Type" : "application/json", "token" : token]
              case .timestamp(let token, _, _, _):
                  return ["Content-Type" : "multipart/form-data", "token" : token]
               
              }
          }
          
      }

    1. 데이터 통신 분기처리를 위한 모델을 만듭니다.

      import Foundation
      import Moya
      
      struct APIService {
      	static let shared = APIService()
      	// 싱글톤 객체 생성
          let provider = MoyaProvider<APITarget>()
      	// MoyaProvider(->요청 보내는 클래스) 인스턴스 생성
         
          func timestamp(_ token: String, _ dateTime: String, _ timeStampContents: String, _ image: UIImage, completion: @escaping (NetworkResult<TimestampData>)->(Void)) {
              // 타임스탬프를 업로드 하는 함수를 만들어 봅니다.
      	    // TimestampData는 서버에서 받아온 data를 넣어줄 구조체 입니다.
              let target: APITarget = .timestamp(token: token, dateTime: dateTime, timeStampContents: timeStampContents, image: image)
              // APITarget에서 만들어준 case 중 하나를 선택합니다!
      	judgeObject(target, completion: completion)
              
          }
      
      	// request하고 decode 하는 코드를 반복해서 사용할 수 있게 함수로 제작해보았습니다
          func judgeObject<T: Codable>(_ target: APITarget, completion: @escaping (NetworkResult<T>) -> Void) {
              provider.request(target) { response in
                  switch response {
                  case .success(let result):
                      do {
                          let decoder = JSONDecoder()
                          let body = try decoder.decode(GenericResponse<T>.self, from: result.data)
                          if let data = body.data {
                              completion(.success(data))
                          }
                      } catch {
                          print("구조체를 확인해보세요")
                      }
                  case .failure(let error):
                      completion(.failure(error.response!.statusCode))
                  }
              }
          }
        }

    1. 원하는 ViewController 에서 서버 통신 함수를 불러옵니다

      var timestampData: TimestampData?
      
      func uploadPictrue(_ token: String, _ dateTime: String, _ timeStampContents: String, _ image: UIImage) {
              APIService.shared.timestamp(token, dateTime, timeStampContents, image) { [self] result in
                      switch result {
                      case .success(let data):
      		    	data = timestampData
                      // 성공 시 처리 로직
                      case .failure(let error):
                          if error == 400 {
      										
      			} else if error = 404 {
      										
      			}
                      }
                  }
          }

    2.AVFoundation 이용해서 TimeStamp Camera 구현하기

    • meaning에서는 타임 스탬프 기능을 위해 카메라 위에 현재 시간과 미닝의 로고를 올려 함께 촬영합니다.
    • 핸드폰에서 보통 사용하는 기본 카메라 UIImagePickerController가 아닌 AVFoundation를 사용해 새로운 카메라 화면을 구현해주었습니다.
    import UIKit
    import AVFoundation
    
    class TimeStampVC: UIViewController {
    
        // MARK: Variable Part
        
        var captureSession: AVCaptureSession!
        // 실시간 캡쳐를 위한 세션
        var stillImageOutput: AVCapturePhotoOutput!
        // 캡쳐한 이미지를 출력
        var videoPreviewLayer: AVCaptureVideoPreviewLayer!
        // 캡쳐된 비디오를 표시해주는 Layer
        var timeStampImage: UIImage?
        var rootView: String?
    
        // MARK: Life Cycle Part
        
        override func viewDidLoad() {
            super.viewDidLoad()
            setCameraView()
        }
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            self.captureSession.stopRunning()
        }
        
        override func viewDidAppear(_ animated: Bool) {
            setCaptureSession()
        }
    
    }
    
    // MARK: Extension
    
    extension TimeStampVC {
        
    // MARK: Function
        
        func setupLivePreview() {
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            // captureSession를 사용해 캡쳐한 비디오를 표시해줌
            
            videoPreviewLayer.videoGravity = .resizeAspectFill
            // videoGravity: 콘텐츠를 표시하는 방법 -> resizeAspectFill: 비율을 유지하면서 채우기
            videoPreviewLayer.connection?.videoOrientation = .portrait
            // portrait - 세로, landscape - 가로모드
            cameraView.layer.addSublayer(videoPreviewLayer)
            // cameraView의 위치에 videoPreviewLayer를 띄움
        }
        
        func setCaptureSession() {
            captureSession = AVCaptureSession()
            captureSession.sessionPreset = .high
            // 캡쳐 화질은 high로 설정
            
            // default video 장치를 찾는다
            guard let backCamera = AVCaptureDevice.default(for: AVMediaType.video)
                else {
                    print("Unable to access back camera!")
                    return
            }
            do {
                // 찾은 video 장치를 캡쳐 장치에 넣음
                let input = try AVCaptureDeviceInput(device: backCamera)
                stillImageOutput = AVCapturePhotoOutput()
    
                // 주어진 세션을 캡쳐에 사용할 수 있는지 + 세션에 추가할 수 있는지 먼저 파악한다
                if captureSession.canAddInput(input) && captureSession.canAddOutput(stillImageOutput) {
                    // 주어진 입력을 추가한다
                    captureSession.addInput(input)
                    // 주어진 출력 추가
                    captureSession.addOutput(stillImageOutput)
                    setupLivePreview()
                }
            }
            catch let error  {
                print(error.localizedDescription)
            }
            
            // startRunning는 시간이 걸릴 수 있는 호출이므로 main queue가 방해되지 않게 serial queue에서 실행해준다
            DispatchQueue.global(qos: .userInitiated).async {
                // 세션 실행 시작
                self.captureSession.startRunning()
                // 콜백 클로저를 통해 세션실행이 시작하는 작업이 끝난다면
                // cameraView에 AVCaptureVideoPreviewLayer를 띄우게 만든다
                DispatchQueue.main.async {
                    self.videoPreviewLayer.frame = self.cameraView.bounds
                }
            }
        }
    }
    • 이제 화면에 나온 이미지를 촬영(캡쳐)하는 역할이 남았습니다. 기존의 카메라 촬영 버튼의 역할을 구현해주어야합니다. AVCapturePhotoCaptureDelegate를 이용해 사진을 캡쳐한 후의 결과를 받습니다.
    // MARK: IBAction
        
        @IBAction func shootingButtonDidTap(_ sender: Any) {
            // 카메라 촬영 버튼 클릭 시 Action
            
            let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
            // jpeg 파일 형식으로 format
            stillImageOutput.capturePhoto(with: settings, delegate: self)
            // AVCapturePhotoCaptureDelegate 위임
        }
    
    extension TimeStampVC: AVCapturePhotoCaptureDelegate {
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
            
            guard let imageData = photo.fileDataRepresentation()
                else { return }
            
            let image = UIImage(data: imageData)
            timeStampImage = image?.cropToBounds(width: Double(cameraView.layer.frame.width), height: Double(cameraView.layer.frame.width))
            // cropToBounds 라는 Extesnion을 통해 정방형 크기로 크롭해주었다.
            
            guard let checkVC = self.storyboard?.instantiateViewController(identifier: "PhotoCheckVC") as? PhotoCheckVC else {
                return
            }
            
            // 다음 뷰로 이미지를 넘겨주었다.
            checkVC.photoImage = timeStampImage
            
            
            self.navigationController?.pushViewController(checkVC, animated: true)
        }
    }
    • 캡쳐 이미지는 내가 원하는 크기로 캡쳐가 되지 않습니다. 단순히 커스텀한 카메라 화면은 보여지는 특정 뷰에서의 user에게 보여지는 크기이고, 캡쳐 이미지는 일반 카메라의 비율은 4:3으로 나오게 됩니다.
    • 그렇기 때문에 cropToBounds 라는 Extension을 만들어 사진을 원하는 크기로 잘라주었습니다.
    import UIKit
    
    extension UIImage {
        func cropToBounds(width: Double, height: Double) -> UIImage {
            // 이미지를 원하는 크기로 잘라줍니다
    
                let cgimage = self.cgImage!
                let contextImage: UIImage = UIImage(cgImage: cgimage)
                let contextSize: CGSize = contextImage.size
                var posX: CGFloat = 0.0
                var posY: CGFloat = 0.0
                var cgwidth: CGFloat = CGFloat(width)
                var cgheight: CGFloat = CGFloat(height)
    
                // width와 height 중 더 큰 길이를 중심으로 자른다.
                if contextSize.width > contextSize.height {
                    posX = ((contextSize.width - contextSize.height) / 2)
                    posY = 0
                    cgwidth = contextSize.height
                    cgheight = contextSize.height
                } else {
                    posX = 0
                    posY = ((contextSize.height - contextSize.width) / 2)
                    cgwidth = contextSize.width
                    cgheight = contextSize.width
                }
    
                let rect: CGRect = CGRect(x: posX, y: posY, width: cgwidth, height: cgheight)
    
                // rect를 이용해서 bitmap 이미지를 생성한다.
                let imageRef: CGImage = cgimage.cropping(to: rect)!
    
                // imageRef 이미지를 기반으로 새 이미지를 만든 후, 원래 방향으로 다시 돌려준다.
                let image: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
    
                return image
            }
    }
    • 또한 타임스탬프 카메라 안에서 시간이 지나면 자동으로 시간이 흐르도록 하기 위해 Timer를 이용해 1초마다 현재 시간을 검사해 분(minutes) 이 바뀐다면 라벨의 시간을 수정해줍니다.
    Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(nowTimeLabel), userInfo: nil, repeats: true)
    
    @objc func nowTimeLabel() {
        // 현재 시간을 기반으로 time과 날짜를 label에 넣어줌
    		stampTimeLabel.text = Date().datePickerToString().recordTime()
    		stampDateLabel.text = Date().datePickerToString().recordDate() + " (\(Date().weekDay()))"
    }

    3. CollectionView Animation

    • 홈에서 카드를 넘길 때 CollectionView를 사용해서 구현을 했는데, 단조로운 느낌을 피하기 위해 가운데 오는 cell을 강조해주는 carousel 효과(혹은 회전목마 효과)의 Animation을 구현해보았습니다.
    • UICollectionViewFlowLayout라는 것을 처음 사용해보았습니다. UICollectionViewFlowLayout를 사용하면 cell을 원하는 형태로 정렬할 수 있게 도와줍니다.
    let customLayout = AnimationFlowLayout()
    missonCardCollectionView.collectionViewLayout = customLayout
    // 원하는 CollectionView에 선언해서 사용합니다. 
    import UIKit
    
    class AnimationFlowLayout: UICollectionViewFlowLayout {
        // 셀이 열의 흐름(세로, 가로)에 따라 이동 할 때 보여지는 것을 담당한다
        
        // MARK: Variable Part
        
        private var firstTime: Bool = false
        // 초기 한번만 설정되기 위해 변수를 선언
        
        override func prepare() {
            super.prepare()
            guard !firstTime else { return }
            
            guard let collectionView = self.collectionView else {
                return
            }
            
            let collectionViewSize = collectionView.bounds
            itemSize = CGSize(width: collectionViewSize.width-50*2, height: 100)
            // itemSize - 셀의 기본 크기
            
            let xInset = (collectionViewSize.width-itemSize.width) / 2 - 50
            self.sectionInset = UIEdgeInsets(top: 0, left: xInset, bottom: 0, right: xInset)
            // sectionInset - 섹션간의 여백
            
            scrollDirection = .horizontal
            // 가로 스크롤에 사용할 것이라는 걸 알려준다
            
            minimumLineSpacing = 10 - (itemSize.width - itemSize.width*0.7)/2
            // minimumLineSpacing - 행 사이에 사용할 최소 간격
            // 셀이 작아지면 더 멀리 있게 보이기 때문에 붙여주기 위해서 사용
            
            firstTime = true
            // 한번 설정을 했으면 다시 선언되지 않기 위해 바꿔준다
        }
        
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            // 레이아웃 변경이 필요한지 묻는 함수
            return true
        }
       
    }
    • CGAffineTransform를 이용해 2D 그래픽을 그려 애니메이션을 화면에 보여줍니다. 가운데 있는 Cell을 기준으로 양 옆의 Cell은 가운데 Cell보다 작아졌다가 가운데로 도달했을 때, scale에서 identify로 커지는 애니메이션을 주었습니다.
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            // 레이아웃 요소를 가져와서 조정하는 함수
                let superAttributes = super.layoutAttributesForElements(in: rect)
                
                superAttributes?.forEach { attributes in
                    guard let collectionView = self.collectionView else { return }
                    
                    let collectionViewCenter = collectionView.frame.size.width / 2
                    // collectionVIewCenter - 컬렉션 뷰의 중앙값으로 변하지 않는 고정 값
                    let offsetX = collectionView.contentOffset.x
                    // offsetX - 사용자가 스크롤할 때 기준점으로부터 이동한 거리(x축)
                    let center = attributes.center.x - offsetX
                    // center - 각 셀들의 중앙값
                    // 기본 center값은 처음에 collectionView가 로드될 때 값이므로 여기서 offsetX 빼줘서 동적으로 계산한다
                    
                    let maxDistance = self.itemSize.width + self.minimumLineSpacing
                    // maxDistance - 아이템 중앙과 아이템 중앙 사이의 거리
                    let dis = min(abs(collectionViewCenter-center), maxDistance)
                    // 현재 CollectionView의 가운데에서 cell의 가운데 값을 빼서 가운데 0을 기준으로 1까지 계산하기 위해 계산하는 값
                    
                    let ratio = (maxDistance - dis)/maxDistance
                    // 비율을 구해서 애니메이션을 주기 위한 값
                    let scale = ratio * (1-0.7) + 0.7
                    
                    attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
                    // scale에서 identify로 커지는 애니메이션을 준다
                }
                
                return superAttributes
           }

    👀 민승
    1. Login Animation 구현

    한번도 해보지는 않았지만, 언제나 iOS 주니어 개발자로서 도전해보고 싶었던 자체 animation 구현을 도전해보았습니다.

    1. 부탁받은 애니메이션에 대한 설명
      먼저 이 디자인은 디자이너분이 제안해주신 소중한 아이디어였습니다. 로그인 버튼을 눌렀을 때, 자연스럽게 아이디, 비밀번호 작성란이 천천히 올라오는 방식으로 화면에 그려지는 애니메이션이었습니다.

    2. 애니메이션이 들어간 부분
      이 애니메이션의 시작 은 로그인 버튼이 눌러진 시점부터 입니다. 따라서 @IBAction 을 로그인버튼에 설정해놓고, 그 IBAction 내부에서 애니메이션 적용을 하였습니다.

    3. 애니메이션 코드

      UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.transitionFlipFromTop, animations: { /* codes */ }, completion: { finished in
                   /* codes */
      })

      흔히 사용하는 UIView.animate() 를 이용하였습니다.

      단순히 자연스럽게 나타나는 애니메이션은 alpha값 즉, 투명도를 이용했습니다.

       //뒤로 가기 버튼 나타나기
       self.backBtn.alpha = 1
       self.backBtn.isHidden = false

      위아래로 움직이는 애니메이션의 경우에는 .center.y 축을 이용했습니다.

      //회원가입 버튼 아래로 내려가기
      self.signUpBtn.center.y += self.view.bounds.height
    4. 초기 위치 설정
      아래에서 위 로 움직여야 하는 애니메이션이었기 때문에 처음부터 autolayout을 200 만큼 아래로 위치를 잡았습니다. 그리고 버튼이 눌러졌을 때 애니메이션 코드를 통해 다시 200만큼 올라오도록 해주었습니다.

    5. 조건문 설정
      한가지 예외처리를 해주어야 했습니다. 로그인 버튼을 처음으로 눌러 들어오면서 애니메이션이 작동되고, 그 다음부터는 버튼을 눌러도 애니메이션이 작동하면 안되었습니다. (그렇게 되면 로그인 버튼을 누를 때마다 아이디 비밀번호 란이 200씩 위로 올라갈테니까요..) 그래서 loginBtnFirstPressed: Bool을 하나 선언해주어서 로그인 버튼이 처음으로 눌릴 때 true처리를 해주고, 그 다음부터는 서버통신이 되고 애니메이션은 작동이 안되도록 처리해주었습니다.

    6. 뒤로 돌아가는 버튼을 눌렀을 때
      로그인 버튼을 눌러 아이디 비밀번호를 치다가, 뒤로 돌아가는 경우가 있습니다. 이 경우에도 똑같이 애니메이션 작동을 넣어주어서 다시 내려가는 애니메이션을 적용해주었습니다.


    👀 세은
    1. UIRefreshControl

    UIRefreshControl은 테이블 뷰를 아래 방향으로 슬라이드 해서 화면을 갱신하는 기능으로, 화면을 새로 고침 할 때 많이 사용됩니다.

    우선 사용하고자 하는 뷰컨트롤러에 다음과 같은 구문을 선언해줍니다!

    lazy var refreshControl: UIRefreshControl = {
            // Add the refresh control to your UIScrollView object.
            let refreshControl = UIRefreshControl()
            refreshControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControl.Event.valueChanged)
            refreshControl.tintColor = UIColor.meaningNavy
            
            return refreshControl
        }()

    refreshControl 속성에 UIRefreshControl를 할당합니다. 새로 고침 중일 때 동작할 메서드를 addTarget를 이용해서 연결해줍니다.

    그리고 뷰에 추가를 시켜줍니다. 테이블 뷰를 내리면 리로드 시키고 싶어서 테이블 뷰에 추가를 해주었습니다.

    groupTableView.addSubview(self.refreshControl)

    아래의의 함수는 refreshControl 선언시 타겟 액션 을 걸어준 함수이기 때문에 , 화면을 당겨서 내릴 때마다 함수가 실행됩니다. 따라서, handleRefresh 함수 안에 하고자 하는 액션을 추가하면 쉽게 구현할 수 있습니다.

    UIRefreshControl 객체는 beginRefreshing() 메서드를 통해 실행이 시작되고 endRefreshing() 메서드를 통해 종료됩니다. 화면 당김이 임계점을 넘게 되면, 자동으로 beginRefreshing() 메서드는 호출됩니다.

    따라서 새로 고침이 완료되면 endRefreshing()만 호출해 주면 됩니다. (endRefreshing() 메서드를 호출하지 않으면 새로 고침 컨트롤이 멈추지 않게 됩니다.)

    //새로고침 함수
    @objc func handleRefresh(_ refreshControl: UIRefreshControl) {
    	//새로고침 시 갱신되어야 할 내용
            groupList(token: UserDefaults.standard.string(forKey: "accesstoken")!)
            checkMyGroup(UserDefaults.standard.string(forKey: "accesstoken")!)
    
            //당겨서 새로고침 종료
            refreshControl.endRefreshing()
        }

    2. Custom TabBar

    UITabBarController에 가운데 카메라 버튼을 코드로 만들어서 addSubView 하는 방식으로 만들어줬습니다.

       var cameraButton: UIButton = {
    	//버튼의 객체 생성
            let button = UIButton()
    
    	//버튼에 이미지를 넣어줍니다.
            button.setBackgroundImage(UIImage(named:"navItemCamera"), for: .normal)
    
    	//생성한 버튼의 이벤트를 지정해줍니다.
            button.addTarget(self, action: #selector(TabBarVC.buttonClicked(sender:)), for: .touchUpInside)
    
            return button
        }()

    탭바 컨트롤러의 기본 설정대로 하게 되면, 탭바 아이콘들이 왼쪽의 사진과 같이 가운데로 쏠려보인다는 것을 볼 수 있다!

    따라서 UIEdgeInsets로 이미지의 인셋을 조정해줍니다.

    func setTabBar() {
            //탭바 설정
           let homeStoryboard = UIStoryboard.init(name: "Home", bundle: nil)
            
            guard let homeVC = homeStoryboard.instantiateViewController(identifier: "HomeNavigationController") as? HomeNavigationController else {
                return
            }
            
            let groupStoryboard = UIStoryboard.init(name: "GroupList", bundle: nil)
            guard let groupVC = groupStoryboard.instantiateViewController(identifier: "GroupListNavigationController") as? GroupListNavigationController else {
                return
            }
            //탭바 아이템 이미지 인셋 조정
            homeVC.tabBarItem.imageInsets = UIEdgeInsets(top: 0, left: -20, bottom: -5, right: 0)
            homeVC.tabBarItem.image = UIImage(named: "tabBarHomeIcInactive")
            homeVC.tabBarItem.selectedImage = UIImage(named: "tabBarHomeIcActive")
            homeVC.title = ""
            
            groupVC.tabBarItem.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: -5, right: -20)
            groupVC.tabBarItem.image = UIImage(named: "tabBarGroupIcInactive")
            groupVC.tabBarItem.selectedImage = UIImage(named: "tabBarGroupIcActive")
            groupVC.title = ""
            
            setViewControllers([homeVC, groupVC], animated: true)
    }

    카메라 버튼의 크기와 위치를 정해주고, 탭바에 addSubView 해줍니다.

    func setTabBar() {
    	//카메라 버튼의 크기와 위치를 선정해줍니다.
            let width: CGFloat = 70/375 * self.view.frame.width
            let height: CGFloat = 70/375 * self.view.frame.width
            
            let posX: CGFloat = self.view.frame.width/2 - width/2
            let posY: CGFloat = -32
            
            cameraButton.frame = CGRect(x: posX, y: posY, width: width, height: height)
            
    	//만들어준 카메라 버튼을 탭바에 추가해줍니다.
            tabBar.addSubview(self.cameraButton)
    }
    				

    📚 Meaning Extension

    1. Toast Alert Extension

    textField에 입력된 값이 존재하거나 올바르지 않은 경우, 미션 수행 순서가 올바르지 못 할 경우, 사용자에게 알림을 주는 토스트 팝업을 extension 으로 만들어서 사용했습니다.

    // MARK: Toast Alert Extension
    
      // 사용법: showToast(message : "원하는 메세지 내용", font: UIFont.spoqaRegular(size: 15), width: 188, bottomY: 181)
        
     func showToast(message : String, font: UIFont, width: Int, bottomY: Int) {
            let guide = view.safeAreaInsets.bottom
            let y = self.view.frame.size.height-guide
            
    	//토스트 라벨의 크기와 위치를 선정해줍니다.
            let toastLabel = UILabel(
                frame: CGRect( x: self.view.frame.size.width/2 - CGFloat(width)/2,
                               y: y-CGFloat(bottomY),
                               width: CGFloat(width),
                               height: 30
                )
            )
            
            toastLabel.backgroundColor = UIColor.gray4
            toastLabel.textColor = UIColor.gray6
            toastLabel.font = font
            toastLabel.textAlignment = .center
            toastLabel.text = message
            toastLabel.alpha = 1.0
            toastLabel.layer.cornerRadius = 6
            toastLabel.clipsToBounds  =  true
    
    	//뷰에 토스트 라벨을 추가시켜줍니다.
            self.view.addSubview(toastLabel)
    
    	//애니메이션을 설정해줍니다.
            UIView.animate(withDuration: 3.0, delay: 0.1, options: .curveEaseOut, animations: {
                toastLabel.alpha = 0.0
            }, completion: {(isCompleted) in
                toastLabel.removeFromSuperview()
            })
        }

    현재 UIView의 애니메이션 옵션을 curveEaseOut 으로 설정해뒀는데, 이는 빠르게 진행됬다가 완료됬을때 천천히 진행되는 애니메이션 효과입니다.

    이와 같이 애니메이션을 설정할 수 있는 옵션으로는 curveEaseInOut, curveEaseIn, curveEaseOut 가 있습니다.

    static var curveEaseInOut: UIView.AnimationOptions

    • 기본값
    • 천천히 진행됬다가 duration의 중간쯤에 빨라지고, 완료되기 전에 다시 천천히 진행되는 옵션

    static var curveEaseIn: UIView.AnimationOptions

    • 애니메이션이 느리게 시작된 다음, 진행에 따라 애니메이션 속도가 빨라짐.

    static var curveEaseOut: UIView.AnimationOptions

    • 애니메이션이 빠르게 시작되고 완료 될 쯤 느려짐.

    2. timeAgoSince Extension

    마이피드와 그룹피드의 게시물 작성 시간이 현재로부터 얼마 전인지 표시해주는 extension입니다.

    사용방법은 다음과 같습니다.

    var createTime = "2021-01-13 14:00:00"
    
    createTime.StringToDate().timeAgoSince()
    // 1. createTime을 StringToDate를 통해 String타입에서 Date 타입으로 바꿔줌
    // 2. timeAgoSince를 통해 이 시간이 현재 시간을 기준으로 얼마전인지 구해주기

    자세히 알아보기 이전에, 날짜 계산에 필요한 NSCalendar 에 대해 알아보도록 하겠습니다!

    쉽게 말해서 NSCalendar 객체는 실질적인 날짜 계산을 수행하는 클래스입니다.

    달력을 이용해서 특정 시점을 날짜 단위로 변경하면 이 날짜는 여러 구성 요소로 나뉘어 년, 월, 일, 요일, 몇 째 주인지 등의 정보가 나오게 됩니다. 이러한 정보를 모아서 표시할 수 있도록 해주는 객체가 components 입니다.

    날짜 구성 요소로 지정된 시작 날짜와 종료 날짜의 차이를 반환하는 components 관련 메소드를 알아보겠습니다.

    func components(_ unitFlags: NSCalendar.Unit, 
               from startingDateComp: DateComponents, 
                 to resultDateComp: DateComponents, 
            options: NSCalendar.Options = []) -> DateComponents

    각 파라미터를 살펴보면, 다음과 같습니다.

    unitFlags : 반환 된 NSDateComponents 개체의 구성 요소를 지정합니다.

    startingDateComp : NSDateComponents 개체로 계산의 시작 날짜입니다.

    resultDateComp : NSDateComponents 개체로 계산의 종료 날짜입니다.

    option : 옵션 매개 변수는 현재 사용되지 않습니다.

    이러한 components 메소드를 바탕으로 게시물의 작성 시간이 현재보다 얼마 전인지 계산할 수 있습니다.

    func timeAgoSince() -> String {
    		        //유저의 캘린더에서 현재시점을 가져옵니다.
                let calendar = Calendar.current
    
    						//date를 string으로 바꾸고, string타입을 date타입으로 바꿔줍니다.
                let now = Date().datePickerToString().stringToDate()
    
    						//연도, 월, 일 및 시간과 같은 달력 단위를 식별해서 넣어줍니다.
                let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfYear, .month, .year]
    						
    						//게시물 작성날짜와 현재 날짜의 차이를 날짜 구성 요소로 반환합니다.
                let components = (calendar as NSCalendar).components(unitFlags, from: self, to: now, options: [])
    
                if let year = components.year, year >= 1 {
                    return "\(year)년 전"
                }
                        
                if let month = components.month, month >= 1 {
                    return "\(month)달 전"
                }
                
                if let week = components.weekOfYear, week >= 1 {
                    return "\(week)주 전"
                }
                        
                if let day = components.day, day >= 1 {
                    return "\(day)일 전"
                }
                
                if let hour = components.hour, hour >= 1 {
                    return "\(hour)시간 전"
                }
                
                if let minute = components.minute, minute >= 1 {
                    return "\(minute)분 전"
                }
                
                if let second = components.second, second >= 3 {
                    return "\(second)초 전"
                }
                
                return "지금"
            }

    👉 About Us


    “미닝의 iOS 개발자들은 코드리뷰와 효율적인 협업으로 함께 성하는 앱개발을 지향합니다.”


    민희 민승 세은
    contact : xwoud@naver.com
    github: xwoud
    contact : seonminseung@naver.com
    github: MinseungSeon
    contact : hotpigtomato@gmail.com
    github: pk33n
    타임스탬프, 홈 화면 담당 스플래시 및 로그인, 미션 화면 담당 그룹 및 커스텀 탭바 담당
    Visit original content creator repository https://github.com/nneaning/meaning_iOS
  • Virtual-Garmin-Pet

    Virtual-Garmin-Pet

    Garmin Watch with animated pet that fits any size watch!

    FULL WORKING PROGRAM for VISUAL STUDIO CODE

    Contents: Description:
    program Jungle and Manifest
    Source .MC files with Monkey C code
    Fonts Handdrawn Fonts and Icons using MBFont
    Layouts Layout of Text loaded with .REZ
    Drawables Art not included, .XML for format .DC
    Images Image Files for Readme

    Developer: Sarah Bass

    DESCRIPTION:

    Feed a virtual pet as you earning steps!

    Every day this neat watch face will randomly generate an animated pet with new colors and new shapes. The file size for this watch is very small, because everything is calculated in memory based on mathematics using your watch size , watch settings, day of the week, date, minute, and seconds data. SEE VIDEO BELOW

    INCLUDES:

    Military and Standard Time

    Full Date

    Steps

    Calories

    Heart

    Weather

    Temperature in C or F

    Notifications

    Alarms

    Battery

    Phone Connection

    Demo.Garmin.MOV

    — DEVELOPER NOTE —

    I do not give permission to publish this identical watch to Garmin IQ Store. However, you can take a look at the code and use for learning purposes only.

    To make it work, you will need the Monkey C extension, the Garmin IQ simulator downloaded, and you will need to generate your own Garmin Key and UUID. Garmin Resources for Set Up

    Visit original content creator repository https://github.com/SarahBass/Virtual-Garmin-Pet
  • Ujian_Back_End_JC07

    Soal Ujian Purwadhika Back-End Dev

    Lintang_Purwadhika

    Materi Back End Web Dev dapat diakses di klik sini!

    Soal 1 – MySQL Database

    Tuliskan langkah-langkah/urutan query MySQL untuk membuat sebuah database “sekolahku” yang memiliki beberapa tabel: “users”, “courses” dan “userCourse”, dengan ketentuan di bawah ini:

    1. Buatlah tabel “users” yang memiliki struktur/model sebagai berikut. Tabel “users” merupakan kumpulan data peserta didik di sebuah sekolah.

      Soal_1.a

      Masukkan beberapa data berikut ke dalam tabel “users”. Hasil yang diharapkan adalah:

      Soal_1.b

    2. Buatlah tabel “courses” yang memiliki struktur/model sebagai berikut. Tabel “courses” merupakan kumpulan data mata kuliah yang diajarkan di sebuah sekolah.

      Soal_2.a

      Masukkan beberapa data berikut ke dalam tabel “courses”. Hasil yang diharapkan adalah:

      Soal_2.b

    3. Buatlah tabel “userCourse” yang memiliki struktur/model sebagai berikut. Tabel “userCourse” merupakan tabel penghubung/transaksi antara tabel “user” & “courses”.

      Soal_3.a

      Masukkan beberapa data berikut ke dalam tabel “userCourse”. Hasil yang diharapkan adalah:

      Soal_3.b

    4. Dari tabel “users”, “courses” dan “userCourse”, tampilkan semua daftar peserta didik beserta mata kuliah yang diikutinya, lengkap dengan nama & gelar mentornya. Hasil yang diharapkan adalah sebagai berikut:

      Soal_4

    5. Dari tabel “users”, “courses” dan “userCourse”, tampilkan daftar peserta didik beserta mata kuliah yang diikutinya, yang mentornya bergelar sarjana. Hasil yang diharapkan adalah sebagai berikut:

      Soal_5

    6. Dari tabel “users”, “courses” dan “userCourse”, tampilkan daftar peserta didik beserta mata kuliah yang diikutinya, yang mentornya bergelar selain sarjana. Hasil yang diharapkan adalah sebagai berikut:

      Soal_6

    7. Dari tabel “users”, “courses” dan “userCourse”, tampilkan jumlah peserta didik untuk setiap mata kuliah. Hasil yang diharapkan adalah sebagai berikut:

      Soal_7

    8. Dari tabel “users”, “courses” dan “userCourse”, tampilkan jumlah peserta didik beserta total fee untuk setiap mentor. Total fee dihitung dengan besaran Rp 2.000.000,- per peserta didik. Hasil yang diharapkan adalah sebagai berikut:

      Soal_8

    Catatan: Soal ini hanya meminta Anda untuk menuliskan langkah-langkah/urutan query MySQL sesuai spesifikasi di atas. Ketik jawaban dalam sebuah file .txt & lampirkan via email lintang@purwadhika.com!

    Soal 2 – Express & MongoDB

    Buatlah sebuah project back-end NodeJS (Express.js) sederhana yang mampu mengakses database MongoDB (gunakan Mongoose!), dengan spesifikasi route sebagai berikut:

    • POST /data → tanpa mengirimkan data via body request, akan memasukkan data ke collection “data” di database “dataCPU”. Data yang tersimpan adalah data seputar sistem operasi yang digunakan user, mencakup: nama CPU, tipe OS, platform OS, versi rilis OS, RAM tersisa dan RAM total. Sekali lagi, data yang akan disimpan tidak perlu dideklarasikan di body request!

      POST /data

    • GET /data → akan memberikan response: menampilkan semua data dari collection “data” di database “dataCPU”. Data satuan yang ditampilkan diharapkan sebagai berikut:

      {   
          _id: 5b453fb83de88413bc523928,
          namacpu: 'Lintang_CPU',
          tipe: 'Windows_NT',
          platform: 'win32',
          rilis: '10.0.17134',
          ramSisa: 11338039296,
          ramTotal: 17063497728
      }
    • Gunakan Express Router untuk memisahkan code route ke MongoDB dengan code utama project.

    Catatan: Upload source code project ke repo Github Anda, kemudian lampirkan url link repo Github Anda via email: lintang@purwadhika.com!

    Soal 3 – Express & MySQL

    Buatlah sebuah project back-end NodeJS (Express.js) sederhana yang mampu melakukan proses autentikasi (signup & login) dengan mengakses tabel “users” di database “sekolahku” (dari soal nomor 1), dengan spesifikasi route sebagai berikut:

    • POST /signup → akan melakukan proses signup: memasukkan data user baru ke tabel “users” di database “sekolahku”. Response yang diberikan setelah request memasukkan data sukses dilakukan adalah sebagai berikut:

      {
          "username": "Lintang",
          "email": "lintang@purwadhika.com",
          "status": "Signup sukses"
      }
    • POST /login → akan melakukan proses login. Client dapat melakukan proses login cukup dengan mengirimkan “username” ATAU “email” saja, beserta “password”-nya. Response yang diberikan setelah request login sukses dilakukan adalah sebagai berikut:

      {
          "login": "ok",
          "status": "Login sukses"
      }

      Jika “username” dan/atau “email” untuk login tidak terdaftar di tabel “users”, maka proses login gagal & response yang ditampilkan sebagai berikut:

      {
          "login": "failed",
          "status": "Akun tidak terdaftar"
      }

      Jika “username” dan/atau “email” untuk login sudah terdaftar di tabel “users”, namun “password” yang dimasukkan salah, maka proses login gagal & response yang ditampilkan sebagai berikut:

      {
          "login": "failed",
          "status": "Password salah"
      }
    • Gunakan Express Router untuk memisahkan code route autentikasi ke MySQL dengan code utama project.

    Catatan: Upload source code project ke repo Github Anda, kemudian lampirkan url link repo Github Anda via email: lintang@purwadhika.com!

    #HappyCoding ☺️

    Lintang Wisesa 💌 lintangwisesa@ymail.com

    Facebook | Twitter | Google+ | Youtube | :octocat: GitHub | Hackster

    Visit original content creator repository https://github.com/LintangWisesa/Ujian_Back_End_JC07
  • Ujian_Back_End_JC07

    Soal Ujian Purwadhika Back-End Dev

    Lintang_Purwadhika

    Materi Back End Web Dev dapat diakses di klik sini!

    Soal 1 – MySQL Database

    Tuliskan langkah-langkah/urutan query MySQL untuk membuat sebuah database “sekolahku” yang memiliki beberapa tabel: “users”, “courses” dan “userCourse”, dengan ketentuan di bawah ini:

    1. Buatlah tabel “users” yang memiliki struktur/model sebagai berikut. Tabel “users” merupakan kumpulan data peserta didik di sebuah sekolah.

      Soal_1.a

      Masukkan beberapa data berikut ke dalam tabel “users”. Hasil yang diharapkan adalah:

      Soal_1.b

    2. Buatlah tabel “courses” yang memiliki struktur/model sebagai berikut. Tabel “courses” merupakan kumpulan data mata kuliah yang diajarkan di sebuah sekolah.

      Soal_2.a

      Masukkan beberapa data berikut ke dalam tabel “courses”. Hasil yang diharapkan adalah:

      Soal_2.b

    3. Buatlah tabel “userCourse” yang memiliki struktur/model sebagai berikut. Tabel “userCourse” merupakan tabel penghubung/transaksi antara tabel “user” & “courses”.

      Soal_3.a

      Masukkan beberapa data berikut ke dalam tabel “userCourse”. Hasil yang diharapkan adalah:

      Soal_3.b

    4. Dari tabel “users”, “courses” dan “userCourse”, tampilkan semua daftar peserta didik beserta mata kuliah yang diikutinya, lengkap dengan nama & gelar mentornya. Hasil yang diharapkan adalah sebagai berikut:

      Soal_4

    5. Dari tabel “users”, “courses” dan “userCourse”, tampilkan daftar peserta didik beserta mata kuliah yang diikutinya, yang mentornya bergelar sarjana. Hasil yang diharapkan adalah sebagai berikut:

      Soal_5

    6. Dari tabel “users”, “courses” dan “userCourse”, tampilkan daftar peserta didik beserta mata kuliah yang diikutinya, yang mentornya bergelar selain sarjana. Hasil yang diharapkan adalah sebagai berikut:

      Soal_6

    7. Dari tabel “users”, “courses” dan “userCourse”, tampilkan jumlah peserta didik untuk setiap mata kuliah. Hasil yang diharapkan adalah sebagai berikut:

      Soal_7

    8. Dari tabel “users”, “courses” dan “userCourse”, tampilkan jumlah peserta didik beserta total fee untuk setiap mentor. Total fee dihitung dengan besaran Rp 2.000.000,- per peserta didik. Hasil yang diharapkan adalah sebagai berikut:

      Soal_8

    Catatan: Soal ini hanya meminta Anda untuk menuliskan langkah-langkah/urutan query MySQL sesuai spesifikasi di atas. Ketik jawaban dalam sebuah file .txt & lampirkan via email lintang@purwadhika.com!

    Soal 2 – Express & MongoDB

    Buatlah sebuah project back-end NodeJS (Express.js) sederhana yang mampu mengakses database MongoDB (gunakan Mongoose!), dengan spesifikasi route sebagai berikut:

    • POST /data → tanpa mengirimkan data via body request, akan memasukkan data ke collection “data” di database “dataCPU”. Data yang tersimpan adalah data seputar sistem operasi yang digunakan user, mencakup: nama CPU, tipe OS, platform OS, versi rilis OS, RAM tersisa dan RAM total. Sekali lagi, data yang akan disimpan tidak perlu dideklarasikan di body request!

      POST /data

    • GET /data → akan memberikan response: menampilkan semua data dari collection “data” di database “dataCPU”. Data satuan yang ditampilkan diharapkan sebagai berikut:

      {   
          _id: 5b453fb83de88413bc523928,
          namacpu: 'Lintang_CPU',
          tipe: 'Windows_NT',
          platform: 'win32',
          rilis: '10.0.17134',
          ramSisa: 11338039296,
          ramTotal: 17063497728
      }
    • Gunakan Express Router untuk memisahkan code route ke MongoDB dengan code utama project.

    Catatan: Upload source code project ke repo Github Anda, kemudian lampirkan url link repo Github Anda via email: lintang@purwadhika.com!

    Soal 3 – Express & MySQL

    Buatlah sebuah project back-end NodeJS (Express.js) sederhana yang mampu melakukan proses autentikasi (signup & login) dengan mengakses tabel “users” di database “sekolahku” (dari soal nomor 1), dengan spesifikasi route sebagai berikut:

    • POST /signup → akan melakukan proses signup: memasukkan data user baru ke tabel “users” di database “sekolahku”. Response yang diberikan setelah request memasukkan data sukses dilakukan adalah sebagai berikut:

      {
          "username": "Lintang",
          "email": "lintang@purwadhika.com",
          "status": "Signup sukses"
      }
    • POST /login → akan melakukan proses login. Client dapat melakukan proses login cukup dengan mengirimkan “username” ATAU “email” saja, beserta “password”-nya. Response yang diberikan setelah request login sukses dilakukan adalah sebagai berikut:

      {
          "login": "ok",
          "status": "Login sukses"
      }

      Jika “username” dan/atau “email” untuk login tidak terdaftar di tabel “users”, maka proses login gagal & response yang ditampilkan sebagai berikut:

      {
          "login": "failed",
          "status": "Akun tidak terdaftar"
      }

      Jika “username” dan/atau “email” untuk login sudah terdaftar di tabel “users”, namun “password” yang dimasukkan salah, maka proses login gagal & response yang ditampilkan sebagai berikut:

      {
          "login": "failed",
          "status": "Password salah"
      }
    • Gunakan Express Router untuk memisahkan code route autentikasi ke MySQL dengan code utama project.

    Catatan: Upload source code project ke repo Github Anda, kemudian lampirkan url link repo Github Anda via email: lintang@purwadhika.com!

    #HappyCoding ☺️

    Lintang Wisesa 💌 lintangwisesa@ymail.com

    Facebook | Twitter | Google+ | Youtube | :octocat: GitHub | Hackster

    Visit original content creator repository https://github.com/LintangWisesa/Ujian_Back_End_JC07
  • Llama_3.1_8b_Fine_Tuning

    Llama 3.1: Fine-Tuning for Hex Color Code Descriptions

    This project demonstrates fine-tuning the Llama 3.1 language model with 8 billion parameters using techniques such as PEFT (Parameter-Efficient Fine-Tuning) and LoRA (Low-Rank Adaptation). The model has been trained to generate descriptions for hex color codes.

    Project Overview

    • Model: Llama 3.1 (8B parameters)
    • Task: Generating human-like descriptions for hex color codes
    • Dataset: Color Dataset on Hugging Face
    • Hardware: Trained on a T4 GPU via Google Colab Notebook

    Run on Google Colab

    You can run the project directly in your browser on Google Colab by clicking the link below:

    Open in Colab

    This will open the Jupyter Notebook in Google Colab, where you can fine-tune the model or run inference to generate color descriptions based on hex codes.

    Results

    The model was fine-tuned for 60 steps due to limited resources on Google Colab, using a T4 GPU. Despite the limited training time, the model demonstrated a promising ability to generate descriptive and creative outputs for various hex color codes. Further training could improve performance and generalization.

    Acknowledgments

    • Special thanks to the Color Dataset on Hugging Face for making the dataset available.
    • Thanks to the Hugging Face team for the amazing Llama models.
    • A big thank to Unsloth, the wonderful library that greatly facilitated the fine-tuning process.
    Visit original content creator repository https://github.com/Akshat122/Llama_3.1_8b_Fine_Tuning
  • nestjs-boilerplate-1

    Nestjs Boilerplate 1

    Features

    • Completely written in Typescript
    • Nestjs Nodejs framework
    • Postgres Powerful, open source object-relational database
    • MongoDB A document database designed for ease of application development and scaling.
    • TypeORM ORM
    • Mongoose MongoDB object modeling

    RUN

    • clone repo
    • run yarn install
    • run mv .env.example .env
    • update the .env with local variables
    • run yarn run start:dev

    Creating migration file

    • run yarn run new:migration <name of table/migration>
    • new migration file will be created in src/migrations
    • edit the file to satisfaction
    • run yarn run migrate to migrate the table into the db

    Testing and Commits

    Unit test and E2E test are at play in this repo, make sure you have a
    separate postgres db setup for test, majorly of E2E. Your test db will be deleted and created every time your do yarn run test.

    There are two pre-commit hooks,

    1. yarn run test => to make sure all your test pass
    2. yarn run lint => to make sure your code pass linting

    Make sure that your test db is separate from the development db else your data will be cleared.
    NOTE: Before testing, create test migration by running yarn run migrate:test

    Visit original content creator repository
    https://github.com/OjerIsaac/nestjs-boilerplate-1

  • nestjs-boilerplate-1

    Nestjs Boilerplate 1

    Features

    • Completely written in Typescript
    • Nestjs Nodejs framework
    • Postgres Powerful, open source object-relational database
    • MongoDB A document database designed for ease of application development and scaling.
    • TypeORM ORM
    • Mongoose MongoDB object modeling

    RUN

    • clone repo
    • run yarn install
    • run mv .env.example .env
    • update the .env with local variables
    • run yarn run start:dev

    Creating migration file

    • run yarn run new:migration <name of table/migration>
    • new migration file will be created in src/migrations
    • edit the file to satisfaction
    • run yarn run migrate to migrate the table into the db

    Testing and Commits

    Unit test and E2E test are at play in this repo, make sure you have a
    separate postgres db setup for test, majorly of E2E. Your test db will be deleted and created every time your do yarn run test.

    There are two pre-commit hooks,

    1. yarn run test => to make sure all your test pass
    2. yarn run lint => to make sure your code pass linting

    Make sure that your test db is separate from the development db else your data will be cleared.
    NOTE: Before testing, create test migration by running yarn run migrate:test

    Visit original content creator repository
    https://github.com/OjerIsaac/nestjs-boilerplate-1