Skip to content

Commit

Permalink
feat: implement database import functionality and refactor anime list…
Browse files Browse the repository at this point in the history
… management (#86)

- Added a new `AnimeImporter` class for importing anime data from an external database.
- Introduced `DbService` for database interactions, including fetching anime and genres.
- Created models for user anime lists, including `UserAnimeListModel` and `UserAnimeListCache`.
- Refactored existing views to utilize the new data models and improve state management.
- Updated hooks in the Makefile for better integration with Xcode and linting processes.
  • Loading branch information
dimensi authored Dec 16, 2024
1 parent 25827f3 commit 0fea081
Show file tree
Hide file tree
Showing 22 changed files with 1,492 additions and 422 deletions.
2 changes: 1 addition & 1 deletion Ichime/ContentView/ContentViewWithSideBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct ContentViewWithSideBar: View {
@Environment(\.modelContext) private var modelContext
@AppStorage("ContentViewWithTabView.selectedTab") private var selectedTab: Tabs = .home

@State var viewModel: ShowListStatusModel = ApplicationDependency.container.resolve()
@State var viewModel: UserAnimeListCache = ApplicationDependency.container.resolve()

var body: some View {
TabView(selection: $selectedTab) {
Expand Down
2 changes: 1 addition & 1 deletion Ichime/ContentView/ContentViewWithTabBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct ContentViewWithTabBar: View {
@Environment(\.modelContext) private var modelContext
@AppStorage("ContentViewWithTabView.selectedTab") private var selectedTab: Tabs = .home

@State var viewModel: ShowListStatusModel = ApplicationDependency.container.resolve()
@State var viewModel: UserAnimeListCache = ApplicationDependency.container.resolve()

var body: some View {
TabView(selection: $selectedTab) {
Expand Down
15 changes: 13 additions & 2 deletions Ichime/DependencyInjection/DI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ShikimoriApiClient
import SwiftData

class ApplicationDependency: DIFramework {

static let container: DIContainer = {
let container = DIContainer()
container.append(framework: ApplicationDependency.self)
Expand All @@ -21,7 +22,13 @@ class ApplicationDependency: DIFramework {

static func load(container: DIContainer) {
container.register {
let schema = Schema([ShowListStatusEntity.self])
let schema = Schema([
UserAnimeListModel.self,
DbAnime.self,
DbGenre.self,
DbStudio.self,
])
let storeURL = URL.documentsDirectory.appending(path: "offline.sqlite")
let modelConfiguration = ModelConfiguration(
schema: schema,
groupContainer: .identifier(ServiceLocator.appGroup)
Expand Down Expand Up @@ -73,7 +80,11 @@ class ApplicationDependency: DIFramework {
}

container.register {
ShowListStatusModel(apiClient: $0, userManager: $1, modelContainer: $2)
UserAnimeListCache(apiClient: $0, userManager: $1, modelContainer: $2)
}

container.register {
DbService(modelContainer: $0)
}

container.register { Anime365Client(apiClient: $0) }
Expand Down
65 changes: 65 additions & 0 deletions Ichime/IchimeApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ struct IchimeApp: App {
let container: ModelContainer = ApplicationDependency.container.resolve()

@Environment(\.scenePhase) private var phase
@State private var isImporting = false
@State private var progressText = "Импорт базы данных..."

var body: some Scene {
WindowGroup {
ContentView()
.overlay(alignment: .bottom) {
if isImporting {
ProgressView(progressText)
.padding()
.background(.ultraThinMaterial)
.cornerRadius(10)
}
}
.onAppear {
VideoPlayerController.enableBackgroundMode()
NotificationCounterWatcher.askBadgePermission()
}
.task {
await importDatabase()
}
}.onChange(of: phase) {
switch phase {
case .background:
Expand All @@ -33,4 +46,56 @@ struct IchimeApp: App {
}
.modelContainer(container)
}

private func importDatabase() async {
guard !isImporting else { return }
isImporting = true

do {
// Запрос к API
let url = URL(string: "https://db.dimensi.dev/api/latest")!
let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(DbServerResponse.self, from: data)

let lastUpdated = UserDefaults().string(forKey: "lastUpdated") ?? ""
let lastTimestamp = Int(lastUpdated) ?? 0

if lastTimestamp == response.date {
isImporting = false
return
}

let animeImporter = AnimeImporter(modelContainer: container)
Task.detached(priority: .high) {
do {
let dbUrl = "https://db.dimensi.dev\(response.url)"
async let result: () = animeImporter.importDatabase(from: dbUrl)
for await progress in await animeImporter.currentProgress {
await MainActor.run {
progressText = progress
}
}
try await result
await MainActor.run {
isImporting = false
UserDefaults().set(response.date, forKey: "lastUpdated")
}
}
catch {
await MainActor.run {
isImporting = false
}
}
}
}
catch {
isImporting = false
print("Error fetching database info: \(error)")
}
}
}

struct DbServerResponse: Codable {
let date: Int
let url: String
}
67 changes: 27 additions & 40 deletions Ichime/MyLists/Component/AnimeList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,60 +56,47 @@ private struct MyListEntry: View {
}

struct AnimeList: View {
let categories: [ScraperAPI.Types.ListByCategory]
let onUpdate: () async -> Void
let status: AnimeWatchStatus
let animeList: [UserAnimeListModel]

@State var selectedShow: ScraperAPI.Types.Show?
@State var selectedShow: MyListShow?

var body: some View {
List {
ForEach(categories, id: \.type) { category in
Section {
ForEach(category.shows, id: \.id) { show in
Button(action: {
selectedShow = show
}) {
MyListEntry(
primaryTitle: show.name.ru, // TODO: сделать romaji primary
secondaryTitle: show.name.romaji,
currentEpisodeProgress: show.episodes.watched,
totalEpisodes: show.episodes.total
)
.contextMenu(
menuItems: {

NavigationLink(destination: ShowView(showId: show.id)) {
Text("Открыть")
}
},
preview: {
IndependentShowCardContextMenuPreview(showId: show.id)
Section {
ForEach(animeList, id: \.id) { show in
Button(action: {
selectedShow = .init(id: show.id, name: show.name.ru, totalEpisodes: show.progress.total)
}) {
MyListEntry(
primaryTitle: show.name.ru, // TODO: сделать romaji primary
secondaryTitle: show.name.romaji,
currentEpisodeProgress: show.progress.watched,
totalEpisodes: show.progress.total
)
.contextMenu(
menuItems: {
NavigationLink(destination: ShowView(showId: show.id)) {
Text("Открыть")
}
)
}
},
preview: {
IndependentShowCardContextMenuPreview(showId: show.id)
}
)
}
} header: {
Text(category.type.rawValue)
}
} header: {
Text(status.title)
}
}
.sheet(
item: $selectedShow,
content: { show in
MyListEditView(
show: .init(id: show.id, name: show.name.ru, totalEpisodes: show.episodes.total)
) {
Task {
await onUpdate()
}
}
show: show
)
}
)
}
}

#Preview {
NavigationStack {
AnimeList(categories: ScraperAPI.Types.ListByCategory.sampleData) {}
}
}
2 changes: 1 addition & 1 deletion Ichime/MyLists/Model/MyListShow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct MyListShow {
struct MyListShow: Identifiable {
let id: Int
let name: String
let totalEpisodes: Int?
Expand Down
132 changes: 0 additions & 132 deletions Ichime/MyLists/Model/ShowListStatus.swift

This file was deleted.

Loading

0 comments on commit 0fea081

Please sign in to comment.