feat(community): 커뮤니티 보기 전환 탭과 아이콘을 추가한다
This commit is contained in:
21
SodaLive/Resources/Assets.xcassets/ic_community_grid.imageset/Contents.json
vendored
Normal file
21
SodaLive/Resources/Assets.xcassets/ic_community_grid.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_community_grid.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
SodaLive/Resources/Assets.xcassets/ic_community_grid.imageset/ic_community_grid.png
vendored
Normal file
BIN
SodaLive/Resources/Assets.xcassets/ic_community_grid.imageset/ic_community_grid.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 B |
21
SodaLive/Resources/Assets.xcassets/ic_community_grid_selected.imageset/Contents.json
vendored
Normal file
21
SodaLive/Resources/Assets.xcassets/ic_community_grid_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_community_grid_selected.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 594 B |
21
SodaLive/Resources/Assets.xcassets/ic_community_list.imageset/Contents.json
vendored
Normal file
21
SodaLive/Resources/Assets.xcassets/ic_community_list.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_community_list.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
SodaLive/Resources/Assets.xcassets/ic_community_list.imageset/ic_community_list.png
vendored
Normal file
BIN
SodaLive/Resources/Assets.xcassets/ic_community_list.imageset/ic_community_list.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 B |
21
SodaLive/Resources/Assets.xcassets/ic_community_list_selected.imageset/Contents.json
vendored
Normal file
21
SodaLive/Resources/Assets.xcassets/ic_community_list_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_community_list_selected.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
@@ -8,57 +8,49 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CreatorCommunityAllView: View {
|
struct CreatorCommunityAllView: View {
|
||||||
|
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
|
|
||||||
@StateObject var viewModel = CreatorCommunityAllViewModel()
|
@StateObject var viewModel = CreatorCommunityAllViewModel()
|
||||||
@StateObject var playerManager = CreatorCommunityMediaPlayerManager.shared
|
@StateObject var playerManager = CreatorCommunityMediaPlayerManager.shared
|
||||||
|
|
||||||
|
@State private var isGridMode = false
|
||||||
|
@State private var isListFromGridTap = false
|
||||||
|
@State private var selectedListIndex = 0
|
||||||
|
@State private var listAnchorIndex = 0
|
||||||
|
@State private var pendingGridAnchorIndex: Int?
|
||||||
|
|
||||||
|
private let gridColumns = [
|
||||||
|
GridItem(.flexible(), spacing: 1),
|
||||||
|
GridItem(.flexible(), spacing: 1),
|
||||||
|
GridItem(.flexible(), spacing: 1)
|
||||||
|
]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
BaseView(isLoading: $viewModel.isLoading) {
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
DetailNavigationBar(title: "커뮤니티")
|
DetailNavigationBar(
|
||||||
|
title: "커뮤니티",
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
backAction: {
|
||||||
LazyVStack(spacing: 26.7) {
|
if isGridMode {
|
||||||
ForEach(0..<viewModel.communityPostList.count, id: \.self) { index in
|
AppState.shared.back()
|
||||||
let item = viewModel.communityPostList[index]
|
} else {
|
||||||
CreatorCommunityAllItemView(
|
if isListFromGridTap {
|
||||||
item: item,
|
returnToGridMode()
|
||||||
onClickLike: {
|
} else {
|
||||||
viewModel.communityPostLike(postId: item.postId)
|
AppState.shared.back()
|
||||||
},
|
|
||||||
onClickComment: {
|
|
||||||
viewModel.postId = item.postId
|
|
||||||
viewModel.isShowSecret = item.price > 0 && item.existOrdered && item.creatorId != UserDefaults.int(forKey: .userId)
|
|
||||||
viewModel.isShowCommentListView = true
|
|
||||||
},
|
|
||||||
onClickWriteComment: { comment, isSecret in
|
|
||||||
viewModel.createCommunityPostComment(
|
|
||||||
comment: comment,
|
|
||||||
postId: item.postId,
|
|
||||||
isSecret: isSecret
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClickShowReportMenu: {
|
|
||||||
viewModel.postId = item.postId
|
|
||||||
viewModel.isShowReportMenu = true
|
|
||||||
},
|
|
||||||
onClickPurchaseContent: {
|
|
||||||
viewModel.postId = item.postId
|
|
||||||
viewModel.postPrice = item.price
|
|
||||||
viewModel.postIndex = index
|
|
||||||
viewModel.isShowPostPurchaseView = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.onAppear {
|
|
||||||
if index == viewModel.communityPostList.count - 1 {
|
|
||||||
viewModel.getCommunityPostList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
communityViewTypeTabView
|
||||||
|
|
||||||
|
if isGridMode {
|
||||||
|
gridContentView
|
||||||
|
} else {
|
||||||
|
listContentView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(
|
.sheet(
|
||||||
@@ -188,12 +180,222 @@ struct CreatorCommunityAllView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var gridContentView: some View {
|
||||||
|
ScrollViewReader { scrollProxy in
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
LazyVGrid(columns: gridColumns, spacing: 1) {
|
||||||
|
ForEach(0..<viewModel.communityPostList.count, id: \.self) { index in
|
||||||
|
let item = viewModel.communityPostList[index]
|
||||||
|
CreatorCommunityAllGridItemView(item: item)
|
||||||
|
.id(index)
|
||||||
|
.onTapGesture {
|
||||||
|
selectedListIndex = index
|
||||||
|
listAnchorIndex = index
|
||||||
|
isListFromGridTap = true
|
||||||
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
isGridMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if index == viewModel.communityPostList.count - 1 {
|
||||||
|
viewModel.getCommunityPostList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
guard let index = pendingGridAnchorIndex else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
scrollProxy.scrollTo(index, anchor: .center)
|
||||||
|
pendingGridAnchorIndex = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var listContentView: some View {
|
||||||
|
ScrollViewReader { scrollProxy in
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
LazyVStack(spacing: 13.3) {
|
||||||
|
ForEach(0..<viewModel.communityPostList.count, id: \.self) { index in
|
||||||
|
let item = viewModel.communityPostList[index]
|
||||||
|
makeCommunityItemView(item: item, index: index)
|
||||||
|
.id(index)
|
||||||
|
.onAppear {
|
||||||
|
listAnchorIndex = index
|
||||||
|
if index == viewModel.communityPostList.count - 1 {
|
||||||
|
viewModel.getCommunityPostList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
let targetIndex = min(max(selectedListIndex, 0), max(viewModel.communityPostList.count - 1, 0))
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
scrollProxy.scrollTo(targetIndex, anchor: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: isGridMode) { mode in
|
||||||
|
guard !mode else { return }
|
||||||
|
let targetIndex = min(max(selectedListIndex, 0), max(viewModel.communityPostList.count - 1, 0))
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
scrollProxy.scrollTo(targetIndex, anchor: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeCommunityItemView(item: GetCommunityPostListResponse, index: Int) -> some View {
|
||||||
|
CreatorCommunityAllItemView(
|
||||||
|
item: item,
|
||||||
|
onClickLike: {
|
||||||
|
viewModel.communityPostLike(postId: item.postId)
|
||||||
|
},
|
||||||
|
onClickComment: {
|
||||||
|
viewModel.postId = item.postId
|
||||||
|
viewModel.isShowSecret = item.price > 0 && item.existOrdered && item.creatorId != UserDefaults.int(forKey: .userId)
|
||||||
|
viewModel.isShowCommentListView = true
|
||||||
|
},
|
||||||
|
onClickWriteComment: { comment, isSecret in
|
||||||
|
viewModel.createCommunityPostComment(
|
||||||
|
comment: comment,
|
||||||
|
postId: item.postId,
|
||||||
|
isSecret: isSecret
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickShowReportMenu: {
|
||||||
|
viewModel.postId = item.postId
|
||||||
|
viewModel.isShowReportMenu = true
|
||||||
|
},
|
||||||
|
onClickPurchaseContent: {
|
||||||
|
viewModel.postId = item.postId
|
||||||
|
viewModel.postPrice = item.price
|
||||||
|
viewModel.postIndex = index
|
||||||
|
viewModel.isShowPostPurchaseView = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func returnToGridMode() {
|
||||||
|
pendingGridAnchorIndex = listAnchorIndex
|
||||||
|
isListFromGridTap = false
|
||||||
|
isGridMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private var communityViewTypeTabView: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Button {
|
||||||
|
isGridMode = false
|
||||||
|
isListFromGridTap = false
|
||||||
|
} label: {
|
||||||
|
Image(isGridMode ? "ic_community_list" : "ic_community_list_selected")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 44)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isGridMode = true
|
||||||
|
isListFromGridTap = false
|
||||||
|
} label: {
|
||||||
|
Image(isGridMode ? "ic_community_grid_selected" : "ic_community_grid")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 44)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color.black)
|
||||||
|
|
||||||
|
ZStack(alignment: .bottomLeading) {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color(hex: "909090"))
|
||||||
|
.frame(height: 1)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(isGridMode ? Color.clear : Color.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(isGridMode ? Color.white : Color.clear)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.frame(height: 2)
|
||||||
|
}
|
||||||
|
.frame(height: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func creatorCommunityModifySuccess() {
|
private func creatorCommunityModifySuccess() {
|
||||||
viewModel.getCommunityPostList()
|
viewModel.getCommunityPostList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct CreatorCommunityAllGridItemView: View {
|
||||||
|
let item: GetCommunityPostListResponse
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
if isPaidLocked {
|
||||||
|
Color.gray33
|
||||||
|
|
||||||
|
Image("ic_lock_bb")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else if let imageUrl = item.imageUrl, !imageUrl.isEmpty {
|
||||||
|
AsyncImage(url: URL(string: imageUrl)) { phase in
|
||||||
|
switch phase {
|
||||||
|
case .empty:
|
||||||
|
Color.gray33
|
||||||
|
case .success(let image):
|
||||||
|
image
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
case .failure:
|
||||||
|
Color(hex: "263238")
|
||||||
|
@unknown default:
|
||||||
|
Color.gray33
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color(hex: "263238")
|
||||||
|
|
||||||
|
Text(fallbackText)
|
||||||
|
.appFont(size: 12, weight: .medium)
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.lineLimit(3)
|
||||||
|
.padding(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isPaidLocked: Bool {
|
||||||
|
item.price > 0 && !item.existOrdered
|
||||||
|
}
|
||||||
|
|
||||||
|
private var fallbackText: String {
|
||||||
|
let content = item.content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if content.isEmpty {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(content.prefix(18))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CreatorCommunityAllView_Previews: PreviewProvider {
|
struct CreatorCommunityAllView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
CreatorCommunityAllView(creatorId: 0)
|
CreatorCommunityAllView(creatorId: 0)
|
||||||
|
|||||||
124
docs/20260305_크리에이터커뮤니티전체보기그리드리스트전환구현.md
Normal file
124
docs/20260305_크리에이터커뮤니티전체보기그리드리스트전환구현.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# 20260305 크리에이터 커뮤니티 전체보기 그리드 리스트 전환 구현
|
||||||
|
|
||||||
|
## 구현 목표
|
||||||
|
- `CreatorCommunityAllView`를 인스타그램과 유사하게 기본 `Grid` 화면으로 노출한다.
|
||||||
|
- 그리드 아이템 탭 시 `List` 화면으로 전환하고, 탭한 아이템 위치에서 리스트를 시작한다.
|
||||||
|
- 그리드 썸네일 규칙을 반영한다: 이미지 있음(이미지), 이미지 없음(텍스트 일부), 유료 미구매(자물쇠 이미지).
|
||||||
|
- 리스트 화면에서 뒤로가기 버튼 및 툴바를 제공하고, 뒤로가기 시 다시 그리드로 복귀한다.
|
||||||
|
- 리스트에서 뒤로가기 시 마지막 리스트 앵커 아이템이 그리드에서 보이도록 스크롤 위치를 복원한다.
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [x] 기존 `CreatorCommunity` 화면/모델/내비게이션 패턴 탐색
|
||||||
|
- [x] `CreatorCommunityAllView` 기본 레이아웃을 Grid 중심으로 전환
|
||||||
|
- [x] Grid 아이템 썸네일 규칙(이미지/텍스트/유료 자물쇠) 구현
|
||||||
|
- [x] 아이템 탭 시 List 전환 + 탭 아이템 앵커 이동 구현
|
||||||
|
- [x] 리스트 상태 툴바/뒤로가기 구현 및 그리드 복귀 동작 연결
|
||||||
|
- [x] 리스트 스크롤 중 앵커 인덱스 추적 및 그리드 복귀 시 위치 복원
|
||||||
|
- [x] LSP/빌드/테스트 검증 및 결과 기록
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-03-05
|
||||||
|
- 무엇: 작업 시작 및 요구사항 분석/패턴 탐색
|
||||||
|
- 왜: 기존 아키텍처와 충돌 없이 `Grid -> List -> Grid` UX를 정확히 반영하기 위해
|
||||||
|
- 어떻게: `CreatorCommunityAllView`, `CreatorCommunityAllItemView`, `DetailNavigationBar`, 응답 모델을 확인해 재사용 가능한 상태/탐색 패턴을 추출
|
||||||
|
- 결과: 완료
|
||||||
|
|
||||||
|
- 2026-03-05 (구현/검증 완료)
|
||||||
|
- 무엇: `CreatorCommunityAllView`를 기본 Grid(한 줄 3개) 노출로 변경하고, 아이템 탭 시 List 전환/뒤로가기 복귀/앵커 복원을 구현했다.
|
||||||
|
- 왜: 인스타그램과 유사한 탐색 UX(그리드 진입, 게시물 리스트 탐색, 원위치 복귀)를 요구사항대로 제공하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- 수정 파일
|
||||||
|
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift`
|
||||||
|
- 적용 내용
|
||||||
|
- `LazyVGrid` 3열 고정(`GridItem` 3개)으로 그리드 기본 화면 구성
|
||||||
|
- 썸네일 규칙 반영: 유료 미구매(`price > 0 && !existOrdered`)는 `ic_lock_bb`, 이미지 없으면 텍스트 앞 18자, 그 외 `AsyncImage`
|
||||||
|
- `DetailNavigationBar`의 `backAction` 분기로 Grid 모드에서는 화면 뒤로가기, List 모드에서는 Grid 복귀
|
||||||
|
- Grid 탭 시 `selectedListIndex`로 List 스크롤 시작점 이동, List 스크롤 중 `listAnchorIndex` 추적
|
||||||
|
- List에서 뒤로가기 시 `pendingGridAnchorIndex`를 사용해 Grid에서 해당 앵커 아이템으로 `scrollTo`
|
||||||
|
- 실행 명령
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- `lsp_diagnostics` (`CreatorCommunityAllView.swift`)
|
||||||
|
- 결과:
|
||||||
|
- 빌드: `SodaLive`, `SodaLive-dev` 모두 `** BUILD SUCCEEDED **` 확인.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 실행 불가.
|
||||||
|
- LSP: SourceKit 단독 진단에서 프로젝트 심볼 미해결 에러가 다수 발생했으나, 실제 xcodebuild 컴파일은 성공.
|
||||||
|
|
||||||
|
- 2026-03-05 (요구사항 보강: 그리드 3열/앵커 복원 타이밍 안정화)
|
||||||
|
- 무엇: `Grid`를 한 줄 3개 고정 상태로 유지하고, List -> Grid 복귀 시 앵커 스크롤이 상태 전환 타이밍에 영향을 받지 않도록 `onAppear` 복원 로직을 추가했다.
|
||||||
|
- 왜: "그리드 리스트 한 줄에 3개 표시" 및 "리스트 앵커 아이템 복귀" 요구를 더 안정적으로 충족하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift`
|
||||||
|
- `gridColumns` 3개 고정 유지
|
||||||
|
- `gridContentView`에 `pendingGridAnchorIndex` 처리용 `.onAppear` 추가
|
||||||
|
- 실행 명령
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- 결과:
|
||||||
|
- 빌드: 두 스킴 모두 `** BUILD SUCCEEDED **`.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`.
|
||||||
|
|
||||||
|
- 2026-03-05 (퍼포먼스 개선: List -> Grid 전환 지연 완화)
|
||||||
|
- 무엇: 리스트에서 그리드로 돌아갈 때 전환이 느린 문제를 완화하도록 복귀 경로를 무애니메이션/단일 복원으로 최적화했다.
|
||||||
|
- 왜: 복귀 시 모드 전환 애니메이션 + 앵커 복원 애니메이션 + 중복 복원 트리거가 겹치면 체감 지연이 커질 수 있기 때문.
|
||||||
|
- 어떻게:
|
||||||
|
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift`
|
||||||
|
- `returnToGridMode()`에서 `withAnimation` 제거 후 즉시 `isGridMode = true`로 전환
|
||||||
|
- `gridContentView`의 `pendingGridAnchorIndex` 복원을 `onAppear` 단일 경로로 통합
|
||||||
|
- 앵커 복원 `scrollTo`를 무애니메이션으로 변경해 렌더링 부하 감소
|
||||||
|
- 실행 명령
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- `lsp_diagnostics` (`CreatorCommunityAllView.swift`)
|
||||||
|
- 결과:
|
||||||
|
- 빌드: `SodaLive`는 `** BUILD SUCCEEDED **`, `SodaLive-dev`는 병렬 빌드 중 `build.db locked` 1회 발생 후 순차 재실행에서 `** BUILD SUCCEEDED **`.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`.
|
||||||
|
- LSP: SourceKit 단독 진단에서 프로젝트 심볼 미해결 에러가 다수 발생했으나, 실제 xcodebuild 컴파일은 성공.
|
||||||
|
|
||||||
|
- 2026-03-06 (UI 변경: Toolbar 아래 List/Grid 탭 및 기본 List 진입)
|
||||||
|
- 무엇: Toolbar 아래에 좌측 List/우측 Grid 탭을 추가하고, 기본 표시를 Grid에서 List로 변경했다.
|
||||||
|
- 왜: 요청된 UI 변경(탭 2개, 기본 List, 뒤로가기 분기)을 정확히 반영하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift`
|
||||||
|
- `communityViewTypeTabView` 추가: 좌 `ic_community_list(_selected)`, 우 `ic_community_grid(_selected)`, 각 버튼 `maxWidth: .infinity`로 가로 꽉찬 탭 구성
|
||||||
|
- 기본 모드 `isGridMode = false`로 변경
|
||||||
|
- `isListFromGridTap` 상태 추가
|
||||||
|
- Grid 아이템 탭으로 List 진입 시 `isListFromGridTap = true`
|
||||||
|
- List 상태 뒤로가기 분기:
|
||||||
|
- `isListFromGridTap == true`면 Grid 복귀
|
||||||
|
- 초기 List(또는 탭 전환 List)는 기본 뒤로가기 수행
|
||||||
|
- 실행 명령
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- `lsp_diagnostics` (`CreatorCommunityAllView.swift`)
|
||||||
|
- 결과:
|
||||||
|
- 빌드: `SodaLive`, `SodaLive-dev` 모두 `** BUILD SUCCEEDED **`.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`.
|
||||||
|
- LSP: SourceKit 단독 진단에서는 프로젝트 심볼 미해결 에러가 발생하나, 실제 xcodebuild는 성공.
|
||||||
|
|
||||||
|
- 2026-03-06 (탭 디테일 조정: 배경/하단 라인/선택 인디케이터)
|
||||||
|
- 무엇: 커뮤니티 전체보기 탭의 시각 디테일을 요청 사양으로 조정했다.
|
||||||
|
- 왜: 탭 배경색, 하단 구분선, 선택 인디케이터를 명시한 최신 UI 요구를 반영하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift`
|
||||||
|
- 탭 배경색을 `#777777`로 변경
|
||||||
|
- 탭 하단에 전체 폭 구분선(`height: 1`, `#909090`) 추가
|
||||||
|
- 선택 탭 하단 인디케이터(`height: 2`, `#ffffff`) 추가
|
||||||
|
- 실행 명령
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- `lsp_diagnostics` (`CreatorCommunityAllView.swift`)
|
||||||
|
- 결과:
|
||||||
|
- 빌드: `SodaLive`, `SodaLive-dev` 모두 `** BUILD SUCCEEDED **`.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`.
|
||||||
|
- LSP: SourceKit 단독 진단에서는 프로젝트 심볼 미해결 및 확장 타입 해석 오류가 발생하나, 실제 xcodebuild는 성공.
|
||||||
Reference in New Issue
Block a user