diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java index d9673019..3b0e9760 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java @@ -2,41 +2,42 @@ import com.rollthedice.backend.domain.bookmark.dto.response.BookmarkResponse; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.data.domain.Pageable; import java.util.List; +@Tag(name = "Bookmark") public interface BookmarkApi { @Operation( summary = "북마크 전체 조회", description = "회원이 북마크한 뉴스를 페이지로 나누어 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"북마크"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "OK" + description = "북마크 전체 조회를 성공했습니다." ) - List getAllBookmarkedNews( + SuccessResponse> getAllBookmarkedNews( Pageable pageable ); @Operation( summary = "뉴스 북마크 여부 조회", description = "로그인한 회원이 해당 뉴스를 북마크 했는지 여부를 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"북마크"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "OK" + description = "뉴스가 북마크 여부 조회를 성공했습니다." ) - BookmarkResponse getIsBookmarked( + SuccessResponse getIsBookmarked( @Parameter(in = ParameterIn.PATH, description = "뉴스 ID", required = true) Long newsId ); @@ -45,14 +46,13 @@ BookmarkResponse getIsBookmarked( @Operation( summary = "북마크 저장", description = "뉴스에 대하여 북마크로 저장합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"북마크"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "201", - description = "Created" + description = "북마크 저장에 성공했습니다." ) - void saveBookmark( + SuccessResponse saveBookmark( @Parameter(in = ParameterIn.PATH, description = "뉴스 ID", required = true) Long newsId ); @@ -60,14 +60,13 @@ void saveBookmark( @Operation( summary = "북마크 삭제", description = "저장된 북마크를 해제합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"북마크"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "204", - description = "No Content" + description = "북마크 삭제에 성공했습니다." ) - void deleteBookmark( + SuccessResponse deleteBookmark( @Parameter(in = ParameterIn.PATH, description = "뉴스 ID", required = true) Long newsId ); diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java index 23dd1b85..d9556f93 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java @@ -3,6 +3,7 @@ import com.rollthedice.backend.domain.bookmark.dto.response.BookmarkResponse; import com.rollthedice.backend.domain.bookmark.service.BookmarkService; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -10,6 +11,8 @@ import java.util.List; +import static com.rollthedice.backend.global.common.response.SuccessCode.*; + @RestController @RequiredArgsConstructor @RequestMapping("bookmarks") @@ -19,28 +22,32 @@ public class BookmarkController implements BookmarkApi { @ResponseStatus(HttpStatus.OK) @GetMapping("") @Override - public List getAllBookmarkedNews(final Pageable pageable) { - return bookmarkService.getAllBookmarkedNews(pageable); + public SuccessResponse> getAllBookmarkedNews(final Pageable pageable) { + List response = bookmarkService.getAllBookmarkedNews(pageable); + return SuccessResponse.of(GET_ALL_BOOKMARK_SUCCESS, response); } @ResponseStatus(HttpStatus.OK) @GetMapping("/{newsId}") @Override - public BookmarkResponse getIsBookmarked(@PathVariable final Long newsId) { - return bookmarkService.getIsBookmarked(newsId); + public SuccessResponse getIsBookmarked(@PathVariable final Long newsId) { + BookmarkResponse response = bookmarkService.getIsBookmarked(newsId); + return SuccessResponse.of(GET_IS_BOOKMARKED_SUCCESS, response); } @ResponseStatus(HttpStatus.CREATED) @PostMapping("/{newsId}") @Override - public void saveBookmark(@PathVariable final Long newsId) { + public SuccessResponse saveBookmark(@PathVariable final Long newsId) { bookmarkService.saveBookmark(newsId); + return SuccessResponse.of(CREATE_BOOKMARK_SUCCESS); } @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{newsId}") @Override - public void deleteBookmark(@PathVariable final Long newsId) { + public SuccessResponse deleteBookmark(@PathVariable final Long newsId) { bookmarkService.deleteBookmark(newsId); + return SuccessResponse.of(DELETE_BOOKMARK_SUCCESS); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java index 6af1d02f..f3cb925a 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java @@ -2,7 +2,7 @@ import com.rollthedice.backend.domain.member.entity.Member; import com.rollthedice.backend.domain.news.entity.News; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateApi.java index f70a4d3b..8c808f47 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateApi.java @@ -6,69 +6,67 @@ import com.rollthedice.backend.domain.debate.dto.response.DebateRoomResponse; import com.rollthedice.backend.domain.debate.dto.response.DebateRoomSaveResponse; import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.RequestBody; import java.util.List; - +@Tag(name = "Debate") public interface DebateApi { @Operation( summary = "토론방 생성", description = "주제가 선택된 토론방을 생성합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "201", - description = "Created" + description = "토론방 생성에 성공했습니다." ) - DebateRoomSaveResponse saveDebateRoom(@RequestBody DebateRoomRequest request); + SuccessResponse saveDebateRoom(@RequestBody DebateRoomRequest request); @Operation( summary = "토론방 전체 조회", description = "회원의 토론방을 페이지로 나누어 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "토론방 전체 조회에 성공했습니다" ) - List getDebateRooms(Pageable pageable); + SuccessResponse> getDebateRooms(Pageable pageable); @Operation( summary = "토론방 삭제", description = "토론방을 삭제합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "204", - description = "토론방 삭제에 성공하였으며, 응답값은 없습니다." + description = "토론방 삭제에 성공했습니다." ) - void deleteDebateRoom(@Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) + SuccessResponse deleteDebateRoom(@Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId ); @Operation( summary = "[인간] 토론 메세지 저장", description = "사용자가 보낸 토론 메세지를 저장합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론 메세지"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "201", - description = "Created" + description = "사용자가 보낸 토론 메세지 저장에 성공했습니다." ) - void saveHumanDebateMessage( + SuccessResponse saveHumanDebateMessage( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId, @@ -78,14 +76,13 @@ void saveHumanDebateMessage( @Operation( summary = "[AI] 토론 메세지 저장", description = "ChatGPT OPENAI가 보낸 토론 메세지를 저장합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론 메세지"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "201", - description = "Created" + description = "AI가 보낸 토론 메세지 저장에 성공했습니다." ) - void saveAIDebateMessage( + SuccessResponse saveAIDebateMessage( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId, @@ -95,20 +92,19 @@ void saveAIDebateMessage( @Operation( summary = "토론 종료", description = "토론을 종료합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponses(value = { @ApiResponse( responseCode = "204", - description = "요청에 성공하였으며 응답값은 없습니다." + description = "토론 종료에 성공했습니다." ), @ApiResponse( responseCode = "404", description = "토론방을 찾지 못했습니다." ) }) - void finishDebate( + SuccessResponse finishDebate( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId ); @@ -116,14 +112,13 @@ void finishDebate( @Operation( summary = "토론 메세지 조회", description = "토론방의 토론 메세지 이력을 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론 메세지"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "토론 메세지 조회에 성공했습니다." ) - List getDebateMessages( + SuccessResponse> getDebateMessages( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId ); @@ -131,20 +126,19 @@ List getDebateMessages( @Operation( summary = "토론 요약", description = "토론방의 토론 메세지들을 요약합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponses(value = { @ApiResponse( responseCode = "201", - description = "토론 요약이 성공하였습니다." + description = "토론 요약에 성공했습니다." ), @ApiResponse( responseCode = "404", description = "토론방을 찾지 못했습니다." ) }) - DebateSummaryResponse summarizeDebate( + SuccessResponse summarizeDebate( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId ); @@ -152,22 +146,20 @@ DebateSummaryResponse summarizeDebate( @Operation( summary = "토론 요약 조회", description = "토론 요약 내용을 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"토론방"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "토론 요약 조회에 성공했습니다." ), @ApiResponse( responseCode = "404", description = "토론방을 찾지 못했습니다." ) }) - DebateSummaryResponse getSummarizedDebate( + SuccessResponse getSummarizedDebate( @Parameter(in = ParameterIn.PATH, description = "토론방 ID", required = true) Long roomId ); - } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateController.java index 9c5863aa..e43c256e 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/api/DebateController.java @@ -8,6 +8,7 @@ import com.rollthedice.backend.domain.debate.dto.response.DebateSummaryResponse; import com.rollthedice.backend.domain.debate.service.DebateMessageService; import com.rollthedice.backend.domain.debate.service.DebateRoomService; +import com.rollthedice.backend.global.common.response.SuccessResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -16,6 +17,8 @@ import java.util.List; +import static com.rollthedice.backend.global.common.response.SuccessCode.*; + @RestController @RequiredArgsConstructor @RequestMapping("debates") @@ -26,62 +29,71 @@ public class DebateController implements DebateApi { @ResponseStatus(HttpStatus.CREATED) @PostMapping("") @Override - public DebateRoomSaveResponse saveDebateRoom(@RequestBody @Valid final DebateRoomRequest request) { - return debateRoomService.saveDebateRoom(request); + public SuccessResponse saveDebateRoom(@RequestBody @Valid final DebateRoomRequest request) { + DebateRoomSaveResponse response = debateRoomService.saveDebateRoom(request); + return SuccessResponse.of(CREATE_DEBATE_ROOM_SUCCESS, response); } @ResponseStatus(HttpStatus.OK) @GetMapping("") @Override - public List getDebateRooms(final Pageable pageable) { - return debateRoomService.getDebateRooms(pageable); + public SuccessResponse> getDebateRooms(final Pageable pageable) { + List response = debateRoomService.getDebateRooms(pageable); + return SuccessResponse.of(GET_ALL_DEBATE_ROOM_SUCCESS, response); } @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{roomId}") @Override - public void deleteDebateRoom(@PathVariable final Long roomId) { + public SuccessResponse deleteDebateRoom(@PathVariable final Long roomId) { debateRoomService.deleteDebateRoom(roomId); + return SuccessResponse.of(DELETE_DEBATE_ROOM_SUCCESS); } @ResponseStatus(HttpStatus.CREATED) @PostMapping("/{roomId}/human") @Override - public void saveHumanDebateMessage(@PathVariable final Long roomId, @RequestBody final DebateMessageRequest request) { + public SuccessResponse saveHumanDebateMessage(@PathVariable final Long roomId, @RequestBody final DebateMessageRequest request) { debateMessageService.saveHumanDebateMessage(roomId, request); + return SuccessResponse.of(CREATE_HUMAN_DEBATE_MESSAGE_SUCCESS); } @ResponseStatus(HttpStatus.CREATED) @PostMapping("/{roomId}/ai") @Override - public void saveAIDebateMessage(@PathVariable final Long roomId, @RequestBody final DebateMessageRequest request) { + public SuccessResponse saveAIDebateMessage(@PathVariable final Long roomId, @RequestBody final DebateMessageRequest request) { debateMessageService.saveAIDebateMessage(roomId, request); + return SuccessResponse.of(CREATE_AI_DEBATE_MESSAGE_SUCCESS); } @ResponseStatus(HttpStatus.NO_CONTENT) @PatchMapping("/{roomId}") - public void finishDebate(@PathVariable final Long roomId) { + public SuccessResponse finishDebate(@PathVariable final Long roomId) { debateRoomService.closeDebate(roomId); + return SuccessResponse.of(UPDATE_DEBATE_ROOM_IS_CLOSED_SUCCESS); } - @ResponseStatus(HttpStatus.CREATED) + @ResponseStatus(HttpStatus.OK) @GetMapping("/{roomId}") @Override - public List getDebateMessages(@PathVariable final Long roomId) { - return debateMessageService.getDebateMessages(roomId); + public SuccessResponse> getDebateMessages(@PathVariable final Long roomId) { + List response = debateMessageService.getDebateMessages(roomId); + return SuccessResponse.of(GET_DEBATE_MESSAGES_SUCCESS, response); } - @ResponseStatus(HttpStatus.OK) + @ResponseStatus(HttpStatus.CREATED) @PostMapping("/summary/{roomId}") @Override - public DebateSummaryResponse summarizeDebate(@PathVariable final Long roomId) { - return debateRoomService.summaryDebate(roomId); + public SuccessResponse summarizeDebate(@PathVariable final Long roomId) { + DebateSummaryResponse response = debateRoomService.summaryDebate(roomId); + return SuccessResponse.of(CREATE_DEBATE_SUMMARY_SUCCESS, response); } @ResponseStatus(HttpStatus.OK) @GetMapping("/summary/{roomId}") @Override - public DebateSummaryResponse getSummarizedDebate(@PathVariable final Long roomId) { - return debateRoomService.getSummarizedDebate(roomId); + public SuccessResponse getSummarizedDebate(@PathVariable final Long roomId) { + DebateSummaryResponse response = debateRoomService.getSummarizedDebate(roomId); + return SuccessResponse.of(GET_DEBATE_MESSAGES_SUCCESS, response); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateMessage.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateMessage.java index 91221c01..162926df 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateMessage.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateMessage.java @@ -1,7 +1,6 @@ package com.rollthedice.backend.domain.debate.entity; -import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -31,5 +30,4 @@ public DebateMessage(String message, SenderType senderType, DebateRoom debateRoo this.senderType = senderType; this.debateRoom = debateRoom; } - } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateRoom.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateRoom.java index 566463e4..8ffeb0f4 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateRoom.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/entity/DebateRoom.java @@ -1,7 +1,7 @@ package com.rollthedice.backend.domain.debate.entity; import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/ClovaSummary.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/ClovaSummary.java index 6f1b71d7..c4a2f3c9 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/ClovaSummary.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/ClovaSummary.java @@ -36,15 +36,12 @@ public class ClovaSummary { private String CLIENT_ID; public String summaryDebate(String messages) { - log.info("요약할 메세지: {}" ,messages); try { URL url = new URL(API_URL); HttpURLConnection connection = createRequestHeader(url); createRequestBody(connection, messages); - log.info("정상1"); StringBuilder response = getResponse(connection); - log.info("정상2"); return parseResponse(response); } catch (Exception e) { e.printStackTrace(); diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberApi.java index 3f87ce34..3ebcc3ee 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberApi.java @@ -1,20 +1,22 @@ package com.rollthedice.backend.domain.member.api; import com.rollthedice.backend.domain.member.dto.response.MemberResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +@Tag(name = "Member") public interface MemberApi { @Operation( summary = "회원 정보 조회", description = "회원 정보를 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"member"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "OK" + description = "사용자 정보 조회에 성공했습니다." ) - MemberResponse getMemberInfo(); + SuccessResponse getMemberInfo(); } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberController.java index 63411b19..d625e588 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/api/MemberController.java @@ -1,15 +1,14 @@ package com.rollthedice.backend.domain.member.api; -import com.rollthedice.backend.domain.member.dto.MemberServiceDto; -import com.rollthedice.backend.domain.member.dto.MemberUpdateDto; import com.rollthedice.backend.domain.member.dto.response.MemberResponse; import com.rollthedice.backend.domain.member.service.MemberService; -import com.rollthedice.backend.global.annotation.LoginMemberEmail; +import com.rollthedice.backend.global.common.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_MEMBER_INFO_SUCCESS; + @RestController @RequiredArgsConstructor @RequestMapping("members") @@ -19,7 +18,8 @@ public class MemberController implements MemberApi{ @ResponseStatus(HttpStatus.OK) @GetMapping("") @Override - public MemberResponse getMemberInfo() { - return memberService.getMemberInfo(); + public SuccessResponse getMemberInfo() { + MemberResponse response = memberService.getMemberInfo(); + return SuccessResponse.of(GET_MEMBER_INFO_SUCCESS, response); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java index 6395057d..390f7490 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java @@ -1,7 +1,7 @@ package com.rollthedice.backend.domain.member.entity; import com.rollthedice.backend.domain.member.dto.MemberServiceDto; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsApi.java index a152eebc..ccc2d8c8 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsApi.java @@ -3,46 +3,47 @@ import com.rollthedice.backend.domain.news.dto.response.NewsDetailResponse; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import com.rollthedice.backend.domain.news.dto.response.ReadNewsResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.data.domain.Pageable; import java.util.List; +@Tag(name = "Tag") public interface NewsApi { @Operation( summary = "요약 뉴스 전체 조회", description = "요약 뉴스를 페이지로 나누어 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"news"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "전체 뉴스 조회에 성공했습니다." ) - List getNews(Pageable pageable); + SuccessResponse> getNews(Pageable pageable); @Operation( summary = "요약 뉴스 상세 조회", description = "하나의 요약 뉴스를 상세 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"news"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "요약 뉴스 상세 조회에 성공했습니다." ), @ApiResponse( responseCode = "404", description = "뉴스를 찾지 못했습니다." ) }) - NewsDetailResponse getDetailNews( + SuccessResponse getDetailNews( @Parameter(in = ParameterIn.PATH, description = "뉴스 ID", required = true) Long newsId ); diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsController.java index 84407564..977d1922 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/api/NewsController.java @@ -3,6 +3,7 @@ import com.rollthedice.backend.domain.news.dto.response.NewsDetailResponse; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import com.rollthedice.backend.domain.news.service.NewsService; +import com.rollthedice.backend.global.common.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -10,6 +11,9 @@ import java.util.List; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_ALL_NEWS_SUCCESS; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_DETAIL_NEWS_SUCCESS; + @RestController @RequiredArgsConstructor @RequestMapping("news") @@ -19,14 +23,16 @@ public class NewsController implements NewsApi { @ResponseStatus(HttpStatus.OK) @GetMapping("") @Override - public List getNews(final Pageable pageable) { - return newsService.getNews(pageable); + public SuccessResponse> getNews(final Pageable pageable) { + List response = newsService.getNews(pageable); + return SuccessResponse.of(GET_ALL_NEWS_SUCCESS, response); } @ResponseStatus(HttpStatus.OK) @GetMapping("/{newsId}") @Override - public NewsDetailResponse getDetailNews(final @PathVariable Long newsId) { - return newsService.getDetailNews(newsId); + public SuccessResponse getDetailNews(final @PathVariable Long newsId) { + NewsDetailResponse response = newsService.getDetailNews(newsId); + return SuccessResponse.of(GET_DETAIL_NEWS_SUCCESS, response); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java index c96859cd..eba53bfe 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java @@ -1,6 +1,6 @@ package com.rollthedice.backend.domain.news.entity; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; import lombok.extern.slf4j.Slf4j; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsApi.java index 59aff96e..6279d7e8 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsApi.java @@ -1,22 +1,24 @@ package com.rollthedice.backend.domain.readNews.api; import com.rollthedice.backend.domain.news.dto.response.ReadNewsResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +@Tag(name = "Read News") public interface ReadNewsApi { @Operation( summary = "최근 읽은 뉴스 조회", description = "가장 최근에 읽은 3개의 뉴스를 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"news"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "최근 읽은 뉴스 조회에 성공했습니다." ) - List getReadNews(); + SuccessResponse> getReadNews(); } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsController.java index e32da2b7..67d02ed2 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/api/ReadNewsController.java @@ -2,6 +2,7 @@ import com.rollthedice.backend.domain.news.dto.response.ReadNewsResponse; import com.rollthedice.backend.domain.readNews.service.ReadNewsService; +import com.rollthedice.backend.global.common.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -11,6 +12,8 @@ import java.util.List; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_RECENT_READ_NEWS_SUCCESS; + @RestController @RequiredArgsConstructor @RequestMapping("read-news") @@ -21,7 +24,8 @@ public class ReadNewsController implements ReadNewsApi { @ResponseStatus(HttpStatus.OK) @GetMapping("/viewed-history") @Override - public List getReadNews() { - return readNewsService.getReadNews(); + public SuccessResponse> getReadNews() { + List response = readNewsService.getReadNews(); + return SuccessResponse.of(GET_RECENT_READ_NEWS_SUCCESS, response); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/entity/ReadNews.java b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/entity/ReadNews.java index 012fb466..2d6f23be 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/entity/ReadNews.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/readNews/entity/ReadNews.java @@ -2,7 +2,7 @@ import com.rollthedice.backend.domain.member.entity.Member; import com.rollthedice.backend.domain.news.entity.News; -import com.rollthedice.backend.global.config.BaseTimeEntity; +import com.rollthedice.backend.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsApi.java index 6866c2bb..be7e86e7 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsApi.java @@ -2,34 +2,35 @@ import com.rollthedice.backend.domain.statistics.dto.response.CategoryStatisticsResponse; import com.rollthedice.backend.domain.statistics.dto.response.DateViewStatisticsResponse; +import com.rollthedice.backend.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +@Tag(name = "Statistics") public interface StatisticsApi { @Operation( summary = "최근 일주일 날짜별 뉴스 조회수 조회", description = "최근 일주일간 날짜별로 뉴스 조회수를 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"통계"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "최근 일주일 날짜별 뉴스 조회수 조회에 성공했습니다." ) - List getViewsOfDates(); + SuccessResponse> getViewsOfDates(); @Operation( summary = "카테고리별 조회수 조회", description = "카테고리별 조회수를 조회합니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"통계"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "200", - description = "요청에 성공하였습니다." + description = "카테고리별 조회수 조회에 성공했습니다." ) - List getCategoryStatistics(); + SuccessResponse> getCategoryStatistics(); } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsController.java index 8dc19f3d..14634964 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/statistics/api/StatisticsController.java @@ -3,6 +3,7 @@ import com.rollthedice.backend.domain.statistics.dto.response.CategoryStatisticsResponse; import com.rollthedice.backend.domain.statistics.dto.response.DateViewStatisticsResponse; import com.rollthedice.backend.domain.statistics.service.StatisticsService; +import com.rollthedice.backend.global.common.response.SuccessResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -12,6 +13,9 @@ import java.util.List; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_CATEGORY_VIEWS_SUCCESS; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_VIEWS_OF_DATE_SUCCESS; + @RestController @RequiredArgsConstructor @RequestMapping("statistics") @@ -21,14 +25,16 @@ public class StatisticsController implements StatisticsApi { @ResponseStatus(HttpStatus.OK) @GetMapping("/per-dates") @Override - public List getViewsOfDates() { - return statisticsService.getViewsOfDates(); + public SuccessResponse> getViewsOfDates() { + List response = statisticsService.getViewsOfDates(); + return SuccessResponse.of(GET_VIEWS_OF_DATE_SUCCESS, response); } @ResponseStatus(HttpStatus.OK) @GetMapping("/categories") @Override - public List getCategoryStatistics() { - return statisticsService.getCategoryStatistics(); + public SuccessResponse> getCategoryStatistics() { + List response = statisticsService.getCategoryStatistics(); + return SuccessResponse.of(GET_CATEGORY_VIEWS_SUCCESS, response); } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/BaseTimeEntity.java b/backend/core/src/main/java/com/rollthedice/backend/global/common/BaseTimeEntity.java similarity index 93% rename from backend/core/src/main/java/com/rollthedice/backend/global/config/BaseTimeEntity.java rename to backend/core/src/main/java/com/rollthedice/backend/global/common/BaseTimeEntity.java index 5aff4ca7..fce2d4d0 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/config/BaseTimeEntity.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/common/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package com.rollthedice.backend.global.config; +package com.rollthedice.backend.global.common; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessCode.java b/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessCode.java new file mode 100644 index 00000000..0adb906f --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessCode.java @@ -0,0 +1,47 @@ +package com.rollthedice.backend.global.common.response; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SuccessCode { + // bookmark + GET_ALL_BOOKMARK_SUCCESS(200, "북마크 전체 조회를 성공했습니다."), + GET_IS_BOOKMARKED_SUCCESS(200, "뉴스가 북마크 여부 조회를 성공했습니다."), + CREATE_BOOKMARK_SUCCESS(201, "북마크 저장에 성공했습니다."), + DELETE_BOOKMARK_SUCCESS(204, "북마크 삭제에 성공했습니다."), + + // debate + CREATE_DEBATE_ROOM_SUCCESS(201, "토론방 생성에 성공했습니다."), + GET_ALL_DEBATE_ROOM_SUCCESS(200, "토론방 전체 조회에 성공했습니다"), + DELETE_DEBATE_ROOM_SUCCESS(204, "토론방 삭제에 성공했습니다."), + CREATE_HUMAN_DEBATE_MESSAGE_SUCCESS(201, "사용자가 보낸 토론 메세지 저장에 성공했습니다."), + CREATE_AI_DEBATE_MESSAGE_SUCCESS(201, "AI가 보낸 토론 메세지 저장에 성공했습니다."), + UPDATE_DEBATE_ROOM_IS_CLOSED_SUCCESS(204, "토론 종료에 성공했습니다."), + GET_DEBATE_MESSAGES_SUCCESS(200, "토론 메세지 조회에 성공했습니다."), + CREATE_DEBATE_SUMMARY_SUCCESS(201, "토론 요약에 성공했습니다."), + GET_DEBATE_SUMMARY_SUCCESS(200, "토론 요약 조회에 성공했습니다."), + + // member + GET_MEMBER_INFO_SUCCESS(200, "사용자 정보 조회에 성공했습니다."), + + // news + GET_ALL_NEWS_SUCCESS(200, "전체 뉴스 조회에 성공했습니다."), + GET_DETAIL_NEWS_SUCCESS(200, "요약 뉴스 상세 조회에 성공했습니다."), + + // read news + GET_RECENT_READ_NEWS_SUCCESS(200, "최근 읽은 뉴스 조회에 성공했습니다."), + + // statistics + GET_VIEWS_OF_DATE_SUCCESS(200, "최근 일주일 날짜별 뉴스 조회수 조회에 성공했습니다."), + GET_CATEGORY_VIEWS_SUCCESS(200, "카테고리별 조회수 조회에 성공했습니다."), + + // auth + GET_SOCIAL_LOGIN_SUCCESS(201, "소셜 로그인에 성공했습니다."), + UPDATE_NICKNAME_SUCCESS(204, "닉네임 입력에 성공했습니다.") + ; + + private final int status; + private final String message; +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessResponse.java b/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessResponse.java new file mode 100644 index 00000000..803b9ac9 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/common/response/SuccessResponse.java @@ -0,0 +1,18 @@ +package com.rollthedice.backend.global.common.response; + +public record SuccessResponse( + int status, + String message, + T data +) { + + private static final String NOTHING = ""; + + public static SuccessResponse of(SuccessCode code) { + return new SuccessResponse<>(code.getStatus(), code.getMessage(), NOTHING); + } + + public static SuccessResponse of(SuccessCode code, T data) { + return new SuccessResponse<>(code.getStatus(), code.getMessage(), data); + } +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthApi.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthApi.java index b8528389..c9cabee7 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthApi.java @@ -1,27 +1,29 @@ package com.rollthedice.backend.global.oauth2.api; import com.rollthedice.backend.domain.member.dto.MemberUpdateDto; +import com.rollthedice.backend.global.common.response.SuccessResponse; import com.rollthedice.backend.global.oauth2.dto.LoginRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; +@Tag(name = "Auth") public interface AuthApi { @Operation( summary = "소셜 로그인", description = "소셜 로그인을 합니다. 회원가입이 되어있지 않은 회원일 경우, 회원가입이 진행됩니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"인증"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( - responseCode = "201", - description = "Created" + responseCode = "200", + description = "소셜 로그인에 성공했습니다." ) - ResponseEntity login( + SuccessResponse login( @RequestBody LoginRequest request, HttpServletResponse response ); @@ -29,14 +31,13 @@ ResponseEntity login( @Operation( summary = "닉네임 입력", description = "닉네임을 입력합니다. 해당 로직이 진행되어야 회원가입이 완료됩니다.", - security = {@SecurityRequirement(name = "access_token")}, - tags = {"인증"} + security = {@SecurityRequirement(name = "access_token")} ) @ApiResponse( responseCode = "201", - description = "Created" + description = "닉네임 입력에 성공했습니다." ) - ResponseEntity updateMember( + SuccessResponse updateMember( String email, @RequestBody MemberUpdateDto memberUpdateDto ); diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthController.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthController.java index dad5b0f7..27ba7e0a 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/api/AuthController.java @@ -3,6 +3,7 @@ import com.rollthedice.backend.domain.member.dto.MemberUpdateDto; import com.rollthedice.backend.domain.member.service.MemberService; import com.rollthedice.backend.global.annotation.LoginMemberEmail; +import com.rollthedice.backend.global.common.response.SuccessResponse; import com.rollthedice.backend.global.oauth2.dto.LoginRequest; import com.rollthedice.backend.global.oauth2.service.AuthService; import jakarta.servlet.http.HttpServletResponse; @@ -12,8 +13,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import static com.rollthedice.backend.global.common.response.SuccessCode.GET_SOCIAL_LOGIN_SUCCESS; +import static com.rollthedice.backend.global.common.response.SuccessCode.UPDATE_NICKNAME_SUCCESS; + @Slf4j @RestController @RequiredArgsConstructor @@ -21,20 +26,20 @@ public class AuthController implements AuthApi { private final AuthService authService; private final MemberService memberService; + @ResponseStatus(HttpStatus.OK) @PostMapping("/login") @Override - public ResponseEntity login(@RequestBody LoginRequest request, HttpServletResponse response) { + public SuccessResponse login(@RequestBody LoginRequest request, HttpServletResponse response) { authService.authenticateOrRegisterUser(request, response); - return new ResponseEntity<>(HttpStatus.OK); + return SuccessResponse.of(GET_SOCIAL_LOGIN_SUCCESS); } @PostMapping("/oauth2/sign-up") @Override - public ResponseEntity updateMember(@LoginMemberEmail String email, + public SuccessResponse updateMember(@LoginMemberEmail String email, @RequestBody MemberUpdateDto memberUpdateDto) { memberService.update(memberUpdateDto); - - return ResponseEntity.status(HttpStatus.OK).build(); + return SuccessResponse.of(UPDATE_NICKNAME_SUCCESS); } } \ No newline at end of file diff --git a/iOS/RollTheDice/.DS_Store b/iOS/RollTheDice/.DS_Store index 0ed7ed28..f7a63841 100644 Binary files a/iOS/RollTheDice/.DS_Store and b/iOS/RollTheDice/.DS_Store differ diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 43967286..8a0b85e1 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -8,6 +8,16 @@ /* Begin PBXBuildFile section */ 3509091A2C1C1248007D76A1 /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 350909192C1C1248007D76A1 /* TokenManager.swift */; }; + 3544D7402C228EFC007DBD18 /* CreateDebateRoomService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3544D73F2C228EFC007DBD18 /* CreateDebateRoomService.swift */; }; + 3544D7432C228F54007DBD18 /* CreateDebateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3544D7422C228F54007DBD18 /* CreateDebateRoomViewModel.swift */; }; + 3544D7462C229436007DBD18 /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3544D7452C229436007DBD18 /* ChatService.swift */; }; + 35710D732C23D9030041ECD2 /* EndDebateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D722C23D9030041ECD2 /* EndDebateViewModel.swift */; }; + 35710D752C23D9350041ECD2 /* EndDebateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D742C23D9350041ECD2 /* EndDebateService.swift */; }; + 35710D772C23DBEA0041ECD2 /* DebateSummaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D762C23DBEA0041ECD2 /* DebateSummaryViewModel.swift */; }; + 35710D7B2C23DC360041ECD2 /* DebateSummaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D7A2C23DC360041ECD2 /* DebateSummaryService.swift */; }; + 35710D7D2C23DC6A0041ECD2 /* GetDebateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D7C2C23DC6A0041ECD2 /* GetDebateRoomViewModel.swift */; }; + 35710D7F2C23DC830041ECD2 /* GetDebateRoomService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D7E2C23DC830041ECD2 /* GetDebateRoomService.swift */; }; + 35710D822C23DCA40041ECD2 /* GetDebateRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35710D812C23DCA40041ECD2 /* GetDebateRoom.swift */; }; 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3576660F2BBD4BF6002C226A /* ReportListView.swift */; }; 357666132BBD54AA002C226A /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357666122BBD54AA002C226A /* SplashView.swift */; }; 3576993A2C09C1EB00AD2DA4 /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 357699392C09C1EB00AD2DA4 /* KakaoSDK */; }; @@ -15,13 +25,29 @@ 3576993E2C09C1EB00AD2DA4 /* KakaoSDKCert in Frameworks */ = {isa = PBXBuildFile; productRef = 3576993D2C09C1EB00AD2DA4 /* KakaoSDKCert */; }; 357699402C09C1EB00AD2DA4 /* KakaoSDKCertCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3576993F2C09C1EB00AD2DA4 /* KakaoSDKCertCore */; }; 357699422C09C1EB00AD2DA4 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 357699412C09C1EB00AD2DA4 /* KakaoSDKCommon */; }; - 357699442C09C7B900AD2DA4 /* AuthAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357699432C09C7B900AD2DA4 /* AuthAPI.swift */; }; + 357699442C09C7B900AD2DA4 /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357699432C09C7B900AD2DA4 /* LoginService.swift */; }; 357FC6EA2BCE866B00AD8915 /* DetailCardNews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357FC6E92BCE866B00AD8915 /* DetailCardNews.swift */; }; +<<<<<<< HEAD + 3586B53C2C22DF41006B1458 /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B53B2C22DF41006B1458 /* News.swift */; }; + 3586B53E2C22DF67006B1458 /* RecentNewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B53D2C22DF67006B1458 /* RecentNewsService.swift */; }; + 3586B5402C22DFCB006B1458 /* RecentNewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B53F2C22DFCB006B1458 /* RecentNewsViewModel.swift */; }; + 3586B5422C232C00006B1458 /* EndDebateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5412C232C00006B1458 /* EndDebateService.swift */; }; + 3586B5452C232C7C006B1458 /* EndDebateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5442C232C7C006B1458 /* EndDebateViewModel.swift */; }; + 3586B5492C2356F2006B1458 /* DebateSummaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5482C2356F2006B1458 /* DebateSummaryService.swift */; }; + 3586B54C2C235731006B1458 /* DebateSummaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B54B2C235731006B1458 /* DebateSummaryViewModel.swift */; }; + 3586B54F2C23B767006B1458 /* GetDebateRoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B54E2C23B767006B1458 /* GetDebateRoom.swift */; }; + 3586B5512C23B79F006B1458 /* GetDebateRoomService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5502C23B79F006B1458 /* GetDebateRoomService.swift */; }; + 3586B5532C23B7DC006B1458 /* GetDebateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5522C23B7DC006B1458 /* GetDebateRoomViewModel.swift */; }; +======= + 3586B5562C23D25A006B1458 /* News.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5552C23D25A006B1458 /* News.swift */; }; + 3586B5582C23D27C006B1458 /* RecentNewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5572C23D27C006B1458 /* RecentNewsService.swift */; }; + 3586B55A2C23D296006B1458 /* RecentNewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3586B5592C23D296006B1458 /* RecentNewsViewModel.swift */; }; +>>>>>>> DeviOS 35C71BF22B79F39900F777D1 /* ExyteChat in Frameworks */ = {isa = PBXBuildFile; productRef = 35C71BF12B79F39900F777D1 /* ExyteChat */; }; - 6C32379F2B7C376D00B699AB /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C32379E2B7C376D00B699AB /* Bookmark.swift */; }; + 6C32379F2B7C376D00B699AB /* Bookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C32379E2B7C376D00B699AB /* Bookmarks.swift */; }; 6C3237A12B7C377600B699AB /* BookmarkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237A02B7C377600B699AB /* BookmarkViewModel.swift */; }; 6C3237A52B7C37D100B699AB /* BookmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237A42B7C37D100B699AB /* BookmarkView.swift */; }; - 6C3237A72B7C37E500B699AB /* BookmarkListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237A62B7C37E500B699AB /* BookmarkListViewModel.swift */; }; + 6C3237A72B7C37E500B699AB /* BookmarksListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237A62B7C37E500B699AB /* BookmarksListViewModel.swift */; }; 6C3237AA2B7C381500B699AB /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237A92B7C381500B699AB /* NewsView.swift */; }; 6C3237AC2B7C382200B699AB /* NewsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237AB2B7C382200B699AB /* NewsList.swift */; }; 6C3237AE2B7C382E00B699AB /* DetailNewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237AD2B7C382E00B699AB /* DetailNewsViewModel.swift */; }; @@ -41,6 +67,8 @@ 6C454A882B9DB6C2006FD9D0 /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A872B9DB6C2006FD9D0 /* CustomNavigationBar.swift */; }; 6C4F7BAB2BDE50C600ED01DA /* DailyReportModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4F7BAA2BDE50C600ED01DA /* DailyReportModel.swift */; }; 6C4F7BAD2BDE510900ED01DA /* DailyReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4F7BAC2BDE510900ED01DA /* DailyReportViewModel.swift */; }; + 6C5B0C8A2C1C308A00A0D5F4 /* ScoopAPIBookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B0C892C1C308A00A0D5F4 /* ScoopAPIBookmarks.swift */; }; + 6C5B0C8C2C1C32C000A0D5F4 /* BookmarksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5B0C8B2C1C32C000A0D5F4 /* BookmarksService.swift */; }; 6C7651382BF37E7200196536 /* MoyaLoggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7651372BF37E7200196536 /* MoyaLoggingPlugin.swift */; }; 6C76513C2BF37ED300196536 /* Extension+Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C76513B2BF37ED300196536 /* Extension+Log.swift */; }; 6C76513E2BF37F1E00196536 /* Extension+OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C76513D2BF37F1E00196536 /* Extension+OSLog.swift */; }; @@ -62,6 +90,10 @@ 6CC4DDC92B5574670080E7E8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC4DDC82B5574670080E7E8 /* ContentView.swift */; }; 6CC4DDCB2B5574690080E7E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CC4DDCA2B5574690080E7E8 /* Assets.xcassets */; }; 6CC4DDCE2B5574690080E7E8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CC4DDCD2B5574690080E7E8 /* Preview Assets.xcassets */; }; + 6CC6737A2C217C4B009FB30E /* ScoopAPILogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC673792C217C4B009FB30E /* ScoopAPILogin.swift */; }; + 6CC6737C2C21BA5B009FB30E /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC6737B2C21BA5B009FB30E /* Auth.swift */; }; + 6CC6737E2C233443009FB30E /* StatisticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC6737D2C233443009FB30E /* StatisticsService.swift */; }; + 6CC673802C233A65009FB30E /* ScoopAPIStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC6737F2C233A65009FB30E /* ScoopAPIStatistics.swift */; }; 6CDB29C92BA97C550081037B /* Pretendard-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = 6CDB29C02BA97C550081037B /* Pretendard-Black.otf */; }; 6CDB29CA2BA97C550081037B /* Pretendard-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 6CDB29C12BA97C550081037B /* Pretendard-ExtraBold.otf */; }; 6CDB29CB2BA97C550081037B /* Pretendard-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 6CDB29C22BA97C550081037B /* Pretendard-Regular.otf */; }; @@ -99,14 +131,40 @@ /* Begin PBXFileReference section */ 350909192C1C1248007D76A1 /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = ""; }; + 3544D73F2C228EFC007DBD18 /* CreateDebateRoomService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDebateRoomService.swift; sourceTree = ""; }; + 3544D7422C228F54007DBD18 /* CreateDebateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDebateRoomViewModel.swift; sourceTree = ""; }; + 3544D7452C229436007DBD18 /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; }; + 35710D722C23D9030041ECD2 /* EndDebateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndDebateViewModel.swift; sourceTree = ""; }; + 35710D742C23D9350041ECD2 /* EndDebateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndDebateService.swift; sourceTree = ""; }; + 35710D762C23DBEA0041ECD2 /* DebateSummaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebateSummaryViewModel.swift; sourceTree = ""; }; + 35710D7A2C23DC360041ECD2 /* DebateSummaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebateSummaryService.swift; sourceTree = ""; }; + 35710D7C2C23DC6A0041ECD2 /* GetDebateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoomViewModel.swift; sourceTree = ""; }; + 35710D7E2C23DC830041ECD2 /* GetDebateRoomService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoomService.swift; sourceTree = ""; }; + 35710D812C23DCA40041ECD2 /* GetDebateRoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoom.swift; sourceTree = ""; }; 3576660F2BBD4BF6002C226A /* ReportListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportListView.swift; sourceTree = ""; }; 357666122BBD54AA002C226A /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; - 357699432C09C7B900AD2DA4 /* AuthAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthAPI.swift; sourceTree = ""; }; + 357699432C09C7B900AD2DA4 /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = ""; }; 357FC6E92BCE866B00AD8915 /* DetailCardNews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailCardNews.swift; sourceTree = ""; }; - 6C32379E2B7C376D00B699AB /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = ""; }; +<<<<<<< HEAD + 3586B53B2C22DF41006B1458 /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; }; + 3586B53D2C22DF67006B1458 /* RecentNewsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentNewsService.swift; sourceTree = ""; }; + 3586B53F2C22DFCB006B1458 /* RecentNewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentNewsViewModel.swift; sourceTree = ""; }; + 3586B5412C232C00006B1458 /* EndDebateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndDebateService.swift; sourceTree = ""; }; + 3586B5442C232C7C006B1458 /* EndDebateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndDebateViewModel.swift; sourceTree = ""; }; + 3586B5482C2356F2006B1458 /* DebateSummaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebateSummaryService.swift; sourceTree = ""; }; + 3586B54B2C235731006B1458 /* DebateSummaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebateSummaryViewModel.swift; sourceTree = ""; }; + 3586B54E2C23B767006B1458 /* GetDebateRoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoom.swift; sourceTree = ""; }; + 3586B5502C23B79F006B1458 /* GetDebateRoomService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoomService.swift; sourceTree = ""; }; + 3586B5522C23B7DC006B1458 /* GetDebateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDebateRoomViewModel.swift; sourceTree = ""; }; +======= + 3586B5552C23D25A006B1458 /* News.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = News.swift; sourceTree = ""; }; + 3586B5572C23D27C006B1458 /* RecentNewsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentNewsService.swift; sourceTree = ""; }; + 3586B5592C23D296006B1458 /* RecentNewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentNewsViewModel.swift; sourceTree = ""; }; +>>>>>>> DeviOS + 6C32379E2B7C376D00B699AB /* Bookmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmarks.swift; sourceTree = ""; }; 6C3237A02B7C377600B699AB /* BookmarkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = ""; }; 6C3237A42B7C37D100B699AB /* BookmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkView.swift; sourceTree = ""; }; - 6C3237A62B7C37E500B699AB /* BookmarkListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListViewModel.swift; sourceTree = ""; }; + 6C3237A62B7C37E500B699AB /* BookmarksListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksListViewModel.swift; sourceTree = ""; }; 6C3237A92B7C381500B699AB /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = ""; }; 6C3237AB2B7C382200B699AB /* NewsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsList.swift; sourceTree = ""; }; 6C3237AD2B7C382E00B699AB /* DetailNewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailNewsViewModel.swift; sourceTree = ""; }; @@ -126,6 +184,8 @@ 6C454A872B9DB6C2006FD9D0 /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; 6C4F7BAA2BDE50C600ED01DA /* DailyReportModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportModel.swift; sourceTree = ""; }; 6C4F7BAC2BDE510900ED01DA /* DailyReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportViewModel.swift; sourceTree = ""; }; + 6C5B0C892C1C308A00A0D5F4 /* ScoopAPIBookmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoopAPIBookmarks.swift; sourceTree = ""; }; + 6C5B0C8B2C1C32C000A0D5F4 /* BookmarksService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksService.swift; sourceTree = ""; }; 6C7651372BF37E7200196536 /* MoyaLoggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoyaLoggingPlugin.swift; sourceTree = ""; }; 6C76513B2BF37ED300196536 /* Extension+Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Log.swift"; sourceTree = ""; }; 6C76513D2BF37F1E00196536 /* Extension+OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+OSLog.swift"; sourceTree = ""; }; @@ -148,6 +208,10 @@ 6CC4DDC82B5574670080E7E8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 6CC4DDCA2B5574690080E7E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6CC4DDCD2B5574690080E7E8 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 6CC673792C217C4B009FB30E /* ScoopAPILogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoopAPILogin.swift; sourceTree = ""; }; + 6CC6737B2C21BA5B009FB30E /* Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = ""; }; + 6CC6737D2C233443009FB30E /* StatisticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsService.swift; sourceTree = ""; }; + 6CC6737F2C233A65009FB30E /* ScoopAPIStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoopAPIStatistics.swift; sourceTree = ""; }; 6CDB29BF2BA9735C0081037B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6CDB29C02BA97C550081037B /* Pretendard-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Black.otf"; sourceTree = ""; }; 6CDB29C12BA97C550081037B /* Pretendard-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-ExtraBold.otf"; sourceTree = ""; }; @@ -202,6 +266,81 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3544D73E2C228EDB007DBD18 /* Service */ = { + isa = PBXGroup; + children = ( + 3544D73F2C228EFC007DBD18 /* CreateDebateRoomService.swift */, +<<<<<<< HEAD + 3586B53D2C22DF67006B1458 /* RecentNewsService.swift */, + 3586B5502C23B79F006B1458 /* GetDebateRoomService.swift */, +======= + 3586B5572C23D27C006B1458 /* RecentNewsService.swift */, + 35710D7E2C23DC830041ECD2 /* GetDebateRoomService.swift */, +>>>>>>> DeviOS + ); + path = Service; + sourceTree = ""; + }; + 3544D7412C228F39007DBD18 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 3544D7422C228F54007DBD18 /* CreateDebateRoomViewModel.swift */, +<<<<<<< HEAD + 3586B53F2C22DFCB006B1458 /* RecentNewsViewModel.swift */, + 3586B5522C23B7DC006B1458 /* GetDebateRoomViewModel.swift */, +======= + 3586B5592C23D296006B1458 /* RecentNewsViewModel.swift */, + 35710D7C2C23DC6A0041ECD2 /* GetDebateRoomViewModel.swift */, +>>>>>>> DeviOS + ); + path = ViewModel; + sourceTree = ""; + }; + 3544D7442C229428007DBD18 /* Service */ = { + isa = PBXGroup; + children = ( + 3544D7452C229436007DBD18 /* ChatService.swift */, +<<<<<<< HEAD + 3586B5412C232C00006B1458 /* EndDebateService.swift */, +======= + 35710D742C23D9350041ECD2 /* EndDebateService.swift */, +>>>>>>> DeviOS + ); + path = Service; + sourceTree = ""; + }; + 35710D712C23D8F10041ECD2 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 35710D722C23D9030041ECD2 /* EndDebateViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 35710D782C23DC100041ECD2 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 35710D762C23DBEA0041ECD2 /* DebateSummaryViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 35710D792C23DC250041ECD2 /* Service */ = { + isa = PBXGroup; + children = ( + 35710D7A2C23DC360041ECD2 /* DebateSummaryService.swift */, + ); + path = Service; + sourceTree = ""; + }; + 35710D802C23DC970041ECD2 /* GetDebateRoomModel */ = { + isa = PBXGroup; + children = ( + 35710D812C23DCA40041ECD2 /* GetDebateRoom.swift */, + ); + path = GetDebateRoomModel; + sourceTree = ""; + }; 357666112BBD5494002C226A /* Splah */ = { isa = PBXGroup; children = ( @@ -210,30 +349,67 @@ path = Splah; sourceTree = ""; }; - 6C32379D2B7C374E00B699AB /* BookmarkCard */ = { +<<<<<<< HEAD + 3586B5392C22DF03006B1458 /* RecentNewsModel */ = { isa = PBXGroup; children = ( - 6C32379E2B7C376D00B699AB /* Bookmark.swift */, - 6C3237A02B7C377600B699AB /* BookmarkViewModel.swift */, - 6C3237A42B7C37D100B699AB /* BookmarkView.swift */, - 6C3237A62B7C37E500B699AB /* BookmarkListViewModel.swift */, + 3586B53B2C22DF41006B1458 /* News.swift */, +======= + 3586B5542C23D246006B1458 /* RecentNewsModel */ = { + isa = PBXGroup; + children = ( + 3586B5552C23D25A006B1458 /* News.swift */, +>>>>>>> DeviOS ); - path = BookmarkCard; + path = RecentNewsModel; sourceTree = ""; }; - 6C3237A82B7C380200B699AB /* NewsCard */ = { +<<<<<<< HEAD + 3586B5432C232C44006B1458 /* ViewModel */ = { isa = PBXGroup; children = ( - 6C7651472BF5FDB100196536 /* WebView */, - 6C94799F2BD3CC0400D5AEEB /* DetailNewsCard */, - 6C77048E2B7229B1001B17CB /* NewsListView.swift */, - 6C3237B12B7C385000B699AB /* NewsListViewModel.swift */, - 6C3237A92B7C381500B699AB /* NewsView.swift */, - 6C3237AD2B7C382E00B699AB /* DetailNewsViewModel.swift */, - 6C3237AB2B7C382200B699AB /* NewsList.swift */, - 6C7651452BF5B45A00196536 /* DetailNews.swift */, + 3586B5442C232C7C006B1458 /* EndDebateViewModel.swift */, + 6CDB29FA2BAA07B10081037B /* GPTChatViewModel.swift */, + 6CDB29FE2BAA08280081037B /* GPTChatListViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 3586B5472C2356E1006B1458 /* Service */ = { + isa = PBXGroup; + children = ( + 3586B5482C2356F2006B1458 /* DebateSummaryService.swift */, ); - path = NewsCard; + path = Service; + sourceTree = ""; + }; + 3586B54A2C23571D006B1458 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 3586B54B2C235731006B1458 /* DebateSummaryViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 3586B54D2C23B748006B1458 /* GetDebateRoomModel */ = { + isa = PBXGroup; + children = ( + 3586B54E2C23B767006B1458 /* GetDebateRoom.swift */, + ); + path = GetDebateRoomModel; + sourceTree = ""; + }; +======= +>>>>>>> DeviOS + 6C32379D2B7C374E00B699AB /* BookmarkCard */ = { + isa = PBXGroup; + children = ( + 6C32379E2B7C376D00B699AB /* Bookmarks.swift */, + 6C3237A62B7C37E500B699AB /* BookmarksListViewModel.swift */, + 6C3237A02B7C377600B699AB /* BookmarkViewModel.swift */, + 6C3237A42B7C37D100B699AB /* BookmarkView.swift */, + ); + path = BookmarkCard; sourceTree = ""; }; 6C3237B32B7C433000B699AB /* ChatType */ = { @@ -256,6 +432,15 @@ 6C41B8D62BE1048500274FA4 /* ChatList */ = { isa = PBXGroup; children = ( +<<<<<<< HEAD + 3586B54D2C23B748006B1458 /* GetDebateRoomModel */, + 3586B5392C22DF03006B1458 /* RecentNewsModel */, +======= + 35710D802C23DC970041ECD2 /* GetDebateRoomModel */, + 3586B5542C23D246006B1458 /* RecentNewsModel */, +>>>>>>> DeviOS + 3544D7412C228F39007DBD18 /* ViewModel */, + 3544D73E2C228EDB007DBD18 /* Service */, 6C41B8D92BE104A800274FA4 /* RecentNewsCardView.swift */, 6C41B8DB2BE1095800274FA4 /* ChatListView.swift */, ); @@ -388,7 +573,9 @@ 6C77048D2B7229A3001B17CB /* News */ = { isa = PBXGroup; children = ( - 6C3237A82B7C380200B699AB /* NewsCard */, + 6CC673782C217999009FB30E /* NewsList */, + 6C7651472BF5FDB100196536 /* WebView */, + 6C94799F2BD3CC0400D5AEEB /* DetailNewsCard */, ); path = News; sourceTree = ""; @@ -432,6 +619,8 @@ 6C94799F2BD3CC0400D5AEEB /* DetailNewsCard */ = { isa = PBXGroup; children = ( + 6C3237AD2B7C382E00B699AB /* DetailNewsViewModel.swift */, + 6C7651452BF5B45A00196536 /* DetailNews.swift */, 357FC6E92BCE866B00AD8915 /* DetailCardNews.swift */, ); path = DetailNewsCard; @@ -499,13 +688,28 @@ path = "Preview Content"; sourceTree = ""; }; + 6CC673782C217999009FB30E /* NewsList */ = { + isa = PBXGroup; + children = ( + 6C3237A92B7C381500B699AB /* NewsView.swift */, + 6C77048E2B7229B1001B17CB /* NewsListView.swift */, + 6C3237B12B7C385000B699AB /* NewsListViewModel.swift */, + 6C3237AB2B7C382200B699AB /* NewsList.swift */, + ); + path = NewsList; + sourceTree = ""; + }; 6CDB29F72BAA06FB0081037B /* ChatGPT */ = { isa = PBXGroup; children = ( +<<<<<<< HEAD + 3586B5432C232C44006B1458 /* ViewModel */, +======= + 35710D712C23D8F10041ECD2 /* ViewModel */, +>>>>>>> DeviOS + 3544D7442C229428007DBD18 /* Service */, 6CDB29F82BAA07350081037B /* GPTChat.swift */, - 6CDB29FA2BAA07B10081037B /* GPTChatViewModel.swift */, 6CDB29FC2BAA07FD0081037B /* GPTChatView.swift */, - 6CDB29FE2BAA08280081037B /* GPTChatListViewModel.swift */, ); path = ChatGPT; sourceTree = ""; @@ -545,6 +749,13 @@ 6CE103182BD57A1600498AA4 /* Summary */ = { isa = PBXGroup; children = ( +<<<<<<< HEAD + 3586B54A2C23571D006B1458 /* ViewModel */, + 3586B5472C2356E1006B1458 /* Service */, +======= + 35710D792C23DC250041ECD2 /* Service */, + 35710D782C23DC100041ECD2 /* ViewModel */, +>>>>>>> DeviOS 6CE103192BD57A2500498AA4 /* DebateSummaryView.swift */, ); path = Summary; @@ -571,7 +782,7 @@ children = ( 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */, 6CF130AE2BAB0C4F00A437B6 /* AuthenticatedView.swift */, - 357699432C09C7B900AD2DA4 /* AuthAPI.swift */, + 6CC6737B2C21BA5B009FB30E /* Auth.swift */, ); path = Authentication; sourceTree = ""; @@ -592,6 +803,9 @@ children = ( 6C7651432BF381B000196536 /* Log */, 6CF130B12BAB74BA00A437B6 /* NewsService.swift */, + 6C5B0C8B2C1C32C000A0D5F4 /* BookmarksService.swift */, + 357699432C09C7B900AD2DA4 /* LoginService.swift */, + 6CC6737D2C233443009FB30E /* StatisticsService.swift */, ); path = Service; sourceTree = ""; @@ -619,6 +833,9 @@ children = ( 6CF130C42BAB79DE00A437B6 /* ScoopAPI.swift */, 6CF130C62BAB7B9800A437B6 /* ScoopAPINews.swift */, + 6C5B0C892C1C308A00A0D5F4 /* ScoopAPIBookmarks.swift */, + 6CC673792C217C4B009FB30E /* ScoopAPILogin.swift */, + 6CC6737F2C233A65009FB30E /* ScoopAPIStatistics.swift */, ); path = ScoopAPI; sourceTree = ""; @@ -725,51 +942,71 @@ files = ( 6C454A882B9DB6C2006FD9D0 /* CustomNavigationBar.swift in Sources */, 6CF130BF2BAB783300A437B6 /* APIConstants.swift in Sources */, - 357699442C09C7B900AD2DA4 /* AuthAPI.swift in Sources */, + 357699442C09C7B900AD2DA4 /* LoginService.swift in Sources */, + 3544D7462C229436007DBD18 /* ChatService.swift in Sources */, 6C76513C2BF37ED300196536 /* Extension+Log.swift in Sources */, 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */, + 6C5B0C8A2C1C308A00A0D5F4 /* ScoopAPIBookmarks.swift in Sources */, 6CF130C52BAB79DE00A437B6 /* ScoopAPI.swift in Sources */, + 35710D822C23DCA40041ECD2 /* GetDebateRoom.swift in Sources */, 6CE103132BD56B1200498AA4 /* DailyReportView.swift in Sources */, 6C454A7A2B9DA67C006FD9D0 /* SignUpViewModel.swift in Sources */, + 6CC6737A2C217C4B009FB30E /* ScoopAPILogin.swift in Sources */, 6C3237AA2B7C381500B699AB /* NewsView.swift in Sources */, 6CF130C92BAB7CC200A437B6 /* BaseTargetType.swift in Sources */, 6C7651492BF5FDB900196536 /* WebView.swift in Sources */, 6CDB29FF2BAA08280081037B /* GPTChatListViewModel.swift in Sources */, + 35710D752C23D9350041ECD2 /* EndDebateService.swift in Sources */, 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */, + 3544D7402C228EFC007DBD18 /* CreateDebateRoomService.swift in Sources */, + 3586B53E2C22DF67006B1458 /* RecentNewsService.swift in Sources */, 6CDB29F92BAA07350081037B /* GPTChat.swift in Sources */, 6C41B8DC2BE1095800274FA4 /* ChatListView.swift in Sources */, 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */, 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */, 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */, 6C41B8D42BDE6D2500274FA4 /* TypePieChartView.swift in Sources */, + 35710D772C23DBEA0041ECD2 /* DebateSummaryViewModel.swift in Sources */, 6C3237A12B7C377600B699AB /* BookmarkViewModel.swift in Sources */, 6C7651462BF5B45A00196536 /* DetailNews.swift in Sources */, 6C3237AC2B7C382200B699AB /* NewsList.swift in Sources */, 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */, 6C76513E2BF37F1E00196536 /* Extension+OSLog.swift in Sources */, + 6CC6737C2C21BA5B009FB30E /* Auth.swift in Sources */, 357FC6EA2BCE866B00AD8915 /* DetailCardNews.swift in Sources */, + 3544D7432C228F54007DBD18 /* CreateDebateRoomViewModel.swift in Sources */, 6CC4DDC92B5574670080E7E8 /* ContentView.swift in Sources */, 6C3237A52B7C37D100B699AB /* BookmarkView.swift in Sources */, + 3586B53C2C22DF41006B1458 /* News.swift in Sources */, 6C3237AE2B7C382E00B699AB /* DetailNewsViewModel.swift in Sources */, 6C77048F2B7229B1001B17CB /* NewsListView.swift in Sources */, + 3586B54C2C235731006B1458 /* DebateSummaryViewModel.swift in Sources */, 357666132BBD54AA002C226A /* SplashView.swift in Sources */, 6C4F7BAD2BDE510900ED01DA /* DailyReportViewModel.swift in Sources */, - 6C3237A72B7C37E500B699AB /* BookmarkListViewModel.swift in Sources */, + 3586B5582C23D27C006B1458 /* RecentNewsService.swift in Sources */, + 6C3237A72B7C37E500B699AB /* BookmarksListViewModel.swift in Sources */, 6C454A822B9DAFA3006FD9D0 /* Path.swift in Sources */, 6C94799E2BD3C00C00D5AEEB /* Image.swift in Sources */, + 3586B5512C23B79F006B1458 /* GetDebateRoomService.swift in Sources */, 6C454A842B9DAFCB006FD9D0 /* PathType.swift in Sources */, + 3586B55A2C23D296006B1458 /* RecentNewsViewModel.swift in Sources */, 6CF130AF2BAB0C4F00A437B6 /* AuthenticatedView.swift in Sources */, + 3586B5422C232C00006B1458 /* EndDebateService.swift in Sources */, 6C3237B52B7C433D00B699AB /* ChatTypeView.swift in Sources */, 6CF130B22BAB74BA00A437B6 /* NewsService.swift in Sources */, 6C3237B22B7C385000B699AB /* NewsListViewModel.swift in Sources */, 6C454A782B9DA657006FD9D0 /* SignUpQuestionView.swift in Sources */, 6CA901962BA2EC0100E20259 /* Font.swift in Sources */, 6CF130C22BAB786600A437B6 /* APIHeaderManager.swift in Sources */, + 6C5B0C8C2C1C32C000A0D5F4 /* BookmarksService.swift in Sources */, 6CDB29FB2BAA07B10081037B /* GPTChatViewModel.swift in Sources */, - 6C32379F2B7C376D00B699AB /* Bookmark.swift in Sources */, + 3586B5492C2356F2006B1458 /* DebateSummaryService.swift in Sources */, + 6C32379F2B7C376D00B699AB /* Bookmarks.swift in Sources */, 6C454A7E2B9DAA3F006FD9D0 /* SignUpFinishView.swift in Sources */, + 6CC673802C233A65009FB30E /* ScoopAPIStatistics.swift in Sources */, 6C7704A12B722CEB001B17CB /* ProfileView.swift in Sources */, 6C4F7BAB2BDE50C600ED01DA /* DailyReportModel.swift in Sources */, + 3586B5562C23D25A006B1458 /* News.swift in Sources */, 6C3237B72B7C434600B699AB /* ChatType.swift in Sources */, 6CE103152BD56CA800498AA4 /* DailyBarChartView.swift in Sources */, 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */, @@ -777,9 +1014,21 @@ 6C77048C2B722686001B17CB /* MainTabView.swift in Sources */, 6C7704992B722A20001B17CB /* MainTabViewModel.swift in Sources */, 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */, + 3586B5402C22DFCB006B1458 /* RecentNewsViewModel.swift in Sources */, 6C41B8DA2BE104A800274FA4 /* RecentNewsCardView.swift in Sources */, + 3586B5532C23B7DC006B1458 /* GetDebateRoomViewModel.swift in Sources */, + 3586B5452C232C7C006B1458 /* EndDebateViewModel.swift in Sources */, 3509091A2C1C1248007D76A1 /* TokenManager.swift in Sources */, +<<<<<<< HEAD + 3586B54F2C23B767006B1458 /* GetDebateRoom.swift in Sources */, +======= + 35710D7D2C23DC6A0041ECD2 /* GetDebateRoomViewModel.swift in Sources */, + 35710D7B2C23DC360041ECD2 /* DebateSummaryService.swift in Sources */, + 35710D732C23D9030041ECD2 /* EndDebateViewModel.swift in Sources */, +>>>>>>> DeviOS 6CF130C72BAB7B9800A437B6 /* ScoopAPINews.swift in Sources */, + 6CC6737E2C233443009FB30E /* StatisticsService.swift in Sources */, + 35710D7F2C23DC830041ECD2 /* GetDebateRoomService.swift in Sources */, 6C7651402BF37F3400196536 /* Log.swift in Sources */, 6CE1031A2BD57A2500498AA4 /* DebateSummaryView.swift in Sources */, 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */, @@ -916,10 +1165,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"RollTheDice/Preview Content\""; - DEVELOPMENT_TEAM = 4YH4UGRTMH; + DEVELOPMENT_TEAM = NTXM48C3F8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = RollTheDice/Resources/Support/Info.plist; @@ -935,6 +1185,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = jinjihan.RollTheDice; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -946,10 +1197,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"RollTheDice/Preview Content\""; - DEVELOPMENT_TEAM = 4YH4UGRTMH; + DEVELOPMENT_TEAM = NTXM48C3F8; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = RollTheDice/Resources/Support/Info.plist; @@ -965,6 +1217,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = jinjihan.RollTheDice; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.xcworkspace/xcuserdata/soop.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/RollTheDice/RollTheDice.xcodeproj/project.xcworkspace/xcuserdata/soop.xcuserdatad/UserInterfaceState.xcuserstate index 9bf8b3f4..3b2a7226 100644 Binary files a/iOS/RollTheDice/RollTheDice.xcodeproj/project.xcworkspace/xcuserdata/soop.xcuserdatad/UserInterfaceState.xcuserstate and b/iOS/RollTheDice/RollTheDice.xcodeproj/project.xcworkspace/xcuserdata/soop.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift index 3e61f628..474be745 100644 --- a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift +++ b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift @@ -20,7 +20,7 @@ struct RollTheDiceApp: App { var signUpViewModel = SignUpViewModel() var newsListViewModel = NewsListViewModel() - @StateObject var bookmarkListViewModel = BookmarkListViewModel() + var bookmarkListViewModel = BookmarksListViewModel() init() { KakaoSDK.initSDK(appKey: "ff09b3d83873ed4e320f0d6bc90759d6") @@ -40,13 +40,13 @@ struct RollTheDiceApp: App { .environmentObject(signUpViewModel) case .completedSignUp: NavigationStack(path: $pathModel.paths) { - MainTabView(newsListViewModel: newsListViewModel) + MainTabView(newsListViewModel: newsListViewModel, bookmarksListViewModel: bookmarkListViewModel) .navigationDestination(for: PathType.self, destination: { pathType in // 각 뷰마다 .navigationBarBackButtonHidden() 설정하기! switch pathType { case .chatView(isAiMode: true) : - GPTChatView() + GPTChatView(topic: "",roomId: 74) .navigationBarBackButtonHidden() case .chatView(isAiMode: false): @@ -66,6 +66,8 @@ struct RollTheDiceApp: App { DebateSummaryView() case .webView(let url): WebView(urlToLoad: url) + case .createdebateroom: + GPTChatView(topic: "",roomId: 74) } }) } @@ -79,13 +81,13 @@ struct RollTheDiceApp: App { // NavigationStack(path: $pathModel.paths) { // MainTabView(newsListViewModel: newsListViewModel) // .navigationDestination(for: PathType.self, destination: { pathType in -// +// // // 각 뷰마다 .navigationBarBackButtonHidden() 설정하기! // switch pathType { // case .chatView(isAiMode: true) : // GPTChatView() // .navigationBarBackButtonHidden() -// +// // case .chatView(isAiMode: false): // Text("user") // .navigationBarBackButtonHidden() @@ -104,10 +106,10 @@ struct RollTheDiceApp: App { // } // }) // } -// +// // .environmentObject(pathModel) -// -// +// +// // } else { // AuthenticatedView() // .environmentObject(pathModel) diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/API/APIHeaderManager.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/API/APIHeaderManager.swift index 81c204d8..cc337cd0 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Domain/API/APIHeaderManager.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/API/APIHeaderManager.swift @@ -12,8 +12,8 @@ public class APIHeaderManager { static let shared = APIHeaderManager() let contentType: String = "application/json" - let scoopHost: String = "ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080" +// let scoopHost: String = "ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080" + let scoopHost: String = "roll-the-dice.store:8080" } - // http://ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080/swagger-ui/index.html diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPI.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPI.swift index 0784adf7..966bd589 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPI.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPI.swift @@ -8,5 +8,6 @@ import Foundation enum ScoopAPI { - static let baseURL = "http://ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080" +// static let baseURL = "http://ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080" + static let baseURL = "http://roll-the-dice.store:8080" } diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIBookmarks.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIBookmarks.swift new file mode 100644 index 00000000..1905043d --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIBookmarks.swift @@ -0,0 +1,12 @@ +// +// ScoopAPIBookmark.swift +// RollTheDice +// +// Created by Subeen on 6/14/24. +// + +import Foundation + +public enum ScoopAPIBookmarks { + public static let bookmarks = String("/bookmarks") +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPILogin.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPILogin.swift new file mode 100644 index 00000000..0aa1c3ba --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPILogin.swift @@ -0,0 +1,12 @@ +// +// ScoopAPILogin.swift +// RollTheDice +// +// Created by Subeen on 6/18/24. +// + +import Foundation + +public enum ScoopAPILogin { + public static let login = String("/login") +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIStatistics.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIStatistics.swift new file mode 100644 index 00000000..22424bdf --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/ScoopAPI/ScoopAPIStatistics.swift @@ -0,0 +1,15 @@ +// +// ScoopAPIStatistics.swift +// RollTheDice +// +// Created by Subeen on 6/20/24. +// + +import Foundation + +public enum ScoopAPIStatistics { +// public static let statistics = String("/statistics") + public static let statisticsPerDates = String("/statistics/per-dates") + public static let statisticsCategories = String("/statistics/categories") + +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/Service/BookmarksService.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/BookmarksService.swift new file mode 100644 index 00000000..1d24ede8 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/BookmarksService.swift @@ -0,0 +1,83 @@ +// +// BookmarksService.swift +// RollTheDice +// +// Created by Subeen on 6/14/24. +// + +import Foundation +import Moya + +enum BookmarksService { + case bookmarksIsChecked(newsId: Int, accessToken: String) ///뉴스 북마크 여부 조회 + case saveBookmarks(newsId: Int, accessToken: String) ///북마크 저장 + case deleteBookmarks(newsId: Int, accessToken: String) ///북마크 삭제 + case allBookmarks(page: Int, size: Int, accessToken: String) ///북마크 전체 조회 +} + +extension BookmarksService: BaseTargetType { + + var baseURL: URL { + return URL(string: ScoopAPI.baseURL)! + } + + var path: String { + switch self { + case .bookmarksIsChecked(let newsId, _), + .saveBookmarks(let newsId, _), + .deleteBookmarks(let newsId, _) + : + return "\(ScoopAPIBookmarks.bookmarks)/\(newsId)" + + case .allBookmarks: + return ScoopAPIBookmarks.bookmarks + } + } + + var method: Moya.Method { + switch self { + case .bookmarksIsChecked: + return .get + case .saveBookmarks: + return .post + case .deleteBookmarks: + return .delete + case .allBookmarks: + return .get + } + } + + var task: Moya.Task { + switch self { + case .bookmarksIsChecked(_, _), + .deleteBookmarks(_, _): + let parameters : [String : Any] = [:] + return .requestParameters(parameters: parameters, encoding: URLEncoding.default) + + case .saveBookmarks(_, _): + return .requestPlain + + case .allBookmarks(let page, let size, _): + let parameters : [String : Any] = [ + "page" : page, + "size" : size, + ] + return .requestParameters(parameters: parameters, + encoding: URLEncoding.queryString) + } + } + + var headers: [String : String]? { + let accessToken: String + switch self { + case .bookmarksIsChecked(_, let accessToken), + .saveBookmarks(_, let accessToken), + .deleteBookmarks(_, let accessToken), + .allBookmarks(_, _, let accessToken): + return [ + "Authorization": "Bearer \(accessToken)", + "X-Content-Type_Options" : "nosniff" + ] + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/Service/LoginService.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/LoginService.swift new file mode 100644 index 00000000..5a82988d --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/LoginService.swift @@ -0,0 +1,56 @@ +// +// AuthAPI.swift +// RollTheDice +// +// Created by 신예진 on 5/31/24. +// + +import Foundation +import Moya + +enum LoginService { + case login(request: AuthRequestModel) +} + +//struct LoginService { +// static let provider = MoyaProvider() +//} + +extension LoginService: BaseTargetType { + + var baseURL: URL { + return URL(string: ScoopAPI.baseURL)! + } + + var path: String { + switch self { + case .login: + return ScoopAPILogin.login + } + } + + var method: Moya.Method { + switch self { + case .login: + return .post + } + } + + var task: Moya.Task { + switch self { + case .login(let request): +// let parameters : [String : Any] = [ +// "token" : token, +// "socialType" : socialType, +// ] +// return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + return .requestJSONEncodable(request) + } + } + + var headers: [String: String]? { + return [ + "X-Content-Type_Options" : "nosniff" + ] + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Domain/Service/StatisticsService.swift b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/StatisticsService.swift new file mode 100644 index 00000000..9ff4993b --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Domain/Service/StatisticsService.swift @@ -0,0 +1,63 @@ +// +// StatisticsService.swift +// RollTheDice +// +// Created by Subeen on 6/20/24. +// + +import Foundation +import Moya + +enum StatisticsService { + case recentSevenPerDate(accessToken: String) /// 최근 일주일 날짜별 뉴스 조회수 조회 + case statisticsCategory(accessToken: String) /// 카테고리별 조회수 조회 +} + +extension StatisticsService: BaseTargetType { + var baseURL: URL { + return URL(string: ScoopAPI.baseURL)! + } + + var path: String { + switch self { + case .recentSevenPerDate(let accessToken): + return ScoopAPIStatistics.statisticsPerDates + + case .statisticsCategory(let accessToken): + return ScoopAPIStatistics.statisticsCategories + } + } + + var method: Moya.Method { + switch self { + case .recentSevenPerDate, + .statisticsCategory: + return .get + + } + } + + var task: Moya.Task { + switch self { + case .recentSevenPerDate(_), + .statisticsCategory(_): + + let parameters : [String : Any] = [ : ] + return .requestPlain + } + } + + var headers: [String : String]? { + let accessToken: String + + switch self { + case .recentSevenPerDate(let accessTokenValue), + .statisticsCategory(let accessTokenValue): + accessToken = accessTokenValue + return [ + "Authorization": "Bearer \(accessToken)", + "X-Content-Type_Options" : "nosniff" + ] + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift index 30f880a2..b464d017 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift @@ -20,4 +20,7 @@ enum PathType: Hashable { case mypageView // 마이페이지뷰 case webView(url: String) + + case createdebateroom //토론방 생성 + } diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift index 99deabbe..ac4c22df 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift @@ -8,13 +8,13 @@ import Foundation import SwiftUI -enum NewsType { - case politics // 정치 - case economy // 경제 - case society // 사회 - case living // 생활/문화 - case world // 세계 - case science // IT/과학 +enum NewsType: String, Codable { + case politics = "정치" // 정치 + case economy = "경제" // 경제 + case society = "사회" // 사회 + case living = "생활/문화" // 생활/문화 + case world = "세계" // 세계 + case science = "IT/과학" // IT/과학 var desciption: String { switch self { diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/Auth.swift b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/Auth.swift new file mode 100644 index 00000000..61d65c61 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/Auth.swift @@ -0,0 +1,23 @@ +// +// Auth.swift +// RollTheDice +// +// Created by Subeen on 6/18/24. +// + +import Foundation + +struct AuthModel: Codable { + let accessToken: String? + let refreshToken: String? + +// enum CodingKeys: String, CodingKey { +// case Authorization +// case AuthorizationRefresh = "Authorization-refresh" +// } +} + +struct AuthRequestModel: Codable { + let token: String? + let socialType: String? +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthAPI.swift b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthAPI.swift deleted file mode 100644 index 54af7c2c..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthAPI.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// AuthAPI.swift -// RollTheDice -// -// Created by 신예진 on 5/31/24. -// - -import Foundation -import Moya - -struct AuthProvider { - static let provider = MoyaProvider() -} - -enum AuthTarget: TargetType { - case login(token: String) - - var baseURL: URL { - return URL(string: "http://ec2-13-124-191-244.ap-northeast-2.compute.amazonaws.com:8080")! - } - - var path: String { - switch self { - case .login: - return "/login" - } - } - - var method: Moya.Method { - switch self { - case .login: - return .post - } - } - - var task: Task { - switch self { - case .login(let token): - return .requestParameters(parameters: ["accessToken": token], encoding: JSONEncoding.default) - } - } - -// var headers: [String: String]? { -// return ["Content-Type": "application/json"] -// } - - var headers: [String: String]? { - var headers = [String: String]() - if let token = TokenManager.shared.accessToken { - headers["Authorization"] = "Bearer \(token)" - } - return headers - } -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticatedView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticatedView.swift index 72eb4ff6..759b8bd8 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticatedView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticatedView.swift @@ -122,45 +122,8 @@ struct AuthenticatedView: View { .frame(height: 50) } -// Button { -// if (UserApi.isKakaoTalkLoginAvailable()) { -// UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in -// print(oauthToken) -// print(error) -// } -// } else { -// UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in -// print(oauthToken) -// print(error) -// } -// } -// -// } label: { -// Image(.kakaoSignInBtn01) -// .resizable() -// .aspectRatio(contentMode: .fit) -// .frame(height: 50) -// } Button { - if UserApi.isKakaoTalkLoginAvailable() { - UserApi.shared.loginWithKakaoTalk { (oauthToken, error) in - if let error = error { - print("Kakao Login Error: \(error)") - } else if let oauthToken = oauthToken { - print("Kakao Login Success: \(oauthToken)") - TokenManager.shared.accessToken = oauthToken.accessToken - authViewModel.authenticationState = .completedSignUp } - } - } else { - UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in - if let error = error { - print("Kakao Login Error: \(error)") - } else if let oauthToken = oauthToken { - print("Kakao Login Success: \(oauthToken)") - TokenManager.shared.accessToken = oauthToken.accessToken - authViewModel.authenticationState = .completedSignUp } - } - } + authViewModel.loginWithKakao() } label: { Image(.kakaoSignInBtn01) .resizable() diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticationViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticationViewModel.swift index 521d3965..89cfa146 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticationViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Authentication/AuthenticationViewModel.swift @@ -8,6 +8,9 @@ import Foundation import KakaoSDKAuth import KakaoSDKUser +import Moya +import Combine +import CombineMoya enum AuthenticationState { @@ -20,10 +23,25 @@ enum AuthenticationState { // 로그인 상태에 따라 화면 분기처리 var authenticationState: AuthenticationState = .unauthenticated var isLoading = false + + + + var authModel: AuthModel? + var authCancellable: AnyCancellable? + + let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + func authModelToViewModel(_ list: AuthModel) { + self.authModel = list + } } extension AuthenticationViewModel { + + func loginWithKakao() { + + print("loginWithKakao") if (UserApi.isKakaoTalkLoginAvailable()) { UserApi.shared.loginWithKakaoTalk { (oauthToken, error) in if let error = error { @@ -34,39 +52,71 @@ extension AuthenticationViewModel { UserApi.shared.me { (user, error) in if let error = error { print("Kakao user info error: \(error)") + self.authenticationState = .unauthenticated } else { // 사용자 정보 처리 - self.authenticationState = .authenticated +// self.authenticationState = .authenticated + + // oauthToken 카카오에서 발급한 accessToken +// self.loginToBackend(with: oauthToken!.accessToken, socialType: "KAKAO") + let authRequest = AuthRequestModel(token: oauthToken!.accessToken, socialType: "KAKAO") + self.loginToBackend(authRequest: authRequest) } } } } } else { // Kakao 계정으로 로그인할 수 없는 경우 처리 - self.authenticationState = .unauthenticated + UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in + if let error = error { + print("Kakao Login Error: \(error)") + self.authenticationState = .unauthenticated + } else if let oauthToken = oauthToken { + print("Kakao Login Success: \(oauthToken)") + self.authenticationState = .authenticated + + let authRequest = AuthRequestModel(token: oauthToken.accessToken, socialType: "KAKAO") + self.loginToBackend(authRequest: authRequest) + + } + } + } } } extension AuthenticationViewModel { - func loginToBackend(with token: String) { - AuthProvider.provider.request(.login(token: token)) { result in + + func loginToBackend(authRequest: AuthRequestModel) { + print("loginToBackend \(authRequest.token)") + + if let cancellable = authCancellable { + cancellable.cancel() + } + + authCancellable = provider.requestWithProgressPublisher( + .login(request: authRequest) + ) + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: AuthModel.self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in switch result { - case .success(let response): - do { - let json = try response.mapJSON() - print("Login response: \(json)") - self.authenticationState = .completedSignUp - } catch { - print("Login error: \(error)") - self.authenticationState = .unauthenticated - } + case .finished: + print("서버 토큰 발급 연결 성공") + + // TODO: 여기 지워야 함. 로그인 -> 회원가입 -> 메인 절차 + self.authenticationState = .completedSignUp case .failure(let error): - print("Login error: \(error)") - self.authenticationState = .unauthenticated + Log.network("network error 서버 토큰 발급 연결 실패", error.localizedDescription) } - } + }, receiveValue: { [weak self] response in + self?.authModelToViewModel(response) + print("서버 토큰 : \(response.accessToken)") + + TokenManager.shared.accessToken = response.accessToken + }) } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmark.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmark.swift deleted file mode 100644 index 39455078..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmark.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Bookmark.swift -// RollTheDice -// -// Created by Subeen on 2/12/24. -// - -import Foundation - -struct Bookmark: Hashable { - var id: UUID = UUID() - var title: String - var date: String - var image: String - var content: String - var isBookmarked: Bool - - init( - title: String = "", - date: String = "", - image: String = "", - content: String = "", - isBookmarked: Bool = false - ) { - self.title = title - self.date = date - self.image = image - self.content = content - self.isBookmarked = isBookmarked - } -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkListViewModel.swift deleted file mode 100644 index 8d2a5731..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkListViewModel.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// BookmarkListViewModel.swift -// RollTheDice -// -// Created by Subeen on 2/12/24. -// - -import Foundation - -public class BookmarkListViewModel: ObservableObject { - @Published var bookmarkList: [Bookmark] - - init( - bookmarkList: [Bookmark] = [ - .init(title: "2024년 ‘소셜 미디어 다이어트’를 위해 바꿔볼 것", date: "2023년12월3일", image: "exampleNews", content: "2024년으로 접어든지 한 달이 넘었다. 하지만 올 해가 어떻게 흘러갈지 예측하기는 쉽지 않다. 한 가지 확실한 것은 정치적으로 매우 중요한 해라는 점이다. 미국과 러시아, 우크라이나, 방글라데시, 인도, 대만, 한국, 남아프리카공화국, 유럽의회, 영국에서 선거가 치러질 예정이다.", isBookmarked: false), - .init(title: "앤스로픽 최신 AI 모델 '클로드3' 출시", date: "2023년3월13일", image: "https://imgnews.pstatic.net/image/014/2024/03/05/0005151141_001_20240305101610000.jpg?type=w647", content: "오픈AI의 대항마 앤스로픽이 생성형 인공지능(AI) 최신 모델 '클로드'(Claude)3를 선보이면서 생성형 AI 주도권을 잡기 위한 경쟁이 다시 뜨거워지고 있다. 앤스로픽은 지난 한 해 동안 구글과 세일즈포스, 아마존 등에서 총 73억 달러(약 9조7309억 원)를 투자받고 '클로드3'를 내놨는데 오픈AI의 챗GPT-4를 능가한다고 도발했다.", isBookmarked: true), - .init(title: "SK C&C, 외부 전문가 대거 영입… “신성장 동력 강화”", date: "2023년2월13일", image: "https://imgnews.pstatic.net/image/366/2024/03/05/0000975131_001_20240305093504301.jpg?type=w647", content: "SK C&C는 국내외 신성장 동력 강화를 위해 인공지능(AI)·클라우드·디지털 팩토리·ESG(환경·사회·지배구조) 등 4대 성장 사업과 디지털 컨설팅 중심으로 외부 전문가를 대거 영입해 전진 배치했다고 5일 밝혔다.", isBookmarked: false), - .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", date: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), - - .init(title: "이종호 과기장관 5년 뒤 구축될 바이오파운드리에 산학연 역량 모아야", date: "2023년3월13일", image: "https://imgnews.pstatic.net/image/003/2024/03/05/NISI20231030_0001398722_web_20231030141808_20240305103108748.jpg?type=w647", content: "정부가 내년부터 합성생물학의 필수 인프라인 '국가 바이오파운드리' 구축에 본격 나서는 가운데 이종호 과학기술정보통신부 장관이 한국생명공학연구원에서 운영 중인 사전연구용 시설을 찾아 구체적인 인프라 구축 계획 등을 점검했다.이 장관은 5일 합성생물학 육성을 위해 바이오파운드리 연구현장을 방문하고, 산·학·연 전문가들을 만났다. 이번 방문은 지난해 미국, 영국과의 정상회담 시 논의됐던 첨단바이오 협력을 위한 후속조치의 일환으로 추진됐다.", isBookmarked: false), - .init(title: "SW산업 육성에 7308억 예산투자, SaaS 등 육성", date: "2023년3월13일", image: "https://imgnews.pstatic.net/image/008/2024/03/05/0005007507_001_20240305135301539.jpg?type=w647", content: "SW(소프트웨어) 산업의 SaaS(서비스형 소프트웨어) 전환 등 SW산업 육성을 위해 정부 예산 7308억원이 투자된다. 강도현 과학기술정보통신부 제2차관은 5일 오전 서울 을지로 더존비즈온 을지사옥에서 열린 'AI(인공지능) 일상화, SW도 이제 서비스형 SW'라는 주제로 열린 현장 간담회에서 정부도 대한민국 SW산업의 미래가 SaaS에 있다고 생각하고 올해 'SaaS 혁신펀드'를 새롭게 조성하는 등 SaaS 육성 및 기존 SW 기업의 SaaS 전환을 위해 다양한 지원을 추진 중이라며 이같이 밝혔다.", isBookmarked: false), - .init(title: "파수, 기업용 AI ‘엔터프라이즈 LLM’ 출시", date: "2023년3월13일", image: "https://imgnews.pstatic.net/image/018/2024/03/05/0005685566_001_20240305103201036.jpg?type=w647", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), - .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", date: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), - .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", date: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), - .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", date: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), - ] - ) { - self.bookmarkList = bookmarkList - } -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkView.swift index c62ed345..f6ddfdd7 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarkView.swift @@ -8,16 +8,14 @@ import SwiftUI struct BookmarkView: View { - - @State var bookmark: Bookmark - + @State var bookmark: Bookmarks var body: some View { ZStack { Color.gray07.ignoresSafeArea(.all) VStack(alignment: .leading, spacing: 0) { - Text(bookmark.title) + Text(bookmark.title ?? "네트워크 통신 중") .font(.system(size: 24, weight: .bold)) .foregroundStyle(.basicWhite) .multilineTextAlignment(.leading) @@ -26,7 +24,7 @@ struct BookmarkView: View { Spacer() HStack { - AsyncImage(url: URL(string: bookmark.image)) { image in + AsyncImage(url: URL(string: bookmark.thumbnailURL ?? "")) { image in image .resizable() .scaledToFill() @@ -54,7 +52,8 @@ struct BookmarkView: View { .overlay { Button { // TODO: 북마크 설정 / 해제 - bookmark.isBookmarked.toggle() +// bookmark.isBookmarked.toggle() + } label: { Image(systemName: "bookmark.fill") @@ -62,14 +61,10 @@ struct BookmarkView: View { .frame(width: 40, height: 65) // .fixedSize() - .foregroundStyle(bookmark.isBookmarked ? .primary01 : .gray01) + .foregroundStyle(bookmark.isBookmarked ?? true ? .primary01 : .gray01) } .offset(x: 110, y: -130) } } } - -#Preview { - BookmarkView(bookmark: .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", date: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false)) -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmarks.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmarks.swift new file mode 100644 index 00000000..13e6518b --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/Bookmarks.swift @@ -0,0 +1,46 @@ +// +// Bookmark.swift +// RollTheDice +// +// Created by Subeen on 2/12/24. +// + +import Foundation + + +// MARK: - Bookmark +struct Bookmarks: Codable, Identifiable { + let id = UUID().uuidString + let newsId: Int + let title, thumbnailURL, postDate: String? + let isBookmarked: Bool? + + enum CodingKeys: String, CodingKey { + case newsId = "id" + case title, thumbnailURL, postDate, isBookmarked + } +} + + +//struct Bookmarks: Hashable { +// var id: UUID = UUID() +// var title: String +// var date: String +// var image: String +// var content: String +// var isBookmarked: Bool +// +// init( +// title: String = "", +// date: String = "", +// image: String = "", +// content: String = "", +// isBookmarked: Bool = false +// ) { +// self.title = title +// self.date = date +// self.image = image +// self.content = content +// self.isBookmarked = isBookmarked +// } +//} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarksListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarksListViewModel.swift new file mode 100644 index 00000000..f31a3395 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkCard/BookmarksListViewModel.swift @@ -0,0 +1,183 @@ +// +// BookmarkListViewModel.swift +// RollTheDice +// +// Created by Subeen on 2/12/24. +// + +import Foundation +import Combine +import CombineMoya +import Moya +import SwiftUI + +@Observable class BookmarksListViewModel{ + var bookmarksList: [Bookmarks]? + var bookmarksCancellable: AnyCancellable? + + let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + func bookmarksToViewModel(_ list: [Bookmarks]) { + self.bookmarksList = list + } +} + +extension BookmarksListViewModel { + + /// 북마크 전체 조회 + public func getAllBookmarksData(page: Int, size: Int) { + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = bookmarksCancellable { + cancellable.cancel() + } + + bookmarksCancellable = provider.requestWithProgressPublisher( + .allBookmarks( + page: page, + size: size, + accessToken: accessToken + ) + + ) + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: [Bookmarks].self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("all bookmarks 조회 성공") + case .failure(let error): + Log.network("network error", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + self?.bookmarksToViewModel(response) + print(response) + }) + } + + /// 뉴스 북마크 여부 조회 + public func isBookmark(newsId: Int) -> Bool { + + var isBookmarked: Bool = false + + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return isBookmarked + } + + if let cancellable = bookmarksCancellable { + cancellable.cancel() + } + + bookmarksCancellable = provider.requestWithProgressPublisher( + .saveBookmarks( + newsId: newsId, + accessToken: accessToken + ) + ) + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: Bookmarks.self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("bookmark 여부 확인 성공") + case .failure(let error): + Log.network("network error bookmark 여부 확인 실패", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + print(response) + + if response.isBookmarked ?? false { + isBookmarked = true + } else { + isBookmarked = false + } + }) + + return isBookmarked + } + + /// 북마크 저장 + public func saveBookmark(newsId: Int) { + print("saveBookmark \(newsId)") + + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = bookmarksCancellable { + cancellable.cancel() + } + + bookmarksCancellable = provider.requestWithProgressPublisher( + .saveBookmarks( + newsId: newsId, + accessToken: accessToken + ) + ) +// .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) +// .decode(type: Bookmarks.self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("bookmarks 저장") + case .failure(let error): + Log.network("network error bookmark 여부 확인 실패", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + print(response) + if response.response?.statusCode == 201 { + print("bookmarks 저장 성공") + } else { + print("bookmarks 저장 실패") + } + }) + + } + + /// 북마크 삭제 + public func deleteBookmark(newsId: Int) { + print("saveBookmark \(newsId)") + + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = bookmarksCancellable { + cancellable.cancel() + } + + bookmarksCancellable = provider.requestWithProgressPublisher( + .deleteBookmarks( + newsId: newsId, + accessToken: accessToken + ) + ) + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("bookmarks 삭제") + case .failure(let error): + Log.network("network error bookmark 여부 확인 실패", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + print(response) + if response.response?.statusCode == 204 { + print("bookmarks 삭제 성공") + } else { + print("bookmarks 삭제 실패") + } + }) + + } + +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift index ad0be59d..d7bb4c80 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift @@ -8,8 +8,10 @@ import SwiftUI struct BookmarkListView: View { + @EnvironmentObject var pathModel: PathModel - @StateObject var bookmarkListViewModel : BookmarkListViewModel + var bookmarkListViewModel : BookmarksListViewModel + var bookmarkPage: Int? @State var selectedIndex: Int = 0 var columns: [GridItem] = [ GridItem(), GridItem()] @@ -39,16 +41,19 @@ struct BookmarkListView: View { Spacer() } } + .task { + bookmarkListViewModel.getAllBookmarksData(page: 0, size: 10) + } .navigationBarBackButtonHidden() } + @ViewBuilder var bookmarkListView: some View { - ScrollViewReader { value in ScrollView(.horizontal, showsIndicators: false) { LazyHGrid(rows: columns, spacing: 10) { - ForEach(bookmarkListViewModel.bookmarkList, id: \.self) { bookmark in + ForEach(bookmarkListViewModel.bookmarksList ?? []) { bookmark in BookmarkView(bookmark: bookmark) // .onTapGesture { // withAnimation { @@ -62,33 +67,9 @@ struct BookmarkListView: View { } } } - -// private struct BookmarkListContentView: View { -// @StateObject var bookmarkListViewModel: BookmarkListViewModel -// var columns: [GridItem] = [ GridItem(), GridItem()] -// -// fileprivate var body: some View { -// ScrollViewReader { value in -// ScrollView(.horizontal, showsIndicators: false) { -// LazyHGrid(rows: columns, spacing: 10) { -// ForEach(bookmarkListViewModel.bookmarkList, id: \.self) { bookmark in -// BookmarkView(bookmark: bookmark) -//// .onTapGesture { -//// withAnimation { -//// selectedIndex = index -//// value.scrollTo(index) -//// } -//// } -// } -// } -// .padding(.vertical, 90) -// } -// } -// } -// } } #Preview { - BookmarkListView(bookmarkListViewModel: BookmarkListViewModel()) - .environmentObject(BookmarkListViewModel()) + BookmarkListView(bookmarkListViewModel: BookmarksListViewModel()) + .environmentObject(PathModel()) } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChat.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChat.swift index 8112a66b..4fd8da8e 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChat.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChat.swift @@ -8,9 +8,10 @@ import Foundation -struct GPTChat: Hashable { +struct GPTChat: Hashable, Identifiable{ var title: String var messages: [Message] + var id: UUID = .init() // id 속성 추가 init( title: String = "", @@ -26,3 +27,4 @@ struct Message: Hashable, Identifiable { var content: String var isUser: Bool } + diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatListViewModel.swift index db009df2..c99720f8 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatListViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatListViewModel.swift @@ -7,25 +7,127 @@ import Foundation import OpenAI +import Combine +import Moya + +//class GPTChatListViewModel: ObservableObject { +// @Published var chatList: [GPTChat] +// private var cancellables = Set() +// private let provider = MoyaProvider() +// +// // OpenAI API 토큰 관리 +// private let openAI: OpenAI +// +//// // TODO: Token Hidden +//// /// token 추가해야 함 +//// let openAI = OpenAI(apiToken: "") +// +// init( +// topic: String, +// chatList: [GPTChat] = [], +//// .init(title: "토론제목", messages: []), +//// ], +// apiToken: String = "" // 깃허브 보안 이슈상으로 제거 +// ) { +//// self.chatList = chatList +// self.chatList = chatList.isEmpty ? [GPTChat(title: topic, messages: [])] : chatList +// self.openAI = OpenAI(apiToken: apiToken) +// } +//} +// +//extension GPTChatListViewModel { +// /// OpenAI +// func sendNewMessage(index: Int, content: String) { +// print("call sendNewMessage func") +// +// let userMessage = Message(content: content, isUser: true) +// self.chatList[index].messages.append(userMessage) +// +// getBotReply(index: index) +// } +// +// func getBotReply(index: Int) { +// print("call getBotReply func") +// +// let messages = self.chatList[index].messages.map { +// Chat(role: $0.isUser ? .user : .system, content: $0.content) +// } +// +// // openAI.chats( +// // query: .init( +// // model: .gpt3_5Turbo, +// // messages: self.chatList[index].messages.map( +// // {Chat(role: .user, content: $0.content)})) +// // ) { result in +// // switch result { +// // case .success(let success): +// // guard let choice = success.choices.first else { +// // return +// // } +// // let message = choice.message.content +// // +// // DispatchQueue.main.async { +// // self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) +// // print("gpt msg: \(self.chatList[index].messages[1])") +// // } +// // +// // case .failure(let failure): +// // print(failure) +// // } +// // } +// openAI.chats( +// query: .init( +// model: .gpt3_5Turbo, +// messages: messages +// ) +// ) { [weak self] result in +// guard let self = self else { return } +// +// switch result { +// case .success(let success): +// guard let choice = success.choices.first else { +// return +// } +// let message = choice.message.content +// +// DispatchQueue.main.async { +// self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) +// print("gpt msg: \(self.chatList[index].messages.last?.content ?? "Error")") +// } +// +// case .failure(let failure): +// print(failure) +// } +// } +// } +//} class GPTChatListViewModel: ObservableObject { @Published var chatList: [GPTChat] - - // TODO: Token Hidden - /// token 추가해야 함 - let openAI = OpenAI(apiToken: "") + private var cancellables = Set() + private let provider = MoyaProvider() + private let debateprovider = MoyaProvider() + private let enddebateprovider = MoyaProvider() + private let openAI: OpenAI + @Published var topic: String = "" init( - chatList: [GPTChat] = [ - .init(title: "111111111", messages: []), - ] + topic: String, + chatList: [GPTChat] = [], + apiToken: String = "" //깃허브 보안 이슈로 계속 제거해야됨. ) { - self.chatList = chatList + self.chatList = chatList.isEmpty ? [GPTChat(title: topic, messages: [])] : chatList + self.openAI = OpenAI(apiToken: apiToken) + + // CreateDebateRoomService를 통해 주제를 받아와 초기 메시지를 설정 + CreateDebateRoomViewModel().createDebate(topic: topic) { debateTopic in + let initialMessage = Message(content: "안녕하세요! 오늘의 토론 주제는 '\(debateTopic)'입니다. 이 주제에 대해 어떻게 생각하시나요?", isUser: false) + DispatchQueue.main.async { + self.chatList[0].messages.append(initialMessage) + } + } } -} - -extension GPTChatListViewModel { - /// OpenAI + func sendNewMessage(index: Int, content: String) { print("call sendNewMessage func") @@ -38,12 +140,18 @@ extension GPTChatListViewModel { func getBotReply(index: Int) { print("call getBotReply func") + let messages = self.chatList[index].messages.map { + Chat(role: $0.isUser ? .user : .system, content: $0.content) + } + openAI.chats( query: .init( model: .gpt3_5Turbo, - messages: self.chatList[index].messages.map( - {Chat(role: .user, content: $0.content)})) - ) { result in + messages: messages + ) + ) { [weak self] result in + guard let self = self else { return } + switch result { case .success(let success): guard let choice = success.choices.first else { @@ -53,7 +161,7 @@ extension GPTChatListViewModel { DispatchQueue.main.async { self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) - print("gpt msg: \(self.chatList[index].messages[1])") + print("gpt msg: \(self.chatList[index].messages.last?.content ?? "Error")") } case .failure(let failure): diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatView.swift index 4fd938b0..33146759 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatView.swift @@ -10,15 +10,27 @@ import SwiftUI struct GPTChatView: View { @EnvironmentObject var pathModel: PathModel - @StateObject var chatListViewModel = GPTChatListViewModel() - @State var selectedChat: GPTChat? = .init(title: "hi", messages: [.init(content: "안녕하세요", isUser: false)]) +// @State var selectedChat: GPTChat? = .init(title: "토론제목입니다", messages: [.init(content: "ChatGPT와의 토론!", isUser: false)]) + @State var selectedChat: GPTChat? + @StateObject var chatListViewModel = GPTChatListViewModel(topic: "토론") + @StateObject var createDebateRoomViewModel = CreateDebateRoomViewModel() + @StateObject private var viewModel = EndDebateViewModel() + var roomId: Int + + init(topic: String, roomId: Int) { + self.roomId = roomId + _chatListViewModel = StateObject(wrappedValue: GPTChatListViewModel(topic: topic)) + _selectedChat = State(initialValue: GPTChat(title: topic, messages: [.init(content: "\(createDebateRoomViewModel.topic) ChatGPT와의 토론!", isUser: false)])) + } var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) VStack { CustomNavigationBar(title: selectedChat?.title ?? "", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {pathModel.paths.popLast()})]) - MessageTitleView(chatListViewModel: chatListViewModel, selectedChat: $selectedChat) + + MessageTitleView(chatListViewModel: chatListViewModel, selectedChat: $selectedChat, roomId: roomId) + } } } @@ -29,6 +41,10 @@ struct GPTChatView: View { @Binding var selectedChat: GPTChat? @State var string: String = "" @State var index: Int? = 0 + @StateObject private var viewModel = EndDebateViewModel() + var roomId: Int + @EnvironmentObject var pathModel: PathModel + fileprivate var body: some View { if chatListViewModel.chatList.isEmpty { @@ -52,6 +68,37 @@ struct GPTChatView: View { MessageCellView(message: message) } } + + //MARK : 토론 종료 버튼 + Button(action: { + print("토론종료버튼 눌림!") + viewModel.endDebate(roomId: "\(roomId)") { success in + if success { + print("토론이 종료되었습니다.") +// pathModel.paths.append(.debateSummaryView) + pathModel.paths.popLast() +<<<<<<< HEAD + +======= +>>>>>>> DeviOS + } else { + print("토론 종료에 실패했습니다.") + } + } + + + }) { + Text("토론종료") + .font(.title3.weight(.semibold)) + .foregroundColor(.white) + .padding() + .background(Color.primary01) + .cornerRadius(15) + .shadow(radius: 4, x: 0, y: 4) + } + .frame(width: 100, height: 30) + .padding(.bottom, 20) + Divider() HStack { @@ -67,6 +114,8 @@ struct GPTChatView: View { string = "" } label: { Image(systemName: "paperplane") + .foregroundColor(.primary01) + } } .padding() @@ -84,7 +133,7 @@ struct GPTChatView: View { } private struct TitleCellView: View { - var title: String + var title: String fileprivate var body: some View { HStack { Text(title) @@ -130,6 +179,163 @@ struct GPTChatView: View { -#Preview { - GPTChatView() -} +//#Preview { +// GPTChatView() +//} + +//struct GPTChatView: View { +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// @EnvironmentObject var pathModel: PathModel +// @State var selectedChat: GPTChat? +// @StateObject var chatListViewModel: GPTChatListViewModel +// @StateObject var createDebateRoomViewModel = CreateDebateRoomViewModel() +// +// init(topic: String) { +// _chatListViewModel = StateObject(wrappedValue: GPTChatListViewModel(topic: topic)) +// _selectedChat = State(initialValue: GPTChat(title: topic, messages: [.init(content: "\(topic)ChatGPT와의 토론!", isUser: false)])) +// } +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// var body: some View { +// ZStack { +// Color.backgroundDark.ignoresSafeArea(.all) +// VStack { +// CustomNavigationBar(title: selectedChat?.title ?? "", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {pathModel.paths.popLast()})]) +// MessageTitleView(chatListViewModel: chatListViewModel, selectedChat: $selectedChat) +// } +// } +// } +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// private struct MessageTitleView: View { +// @ObservedObject var chatListViewModel: GPTChatListViewModel +// @Binding var selectedChat: GPTChat? +// @State var string: String = "" +// @State var index: Int? = 0 +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// fileprivate var body: some View { +// if chatListViewModel.chatList.isEmpty { +// // TODO: 채팅방 생성 뷰 +// } else { +// HStack { +// ScrollView { +// ForEach(chatListViewModel.chatList.indices, id: \.self) { index in +// Button { +// selectedChat = chatListViewModel.chatList[index] +// } label: { +// TitleCellView(title: chatListViewModel.chatList[index].title) +// } +// } +// } +// if selectedChat != nil { +// VStack { +// ScrollView { +// ForEach(selectedChat?.messages ?? []) { message in +// MessageCellView(message: message) +// } +// } +// Divider() +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// HStack { +// TextField("Message...", text: $string, axis: .vertical) +// .padding(5) +// .background(Color.gray.opacity(0.1)) +// .foregroundStyle(.basicWhite) +// .cornerRadius(15) +// Button { +// if let index = chatListViewModel.chatList.firstIndex(where: { $0.id == selectedChat?.id }) { +// chatListViewModel.sendNewMessage(index: index, content: string) +// string = "" +// print("왜 채팅하기 버튼 안 눌려?") +// } +// } label: { +// Image(systemName: "paperplane") +// } +// } +// .padding() +// } +// } else { +// // TODO: 채팅방 선택 유도 뷰 +// Spacer() +// } +// } +// } +// } +// } +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// private struct TitleCellView: View { +// var title: String +// fileprivate var body: some View { +// HStack { +// Text(title) +// .multilineTextAlignment(.leading) +// .padding(.horizontal) +// .background(Color.basicBlack) +// .foregroundStyle(.basicWhite) +// .clipShape(RoundedRectangle(cornerRadius: 15)) +// } +// } +// } +<<<<<<< HEAD +// +// private struct MessageCellView: View { +// var message: Message +// +======= +// +// private struct MessageCellView: View { +// var message: Message +// +>>>>>>> DeviOS +// fileprivate var body: some View { +// Group { +// if message.isUser { +// HStack { +// Spacer() +// Text(message.content) +// .multilineTextAlignment(.leading) +// .padding() +// .background(Color.blue) +// .foregroundStyle(.white) +// .clipShape(RoundedRectangle(cornerRadius: 15)) +// } +// } else { +// HStack { +// Text(message.content) +// .padding() +// .background(Color.orange) +// .foregroundStyle(.white) +// .clipShape(RoundedRectangle(cornerRadius: 15)) +// Spacer() +// } +// } +// } +// } +// } +//} +<<<<<<< HEAD +======= + +>>>>>>> DeviOS diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/ChatService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/ChatService.swift new file mode 100644 index 00000000..2b14f250 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/ChatService.swift @@ -0,0 +1,45 @@ +// +// ChatService.swift +// RollTheDice +// +// Created by 신예진 on 6/19/24. +// + +import Foundation +import Moya + +enum ChatService { + case sendMessageToAI(roomId: Int, message: String) + case sendMessageToHuman(roomId: Int, message: String) +} + +extension ChatService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .sendMessageToAI(let roomId, _): + return "/debates/\(roomId)/ai" + case .sendMessageToHuman(let roomId, _): + return "/debates/\(roomId)/human" + } + } + + var method: Moya.Method { + return .post + } + + var task: Task { + switch self { + case .sendMessageToAI(_, let message), .sendMessageToHuman(_, let message): + return .requestParameters(parameters: ["message": message], encoding: JSONEncoding.default) + } + } + + var headers: [String: String]? { + guard let token = TokenManager.shared.accessToken else { return nil } + return ["Authorization": "Bearer \(token)"] + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/EndDebateService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/EndDebateService.swift new file mode 100644 index 00000000..af1fdf4f --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/Service/EndDebateService.swift @@ -0,0 +1,43 @@ +// +// EndDebateService.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation +import Moya + +enum EndDebateService { + case endDebate(roomId: String) +} + +extension EndDebateService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .endDebate(let roomId): + return "/debates/\(roomId)" + } + } + + var method: Moya.Method { + return .patch + } + + var task: Task { + return .requestPlain + } + + var headers: [String : String]? { + guard let token = TokenManager.shared.accessToken else { return nil } + return ["Authorization": "Bearer \(token)"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/EndDebateViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/EndDebateViewModel.swift new file mode 100644 index 00000000..a8f42303 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/EndDebateViewModel.swift @@ -0,0 +1,26 @@ +// +// EndDebateViewModel.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation +import Moya + +class EndDebateViewModel: ObservableObject { + private let provider = MoyaProvider() + + func endDebate(roomId: String, completion: @escaping (Bool) -> Void) { + provider.request(.endDebate(roomId: roomId)) { result in + switch result { + case .success: + print("토론 종료 성공") + completion(true) + case .failure: + print("토론 종료 실패") + completion(false) + } + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/GPTChatListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/GPTChatListViewModel.swift new file mode 100644 index 00000000..a723228a --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/GPTChatListViewModel.swift @@ -0,0 +1,172 @@ +// +// GPTChatListViewModel.swift +// RollTheDice +// +// Created by Subeen on 3/20/24. +// + +import Foundation +import OpenAI +import Combine +import Moya + +//class GPTChatListViewModel: ObservableObject { +// @Published var chatList: [GPTChat] +// private var cancellables = Set() +// private let provider = MoyaProvider() +// +// // OpenAI API 토큰 관리 +// private let openAI: OpenAI +// +//// // TODO: Token Hidden +//// /// token 추가해야 함 +//// let openAI = OpenAI(apiToken: "") +// +// init( +// topic: String, +// chatList: [GPTChat] = [], +//// .init(title: "토론제목", messages: []), +//// ], +// apiToken: String = "" // 깃허브 보안 이슈상으로 제거 +// ) { +//// self.chatList = chatList +// self.chatList = chatList.isEmpty ? [GPTChat(title: topic, messages: [])] : chatList +// self.openAI = OpenAI(apiToken: apiToken) +// } +//} +// +//extension GPTChatListViewModel { +// /// OpenAI +// func sendNewMessage(index: Int, content: String) { +// print("call sendNewMessage func") +// +// let userMessage = Message(content: content, isUser: true) +// self.chatList[index].messages.append(userMessage) +// +// getBotReply(index: index) +// } +// +// func getBotReply(index: Int) { +// print("call getBotReply func") +// +// let messages = self.chatList[index].messages.map { +// Chat(role: $0.isUser ? .user : .system, content: $0.content) +// } +// +// // openAI.chats( +// // query: .init( +// // model: .gpt3_5Turbo, +// // messages: self.chatList[index].messages.map( +// // {Chat(role: .user, content: $0.content)})) +// // ) { result in +// // switch result { +// // case .success(let success): +// // guard let choice = success.choices.first else { +// // return +// // } +// // let message = choice.message.content +// // +// // DispatchQueue.main.async { +// // self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) +// // print("gpt msg: \(self.chatList[index].messages[1])") +// // } +// // +// // case .failure(let failure): +// // print(failure) +// // } +// // } +// openAI.chats( +// query: .init( +// model: .gpt3_5Turbo, +// messages: messages +// ) +// ) { [weak self] result in +// guard let self = self else { return } +// +// switch result { +// case .success(let success): +// guard let choice = success.choices.first else { +// return +// } +// let message = choice.message.content +// +// DispatchQueue.main.async { +// self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) +// print("gpt msg: \(self.chatList[index].messages.last?.content ?? "Error")") +// } +// +// case .failure(let failure): +// print(failure) +// } +// } +// } +//} + +class GPTChatListViewModel: ObservableObject { + @Published var chatList: [GPTChat] + private var cancellables = Set() + private let provider = MoyaProvider() + private let debateprovider = MoyaProvider() + private let enddebateprovider = MoyaProvider() + private let openAI: OpenAI + @Published var topic: String = "" + + init( + topic: String, + chatList: [GPTChat] = [], + apiToken: String = "" //깃허브 보안 이슈로 계속 제거해야됨. + ) { + self.chatList = chatList.isEmpty ? [GPTChat(title: topic, messages: [])] : chatList + self.openAI = OpenAI(apiToken: apiToken) + + // CreateDebateRoomService를 통해 주제를 받아와 초기 메시지를 설정 + CreateDebateRoomViewModel().createDebate(topic: topic) { debateTopic in + let initialMessage = Message(content: "안녕하세요! 오늘의 토론 주제는 '\(debateTopic)'입니다. 이 주제에 대해 어떻게 생각하시나요?", isUser: false) + DispatchQueue.main.async { + self.chatList[0].messages.append(initialMessage) + } + } + } + + func sendNewMessage(index: Int, content: String) { + print("call sendNewMessage func") + + let userMessage = Message(content: content, isUser: true) + self.chatList[index].messages.append(userMessage) + + getBotReply(index: index) + } + + func getBotReply(index: Int) { + print("call getBotReply func") + + let messages = self.chatList[index].messages.map { + Chat(role: $0.isUser ? .user : .system, content: $0.content) + } + + openAI.chats( + query: .init( + model: .gpt3_5Turbo, + messages: messages + ) + ) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success(let success): + guard let choice = success.choices.first else { + return + } + let message = choice.message.content + + DispatchQueue.main.async { + self.chatList[index].messages.append(.init(content: message ?? "Error", isUser: false)) + print("gpt msg: \(self.chatList[index].messages.last?.content ?? "Error")") + } + + case .failure(let failure): + print(failure) + } + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/GPTChatViewModel.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/GPTChatViewModel.swift rename to iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatGPT/ViewModel/GPTChatViewModel.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift index 67ed690b..10b180de 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift @@ -10,6 +10,12 @@ import SwiftUI struct ChatListView: View { @EnvironmentObject var pathModel: PathModel + @StateObject private var newsViewModel = RecentNewsViewModel() + @StateObject private var viewModel = DebateSummaryViewModel() + @StateObject private var endDebateViewModel = EndDebateViewModel() + @State private var roomId: String = "" // EndDebateViewModel로부터 받아올 roomId + @StateObject private var getdebateroomviewModel = GetDebateRoomViewModel() + var body: some View { ZStack { @@ -31,13 +37,28 @@ struct ChatListView: View { Text("최근 본 뉴스") .foregroundStyle(.basicWhite) .font(.pretendardBold32) + // HStack { + // RecentNewsCardView() + // Spacer() + // RecentNewsCardView() + // Spacer() + // RecentNewsCardView() + // } HStack { - RecentNewsCardView() - Spacer() - RecentNewsCardView() - Spacer() - RecentNewsCardView() + if newsViewModel.news.isEmpty { + Text("최근 읽은 뉴스를 불러오는 중...") + .onAppear { + print("뷰가 나타남 - 최근 읽은 뉴스 조회 시작") + newsViewModel.fetchViewedHistory() + } + } else { + ForEach(newsViewModel.news.prefix(3), id: \.id) { news in + RecentNewsCardView(news: news) + Spacer() + } + } } + .padding() } } @@ -47,173 +68,288 @@ struct ChatListView: View { Text("채팅방") .foregroundStyle(.basicWhite) .font(.pretendardBold32) - debateChatCellView - debateChatCellView2 - debateChatCellView3 - debateChatCellView4 - debateChatCellView5 + // debateChatCellView + // debateChatCellView2 + // debateChatCellView3 + // debateChatCellView4 + // debateChatCellView5 + ForEach(getdebateroomviewModel.debates) { debate in + DebateChatCellView(debate: debate) + } + } + .onAppear { + print("채팅방 불러와짐") + getdebateroomviewModel.fetchDebates(page: 5, size: 10) } } - var debateChatCellView: some View { - HStack { - HStack(alignment: .center, spacing: 16) { - Text("🏛️") - .padding(.leading, 26) - .font(.pretendardBold32) - Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") - .foregroundStyle(.gray07) - .font(.pretendardBold24) - .padding(.vertical, 24) - - Spacer() + struct DebateChatCellView: View { + let debate: GetDebateRoom + let emojis = ["🏛️", "🔥", "📌", "⭐️", "🧬", "👩🏼‍💻", "🎨", "🎬", "💌", "🔗", "👀"] + + @EnvironmentObject var pathModel: PathModel + + var body: some View { + let randomEmoji = emojis.randomElement() ?? "🏛️" // 이모지가 선택되지 않으면 기본값을 설정합니다. + + HStack { + HStack(alignment: .center, spacing: 16) { +// Text("🏛️") +// .padding(.leading, 26) +// .font(.pretendardBold32) + Text(randomEmoji) + .padding(.leading, 26) + .font(.pretendardBold32) + Text(debate.topic.isEmpty ? "토론을 시작해주세요!" : debate.topic) + .foregroundStyle(.gray07) + .font(.pretendardBold24) + .padding(.vertical, 24) + + Spacer() + Button { + pathModel.paths.append(.chatView(isAiMode: true)) + } label: { + Image(systemName: "chevronRight") + .foregroundColor(.gray01) + } + } + .background(.gray01) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .padding(.trailing, 16) + Button { - pathModel.paths.append(.chatView(isAiMode: true)) + pathModel.paths.append(.debateSummaryView) } label: { - Image(.chevronRight) + Image("quote.bubble") + .foregroundColor(.gray01) + } + .frame(width: 80, height: 80) + .background(.gray01) + .clipShape(RoundedRectangle(cornerRadius: 8)) } - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.trailing, 16) - - //TODO: 버튼 영역 수정하기 - Button { - pathModel.paths.append(.debateSummaryView) - } label: { - Image(.chevronLeft) -// .background(.gray01) - - } - .frame(width: 80, height: 80) - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) } } - var debateChatCellView2: some View { - HStack { - HStack(alignment: .center, spacing: 16) { - Text("📱") - .padding(.leading, 26) - .font(.pretendardBold32) - Text("모친 내친 한미약품 형제… 2644억 상속세 마련방안은 `아직`") - .foregroundStyle(.gray07) - .font(.pretendardBold24) - .padding(.vertical, 24) - - Spacer() - Image(.chevronRight) - } - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.trailing, 16) - - //TODO: 버튼 영역 수정하기 - Button { - pathModel.paths.append(.debateSummaryView) - } label: { - Image(.chevronLeft) -// .background(.gray01) - - } - .frame(width: 80, height: 80) - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } - var debateChatCellView3: some View { - HStack { - HStack(alignment: .center, spacing: 16) { - Text("💌") - .padding(.leading, 26) - .font(.pretendardBold32) - Text("[ET단상]한바탕 휩쓴 방산 해킹 사건, 보안의 다른 \'답\'을 찾아야 할 때") - .foregroundStyle(.gray07) - .font(.pretendardBold24) - .padding(.vertical, 24) - - Spacer() - Image(.chevronRight) - } - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.trailing, 16) - - //TODO: 버튼 영역 수정하기 - Button { - pathModel.paths.append(.debateSummaryView) - } label: { - Image(.chevronLeft) -// .background(.gray01) - - } - .frame(width: 80, height: 80) - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } - var debateChatCellView4: some View { - HStack { - HStack(alignment: .center, spacing: 16) { - Text("📱") - .padding(.leading, 26) - .font(.pretendardBold32) - Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") - .foregroundStyle(.gray07) - .font(.pretendardBold24) - .padding(.vertical, 24) - - Spacer() - Image(.chevronRight) - } - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.trailing, 16) - - //TODO: 버튼 영역 수정하기 - Button { - pathModel.paths.append(.debateSummaryView) - } label: { - Image(.chevronLeft) -// .background(.gray01) - - } - .frame(width: 80, height: 80) - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } - var debateChatCellView5: some View { - HStack { - HStack(alignment: .center, spacing: 16) { - Text("🏛️") - .padding(.leading, 26) - .font(.pretendardBold32) - Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") - .foregroundStyle(.gray07) - .font(.pretendardBold24) - .padding(.vertical, 24) - - Spacer() - Image(.chevronRight) - } - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.trailing, 16) - - //TODO: 버튼 영역 수정하기 - Button { - pathModel.paths.append(.debateSummaryView) - } label: { - Image(.chevronLeft) +// var debateChatCellView: some View { +// HStack { +// HStack(alignment: .center, spacing: 16) { +// Text("🏛️") +// .padding(.leading, 26) +// .font(.pretendardBold32) +// Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .padding(.vertical, 24) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// Spacer() +// Button { +// pathModel.paths.append(.chatView(isAiMode: true)) +// } label: { +// Image(systemName: "chevronRight") +// .foregroundColor(.gray01) +// } +// } +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// .padding(.trailing, 16) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// //TODO: 버튼 영역 수정하기 +// Button { +// pathModel.paths.append(.debateSummaryView) +// } label: { +// Image("quote.bubble") +// .foregroundColor(.gray01) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// } +// .frame(width: 80, height: 80) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// } +// var debateChatCellView2: some View { +// HStack { +// HStack(alignment: .center, spacing: 16) { +// Text("📱") +// .padding(.leading, 26) +// .font(.pretendardBold32) +// Text("모친 내친 한미약품 형제… 2644억 상속세 마련방안은 `아직`") +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .padding(.vertical, 24) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// Spacer() +// Image(.chevronRight) +// } +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// .padding(.trailing, 16) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// //TODO: 버튼 영역 수정하기 +// Button { +// pathModel.paths.append(.debateSummaryView) +// } label: { +// Image(.chevronLeft) +//// .background(.gray01) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// } +// .frame(width: 80, height: 80) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// } +// var debateChatCellView3: some View { +// HStack { +// HStack(alignment: .center, spacing: 16) { +// Text("💌") +// .padding(.leading, 26) +// .font(.pretendardBold32) +// Text("[ET단상]한바탕 휩쓴 방산 해킹 사건, 보안의 다른 \'답\'을 찾아야 할 때") +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .padding(.vertical, 24) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// Spacer() +// Image(.chevronRight) +// } +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// .padding(.trailing, 16) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// //TODO: 버튼 영역 수정하기 +// Button { +// pathModel.paths.append(.debateSummaryView) +// } label: { +// Image(.chevronLeft) +//// .background(.gray01) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// } +// .frame(width: 80, height: 80) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// } +// var debateChatCellView4: some View { +// HStack { +// HStack(alignment: .center, spacing: 16) { +// Text("📱") +// .padding(.leading, 26) +// .font(.pretendardBold32) +// Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .padding(.vertical, 24) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// Spacer() +// Image(.chevronRight) +// } +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// .padding(.trailing, 16) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// //TODO: 버튼 영역 수정하기 +// Button { +// pathModel.paths.append(.debateSummaryView) +// } label: { +// Image(.chevronLeft) +//// .background(.gray01) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// } +// .frame(width: 80, height: 80) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// } +// var debateChatCellView5: some View { +// HStack { +// HStack(alignment: .center, spacing: 16) { +// Text("🏛️") +// .padding(.leading, 26) +// .font(.pretendardBold32) +// Text("삼성, 갤럭시Z플립6 두뇌 전량 퀄컴칩 탑재하나") +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .padding(.vertical, 24) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// Spacer() +// Image(.chevronRight) +// } +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// .padding(.trailing, 16) +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// //TODO: 버튼 영역 수정하기 +// Button { +// pathModel.paths.append(.debateSummaryView) +// } label: { +// Image(systemName: "quote.bubble") // .background(.gray01) - - } - .frame(width: 80, height: 80) - .background(.gray01) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } +<<<<<<< HEAD +// +======= +// +>>>>>>> DeviOS +// } +// .frame(width: 80, height: 80) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// } } #Preview { diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/GetDebateRoomModel/GetDebateRoom.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/GetDebateRoomModel/GetDebateRoom.swift new file mode 100644 index 00000000..a24fbd01 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/GetDebateRoomModel/GetDebateRoom.swift @@ -0,0 +1,15 @@ +// +// GetDebateRoom.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation + + +struct GetDebateRoom: Identifiable, Codable { + let id: Int + let topic: String + let isClosed: Bool? +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Model/News.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Model/News.swift new file mode 100644 index 00000000..aa9f64f6 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Model/News.swift @@ -0,0 +1,8 @@ +// +// News.swift +// RollTheDice +// +// Created by 신예진 on 6/19/24. +// + +import Foundation diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift index 9fa0b186..b07a3df6 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift @@ -5,23 +5,124 @@ // Created by Subeen on 4/30/24. // +//import SwiftUI +// +//struct RecentNewsCardView: View { +// @EnvironmentObject var pathModel: PathModel +// @StateObject private var viewModel = CreateDebateRoomViewModel() +// @StateObject private var newsviewModel = RecentNewsViewModel() +// @State private var topic: String = "" +// +// var body: some View { +// HStack { +// titleView +// } +//// .frame(width: 260, height: 244) +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } +// +// var titleView: some View { +// ZStack { +// VStack(alignment: .center, spacing: 20) { +// Text("📌") +// .font(.title) +// Text("내 안경 못 봤어?\" 핸드폰이 알려준다…구글 \'일상 AI\' 공략 [팩플]") +// .multilineTextAlignment(.center) +// .foregroundStyle(.gray07) +// .font(.pretendardBold24) +// .frame(width: 240, height: 96) +// .padding(.horizontal, 10) +// .padding(.vertical, 30) +// .background(.gray01) +// .clipShape(RoundedRectangle(cornerRadius: 2)) +// .overlay { +// RoundedRectangle(cornerRadius: 2) +// .stroke(Color.gray05, lineWidth: 1.0) +// } +// .shadow(color: .basicBlack.opacity(0.1), radius: 2) +// Button { +// print("버튼 클릭됨 - 주제: \(topic)") +// viewModel.createDebate(topic: topic) +// pathModel.paths.append(.createdebateroom) +// } label: { +// Text("토론 시작하기") +// .foregroundStyle(.basicWhite) +// .font(.pretendardRegular14) +// .padding(.horizontal, 38) +// .padding(.vertical, 10) +// .background(.primary01) +// .clipShape(RoundedRectangle(cornerRadius: 16)) +// } +// } +// +// if let debateID = viewModel.debateID { +// Text("토론방 ID: \(debateID)") +// } +// +// if let errorMessage = viewModel.errorMessage { +// Text("Error: \(errorMessage)") +// .foregroundColor(.red) +// } +// } +// .padding(.horizontal, 20) +// .padding(.top, 24) +// .padding(.bottom, 32) +// .background(.gray02) +// } +//} +// +//#Preview { +// RecentNewsCardView() +//} + +// +// RecentNewsCardView.swift +// RollTheDice +// +// Created by Subeen on 4/30/24. +// + import SwiftUI +import Combine struct RecentNewsCardView: View { - var body: some View { - HStack { - titleView - } -// .frame(width: 260, height: 244) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } + let news: News + @EnvironmentObject var pathModel: PathModel +// @StateObject private var newsViewModel = RecentNewsViewModel() + @StateObject private var debateRoomViewModel = CreateDebateRoomViewModel() + @State private var topic: String = "" + +// var body: some View { +// VStack { +// if let news = newsViewModel.news.first { +// HStack { +// titleView(news: news) +// } +// .clipShape(RoundedRectangle(cornerRadius: 8)) +// } else { +// Text("최근 읽은 뉴스를 불러오는 중...") +// .onAppear { +// print("뷰가 나타남 - 최근 읽은 뉴스 조회 시작") +// newsViewModel.fetchViewedHistory() +// } +// } +// } +// } +// + var body: some View { + HStack { + titleView + } + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + - var titleView: some View { + var titleView : some View { ZStack { VStack(alignment: .center, spacing: 20) { Text("📌") .font(.title) - Text("내 안경 못 봤어?\" 핸드폰이 알려준다…구글 \'일상 AI\' 공략 [팩플]") + Text(news.title) .multilineTextAlignment(.center) .foregroundStyle(.gray07) .font(.pretendardBold24) @@ -36,7 +137,14 @@ struct RecentNewsCardView: View { } .shadow(color: .basicBlack.opacity(0.1), radius: 2) Button { - + print("버튼 클릭됨 - 주제: \(news.title)") + topic = news.title + debateRoomViewModel.createDebate(topic: topic) { debateTopic in + pathModel.paths.append(.createdebateroom) + print("토론방 생성 완료 - 주제: \(debateTopic)") + + } +// pathModel.paths.append(.createdebateroom) } label: { Text("토론 시작하기") .foregroundStyle(.basicWhite) @@ -47,6 +155,15 @@ struct RecentNewsCardView: View { .clipShape(RoundedRectangle(cornerRadius: 16)) } } + + if let debateID = debateRoomViewModel.debateID { + Text("토론방 ID: \(debateID)") + } + + if let errorMessage = debateRoomViewModel.errorMessage { + Text("Error: \(errorMessage)") + .foregroundColor(.red) + } } .padding(.horizontal, 20) .padding(.top, 24) @@ -55,6 +172,4 @@ struct RecentNewsCardView: View { } } -#Preview { - RecentNewsCardView() -} + diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsModel/News.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsModel/News.swift new file mode 100644 index 00000000..c2204feb --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsModel/News.swift @@ -0,0 +1,14 @@ +// +// News.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation + +//MARK : 최근 읽은 뉴스 Model +struct News: Identifiable, Codable { + let id: Int + let title: String +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/CreateDebateRoomService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/CreateDebateRoomService.swift new file mode 100644 index 00000000..bd80ce2f --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/CreateDebateRoomService.swift @@ -0,0 +1,50 @@ +// +// CreateDebateRoomService.swift +// RollTheDice +// +// Created by 신예진 on 6/19/24. +// + +import Foundation +import Moya + +enum CreateDebateRoomService { + case createDebate(topic: String) +} + +extension CreateDebateRoomService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .createDebate: + return "/debates" + } + } + + var method: Moya.Method { + switch self { + case .createDebate: + return .post + } + } + + var task: Task { + switch self { + case .createDebate(let topic): + let parameters: [String: Any] = ["topic": topic] + return .requestParameters(parameters: parameters, encoding: JSONEncoding.default) + } + } + + var headers: [String: String]? { + guard let token = TokenManager.shared.accessToken else { return nil } + return ["Authorization": "Bearer \(token)"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/GetDebateRoomService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/GetDebateRoomService.swift new file mode 100644 index 00000000..49601d7d --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/GetDebateRoomService.swift @@ -0,0 +1,50 @@ +// +// GetDebateRoomService.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +//MARK : 토론방 전체 조회 +import Foundation +import Moya + +enum GetDebateRoomService { + case getDebates(page: Int, size: Int) +} + +extension GetDebateRoomService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .getDebates: + return "/debates" + } + } + + var method: Moya.Method { + switch self { + case .getDebates: + return .get + } + } + + var task: Task { + switch self { + case let .getDebates(page, size): + return .requestParameters(parameters: ["page": page, "size": size], encoding: URLEncoding.queryString) + } + } + + var headers: [String: String]? { + guard let token = TokenManager.shared.accessToken else { return nil } + return ["Authorization": "Bearer \(token)"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/RecentNewsService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/RecentNewsService.swift new file mode 100644 index 00000000..4250f45e --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/Service/RecentNewsService.swift @@ -0,0 +1,53 @@ +// +// RecentNewsService.swift +// RollTheDice +// +<<<<<<< HEAD +// Created by 신예진 on 6/19/24. +======= +// Created by 신예진 on 6/20/24. +>>>>>>> DeviOS +// + +import Foundation +import Moya + +//MARK : 최근읽은 뉴스 관련 Service +enum RecentNewsService { + case getViewedHistory +} + +extension RecentNewsService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .getViewedHistory: + return "/news/viewed-history" + } + } + + var method: Moya.Method { + switch self { + case .getViewedHistory: + return .get + } + } + + var task: Task { + return .requestPlain + } + + var headers: [String : String]? { + guard let token = TokenManager.shared.accessToken else { + return nil + } + return ["Authorization": "Bearer \(token)"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/CreateDebateRoomViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/CreateDebateRoomViewModel.swift new file mode 100644 index 00000000..c19b0ae7 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/CreateDebateRoomViewModel.swift @@ -0,0 +1,50 @@ +// +// CreateDebateRoomViewModel.swift +// RollTheDice +// +// Created by 신예진 on 6/19/24. +// + +import Foundation +import Moya +import Combine + +class CreateDebateRoomViewModel: ObservableObject { + private let provider = MoyaProvider() + @Published var debateID: Int? + @Published var errorMessage: String? + @Published var topic: String = "" + + + func createDebate(topic: String, completion: @escaping (String) -> Void) { + print("API 호출 시작 - 토론 주제: \(topic)") + self.topic = topic // 토론 주제 저장 + provider.request(.createDebate(topic: topic)) { result in + switch result { + case .success(let response): + print("API 호출 성공 - 응답 코드: \(response.statusCode)") + do { + let json = try JSONSerialization.jsonObject(with: response.data, options: []) as? [String: Any] + if let id = json?["id"] as? Int { + DispatchQueue.main.async { + self.debateID = id + print("토론방 생성 성공 - ID: \(id)") + completion(topic) // 주제를 반환하여 초기 메시지 설정 + } + } + } catch { + DispatchQueue.main.async { + self.errorMessage = "Failed to parse response" + print("응답 파싱 중 에러: \(error.localizedDescription)") + } + } + case .failure(let error): + DispatchQueue.main.async { + self.errorMessage = error.localizedDescription + print("API 호출 실패 - 에러: \(error.localizedDescription)") + } + } + } + } +} + diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/GetDebateRoomViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/GetDebateRoomViewModel.swift new file mode 100644 index 00000000..b003450b --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/GetDebateRoomViewModel.swift @@ -0,0 +1,34 @@ +// +// GetDebateRoomViewModel.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation +import Combine +import Moya + +class GetDebateRoomViewModel: ObservableObject { + @Published var debates: [GetDebateRoom] = [] + private var cancellables = Set() + private let provider = MoyaProvider() + + func fetchDebates(page: Int, size: Int) { + provider.requestPublisher(.getDebates(page: page, size: size)) + .map(\.data) + .decode(type: [GetDebateRoom].self, decoder: JSONDecoder()) + .sink(receiveCompletion: { completion in + switch completion { + case .failure(let error): + print("Error fetching debates: \(error)") + case .finished: + break + } + }, receiveValue: { [weak self] debates in + self?.debates = debates + print("Fetched debates: \(debates)") // 로그 확인용 + }) + .store(in: &cancellables) + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/RecentNewsViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/RecentNewsViewModel.swift new file mode 100644 index 00000000..28d942da --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ViewModel/RecentNewsViewModel.swift @@ -0,0 +1,42 @@ +// +// RecentNewsViewModel.swift +// RollTheDice +// +<<<<<<< HEAD +// Created by 신예진 on 6/19/24. +======= +// Created by 신예진 on 6/20/24. +>>>>>>> DeviOS +// + +import Foundation +import Combine +import Moya +import Combine + +class RecentNewsViewModel: ObservableObject { + @Published var news: [News] = [] + private var cancellables = Set() + private let provider = MoyaProvider() + + func fetchViewedHistory() { + print("최근 읽은 뉴스 조회 요청 시작") + provider.requestPublisher(.getViewedHistory) + .map([News].self) + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { completion in + switch completion { + case .failure(let error): + print("최근 읽은 뉴스 조회 실패 - 에러: \(error)") + case .finished: + print("최근 읽은 뉴스 조회 완료") + } + }, receiveValue: { [weak self] news in + self?.news = news + for item in news { + print("최근 읽은 뉴스 조회 성공 - 뉴스 id: \(item.id), 뉴스 제목: \(item.title)") + } + }) + .store(in: &cancellables) + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift index 62319d1e..55ba3742 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift @@ -9,6 +9,9 @@ import SwiftUI struct DebateSummaryView: View { @EnvironmentObject var pathModel: PathModel + @StateObject private var viewModel = DebateSummaryViewModel() + @StateObject private var endDebateViewModel = EndDebateViewModel() + @State private var roomId: String = "" // EndDebateViewModel로부터 받아오는 roomid var body: some View { ZStack { @@ -53,7 +56,7 @@ struct DebateSummaryView: View { } - // TODO: 말풍선 수정하기 + // TODO: 말풍선 수정하기 .overlay { VStack(spacing: 20) { Rectangle() @@ -115,3 +118,4 @@ struct DebateSummaryView: View { #Preview { DebateSummaryView() } + diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/Service/DebateSummaryService.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/Service/DebateSummaryService.swift new file mode 100644 index 00000000..b161bd3d --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/Service/DebateSummaryService.swift @@ -0,0 +1,43 @@ +// +// DebateSummaryService.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation +import Moya + +enum DebateSummaryService { + case getSummary(roomId: Int) +} + +extension DebateSummaryService: TargetType { + var baseURL: URL { + return URL(string: "http://roll-the-dice.store:8080")! + } + + var path: String { + switch self { + case .getSummary(let roomId): + return "/debates/summary/\(roomId)" + } + } + + var method: Moya.Method { + return .post + } + + var task: Task { + return .requestPlain + } + + var headers: [String : String]? { + guard let token = TokenManager.shared.accessToken else { return nil } + return ["Authorization": "Bearer \(token)"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/ViewModel/DebateSummaryViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/ViewModel/DebateSummaryViewModel.swift new file mode 100644 index 00000000..638529c4 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/ViewModel/DebateSummaryViewModel.swift @@ -0,0 +1,46 @@ +// +// DebateSummaryViewModel.swift +// RollTheDice +// +// Created by 신예진 on 6/20/24. +// + +import Foundation +import Combine +import Moya + +class DebateSummaryViewModel: ObservableObject { + @Published var summary: String? + @Published var roomId: Int? + @Published var isLoading: Bool = false + @Published var errorMessage: String? + + private let provider = MoyaProvider() + + func fetchSummary(roomId: Int) { + isLoading = true + provider.request(.getSummary(roomId: roomId)) { result in + self.isLoading = false + switch result { + case .success(let response): + do { + let json = try JSONSerialization.jsonObject(with: response.data, options: []) + if let dict = json as? [String: Any], let summary = dict["summary"] as? String { + self.summary = summary + self.roomId = roomId + print("토론 요약 성공") + } else { + self.errorMessage = "Invalid response format" + print("토론 요약 유효하지 않은 포맷") + } + } catch { + self.errorMessage = "Failed to parse response" + print("토론 요약 reponse 못 받아옴") + } + case .failure(let error): + self.errorMessage = error.localizedDescription + print("토론 요약 오류/실패") + } + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift index 242bc53b..491633c9 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift @@ -11,6 +11,7 @@ struct MainTabView: View { @EnvironmentObject private var pathModel: PathModel var newsListViewModel: NewsListViewModel + var bookmarksListViewModel: BookmarksListViewModel @EnvironmentObject var authViewModel: AuthenticationViewModel @@ -37,7 +38,7 @@ struct MainTabView: View { } .tag(0) - NewsListView(newsListViewModel: newsListViewModel) + NewsListView(newsListViewModel: newsListViewModel, bookmarksViewModel: bookmarksListViewModel) .tabItem { Image(systemName: "square.3.layers.3d.down.left") } @@ -59,7 +60,7 @@ struct MainTabView: View { } #Preview { - MainTabView(newsListViewModel: NewsListViewModel()) + MainTabView(newsListViewModel: NewsListViewModel(), bookmarksListViewModel: BookmarksListViewModel()) .environmentObject(PathModel()) // .environmentObject(NewsListViewModel()) } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNewsCard/DetailCardNews.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailCardNews.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNewsCard/DetailCardNews.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailCardNews.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNews.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailNews.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNews.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailNews.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNewsViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailNewsViewModel.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/DetailNewsViewModel.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/DetailNewsCard/DetailNewsViewModel.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListViewModel.swift deleted file mode 100644 index 2b9cbfbb..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListViewModel.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// NewsListViewModel.swift -// RollTheDice -// -// Created by Subeen on 2/9/24. -// - -import Foundation -import Combine -import CombineMoya -import Moya -import SwiftUI - -// Macro -> iOS 17.0 이상부터. @Published 안 해도 됨. -// 사용할 때 var 형식으로 해도 됨. -@Observable class NewsListViewModel { - var newsList: [NewsList]? -// var selectedNewsId: Int? - var currentPage = 0 - var newsCancellable: AnyCancellable? - - - //TODO: Plugin Settings - let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) - - func newsToViewModel(_ list: [NewsList]) { - self.newsList = list - } -} - -extension NewsListViewModel { - public func getAllNewsData(page: Int, size: Int) { - -// let accessToken = "" - // TokenManager에서 accessToken 가져오기 - guard let accessToken = TokenManager.shared.accessToken else { - print("Access token 사용 불가능...") - return - } - - if let cancellable = newsCancellable { - cancellable.cancel() - } - - newsCancellable = - provider.requestWithProgressPublisher( - .news( - page: page, - size: size, - accessToken: accessToken - ) - ) - .compactMap { $0.response?.data } - .receive(on: DispatchQueue.main) - .decode(type: [NewsList].self, decoder: JSONDecoder()) - .sink(receiveCompletion: { result in - switch result { - // success - case .finished: - print("news 조회 성공") - break - case .failure(let error): -// print(error.localizedDescription) - Log.network("network error", error.localizedDescription) - } - }, receiveValue: { [weak self] response in - self?.newsToViewModel(response) - print(response) - // 여기서 status 분기 처리 - }) - } -} - - -//public class NewsListViewModel: ObservableObject { -// // News = [NewsModel] -// @Published var newsList: [News] = [] -//// var currentPage = 0 -// -// init( -// newsList: [News] = [ -// -// .init(title: "네이버, 사우디 'LEAP 2024'서 AI·로봇 등 자사 기술력 뽐낸다", postDate: "2023년3월3일", image: "https://imgnews.pstatic.net/image/008/2024/03/05/0005007355_001_20240305100101016.jpg?type=w647", content: "네이버(NAVER)는 사우디아라비아에서 지난 4일부터 7일까지 열리는 글로벌 IT전시회 LEAP 2024에서 AI(인공지능), 클라우드, 로봇 등 자사 핵심 기술을 선보이고, 글로벌 업체들과 비즈니스 협력을 강화한다고 5일 밝혔다.", isBookmarked: false), -// .init(title: "앤스로픽 최신 AI 모델 '클로드3' 출시", postDate: "2023년3월13일", image: "https://imgnews.pstatic.net/image/014/2024/03/05/0005151141_001_20240305101610000.jpg?type=w647", content: "오픈AI의 대항마 앤스로픽이 생성형 인공지능(AI) 최신 모델 '클로드'(Claude)3를 선보이면서 생성형 AI 주도권을 잡기 위한 경쟁이 다시 뜨거워지고 있다. 앤스로픽은 지난 한 해 동안 구글과 세일즈포스, 아마존 등에서 총 73억 달러(약 9조7309억 원)를 투자받고 '클로드3'를 내놨는데 오픈AI의 챗GPT-4를 능가한다고 도발했다.", isBookmarked: true), -// .init(title: "SK C&C, 외부 전문가 대거 영입… “신성장 동력 강화”", postDate: "2023년2월13일", image: "https://imgnews.pstatic.net/image/366/2024/03/05/0000975131_001_20240305093504301.jpg?type=w647", content: "SK C&C는 국내외 신성장 동력 강화를 위해 인공지능(AI)·클라우드·디지털 팩토리·ESG(환경·사회·지배구조) 등 4대 성장 사업과 디지털 컨설팅 중심으로 외부 전문가를 대거 영입해 전진 배치했다고 5일 밝혔다.", isBookmarked: false), -// .init(title: "NHN, 작년 영업익 555억원...전년비 42% ↑", postDate: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false), -// ] -// ) { -// self.newsList = newsList -// } -//} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsList.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsList.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsList.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsList.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListView.swift similarity index 84% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListView.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListView.swift index faf04761..45dd6652 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListView.swift @@ -10,6 +10,7 @@ import SwiftUI struct NewsListView: View { var newsListViewModel: NewsListViewModel + var bookmarksViewModel: BookmarksListViewModel var newsId: Int? @State var selectedIndex: Int = 0 @@ -18,7 +19,7 @@ struct NewsListView: View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) - NewsListContentView(newsList: newsListViewModel.newsList ?? []) + NewsListContentView(newsList: newsListViewModel.newsList ?? [], bookmarksViewModel: bookmarksViewModel) } .task { newsListViewModel.getAllNewsData(page: 0, size: 10) @@ -27,16 +28,16 @@ struct NewsListView: View { private struct NewsListContentView: View { var newsList: [NewsList] + var bookmarksViewModel: BookmarksListViewModel - fileprivate var body: some View { GeometryReader { proxy in ScrollView(.horizontal, showsIndicators: false) { - LazyHStack(spacing: -234567876455) { + LazyHStack { ForEach(newsList) { news in - NewsView(news: news) + NewsView(news: news, bookmarksViewModel: bookmarksViewModel) .frame(width: proxy.size.width) .scrollTransition(.interactive, axis: .horizontal) { effect, phase in effect @@ -70,6 +71,6 @@ struct NewsListView: View { } #Preview { - NewsListView(newsListViewModel: NewsListViewModel()) + NewsListView(newsListViewModel: NewsListViewModel(), bookmarksViewModel: BookmarksListViewModel()) .previewInterfaceOrientation(.landscapeLeft) } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListViewModel.swift new file mode 100644 index 00000000..6948b661 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsListViewModel.swift @@ -0,0 +1,71 @@ +// +// NewsListViewModel.swift +// RollTheDice +// +// Created by Subeen on 2/9/24. +// + +import Foundation +import Combine +import CombineMoya +import Moya +import SwiftUI + +// Macro -> iOS 17.0 이상부터. @Published 안 해도 됨. +// 사용할 때 var 형식으로 해도 됨. +@Observable class NewsListViewModel { + var newsList: [NewsList]? +// var selectedNewsId: Int? + var currentPage = 0 + var newsCancellable: AnyCancellable? + + + //TODO: Plugin Settings + let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + func newsToViewModel(_ list: [NewsList]) { + self.newsList = list + } +} + +extension NewsListViewModel { + public func getAllNewsData(page: Int, size: Int) { + +// let accessToken = "" + // TokenManager에서 accessToken 가져오기 + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = newsCancellable { + cancellable.cancel() + } + + newsCancellable = + provider.requestWithProgressPublisher( + .news( + page: page, + size: size, + accessToken: accessToken + ) + ) + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: [NewsList].self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + // success + case .finished: + print("news 조회 성공") + break + case .failure(let error): + Log.network("network error", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + self?.newsToViewModel(response) + print(response) + // 여기서 status 분기 처리 + }) + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsView.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsView.swift similarity index 50% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsView.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsView.swift index 81e81d9d..d346735d 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/NewsView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/News/NewsList/NewsView.swift @@ -10,12 +10,9 @@ import SwiftUI struct NewsView: View { @EnvironmentObject var pathModel : PathModel - -// var newsListViewModel: NewsListViewModel + var news: NewsList -// var isVisibleView: Bool = true -// var cardWidth: Double = 0.0 -// var cardHeight: Double = 0.0 + var bookmarksViewModel: BookmarksListViewModel var body: some View { VStack(alignment: .center, spacing: 20) { @@ -43,49 +40,15 @@ struct NewsView: View { .frame(width: 322, height: 160) .clipShape(RoundedRectangle(cornerRadius: 8)) .shadow(color: .black.opacity(0.25), radius: 2, x: 0, y: 0) -// case .empty: -// Image(.exampleNews) -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 322, height: 160) -// .clipShape(RoundedRectangle(cornerRadius: 8)) -// .shadow(color: .black.opacity(0.25), radius: 2, x: 0, y: 0) -// case .failure(_): -// Image(.exampleNews) -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 322, height: 160) -// .clipShape(RoundedRectangle(cornerRadius: 8)) -// .shadow(color: .black.opacity(0.25), radius: 2, x: 0, y: 0) + @unknown default: Text(""); } } - - -// if isVisibleView { -// Button { -// let newsId:Int = news.newsId -// print("!!!!!!!!!!! newsID : \(news.newsId)") -// pathModel.paths.append(.detailNewsView(newsId: newsId)) -// } label: { -// Text("더보기") -// .font(.pretendardBold14) -// .foregroundStyle(.gray01) -// .padding(.horizontal, 30) -// .padding(.vertical, 13) -// .background(.primary01) -// .clipShape( -// RoundedRectangle(cornerRadius: 16) -// ) -// .shadow(color: .basicBlack.opacity(0.25), radius: 2, x: 0, y: 0) -// -// } -// } + Button { let newsId:Int = news.newsId - print("!!!!!!!!!!! newsID : \(news.newsId)") pathModel.paths.append(.detailNewsView(newsId: newsId)) } label: { Text("더보기") @@ -107,7 +70,6 @@ struct NewsView: View { .padding(.top, 50) .frame(width: 380) .background( -// LinearGradient(colors: isVisibleView ? [.basicWhite, .primaryLight01] : [.basicWhite], startPoint: .top, endPoint: .bottom) LinearGradient(colors: [.basicWhite, .primaryLight01], startPoint: .top, endPoint: .bottom) ) .clipShape(RoundedRectangle(cornerRadius: 12)) @@ -117,9 +79,15 @@ struct NewsView: View { HStack { Spacer() Button { - + print("북마크 버튼눌림") + print(news.isBookmarked) + if news.isBookmarked ?? false { + bookmarksViewModel.deleteBookmark(newsId: news.newsId) + } else { + bookmarksViewModel.saveBookmark(newsId: news.newsId) + } } label: { - Image(.bookmarkfill) + Image(news.isBookmarked ?? false ? .bookmarkfill : .bookmarkunfill) .shadow(color: .gray06, radius: 2) } .offset(CGSize(width: -20, height: -10)) @@ -131,12 +99,6 @@ struct NewsView: View { } #Preview { - NewsView(news: .init(newsId: 1, title: "1분기 선방한 dddddddd韓게임사들…엔씨만 울었다", content: "내용내용ㄴㅇㅇㅇ", thumbnailUrl: "", postDate: "2222-22-22", isBookmarked: true)) + NewsView(news: .init(newsId: 1, title: "1분기 선방한 dddddddd韓게임사들…엔씨만 울었다", content: "내용내용ㄴㅇㅇㅇ", thumbnailUrl: "", postDate: "2222-22-22", isBookmarked: true), bookmarksViewModel: BookmarksListViewModel()) .environmentObject(PathModel()) } -//#Preview(traits: .sizeThatFitsLayout) { -// NewsView(news: .init(title: "NHN, 작년 영업익 555억원...전년비 42%", postDate: "2023년2월13일", image: "https://cdnimage.dailian.co.kr/news/202402/news_1707866329_1327972_m_1.png", content: "2NHN은 연결기준 지난해 영업이익이 555억원으로 전년 대비 42.2% 증가했다고 14일 밝혔다.같은 기간 매출은 7.3% 증가한 2조2696억원으로 연간 최대치를 기록했다. 작년 4분기 매출은 5983억원으로 전년 동기 대비 6.7% 올랐다. 반면 영업손실은 78억원으로 적자전환했다. 커머스 부문의 장기 미회수채권 대손상각비 인식과 기술 부문의 기 인식 매출 차감 등 일회성 요인이 영향을 미쳤다.", isBookmarked: false)) -// .previewInterfaceOrientation(.landscapeLeft) -// .previewLayout(.sizeThatFits) -// .colorScheme(.dark) -//} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/WebView/WebView.swift b/iOS/RollTheDice/RollTheDice/Source/View/News/WebView/WebView.swift similarity index 100% rename from iOS/RollTheDice/RollTheDice/Source/View/News/NewsCard/WebView/WebView.swift rename to iOS/RollTheDice/RollTheDice/Source/View/News/WebView/WebView.swift diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index 8f026af7..2b02f12c 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -11,7 +11,7 @@ import Charts struct DailyBarChartView: View { - @StateObject var dailyViewModel: DailyReportViewModel + var dailyViewModel: DailyReportViewModel @State var selectedDay: Date? @State var selectedView: Int? @@ -21,9 +21,9 @@ struct DailyBarChartView: View { var selectedValue: (date: Date, views: Int)? { if let selectedDay { - for preview in dailyViewModel.dailyReportList.reportList { + for preview in dailyViewModel.dailyReportList ?? [] { if preview.date.formatted(date: .long, time: .omitted) == selectedDay.formatted(date: .long, time: .omitted) { - return (selectedDay, preview.views) + return (selectedDay, preview.views!) } } } @@ -57,10 +57,10 @@ struct DailyBarChartView: View { Chart{ - ForEach(dailyViewModel.dailyReportList.reportList) { day in + ForEach(dailyViewModel.dailyReportList ?? []) { day in BarMark( x: .value("Day", day.date, unit: .weekdayOrdinal), - y: .value("Views", day.views) + y: .value("Views", day.views ?? 0) ) .cornerRadius(8) @@ -100,6 +100,9 @@ struct DailyBarChartView: View { } .chartYAxis(.hidden) .chartXSelection(value: $selectedDay) + .task { + dailyViewModel.getDailyViews() + } } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift index 9a5051cc..10a34175 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift @@ -8,24 +8,25 @@ import Foundation import SwiftUI -struct DailyReportList: Hashable { - var reportList: [DailyReport] -} +//struct DailyReportList: Codable { +// var reportList: [DailyReport] +//} -struct DailyReport: Hashable, Identifiable { - let id = UUID() - let dateStr: String // DateFormatter로 변환 - let views: Int +struct DailyReport: Codable, Identifiable { + + let id = UUID().uuidString + let views: Int? + let dateTime: String? var date: Date { // TODO: Format Style 사용해서 String -> Date 생성하기 // let strategy = Date.ParseStrategy(format: "\(month: .twoDigits)년 \(month: .twoDigits)월 \(day: .defaultDigits)", timeZone: TimeZone(abbreviation: "CDT")!) let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy년 MM월 dd일" + dateFormatter.dateFormat = "yyyy-MM-dd" - let convertedDate = dateFormatter.date(from: dateStr)! + let convertedDate = dateFormatter.date(from: dateTime ?? "0000-00-00") - return convertedDate + return convertedDate! } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift index 344ca3bb..b9aa5374 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift @@ -6,34 +6,62 @@ // import Foundation +import Combine +import CombineMoya +import Moya +import SwiftUI -class DailyReportViewModel: ObservableObject{ - @Published var dailyReportList: DailyReportList +@Observable class DailyReportViewModel { + var dailyReportList: [DailyReport]? /// 일주일 평균 조회수 var averageView: String { var aver = 0.0 - for daily in dailyReportList.reportList { - aver += Double(daily.views) + for daily in dailyReportList ?? [] { + aver += Double(daily.views ?? 0) } return String(format: "%.1f", aver / 7) } - init( - dailyReportList: DailyReportList = .init( - reportList: - [.init(dateStr: "2024년 1월 1일", views: 32), - .init(dateStr: "2024년 1월 2일", views: 2), - .init(dateStr: "2024년 1월 3일", views: 300), - .init(dateStr: "2024년 1월 4일", views: 999), - .init(dateStr: "2024년 1월 5일", views: 12), - .init(dateStr: "2024년 1월 6일", views: 1), - .init(dateStr: "2024년 1월 7일", views: 99), - ] + var dailyCancellable: AnyCancellable? + + let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + func dailyToViewModel(_ list: [DailyReport]) { + self.dailyReportList = list + } +} + +extension DailyReportViewModel { + + public func getDailyViews() { + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = dailyCancellable { + cancellable.cancel() + } + + dailyCancellable = provider.requestWithProgressPublisher( + .recentSevenPerDate(accessToken: accessToken) ) - ) { - self.dailyReportList = dailyReportList + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: [DailyReport].self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("daily report 조회성공") + case .failure(let error): + Log.network("daily report network error", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + self?.dailyToViewModel(response) + print(response) + }) } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift index bbd26054..7a2e0399 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift @@ -10,25 +10,24 @@ import Charts struct TypePieChartView: View { - @StateObject var reportViewModel: TypeReportViewModel + var reportViewModel: TypeReportViewModel var isPreview: Bool = false var mostViewed: NewsType { - return reportViewModel.sortedList.first!.newsType + return reportViewModel.sortedList.first?.category ?? .economy } var body: some View { Chart(reportViewModel.sortedList) { report in SectorMark( - angle: .value("Views", report.view), + angle: .value("Views", report.views ?? 0), innerRadius: .ratio(0.7), angularInset: 2.0 ) .cornerRadius(8) - .foregroundStyle(report.newsType.color.gradient) + .foregroundStyle(report.category?.color.gradient ?? Color.primary01.gradient) } - /// pie chart의 가운데 문구 .chartBackground { chartProxy in GeometryReader { geometry in let frame = geometry[chartProxy.plotFrame!] @@ -47,6 +46,9 @@ struct TypePieChartView: View { } } .padding(100) + .task { + reportViewModel.getTypeReport() + } } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift index 1c35be6e..2c8c56a1 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift @@ -7,19 +7,9 @@ import Foundation -struct TypeReportList: Hashable { - var reportList: [TypeReport] -} - -struct TypeReport: Hashable, Identifiable { - var id = UUID() - var newsType: NewsType - var view: Int - - // case politics // 정치 - // case economy // 경제 - // case society // 사회 - // case living // 생활/문화 - // case world // 세계 - // case science // IT/과학 +struct TypeReport: Codable, Identifiable { + + let id = UUID().uuidString + let category: NewsType? + let views: Int? } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift index d23bce97..fcc6bfde 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift @@ -6,28 +6,61 @@ // import Foundation +import Combine +import CombineMoya +import Moya +import SwiftUI -class TypeReportViewModel: ObservableObject { +@Observable class TypeReportViewModel { - @Published var reportList: TypeReportList + var typeReportList: [TypeReport]? + + // 비율이 낮은 순으로 정렬 (파이 차트에서 반시계방향으로 그래프 차지) + var sortedList: [TypeReport] { + if let reportList = typeReportList { + return reportList.sorted { ($0.views ?? 0) < ($1.views ?? 0) } + } else { + return [] + } + } - init( - reportList: TypeReportList = .init( - reportList: [ - .init(newsType: .economy, view: 10), - .init(newsType: .living, view: 20), - .init(newsType: .politics, view: 30), - .init(newsType: .science, view: 5), - .init(newsType: .society, view: 5), - .init(newsType: .world, view: 30) - ] - ) - ) { - self.reportList = reportList + var typeCancellable: AnyCancellable? + + let provider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) + + func typeToViewModel(_ list: [TypeReport]) { + self.typeReportList = list } +} + +extension TypeReportViewModel { - // 비율이 낮은 순으로 정렬 (파이 차트에서 반시계방향으로 그래프 차지) - var sortedList: [TypeReport] { - return reportList.reportList.sorted { $0.view > $1.view } + public func getTypeReport() { + guard let accessToken = TokenManager.shared.accessToken else { + print("Access token 사용 불가능...") + return + } + + if let cancellable = typeCancellable { + cancellable.cancel() + } + + typeCancellable = provider.requestWithProgressPublisher( + .statisticsCategory(accessToken: accessToken) + ) + .compactMap { $0.response?.data } + .receive(on: DispatchQueue.main) + .decode(type: [TypeReport].self, decoder: JSONDecoder()) + .sink(receiveCompletion: { result in + switch result { + case .finished: + print("type report 조회성공") + case .failure(let error): + Log.network("type report network error", error.localizedDescription) + } + }, receiveValue: { [weak self] response in + self?.typeToViewModel(response) + print(response) + }) } }