Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

선호 학과 정보 저장 #323

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
matrix:
# see https://github.com/actions/virtual-environments/blob/main/images/macos/macos-13-Readme.md for available versions
xcode:
- "15.1"
macos: ["macos-14"]
- "16.2"
macos: ["macos-15"]
command: ["build", "test"]
scheme: ["SNUTT"]

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
GIT_PAT_READONLY: ${{ secrets.GIT_PAT_READONLY }}
GIT_TAG_NAME: ${{ github.ref_name }}
XCODE_VERSION: "15.1"
XCODE_VERSION: "16.2"

runs-on: macos-14
runs-on: macos-15

steps:
- name: Parse tag name
Expand Down
4 changes: 4 additions & 0 deletions SNUTT-2022/SNUTT.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
731DA003297BC5740027BA25 /* BookmarkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA002297BC5740027BA25 /* BookmarkRouter.swift */; };
731DA005297BC8990027BA25 /* BookmarkScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731DA004297BC8990027BA25 /* BookmarkScene.swift */; };
734A831F2C2FD41200D6CB95 /* KakaoLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734A831E2C2FD41200D6CB95 /* KakaoLogin.swift */; };
734B0DE02D2CF23E00A0BAB9 /* View+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734B0DDF2D2CF23600A0BAB9 /* View+Gesture.swift */; };
736AF84C2C2F275E00ED9C1A /* GoogleLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736AF84B2C2F275E00ED9C1A /* GoogleLogin.swift */; };
736AF84F2C2F279900ED9C1A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 736AF84E2C2F279900ED9C1A /* GoogleSignIn */; };
736AF8512C2F279900ED9C1A /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 736AF8502C2F279900ED9C1A /* GoogleSignInSwift */; };
Expand Down Expand Up @@ -365,6 +366,7 @@
731DA002297BC5740027BA25 /* BookmarkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRouter.swift; sourceTree = "<group>"; };
731DA004297BC8990027BA25 /* BookmarkScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkScene.swift; sourceTree = "<group>"; };
734A831E2C2FD41200D6CB95 /* KakaoLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoLogin.swift; sourceTree = "<group>"; };
734B0DDF2D2CF23600A0BAB9 /* View+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Gesture.swift"; sourceTree = "<group>"; };
736AF84B2C2F275E00ED9C1A /* GoogleLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLogin.swift; sourceTree = "<group>"; };
738406ED2B57107C00007E62 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
738406F02B5710C200007E62 /* ThemeDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeDto.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -748,6 +750,7 @@
BE419B8B288B8C4900FA9590 /* Extensions */ = {
isa = PBXGroup;
children = (
734B0DDF2D2CF23600A0BAB9 /* View+Gesture.swift */,
BE419B8C288B8C5A00FA9590 /* View+Debug.swift */,
BEB3B6B028D4D4D900E56062 /* View+ResignResponder.swift */,
B87DF6F229152649008BB95B /* View+Screenshot.swift */,
Expand Down Expand Up @@ -1552,6 +1555,7 @@
BE1D2B3A28014527008F9134 /* Weekday.swift in Sources */,
BEDE34CA28754F3100525014 /* Sheet.swift in Sources */,
BEB3B6B128D4D4D900E56062 /* View+ResignResponder.swift in Sources */,
734B0DE02D2CF23E00A0BAB9 /* View+Gesture.swift in Sources */,
CE4777F72A6AE41C00E03253 /* VacancyScene.swift in Sources */,
B87DF6F329152649008BB95B /* View+Screenshot.swift in Sources */,
DCD41A6027E5CC7700CF380E /* Timetable.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion SNUTT-2022/SNUTT/AppState/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ extension AppEnvironment {
let timetableService = TimetableService(appState: appState, webRepositories: webRepositories, localRepositories: localRepositories)
let userService = UserService(appState: appState, webRepositories: webRepositories, localRepositories: localRepositories)
let lectureService = LectureService(appState: appState, webRepositories: webRepositories, localRepositories: localRepositories)
let searchService = SearchService(appState: appState, webRepositories: webRepositories)
let searchService = SearchService(appState: appState, webRepositories: webRepositories, localRepositories: localRepositories)
let globalUIService = GlobalUIService(appState: appState, localRepositories: localRepositories, webRepositories: webRepositories)
let courseBookService = CourseBookService(appState: appState, webRepositories: webRepositories)
let authService = AuthService(appState: appState, webRepositories: webRepositories, localRepositories: localRepositories)
Expand Down
1 change: 1 addition & 0 deletions SNUTT-2022/SNUTT/AppState/States/SearchState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class SearchState {
@Published var isFilterOpen = false
@Published var searchTagList: SearchTagList?
@Published var selectedTagList: [SearchTag] = []
@Published var pinnedTagList: [SearchTag] = []
@Published var selectedTimeRange: [SearchTimeMaskDto] = []

@Published var displayMode: SearchDisplayMode = .search
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "exit@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "exit@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions SNUTT-2022/SNUTT/Extensions/View+Gesture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// View+Gesture.swift
// SNUTT
//
// Created by 이채민 on 1/7/25.
//

import SwiftUI

extension View {
@ViewBuilder
func sheetGesture(_ translation: Binding<CGFloat>, dismiss: @escaping @MainActor () -> Void) -> some View {
if #available(iOS 18.0, *) {
gesture(SheetGestureRecognizer(translation: translation, dismiss: dismiss))
} else {
highPriorityGesture(DragGesture().onChanged { value in
translation.wrappedValue = value.translation.height
}.onEnded { value in
translation.wrappedValue = 0
if value.velocity.height < -500 || value.translation.height < -100 {
dismiss()
}
})
}
}
}

@available(iOS 18.0, *)
private struct SheetGestureRecognizer: UIGestureRecognizerRepresentable {
func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
Coordinator(translation: $translation, dismiss: dismiss)
}

@Binding var translation: CGFloat
let dismiss: () -> Void

func makeUIGestureRecognizer(context: Context) -> UIPanGestureRecognizer {
let recognizer = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePan))
return recognizer
}

final class Coordinator: NSObject {
@Binding var translation: CGFloat
let dismiss: () -> Void

init(translation: Binding<CGFloat>, dismiss: @escaping () -> Void) {
_translation = translation
self.dismiss = dismiss
}

@objc func handlePan(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
translation = recognizer.translation(in: recognizer.view).y
case .ended:
if shouldDismiss(recognizer) {
dismiss()
}
translation = 0
default:
break
}
}

private func shouldDismiss(_ recognizer: UIPanGestureRecognizer) -> Bool {
let velocity = recognizer.velocity(in: recognizer.view).y
let translation = recognizer.translation(in: recognizer.view).y
return velocity < -500 || translation < -100
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum STDefaultsKey: String {
case userDto
case fcmToken
case preferredColorScheme
case recentDepartmentTags

case currentTimetable
case timetableConfig
Expand Down
21 changes: 21 additions & 0 deletions SNUTT-2022/SNUTT/Services/SearchService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ protocol SearchServiceProtocol: Sendable {
struct SearchService: SearchServiceProtocol {
var appState: AppState
var webRepositories: AppEnvironment.WebRepositories
var localRepositories: AppEnvironment.LocalRepositories

var searchRepository: SearchRepositoryProtocol {
webRepositories.searchRepository
}

var userDefaultsRepository: UserDefaultsRepositoryProtocol {
localRepositories.userDefaultsRepository
}

var searchState: SearchState {
appState.search
}
Expand Down Expand Up @@ -61,11 +66,27 @@ struct SearchService: SearchServiceProtocol {
let dto = try await searchRepository.fetchTags(quarter: quarter)
let model = SearchTagList(from: dto)
appState.search.searchTagList = model
guard let recentTagNames = userDefaultsRepository.get([String].self, key: .recentDepartmentTags) else { return }
appState.search.pinnedTagList = model?.tagList.filter { $0.type == .department && recentTagNames.contains($0.text) } ?? []
}

private func _saveDepartmentTagsToUserDefaults(from tagList: [SearchTag]) async throws {
let departmentTags = tagList.filter { tag in tag.type == .department &&
!appState.search.pinnedTagList.contains { $0.text == tag.text }
}
var updatedTags = departmentTags + appState.search.pinnedTagList
if updatedTags.count > 5 {
updatedTags = Array(updatedTags.suffix(5))
}
appState.search.pinnedTagList = updatedTags
let savedTags = updatedTags.map { $0.text }
userDefaultsRepository.set([String].self, key: .recentDepartmentTags, value: savedTags)
}

private func _fetchSearchResult() async throws {
guard let currentTimetable = timetableState.current else { return }
let tagList = searchState.selectedTagList
try await _saveDepartmentTagsToUserDefaults(from: tagList)
let timeList = tagList.contains(where: { $0.type == .time && TimeType(rawValue: $0.text) == .range }) ? searchState.selectedTimeRange : nil
let excludedTimeList = tagList.contains(where: { $0.type == .time && TimeType(rawValue: $0.text) == .empty }) ? currentTimetable.timeMask : nil
let offset = searchState.perPage * searchState.pageNum
Expand Down
7 changes: 7 additions & 0 deletions SNUTT-2022/SNUTT/ViewModels/FilterSheetViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
class FilterSheetViewModel: BaseViewModel, ObservableObject {
@Published var selectedTagList: [SearchTag] = []
@Published var searchTagList: SearchTagList?
@Published var pinnedTagList: [SearchTag] = []
@Published private var _selectedTimeRange: [SearchTimeMaskDto] = []
@Published private var _isFilterOpen: Bool = false

Expand All @@ -36,6 +37,7 @@ class FilterSheetViewModel: BaseViewModel, ObservableObject {
appState.search.$selectedTagList.assign(to: &$selectedTagList)
appState.search.$selectedTimeRange.assign(to: &$_selectedTimeRange)
appState.search.$searchTagList.assign(to: &$searchTagList)
appState.search.$pinnedTagList.assign(to: &$pinnedTagList)
appState.search.$isFilterOpen.assign(to: &$_isFilterOpen)
}

Expand Down Expand Up @@ -63,4 +65,9 @@ class FilterSheetViewModel: BaseViewModel, ObservableObject {
func isSelected(tag: SearchTag) -> Bool {
return appState.search.selectedTagList.contains(where: { $0.id == tag.id })
}

func removePin(tag: SearchTag) {
guard let index = appState.search.pinnedTagList.firstIndex(where: { $0.id == tag.id }) else { return }
appState.search.pinnedTagList.remove(at: index)
}
}
Loading
Loading