Skip to content

Commit eab8542

Browse files
jungseokyoung-cloudaround-forestLURKS02051198Hz
authored
v.0.5.0 (#53)
* docs/#feature1 (#5) * [DOCS]: SwiftLint 추가 * [CHORE]: xcodeproject파일 의존성 수정 및 SwiftLint 스크립트 파일 추가 * [FIX]: 바이너리 파일 생성안되는 버그 수정 * [CHORE]: Implementation, Interface 분리 * [DOCS]: lint 오타 수정 * [DOCS]: Interface에 Script파일 추가 * Update README.md [DOCS] README.md 수정 * [FEAT]: P2P Socket을 통해 유저 연결 혹은 끊김 정보를 제공하는 로직 구현 (#8) * [CHORE]: SocketProvider에서 P2PSocket으로 모듈 이름 변경 * [FEAT]: P2PSocket에 외부로 리턴해주는 SocketPeer객체 구현 * [CHORE]: Info에 Bonjour Service관련 plist 추가 * [FEAT]: P2PSocket에 MCPeerID들을 저장하는 Storage 구현 * [FEAT]: P2PSocket을 할 수 있는 Provider 구현 * [CHORE]: Entity 모듈 추가 * [FEAT]: Domain에 BrowsingUser Entity 추가 * [FEAT]: BrowsingUserRepository 인터페이스 구현 * [FEAT]: BrowsingUserRepository 구현 * [REFACTOR]: SockerProvider, Repository CurrentValueSubject에서 PassThroughSubject로 변경 * [FEAT]: BrowsingUser 유즈케이스 인터페이스 구현 * [FEAT]: BrowsingUser 유즈케이스 구현 * [CHORE]: Domain, DomainInterface에서 UseCase, UseCaseInterface로 모듈이름 변경 * [CHORE]: 기존 DataInterface 위치 Data에서 Domain으로 변경 * [STYLE]: SocketPeer 프로퍼티 순서 변경 * [TEST]: Mocking 파일 추가(SocketProvider, Repository, UseCase) * [TEST]: BrowsingUserUseCase 테스트 코드 작성 * [REFACTOR]: 객체 잘못 잘못적용 되었던 것 수정 * [STYLE]: BrowsingUser에서 BrowsedUse네이밍으로 변경 * [REFACT]: 동기로 오는 이벤트이기에 Expectation 삭제 * [FEAT]: UIColor Extension에 UIColor를 Hex 문자열로 생성할 수 있게 해주는 초기화자 구현 * [FEAT]: UIControl 이벤트를 Publisher로 받을 수 있게 해주는 EventPublisher 구현 * [FEAT] AnyPublisher와 value를 제공하는 ReadOnlyPublisher 구현 * [FEAT] 비디오 리스트를 보여줄 때 사용하는 Presentation Model 구현 * [FEAT]: 비디오의 duration을 보여주는 커스텀 뷰인 DurationView 구현 * [FEAT]: 오른쪽에 아이콘이 있는 커스텀 iconButton 구현 * [FEAT]: 비디오 리스트의 불러온 정보와 추가 버튼을 보여주는 VideoListHeaderView 구현 * [FEAT]: 비디오 리스트 화면에 연결되는 ViewModel과 구현체 구현 * [FEAT]: 비디오 리스트에서 비디오 썸네일과 정보를 보여줄 때 사용되는 컬렉션 뷰 셀 구현 * [FEAT]: 비디오 리스트를 보여주는 화면인 VideoListViewController 구현 * [FEAT]: 비디오를 클릭했을 때 상세 화면을 보여주기 위한 임시 VideoDetailViewController 구현 * [CHORE]: 기능 구현으로 인한 파일 구조 변경 사항 (pbxproj) * [REFACTOR]: UI Component 설정을 클로저로 하지 않고 setup에서 진행하도록 변경 * [REFACTOR]: import 순서를 사전순으로 정렬하도록 변경 * [CHORE]: DiffableDataSource 적용을 위해서 PresentationModel 네이밍 대신 Item으로 변경 - CollectoinView 관련 파일을 따로 CollectionView로 폴더링하여 정리 * [REFACTOR]: 기존 DataSource 대신 DiffableDataSource를 사용하도록 변경 * [TEST]: 테스트를 위해 Mock 비디오 파일을 추가하는 동작 구현 * [CHORE]: 기능 구현으로 인한 파일 구조 변경 사항 (pbxproj) * [Feature/#2] 참여자의 상태를 표시할 수 있는 기능 구현 (#6) * [DOCS]: 더이상 swiftlint 실행파일을 추적하지 않음 * [FIX]: git 캐시파일로 인해 추적하지 않는 파일을 추적하는 버그 * [Chore]: 사용하지 않는 코드 삭제 * [Feat]: 그룹의 정보를 보여주는 뷰의 행동을 추상화한 GroupInfoViewModel 구현 * [Feat]: 그룹 정보를 보여주는 뷰 GroupInfoViewController 구현 * [Feat]: 그룹에 참여한 유저의 정보를 보여주는 뷰 구현 * [Feat]: 그룹의 참가자 수를 보여주는 뷰 구현 * [Chore]: 파일 추가로 인한 프로젝트 변경사항 적용 * [Chore]: 파일 및 폴더 구조 변경 * [Refactor]: 사용하지 않는 코드 삭제 * [Refactor]: 시용하지 않는 파일 삭제 * [Test]: 테스트를 위한 임시 프로토콜 구현 * [TEST]: 코드 동작을 위한 임시 구현체 구현 * [TEST]: 테스트 가능한 코드를 위한 임시 의존성 추가 * [CHORE]: 테스트를 위한 타겟 추가 * [TEST]: 그룹에 유저가 참가할 때, 유저의 연결 상태가 변할 때 올바른 동작을 검증하는 테스트 코드 작성 * [FIX]: 참가자수가 3으로 고정돼있는 버그 * [REFACTOR]: GroupInfoViewController가 데이터에 관한 프로퍼티를 갖지 않도록 수정 * [REFACTOR]: 생성자를 통해 뷰모델을 주입받을 수 있도록 수정 * [REFACTOR]: 프로퍼티 제거에 따른 코드 수정 * [FIX]: 바인딩 코드를 호출하기 전에 이벤트를 전달하고 있는 버그 * [REFACTOR]: 유스케이스를 주입받을 수 있는 생성자 추가 및 접근제어자 변경 * [REFACTOR]: 싱크홀 코드 수정 * [FIX]: 새 유저가 그룹에 참가하여도 그룹 참여자수가 변하지 않는 버그 * [FIX]: Feature 외 레이어 변경사항 제거 * [REFACTOR]: 뷰모델이 UI관련 로직만 갖도록 수정 * [CHORE]: 파일 제거 및 이동에 의한 프로젝트 변경사항 * [FIX]: 테스크 코드 삭제 * [REFACTOR]: 사용하지 않는 의존성 제거 * [REFACTOR]: 프레젠테이션 모델이 상태를 변경하지 않도록 수정 * [CHORE]: Domain 변경사항 제거 * [REFACTOR]: 사용하지 않는 의존성 제거 * [Feature]: 여러 참여자를 보여줄 수 있는 스크롤뷰 및 그룹 나가기 버튼 추가 * [REFACTOR]: Nib파일로부터 생성하지 못하도록 수정 * [REFACTOR]:성능 개선을 위한 접근제한자 수정 * [STYLE]: 뷰의 색상을 Figma와 같도록 수정 * [REFACTOR]: 필요하지 않은 프로토콜의 코드 제거 * [FIX]: 뷰컨트롤러가 나타날 때 마다 뷰 추가, 레이아웃 작업을 실행하는 버그 * [SYTLE]: 컨벤션 일치 * [REFACTOR]: DiffableDataSource 사용 시 String 대신 Section 타입으로 섹션을 표현하도록 변경 * [CHORE]: 테스트와 모델에 MARK 및 설명 주석 추가 * [STYLE]: 배열을 나열하는 코드를 코드 컨벤션에 맞게 수정 * [REFACTOR]: DurationView를 별도의 뷰로 빼지 않고 뷰 컨트롤러에서 생성하도록 변경 * [CHORE]: 변경 사항으로 인한 파일 구조 변경 (pbxproj) * [Feature/#11] 참여자의 상태 및 참가자수를 리턴하는 기능 추가 (#12) * [FEAT]: ConnectedUser Entity 추가 * [FEAT]: Browsing 기능을 기준으로 socket에 참가한 유저를 리턴하는 기능 구현 * [CHORE]: 오타 수정 * [Feature/#4] 레이더 형태로 주변 유저 목록을 표시하는 UI 설계 (#15) * [CHORE]: closed branch 내용 재구현 * [FEAT]: Interface와 Entity import 및 BrowsingUser를 BrowsedUser로 수정 * [CHORE]: swiftlint 경고 수정 * [FEAT]: ViewModel이 UIKit를 모르도록 수정 * [FEAT]: ViewModel의 transform 메서드 코드 단순화 * [FEAT]: ViewModel의 found와 lost 메서드의 파라미터를 명시적으로 수정 및 코드 단순화 * [FEAT]: Input-Output 관련 제네릭 제거 * [CHORE]: Markdown 추가 * [STYLE]: State에 주석 추가 * [FEAT]: SocketProvider에 초대 및 수락/거절 로직 구현 * [FEAT]: BrowsingRepository에 초대 및 수락/ 거절 로직 구현 * [FEAT]: BrowingUseCase에 초대 및 수락/ 거절 로직 구현 * [FEAT]: BrowingUseCase 초대했을 때, 다른 유저의 초대 모두 거절하도록 구현 * [FEAT]: 초대 받는 유저가 초대 받는 동안, 다른 초대 중첩되지 않도록 하는 기능 추가 * [FEAT]: 초대받는 사람, 도중에 초대 중첩되는 경우 적절한 이벤트 전달 * [TEST]: 테스트 코드 추가 * [CHORE]: 빌드 세팅 변경 * [STYLE]: 불필요한 코드 삭제 * [REFACT]: 초대 중복시 timeout발생시키는 것이 아닌, 바로 거절하도록 변경 * [REFACT]: 불필요한 코드 삭제 및, invitationTimeout시간을 외부에서 지정할 수 있도록 수정 * [REFACT]: InvitedUser의 불필요한 이벤트 삭제 * [CHORE]: Interfaces에 P2PSocket 의존성 추가 * [REFACT]: BrowsigUserUseCase의 이니셜라이저 파라미터 변경사항 적용 * [CHORE]: FeatureInterface 모듈 삭제 * [DOCS]: Bundle Display name 시정 * [CHORE]: PR 관련 이벤트에 슬랙 알림을 보내주는 action 추가 (#32) * [CHORE]: PR 관련 이벤트에 슬랙 알림을 보내주는 action 추가 - 나를 리뷰어로 지정한 PR이 있을 때 - 내 PR에 리뷰어가 코드 리뷰를 완료했을 때 - 나를 리뷰어로 지정한 PR에 새로운 커밋이 추가되었을 때 * [CHORE]: yml 파일의 오타 수정 * [FEAT]: SocketProvider에 파일 업로드 및 다운로드 기능 추가 * [FEAT]: 공유된 파일을 나타내기 위한 타입 선언 * [FEAT]: 다운로드를 완료한 피어들의 수를 세기 위한 타입 선언 * [FEAT]: 다운로드 받은 리소스의 이름을 검증하고, 파싱하는 ResourceValidator 타입 선언 * [FEAT]: 공유된 비디오를 나타내기 위한 타입 SharedVideo 선언 * [FEAT]: 공유된 비디오의 저장소를 나타내는 프로토콜 SharingVideoRepositoryInterface 선언 * [FEAT]: 공유 비디오 저장소 구현체 SharingVideoRepository 구현 * [CHORE]: 파일 추가로 인한 프로젝트 변경사항 * [FIX]: 업/다운로드한 파일을 공유 파일 목록에 등록하지 않는 버그 * [FIX]: 스스로의 데이터를 공유할 때, UUID가 포함된 이름으로 SharedResource를 생성하는 버그 * [Feature/#17] 초대 로직 구현 (#30) * [STYLE]: State에 주석 추가 * [FEAT]: SocketProvider에 초대 및 수락/거절 로직 구현 * [FEAT]: BrowsingRepository에 초대 및 수락/ 거절 로직 구현 * [FEAT]: BrowingUseCase에 초대 및 수락/ 거절 로직 구현 * [FEAT]: BrowingUseCase 초대했을 때, 다른 유저의 초대 모두 거절하도록 구현 * [FEAT]: 초대 받는 유저가 초대 받는 동안, 다른 초대 중첩되지 않도록 하는 기능 추가 * [FEAT]: 초대받는 사람, 도중에 초대 중첩되는 경우 적절한 이벤트 전달 * [TEST]: 테스트 코드 추가 * [CHORE]: 빌드 세팅 변경 * [STYLE]: 불필요한 코드 삭제 * [REFACT]: 초대 중복시 timeout발생시키는 것이 아닌, 바로 거절하도록 변경 * [REFACT]: 불필요한 코드 삭제 및, invitationTimeout시간을 외부에서 지정할 수 있도록 수정 * [REFACT]: InvitedUser의 불필요한 이벤트 삭제 * [CHORE]: Interfaces에 P2PSocket 의존성 추가 * [REFACT]: BrowsigUserUseCase의 이니셜라이저 파라미터 변경사항 적용 * [CHORE]: FeatureInterface 모듈 삭제 * [DOCS]: Bundle Display name 시정 * [CHORE]: 파일 구조 변경 * [CHORE]: Domain에 P2PSocket의존성 삭제 * [REFACT]: Repository 인터페이스에서 Socket 관련 의존성 삭제 * [REFACTOR]: 축약할 수 있는 타입 구분자 제거 * [REFACTOR]: 코드 컨벤션과 일치하지 않는 코드 수정 * [REFACTOR] ResourceValidator인스턴스를 생성할 수 없도록 타입 변경 * [REFACTOR]: shareResource함수의 핸들러 생성 분리등을 통한 경량화 * Revert "Merge branch 'feature/#13' into develop" This reverts commit 01c1b43, reversing changes made to e120292. * [Feature/#14] 비디오 선택 및 업로드, 상세보기 화면 구현 (#33) * [REFACTOR]: ViewModel 구현을 Input enum + Output enum 구조로 수정 * [CHORE]: 뷰 구현 코드를 모아놓은 폴더 이름을 Components에서 View로 수정 * [FEAT]: Picker를 통해 선택한 비디오를 저장하기 위한 VideoManager 구현 * [FEAT]: 비디오 Picker를 통해 선택한 비디오 url을 Input으로 방출하는 로직 구현 * [FEAT]: 동영상 url로 UI를 구성하기 위해 VideoListItem에 url 추가 및 로직 작업 * [REFACTOR]: 비디오 리스트 셀 썸네일 이미지가 정상적으로 보이도록 수정 * [FEAT]: AVAsset의 메타데이터를 받아오는 계산 프로퍼티를 AVAsset Extension으로 추가 * [FEAT]: 동영상 썸네일 이미지를 생성하는 AVAsset Extension 메서드 추가 * [REFACTOR]: EventPublisher가 Void가 아닌 UIControl을 보내도록 수정 * [FEAT]: 동영상을 기반으로 item을 생성하는 실제 ViewModel 구현 - title: 파일 시스템 상에서의 파일 이름 - authorTitle: 메타데이터 기반 사용자 이름 - thumbnailImage: 동영상 썸네일 - videoURL: 동영상 url - duration: 동영상 길이 - date: 생성 날짜 * [FEAT]: 동영상을 확인할 수 있는 상세보기 화면 구현 (재생 / 정지 / 슬라이더) * [CHORE]: 변경 사항으로 인한 파일 구조 변경 (pbxproj) * [FEAT]: 저장된 비디오 파일을 모두 제거하기 위한 deleteAllFiles 메서드 추가 * [CHORE]: import 순서 알맞게 변경 * [REFACTOR]: Asset Thumbnail에서 잘못 추가되어있던 async 제거 * [STYLE]: VideoManager에서 코드 컨벤션에 맞게 개행 수정 * [REFACTOR]: 코드 리뷰 반영하여 중복되는 코드 제거 및 메서드 분리 * [CHORE]: develop에서 발생하는 충돌 해결 * [CHORE]: develop rebase로 인한 git 충돌 해결 * [CHORE]: SceneDelegate에서 테스트용 코드 제거 * Reapply "Merge branch 'feature/#13' into develop" (#38) * [Fix/#39] 깃헙핑 - 리뷰어에게 여러 번 알림이 오는 버그 수정 (#40) * Update slackPing.yml * Update slackPing.yml * [REFACTORING/#36] Socket에서 유저 상태 수정 (#41) * [REFACT]: SocketPeer에서 state connecting대신에 pending 추가 * [FEAT]: SocketProvidable 기능별로 프로토콜 분리 * [REFACT]: SocketProvider 유저 상태 수정 * [STYLE]: ConnectedUser에서 UpdatedConnectedPeer로 네이밍 수정 * [REFACT]: ConnectedUser에서 상태 수정 * [REFACT]: 유저 State별로 이벤트 적절하게 올 수 있도록 UseCase수정 * [STYLE]: 불필요한 개행 제거 * [REFACT]: ConnectedUserUseCase 메모리 이점을 위해 ID만 들고 있도록 수정 * [FIX]: 불필요한 파라미터 삭제 * [Fix/#45] 다이나믹 로딩 안되는 문제 해결 (#46) * [CHORE]: Bundle Identifier 및 Team 수정 * [FIX]: Feature에 compile Source 비워져있던 버그 수정 * [CHORE]: 불필요한 의존성 삭제 * [CHORE]: App Target에 의존성 추가 * [Feature/#19] DI Container를 구현하여 객체를 등록하고 주입한다 (#42) * [FEAT]: Dynamic Library로 Core Layer 생성 및 빌드 오류 해결 * [FEAT]: Extensions와 Utilities로 Feature의 파일 이동 및 Public 수정 * [FEAT]: DIContainer를 App Layer에 추가 * [FEAT]: DIContainer에서 의존성을 주입하는 registerDependency 메서드 구현 * [FEAT]: SceneDelegate에서 DIContainer를 사용하여 초기 VC 호출 * [CHORE]: App Layer에서 필요없는 framework 제거 * [FEAT]: ConnectionViewController의 addUserCircleView에서 코드 순서 수정 * [FEAT]: Extensions와 Utilities를 다시 Core Dynamic Library로 합치도록 수정 * [CHORE]: project 파일 내용 수정 * [CHORE]: Core에서 Feature 의존성 제거 * [CHORE]: Core의 Defines Module 세팅 수정 * [FEAT]: DIContainer를 Core로 이동 및 SceneDelegate에서 의존성 주입 * [FIX]: Bundle Identifier 수정 * [FIX]: DI의 initializer를 private로 수정 * [Feature/#35] invite 관련 세부 UI 연결 (#43) * [FEAT]: ConnectionViewModel에서 invitation 관련 코드 추가 * [FEAT]: 아무런 action이 없는 Alert 메서드 추가 * [FEAT]: ViewModel의 output에 따른 ViewController에서의 처리 로직 추가 * [FIX]: userCircleView의 center point 값이 CGPoint로 변환하는 과정에서 발생하는 소수점 문제를 반올림하여 해결 * [FIX]: Bundle Identifier 수정 * [CHORE]: project 파일 수정 * [FEAT]: accept와 reject 수정 * [CHORE]: warning 수정 * [CHORE]: App에서 core.framework embed 하도록 수정 * [CHORE]: 파일 위치 변경 * [CHORE]: Core Target 버전 수정 * [CHORE]: Compile Source 누락된거 추가 * [FIX]: 요청 응답 안되었던 버그 수정 * [FIX]: 앱 재진입시 똑같은 Peer 재발견 하는 버그 수정 * [STYLE]: 고정되는 Enum 케이스 @Frozen 어노테이션 추가 * [CHORE]: 사용하지 않는 default 제거 --------- Co-authored-by: Jung SeokYoung <thrdud0423@gmail.com> * [Feature/#47] 그룹 참여자들의 상태를 보여주는 뷰와 UseCase 연결 (#49) * [REFACTOR]: 컨벤션 일치 * [REFACTOR]: 컨벤션 일치 * [CHORE]: 뷰모델 컨벤션 일치에 따른 파일 추가사항 * [CHORE]: P2P모듈의 빌드 설정 중 팀을 codeSquad로 설정 * [REFACTOR]: 뷰모델의 In/Output을 분리 * [FEATURE]: GroupInfoViewModel과 유즈케이스를 연결 * [REFACTOR]: 뷰모델 연결에 따른 엔티티 타입 변경 적용 * [REFACTOR]: 뷰모델 연결에 따른 엔티티 타입 변경 적용 * [FEAT]: DIContainer에 connectionView관련 인스턴스 등록 * [FIX]: Pending상태 이벤트 제대로 안가는 것 오류 수정 * [FIX]: UseCase에 메서드 바인딩 안되어있던 오류 수정 * [REFACTOR]: 불필요한 print문 제거 * [REFACTOR]: 타이틀 삭제 * [FIX]: Pending상태의 유저가 Connected상태로 바뀌면 뷰가 추가되는 버그 * [CHORE]: iOS18에서 권한 요청을 명시하지 않으면 MultipeerConectivity를 사용하지 않는 변경점 대응 * [REFACTOR]: 구현내용이 없는 버튼 비활성화 * [REFACTOR]: 필요하지 않은 상태변화 반영 제거 * [FIX]: 봉주르에 등록한 서비스 네임과 실제 서비스네임이 달라 연결되지 않는 버그 --------- Co-authored-by: Jung SeokYoung <thrdud0423@gmail.com> * [Feature/#48] MainViewController를 구현하여 UI의 흐름을 연결한다 (#50) * [FEAT]: 가장 기본적인 MainViewController 구현 * [FEAT]: topViewController를 Overlay 스타일로 보이도록 수정 * [FIX]: 다음 화면에서 안보이는 관계로 다시 기본 스타일로 수정 * [FIX]: userCircleView가 위치하는 범위 수정 * [FIX]: userCircleView가 위치하는 범위 재수정 * [FEAT]: 연결이 완료되면 ConnectionView에서 서로를 표시하지 않도록 수정 * [FEAT]: Emoji를 ConnectionView와 GroupInfoView에서 공유하도록 EmojiManager 구현 * [FIX]: userCircleView가 위치하는 범위 재수정 * [FIX]: 초대 후 Waiting 상태에서 상대방의 연결 종료 시 alert dismiss 추가 * [FIX]: UserCircleView 위치 범위 재조정 * [FIX]: (Double, Double)을 CGPoint로 수정 * [FIX]: VideoListViewController에 VM 주입 수정 * [STYLE]: 코드 스타일 수정 * [FIX]: UserCircleView의 위치가 벗어나는 문제를 해결하기 위해 viewDidLayoutSubviews 수정 * [FIX]: 초대 거절 후, 다시 초대 이벤트 보낼 수 있도록 수정 * [FIX]: 초대를 받았을 때 유저 위치를 제거하던 문제 수정 * [FIX]: SceneDelegate에서 클로저를 모르게 수정 * [REFACTOR]: 불필요한 내장 사이즈 사용 코드 제거 * [STYLE]: 사용자 프로필 이모지 크기와 위치를 디자인과 일치하도록 수정 * [FIX]: VideoListVC에 VM을 DI를 통해 주입하도록 수정 * [FEAT]: id를 기준으로 userCircleView를 삭제하도록 수정 * [FEAT]: Alert를 Type으로 하여 생성하도록 수정 * [REFACTOR]: 불필요한 테스트 타입 제거 * [FIX]: Emoji 메서드를 하나로 병합 수정 * [FIX] Alert 흐름 수정 * [FIX]: Alert 흐름 수정 * [STYLE]: 린트 경고 수정 --------- Co-authored-by: Jung SeokYoung <thrdud0423@gmail.com> Co-authored-by: 051198Hz <peeper_o_o@naver.com> * [Chore/#51] Dynamic Framework Embeded 옵션 수정 및 의존성 수정 (#52) --------- Co-authored-by: Forest Lee <86788943+around-forest@users.noreply.github.com> Co-authored-by: LURKS02 <thecrooks2@gmail.com> Co-authored-by: Yune gim <82448654+051198Hz@users.noreply.github.com> Co-authored-by: 051198Hz <peeper_o_o@naver.com>
1 parent a9b49c1 commit eab8542

File tree

80 files changed

+5723
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5723
-188
lines changed

.gitIgnore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
## User settings
1010
.DS_Store
1111
xcuserdata/
12+
swiftlint
1213

1314
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
1415
*.xcscmblueprint

.github/workflows/slackPing.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: Slack Ping
2+
3+
on:
4+
pull_request:
5+
types:
6+
- review_requested
7+
- synchronize
8+
pull_request_review:
9+
types:
10+
- submitted
11+
12+
jobs:
13+
notify:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Install jq
17+
run: sudo apt-get update && sudo apt-get install -y jq
18+
19+
- name: Debug requested reviewers
20+
run: echo '${{ toJson(github.event.pull_request.requested_reviewers) }}'
21+
22+
- name: PR message
23+
env:
24+
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
25+
run: |
26+
declare -A USER_SLACK_IDS
27+
USER_SLACK_IDS["LURKS02"]="${{ secrets.SLACK_LURKS02 }}"
28+
USER_SLACK_IDS["051198Hz"]="${{ secrets.SLACK_051198HZ }}"
29+
USER_SLACK_IDS["around-forest"]="${{ secrets.SLACK_AROUNDFOREST }}"
30+
USER_SLACK_IDS["jungseokyoung-cloud"]="${{ secrets.SLACK_JUNGSEOKYOUNGCLOUD }}"
31+
32+
MESSAGE=""
33+
34+
if [[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ github.event.action }}" == "review_requested" ]]; then
35+
MESSAGE="리뷰 요청: 나를 리뷰어로 지정한 PR이 생성되었어요.\n :arrow_right: <${{ github.event.pull_request.html_url }}|PR 링크>"
36+
NEW_REVIEWER="${{ github.event.requested_reviewer.login }}"
37+
SLACK_USER_ID="${USER_SLACK_IDS[$NEW_REVIEWER]}"
38+
if [[ -n "$SLACK_USER_ID" ]]; then
39+
curl -X POST -H "Authorization: Bearer $SLACK_TOKEN" -H "Content-Type: application/json" \
40+
-d "{\"channel\":\"$SLACK_USER_ID\", \"text\":\"$MESSAGE\"}" \
41+
https://slack.com/api/chat.postMessage
42+
else
43+
echo "'$GITHUB_USER'에 해당하는 슬랙 ID를 찾지 못했습니다."
44+
fi
45+
46+
elif [[ "${{ github.event_name }}" == "pull_request_review" ]] && [[ "${{ github.event.action }}" == "submitted" ]]; then
47+
GITHUB_USER="${{ github.event.pull_request.user.login }}"
48+
SLACK_USER_ID="${USER_SLACK_IDS[$GITHUB_USER]}"
49+
REVIEW_STATE="${{ github.event.review.state }}"
50+
51+
if [[ "$REVIEW_STATE" == "approved" ]]; then
52+
STATE_MESSAGE="리뷰 완료 :white_check_mark:: PR이 승인되었어요! :partying_face:"
53+
elif [[ "$REVIEW_STATE" == "changes_requested" ]]; then
54+
STATE_MESSAGE="리뷰 완료 :warning:: PR에 변경 요청이 있어요."
55+
else
56+
STATE_MESSAGE="리뷰 완료 :speech_balloon:: PR에 코멘트가 추가되었어요."
57+
fi
58+
59+
MESSAGE="${STATE_MESSAGE}\n :arrow_right: <${{ github.event.pull_request.html_url }}|PR 링크>"
60+
61+
if [[ -n "$SLACK_USER_ID" ]]; then
62+
curl -X POST -H "Authorization: Bearer $SLACK_TOKEN" -H "Content-Type: application/json" \
63+
-d "{\"channel\":\"$SLACK_USER_ID\", \"text\":\"$MESSAGE\"}" \
64+
https://slack.com/api/chat.postMessage
65+
else
66+
echo "'$GITHUB_USER'에 해당하는 슬랙 ID를 찾지 못했습니다."
67+
fi
68+
69+
elif [[ "${{ github.event_name }}" == "pull_request" ]] && [[ "${{ github.event.action }}" == "synchronize" ]]; then
70+
HAS_REVIEWER_CHANGES=$(echo '${{ toJson(github.event.changes) }}' | jq '.requested_reviewers | length')
71+
HAS_REVIEW_STATE_CHANGES=$(echo '${{ toJson(github.event.changes) }}' | jq '.review_state | length')
72+
73+
if [[ "$HAS_REVIEWER_CHANGES" -gt 0 || "$HAS_REVIEW_STATE_CHANGES" -gt 0 ]]; then
74+
echo "리뷰어 관련 변경 또는 리뷰 상태 변경과 관련된 synchronize 이벤트입니다."
75+
else
76+
MESSAGE="커밋 추가: PR에 새로운 커밋이 추가되었어요. 리뷰 요청 사항이 반영되었는지 확인해보세요.\n :arrow_right: <${{ github.event.pull_request.html_url }}|PR 링크>"
77+
for GITHUB_USER in $(echo '${{ toJson(github.event.pull_request.requested_reviewers) }}' | jq -r '.[] | .login'); do
78+
SLACK_USER_ID="${USER_SLACK_IDS[$GITHUB_USER]}"
79+
if [[ -n "$SLACK_USER_ID" ]]; then
80+
curl -X POST -H "Authorization: Bearer $SLACK_TOKEN" -H "Content-Type: application/json" \
81+
-d "{\"channel\":\"$SLACK_USER_ID\", \"text\":\"$MESSAGE\"}" \
82+
https://slack.com/api/chat.postMessage
83+
else
84+
echo "'$GITHUB_USER'에 해당하는 슬랙 ID를 찾지 못했습니다."
85+
fi
86+
done
87+
fi
88+
fi

.swiftlint.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# 사용하지 않는 룰
2+
disabled_rules:
3+
# 라인의 마지막에는 빈 여백이 있으면 안 됨
4+
- trailing_whitespace
5+
6+
# 타입은 최대 1단계로 중첩되어야 함
7+
- nesting
8+
9+
# 함수 안은 복잡하면 안 됨 (warning: 10, error: 20)
10+
- cyclomatic_complexity
11+
12+
# Void 함수를 호출하기 위해 삼항연산자을 사용하는 것은 피해야 한다.
13+
- void_function_in_ternary
14+
15+
# 스위치 문의 케이스는 스위치와 같은 줄에 있어야 함
16+
- switch_case_alignment
17+
18+
# fileprivate보다 private을 선호함.
19+
- private_over_fileprivate
20+
21+
# TODO, FIXME 주석 제한
22+
- todo
23+
24+
analyzer_rules:
25+
# 모든 선언은 한 번 이상 사용되어야 함
26+
- unused_declaration
27+
28+
# 명시적 룰 활성화
29+
opt_in_rules:
30+
# type name은 영숫자만 포함, 대문자로 시작해 3-40자 사이어야 함
31+
- type_name
32+
33+
# delegate protocol은 class-only로 참조해 weak로 참조되도록 권장
34+
- class_delegate_protocol
35+
36+
# 닫는 괄호 ')'와 '}' 사이에는 공백이 없어야 함
37+
- closing_brace
38+
39+
# 클로저 내용과 괄호 사이에 공백이 있어야 함
40+
- closure_spacing
41+
42+
# collection elem은 vertically aligned 되어야 함
43+
- collection_alignment
44+
45+
# colon 사용 시 앞 공백 필수, 뒷 공백이 있으면 안 됨
46+
- colon
47+
48+
# comma 사용 시 앞 공백 필수, 뒷 공백이 있으면 안 됨
49+
- comma
50+
51+
# first(where:) != nil, firstIndex(where:) != nil 대신 contains 사용을 권장 (https://realm.github.io/SwiftLint/contains_over_first_not_nil.html)
52+
- contains_over_first_not_nil
53+
54+
# filter.count 사용 시 isEmpty 대신 contains 사용 권장 (https://realm.github.io/SwiftLint/contains_over_filter_is_empty.html)
55+
- contains_over_filter_is_empty
56+
57+
# filter.count가 0인지 비교할 때 contains 사용 권장 (https://realm.github.io/SwiftLint/contains_over_filter_count.html)
58+
- contains_over_filter_count
59+
60+
# range(of:) == nil 체크 대신 contains 사용 권장 (https://realm.github.io/SwiftLint/contains_over_range_nil_comparison.html)
61+
- contains_over_range_nil_comparison
62+
63+
# if, for, guard, switch, while, catch 사용 시 () 사용 권장하지 않음
64+
- control_statement
65+
66+
# deployment target 보다 낮은 버전의 @available 사용 시 warning
67+
- deployment_target
68+
69+
# 중복 import 방지
70+
- duplicate_imports
71+
72+
# count가 0인지 체크할 때는 isEmpty 사용 권장
73+
- empty_count
74+
75+
# collection, array count 체크 시 isEmpty 사용 권장
76+
- empty_collection_literal
77+
78+
# string empty 체크 시 isEmpty 사용 권장
79+
- empty_string
80+
81+
# 강제 언래핑 사용 금지
82+
- force_try
83+
84+
# array, dict 사용 시 동일한 indent로 표현
85+
- literal_expression_end_indentation
86+
87+
# let, var 선언 시 다른 statments와 한 줄 공백이 필요함
88+
- let_var_whitespace
89+
90+
# 수직 공백 3줄 이상 사용 지양
91+
- vertical_whitespace
92+
93+
# 여는 괄호 앞에서 한 줄 이상의 공백 사용 지양
94+
- vertical_whitespace_opening_braces
95+
96+
# 닫는 괄호 앞에서 한 줄 이상의 공백 사용 지양
97+
- vertical_whitespace_closing_braces
98+
99+
# delegate는 약한 참조 사용 권장
100+
- weak_delegate
101+
102+
# options
103+
empty_count:
104+
only_after_dot: true
105+
106+
force_try:
107+
severity: warning # 명시적으로 지정
108+
109+
# path
110+
excluded:
111+
- ./App/App/AppDelegate.swift
112+
- ./App/App/SceneDelegate.swift
113+
reporter: "xcode"

App/App.xcodeproj/project.pbxproj

Lines changed: 97 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,56 @@
66
objectVersion = 77;
77
objects = {
88

9+
/* Begin PBXBuildFile section */
10+
853CF5CF2CEDE80800936AD3 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 853CF5CE2CEDE80800936AD3 /* Core.framework */; };
11+
853CF5D02CEDE80B00936AD3 /* Core.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 853CF5CE2CEDE80800936AD3 /* Core.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
12+
FE8207DE2CED780200307694 /* P2PSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DB2CED780200307694 /* P2PSocket.framework */; };
13+
FE8207DF2CED780200307694 /* P2PSocket.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DB2CED780200307694 /* P2PSocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
14+
FE8207E02CED780200307694 /* Entity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DC2CED780200307694 /* Entity.framework */; };
15+
FE8207E12CED780200307694 /* Entity.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DC2CED780200307694 /* Entity.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16+
FE8207E22CED780200307694 /* Interfaces.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DD2CED780200307694 /* Interfaces.framework */; };
17+
FE8207E32CED780200307694 /* Interfaces.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE8207DD2CED780200307694 /* Interfaces.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18+
FEF778B92CDB00F000FFE089 /* libData.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FEF778B72CDB00F000FFE089 /* libData.a */; };
19+
FEF778BA2CDB00F000FFE089 /* libFeature.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FEF778B82CDB00F000FFE089 /* libFeature.a */; };
20+
FEF779742CDCB35800FFE089 /* libUseCase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FEF779732CDCB35800FFE089 /* libUseCase.a */; };
21+
/* End PBXBuildFile section */
22+
23+
/* Begin PBXCopyFilesBuildPhase section */
24+
FE0334F32CD9DF1800AA58C9 /* Dependencies */ = {
25+
isa = PBXCopyFilesBuildPhase;
26+
buildActionMask = 12;
27+
dstPath = "";
28+
dstSubfolderSpec = 16;
29+
files = (
30+
);
31+
name = Dependencies;
32+
runOnlyForDeploymentPostprocessing = 0;
33+
};
34+
FEF778BE2CDB0B3C00FFE089 /* Embed Frameworks */ = {
35+
isa = PBXCopyFilesBuildPhase;
36+
buildActionMask = 2147483647;
37+
dstPath = "";
38+
dstSubfolderSpec = 10;
39+
files = (
40+
853CF5D02CEDE80B00936AD3 /* Core.framework in Embed Frameworks */,
41+
FE8207DF2CED780200307694 /* P2PSocket.framework in Embed Frameworks */,
42+
FE8207E32CED780200307694 /* Interfaces.framework in Embed Frameworks */,
43+
FE8207E12CED780200307694 /* Entity.framework in Embed Frameworks */,
44+
);
45+
name = "Embed Frameworks";
46+
runOnlyForDeploymentPostprocessing = 0;
47+
};
48+
/* End PBXCopyFilesBuildPhase section */
49+
950
/* Begin PBXFileReference section */
51+
853CF5CE2CEDE80800936AD3 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1052
FE6EF2EA2CD8B005005DC39D /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
11-
FED968412CD90E4500CD445C /* libData.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libData.a; sourceTree = BUILT_PRODUCTS_DIR; };
12-
FED968432CD90E4500CD445C /* libDomain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libDomain.a; sourceTree = BUILT_PRODUCTS_DIR; };
13-
FED968452CD90E4500CD445C /* libFeature.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFeature.a; sourceTree = BUILT_PRODUCTS_DIR; };
53+
FE8207DB2CED780200307694 /* P2PSocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = P2PSocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54+
FE8207DC2CED780200307694 /* Entity.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Entity.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55+
FE8207DD2CED780200307694 /* Interfaces.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Interfaces.framework; sourceTree = BUILT_PRODUCTS_DIR; };
56+
FEF778B72CDB00F000FFE089 /* libData.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libData.a; sourceTree = BUILT_PRODUCTS_DIR; };
57+
FEF778B82CDB00F000FFE089 /* libFeature.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFeature.a; sourceTree = BUILT_PRODUCTS_DIR; };
58+
FEF779732CDCB35800FFE089 /* libUseCase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libUseCase.a; sourceTree = BUILT_PRODUCTS_DIR; };
1459
/* End PBXFileReference section */
1560

1661
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -39,6 +84,13 @@
3984
isa = PBXFrameworksBuildPhase;
4085
buildActionMask = 2147483647;
4186
files = (
87+
853CF5CF2CEDE80800936AD3 /* Core.framework in Frameworks */,
88+
FEF778B92CDB00F000FFE089 /* libData.a in Frameworks */,
89+
FEF778BA2CDB00F000FFE089 /* libFeature.a in Frameworks */,
90+
FE8207E02CED780200307694 /* Entity.framework in Frameworks */,
91+
FE8207DE2CED780200307694 /* P2PSocket.framework in Frameworks */,
92+
FE8207E22CED780200307694 /* Interfaces.framework in Frameworks */,
93+
FEF779742CDCB35800FFE089 /* libUseCase.a in Frameworks */,
4294
);
4395
runOnlyForDeploymentPostprocessing = 0;
4496
};
@@ -65,9 +117,13 @@
65117
FE6EF3E12CD8B6A2005DC39D /* Frameworks */ = {
66118
isa = PBXGroup;
67119
children = (
68-
FED968412CD90E4500CD445C /* libData.a */,
69-
FED968432CD90E4500CD445C /* libDomain.a */,
70-
FED968452CD90E4500CD445C /* libFeature.a */,
120+
853CF5CE2CEDE80800936AD3 /* Core.framework */,
121+
FE8207DB2CED780200307694 /* P2PSocket.framework */,
122+
FE8207DC2CED780200307694 /* Entity.framework */,
123+
FE8207DD2CED780200307694 /* Interfaces.framework */,
124+
FEF779732CDCB35800FFE089 /* libUseCase.a */,
125+
FEF778B72CDB00F000FFE089 /* libData.a */,
126+
FEF778B82CDB00F000FFE089 /* libFeature.a */,
71127
);
72128
name = Frameworks;
73129
sourceTree = "<group>";
@@ -79,9 +135,12 @@
79135
isa = PBXNativeTarget;
80136
buildConfigurationList = FE6EF2FD2CD8B007005DC39D /* Build configuration list for PBXNativeTarget "App" */;
81137
buildPhases = (
138+
FEF35FBF2CD9BC8F006FF6F2 /* Run Script */,
82139
FE6EF2E62CD8B005005DC39D /* Sources */,
140+
FE0334F32CD9DF1800AA58C9 /* Dependencies */,
83141
FE6EF2E72CD8B005005DC39D /* Frameworks */,
84142
FE6EF2E82CD8B005005DC39D /* Resources */,
143+
FEF778BE2CDB0B3C00FFE089 /* Embed Frameworks */,
85144
);
86145
buildRules = (
87146
);
@@ -141,6 +200,28 @@
141200
};
142201
/* End PBXResourcesBuildPhase section */
143202

203+
/* Begin PBXShellScriptBuildPhase section */
204+
FEF35FBF2CD9BC8F006FF6F2 /* Run Script */ = {
205+
isa = PBXShellScriptBuildPhase;
206+
alwaysOutOfDate = 1;
207+
buildActionMask = 2147483647;
208+
files = (
209+
);
210+
inputFileListPaths = (
211+
);
212+
inputPaths = (
213+
);
214+
name = "Run Script";
215+
outputFileListPaths = (
216+
);
217+
outputPaths = (
218+
);
219+
runOnlyForDeploymentPostprocessing = 0;
220+
shellPath = /bin/sh;
221+
shellScript = "ROOT_DIR=\"${PROJECT_DIR}/../\"\nCONFIGPATH=\"/Users/jeongseog-yeong/Github/BeStory/.swiftlint.yml\"\n\n\"${ROOT_DIR}swiftlint\" --config \"${ROOT_DIR}.swiftlint.yml\"\n";
222+
};
223+
/* End PBXShellScriptBuildPhase section */
224+
144225
/* Begin PBXSourcesBuildPhase section */
145226
FE6EF2E62CD8B005005DC39D /* Sources */ = {
146227
isa = PBXSourcesBuildPhase;
@@ -159,9 +240,11 @@
159240
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
160241
CODE_SIGN_STYLE = Automatic;
161242
CURRENT_PROJECT_VERSION = 1;
162-
DEVELOPMENT_TEAM = 9L2J37V6PK;
243+
DEVELOPMENT_TEAM = B3PWYBKFUK;
244+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
163245
GENERATE_INFOPLIST_FILE = YES;
164246
INFOPLIST_FILE = App/Info.plist;
247+
INFOPLIST_KEY_CFBundleDisplayName = BeStory;
165248
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
166249
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
167250
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
@@ -172,8 +255,8 @@
172255
"@executable_path/Frameworks",
173256
);
174257
LIBRARY_SEARCH_PATHS = "";
175-
MARKETING_VERSION = 1.0;
176-
PRODUCT_BUNDLE_IDENTIFIER = jung.App;
258+
MARKETING_VERSION = 0.5.0;
259+
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.bestory;
177260
PRODUCT_NAME = "$(TARGET_NAME)";
178261
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
179262
SUPPORTS_MACCATALYST = NO;
@@ -192,9 +275,11 @@
192275
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
193276
CODE_SIGN_STYLE = Automatic;
194277
CURRENT_PROJECT_VERSION = 1;
195-
DEVELOPMENT_TEAM = 9L2J37V6PK;
278+
DEVELOPMENT_TEAM = B3PWYBKFUK;
279+
ENABLE_USER_SCRIPT_SANDBOXING = NO;
196280
GENERATE_INFOPLIST_FILE = YES;
197281
INFOPLIST_FILE = App/Info.plist;
282+
INFOPLIST_KEY_CFBundleDisplayName = BeStory;
198283
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
199284
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
200285
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
@@ -205,8 +290,8 @@
205290
"@executable_path/Frameworks",
206291
);
207292
LIBRARY_SEARCH_PATHS = "";
208-
MARKETING_VERSION = 1.0;
209-
PRODUCT_BUNDLE_IDENTIFIER = jung.App;
293+
MARKETING_VERSION = 0.5.0;
294+
PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.boostcamp9.bestory;
210295
PRODUCT_NAME = "$(TARGET_NAME)";
211296
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
212297
SUPPORTS_MACCATALYST = NO;

App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1600"
3+
LastUpgradeVersion = "1610"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

0 commit comments

Comments
 (0)