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

Manage static archive in archive and category view #177

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions DuManga.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_ASSET_PATHS = "\"DuManga/Preview Content\"";
DEVELOPMENT_TEAM = UUEBW58SA6;
ENABLE_PREVIEWS = YES;
Expand All @@ -1062,7 +1062,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_ASSET_PATHS = "\"DuManga/Preview Content\"";
DEVELOPMENT_TEAM = UUEBW58SA6;
ENABLE_PREVIEWS = YES;
Expand Down Expand Up @@ -1139,7 +1139,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = UUEBW58SA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Action/Info.plist;
Expand Down Expand Up @@ -1168,7 +1168,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 65;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = UUEBW58SA6;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Action/Info.plist;
Expand Down
67 changes: 50 additions & 17 deletions DuManga/Category/CategoryArchiveList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import NotificationBannerSwift
struct CategoryArchiveList: View {
@StateObject private var categoryArchiveListModel = CategoryArchiveListModel()

@State private var enableSelect: EditMode = .inactive

private let categoryItem: CategoryItem

init(categoryItem: CategoryItem) {
Expand All @@ -17,26 +19,34 @@ struct CategoryArchiveList: View {
var body: some View {
GeometryReader { geometry in
ZStack {
ArchiveList(archives: categoryArchiveListModel.loadCategory())
.task {
if !categoryItem.archives.isEmpty {
categoryArchiveListModel.loadStaticCategory(ids: categoryItem.archives)
} else if !categoryItem.search.isEmpty {
await categoryArchiveListModel.loadDynamicCategory(keyword: categoryItem.search)
if enableSelect == .active {
ArchiveSelection(
archives: categoryArchiveListModel.loadCategory(),
archiveSelectFor: categoryItem.search.isEmpty ? .categoryStatic : .categoryDynamic,
categoryId: categoryItem.id
)
} else {
ArchiveList(archives: categoryArchiveListModel.loadCategory())
.task {
if categoryItem.search.isEmpty {
categoryArchiveListModel.loadStaticCategory(id: categoryItem.id)
} else {
await categoryArchiveListModel.loadDynamicCategory(keyword: categoryItem.search)
}
}
}
.onDisappear {
categoryArchiveListModel.reset()
}
.onChange(of: categoryArchiveListModel.isError) { isError in
if isError {
let banner = NotificationBanner(title: NSLocalizedString("error", comment: "error"),
subtitle: categoryArchiveListModel.errorMessage,
style: .danger)
banner.show()
.onDisappear {
categoryArchiveListModel.reset()
}
}
.onChange(of: categoryArchiveListModel.isError) { isError in
if isError {
let banner = NotificationBanner(title: NSLocalizedString("error", comment: "error"),
subtitle: categoryArchiveListModel.errorMessage,
style: .danger)
banner.show()
categoryArchiveListModel.reset()
}
}
}
if categoryArchiveListModel.isLoading {
LoadingView(geometry: geometry)
}
Expand All @@ -48,6 +58,29 @@ struct CategoryArchiveList: View {
categoryArchiveListModel.disconnectStore()
}
.toolbar(.hidden, for: .tabBar)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(enableSelect == .active ? "cancel" : "select") {
switch enableSelect {
case .active:
self.enableSelect = .inactive
case .inactive:
self.enableSelect = .active
default:
break
}
}
}
}
.environment(\.editMode, $enableSelect)
.onChange(of: categoryArchiveListModel.categoryItems[categoryItem.id]) {
// swiftlint:disable closure_parameter_position
[oldCategory = categoryArchiveListModel.categoryItems[categoryItem.id]] newCategory in
// swiftlint:enable closure_parameter_position
if oldCategory?.archives != newCategory?.archives {
categoryArchiveListModel.loadStaticCategory(id: categoryItem.id)
}
}
}
}
}
11 changes: 8 additions & 3 deletions DuManga/Category/CategoryArchiveListModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ class CategoryArchiveListModel: ObservableObject {
private static let logger = Logger(label: "CategoryArchiveListModel")

@Published private(set) var archiveItems = [String: ArchiveItem]()
@Published private(set) var categoryItems = [String: CategoryItem]()
@Published private(set) var result = [String]()
@Published private(set) var isLoading = false
@Published private(set) var isError = false
@Published private(set) var errorMessage = ""

private let service = LANraragiService.shared
private let store = AppStore.shared

private var result = [String]()
private var cancellable: Set<AnyCancellable> = []

init() {
Expand All @@ -26,10 +27,14 @@ class CategoryArchiveListModel: ObservableObject {

func connectStore() {
archiveItems = store.state.archive.archiveItems
categoryItems = store.state.category.categoryItems

store.state.archive.$archiveItems.receive(on: DispatchQueue.main)
.assign(to: \.archiveItems, on: self)
.store(in: &cancellable)
store.state.category.$categoryItems.receive(on: DispatchQueue.main)
.assign(to: \.categoryItems, on: self)
.store(in: &cancellable)
}

func disconnectStore() {
Expand All @@ -48,8 +53,8 @@ class CategoryArchiveListModel: ObservableObject {
}
}

func loadStaticCategory(ids: [String]) {
result = ids
func loadStaticCategory(id: String) {
result = categoryItems[id]?.archives ?? []
}

@MainActor
Expand Down
100 changes: 85 additions & 15 deletions DuManga/Common/ArchiveSelection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ struct ArchiveSelection: View {
@AppStorage(SettingsKey.archiveListOrder) var archiveListOrder: String = ArchiveListOrder.name.rawValue

@State var selected: Set<String> = .init()
@State private var showingAlert = false
@State private var deleteAlert = false
@State private var removeAlert = false
@StateObject var archiveSelectionModel = ArchiveSelectionModel()

private let archives: [ArchiveItem]
private let archiveSelectFor: ArchiveSelectFor
private let categoryId: String?

init(archives: [ArchiveItem]) {
init(archives: [ArchiveItem], archiveSelectFor: ArchiveSelectFor, categoryId: String? = nil) {
self.archives = archives
self.archiveSelectFor = archiveSelectFor
self.categoryId = categoryId
}

var body: some View {
Expand All @@ -31,18 +36,28 @@ struct ArchiveSelection: View {
}
}
.overlay(alignment: .bottomTrailing, content: {
selected.contains(item.id) ? Image(systemName: "checkmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 50)
.foregroundColor(.accentColor)
.padding()
: nil
if selected.contains(item.id) {
Image(systemName: "checkmark.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 50)
.foregroundColor(.accentColor)
.padding()
} else { Image(systemName: "circle")
.resizable()
.scaledToFit()
.frame(width: 50)
.foregroundColor(.secondary)
.padding()
}
})
}
}
.padding()
}
.task {
await archiveSelectionModel.fetchCategories()
}
.onAppear {
archiveSelectionModel.connectStore()
}
Expand All @@ -52,10 +67,54 @@ struct ArchiveSelection: View {
.toolbar(.hidden, for: .tabBar)
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button {
// Not yet implemented
} label: {
Image(systemName: "folder.badge.plus")
if archiveSelectFor == .library {
Menu {
ForEach(archiveSelectionModel.getStaticCategories()) { category in
Button {
Task {
let addedIds = await archiveSelectionModel.addArchivesToCategory(
categoryId: category.id,
archiveIds: selected
)
addedIds.forEach { id in
selected.remove(id)
}
}
} label: {
Text(category.name)
}
}
Text("archive.selected.category.add")
} label: {
Image(systemName: "folder.badge.plus")
}
.disabled(archiveSelectionModel.loading || selected.isEmpty)
} else if archiveSelectFor == .categoryStatic {
Button(role: .destructive) {
removeAlert = true
} label: {
Image(systemName: "folder.badge.minus")
}
.disabled(archiveSelectionModel.loading || selected.isEmpty)
.alert("archive.selected.category.remove", isPresented: $removeAlert) {
Button(role: .destructive) {
Task {
let removedIds = await archiveSelectionModel.removeArchivesFromCategory(
categoryId: categoryId!, archiveIds: selected
)
removedIds.forEach { id in
selected.remove(id)
}
}
} label: {
Text("remove")
}

Button("cancel", role: .cancel) { }
}
} else {
// placeholder
Color.clear
}

Spacer()
Expand All @@ -70,12 +129,12 @@ struct ArchiveSelection: View {
Spacer()

Button(role: .destructive) {
showingAlert = true
deleteAlert = true
} label: {
Image(systemName: "trash")
}
.disabled(archiveSelectionModel.loading || selected.isEmpty)
.alert("archive.selected.delete", isPresented: $showingAlert) {
.alert("archive.selected.delete", isPresented: $deleteAlert) {
Button("delete", role: .destructive) {
Task {
let removedIds = await archiveSelectionModel.deleteArchives(ids: selected)
Expand All @@ -99,5 +158,16 @@ struct ArchiveSelection: View {
archiveSelectionModel.reset()
}
}
.onChange(of: archiveSelectionModel.successMessage) { successMessage in
if !successMessage.isEmpty {
let banner = NotificationBanner(
title: NSLocalizedString("success", comment: "success"),
subtitle: successMessage,
style: .success
)
banner.show()
archiveSelectionModel.reset()
}
}
}
}
Loading