Skip to content
This repository has been archived by the owner on Dec 28, 2024. It is now read-only.

Feature // Apply mihoyo new widget daily note api #114

Merged
merged 4 commits into from
Jul 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ extension DailyNoteTimelineProvider {
func placeholder(in context: Context) -> DailyNoteEntry {
.init(
date: Date(),
dailyNoteResult: .success(.example()),
dailyNoteResult: .success(GeneralDailyNote.example()),
configuration: defaultConfiguration
)
}
Expand All @@ -58,7 +58,7 @@ extension DailyNoteTimelineProvider {
completion(
.init(
date: Date(),
dailyNoteResult: .success(.example()),
dailyNoteResult: .success(GeneralDailyNote.example()),
configuration: defaultConfiguration
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ extension GIStyleTimelineProvider {
extension GIStyleTimelineProvider {
func placeholder(in context: Context) -> GIStyleEntry {
var expeditionWithUIImage: [(ExpeditionInformation.Expedition, [UIImage?])] = []
expeditionWithUIImage = DailyNote.example().expeditionInformation.expeditions.map { expedition in
expeditionWithUIImage = GeneralDailyNote.example().expeditionInformation.expeditions.map { expedition in
var images: [UIImage?] = []
expedition.avatarIconURLs.forEach { url in
if let data = try? Data(contentsOf: url) {
Expand All @@ -59,15 +59,15 @@ extension GIStyleTimelineProvider {
}
return .init(
date: Date(),
dailyNoteResult: .success(.example()),
dailyNoteResult: .success(GeneralDailyNote.example()),
configuration: defaultConfiguration,
expeditionWithUIImage: expeditionWithUIImage
)
}

func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (GIStyleEntry) -> ()) {
var expeditionWithUIImage: [(ExpeditionInformation.Expedition, [UIImage?])] = []
expeditionWithUIImage = DailyNote.example().expeditionInformation.expeditions.map { expedition in
expeditionWithUIImage = GeneralDailyNote.example().expeditionInformation.expeditions.map { expedition in
var images: [UIImage?] = []
expedition.avatarIconURLs.forEach { url in
if let data = try? Data(contentsOf: url) {
Expand All @@ -79,7 +79,7 @@ extension GIStyleTimelineProvider {
completion(
.init(
date: Date(),
dailyNoteResult: .success(.example()),
dailyNoteResult: .success(GeneralDailyNote.example()),
configuration: defaultConfiguration,
expeditionWithUIImage: expeditionWithUIImage
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct LockscreenTimelineProvider: IntentTimelineProvider, HasDefaultAccount {
.init(
date: Date(),
configuration: defaultConfiguration,
dailyNoteResult: .success(.example())
dailyNoteResult: .success(GeneralDailyNote.example())
)
}

Expand All @@ -51,7 +51,7 @@ struct LockscreenTimelineProvider: IntentTimelineProvider, HasDefaultAccount {
.init(
date: Date(),
configuration: defaultConfiguration,
dailyNoteResult: .success(.example())
dailyNoteResult: .success(GeneralDailyNote.example())
)
)
}
Expand Down
17 changes: 17 additions & 0 deletions Features/Daily Note/View/InAppDailyNoteCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ private struct NoteView: View {
}
}
}
// Daily Training & Simulated Universe (China mainland user only)
if let dailyNote = note as? WidgetDailyNote {
HStack {
Text("app.dailynote.card.daily_training.label").bold()
Spacer()
let currentScore = dailyNote.dailyTrainingInformation.currentScore
let maxScore = dailyNote.dailyTrainingInformation.maxScore
Text("\(currentScore)/\(maxScore)")
}
HStack {
Text("app.dailynote.card.simulated_universe.label").bold()
Spacer()
let currentScore = dailyNote.simulatedUniverseInformation.currentScore
let maxScore = dailyNote.simulatedUniverseInformation.maxScore
Text("\(currentScore)/\(maxScore)")
}
}
// Dispatch
VStack {
HStack {
Expand Down
2 changes: 2 additions & 0 deletions Internationalization/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,5 @@
"account.test.footer.recommend_device_fp" = "To avoid consistently being requested for verification, please set up device finger print according to [this tutorial](https://hsr.ophelper.top/static/fp_tutorial).";
"sys.manage_hoyolab_account" = "Manage your HoYoLAB account";
"sys.manage_hoyolab_account.footer" = "You can delete your HoYoLAB account here.";
"app.dailynote.card.daily_training.label" = "Daily Training";
"app.dailynote.card.simulated_universe.label" = "Simulated Universe";
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension MiHoYoAPI {
/// - Parameter uid: The uid of the user whose daily note to fetch.
/// - Parameter cookie: The cookie of the user. This is used for authentication purposes.
///
/// - Throws: An error of type `MiHoYoAPI.Error` if an error occurs while making the API request.
/// - Throws: An error of type `MiHoYoAPIError` if an error occurs while making the API request.
///
/// - Returns: An instance of `DailyNote` that represents the user's daily note.
public static func note(
Expand All @@ -25,6 +25,35 @@ extension MiHoYoAPI {
deviceFingerPrint: String?
) async throws
-> DailyNote {
switch server.region {
case .china:
return try await widgetNote(cookie: cookie, deviceFingerPrint: deviceFingerPrint)
case .global:
return try await generalDailyNote(
server: server,
uid: uid,
cookie: cookie,
deviceFingerPrint: deviceFingerPrint
)
}
}

/// Fetches the daily note of the specified user.
///
/// - Parameter server: The server where the user's account exists.
/// - Parameter uid: The uid of the user whose daily note to fetch.
/// - Parameter cookie: The cookie of the user. This is used for authentication purposes.
///
/// - Throws: An error of type `MiHoYoAPI.Error` if an error occurs while making the API request.
///
/// - Returns: An instance of `GeneralDailyNote` that represents the user's daily note.
static func generalDailyNote(
server: Server,
uid: String,
cookie: String,
deviceFingerPrint: String?
) async throws
-> GeneralDailyNote {
// #if DEBUG
// return .example()
// #else
Expand Down Expand Up @@ -52,4 +81,32 @@ extension MiHoYoAPI {
return try await .decodeFromMiHoYoAPIJSONResult(data: data, with: request)
// #endif
}

/// Fetches the daily note of the specified user. Using widget api.
/// - Parameters:
/// - cookie: The cookie of the user.
/// - deviceFingerPrint: The device finger print of the user.
static func widgetNote(
cookie: String,
deviceFingerPrint: String?
) async throws
-> WidgetDailyNote {
let additionalHeaders: [String: String]? = {
if let deviceFingerPrint, !deviceFingerPrint.isEmpty {
return ["x-rpc-device_fp": deviceFingerPrint]
} else {
return nil
}
}()
let request = try await Self.generateRecordAPIRequest(
region: .china,
path: "/game_record/app/hkrpg/aapi/widget",
queryItems: [],
cookie: cookie,
additionalHeaders: additionalHeaders
)

let (data, _) = try await URLSession.shared.data(for: request)
return try await .decodeFromMiHoYoAPIJSONResult(data: data, with: request)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,21 @@ extension ReferencingBenchmarkTime {
}
}

// MARK: - DailyNote + BenchmarkTimeEditable
// MARK: - GeneralDailyNote + BenchmarkTimeEditable

extension DailyNote: BenchmarkTimeEditable {
public func replacingBenchmarkTime(_ newBenchmarkTime: Date) -> DailyNote {
extension GeneralDailyNote: BenchmarkTimeEditable {
public func replacingBenchmarkTime(_ newBenchmarkTime: Date) -> GeneralDailyNote {
var newDailyNote = self
newDailyNote.staminaInformation = staminaInformation.replacingBenchmarkTime(newBenchmarkTime)
newDailyNote.expeditionInformation = expeditionInformation.replacingBenchmarkTime(newBenchmarkTime)
return newDailyNote
}
}

// MARK: - WidgetDailyNote + BenchmarkTimeEditable

extension WidgetDailyNote: BenchmarkTimeEditable {
public func replacingBenchmarkTime(_ newBenchmarkTime: Date) -> WidgetDailyNote {
var newDailyNote = self
newDailyNote.staminaInformation = staminaInformation.replacingBenchmarkTime(newBenchmarkTime)
newDailyNote.expeditionInformation = expeditionInformation.replacingBenchmarkTime(newBenchmarkTime)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,20 @@
//
// Model.swift
// File.swift
//
//
// Created by 戴藏龙 on 2023/5/2.
// Created by 戴藏龙 on 2023/7/2.
//

import Foundation

// MARK: - DailyNote

/// A struct representing the result of note API
public struct DailyNote: Decodable {
// MARK: Lifecycle

public init(from decoder: Decoder) throws {
let decoder = try decoder.singleValueContainer()
self.staminaInformation = try decoder.decode(StaminaInformation.self)
self.expeditionInformation = try decoder.decode(ExpeditionInformation.self)
}

// MARK: Public

/// Daily note protocol. The result from 2 kind of note api use this protocol.
public protocol DailyNote: BenchmarkTimeEditable {
/// Stamina information
public var staminaInformation: StaminaInformation
var staminaInformation: StaminaInformation { get }
/// Expedition information
public var expeditionInformation: ExpeditionInformation
var expeditionInformation: ExpeditionInformation { get }
/// The time when this struct is generated
public let fetchTime: Date = .init()

// MARK: Internal

/// Deccoding keys for the decoder
enum CodingKeys: String, CodingKey {
case staminaInformation = "stamina_info"
case expeditionInformation = "daily_expedition"
}
}

// MARK: DecodableFromMiHoYoAPIJSONResult

extension DailyNote: DecodableFromMiHoYoAPIJSONResult {}

@available(iOS 15.0, *)
extension DailyNote {
public static func example() -> DailyNote {
let exampleURL = Bundle.module.url(forResource: "daily_note_example", withExtension: "json")!
// swiftlint:disable:next force_try
let exampleData = try! Data(contentsOf: exampleURL)
// swiftlint:disable:next force_try
return try! DailyNote.decodeFromMiHoYoAPIJSONResult(
data: exampleData
)
}
var fetchTime: Date { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// File.swift
//
//
// Created by 戴藏龙 on 2023/7/2.
//

import Foundation

// MARK: - DailyTrainingInformation

public struct DailyTrainingInformation {
public let currentScore: Int
public let maxScore: Int
}

// MARK: Decodable

extension DailyTrainingInformation: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.currentScore = try container.decode(Int.self, forKey: .currentScore)
self.maxScore = try container.decode(Int.self, forKey: .maxScore)
}

enum CodingKeys: String, CodingKey {
case currentScore = "current_train_score"
case maxScore = "max_train_score"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public struct ExpeditionInformation {
case expeditions
case totalExpeditionNumber = "total_expedition_num"
case acceptedExpeditionNumber = "accepted_epedition_num"
// Mihoyo's api has a spell error here. So there are 2 keys for this field.
case alterKeyForAcceptedExpeditionNumber = "accepted_expedition_num"
}
}

Expand All @@ -100,7 +102,11 @@ extension ExpeditionInformation: Decodable {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.expeditions = try container.decode([ExpeditionInformation.Expedition].self, forKey: .expeditions)
self.totalExpeditionNumber = try container.decode(Int.self, forKey: .totalExpeditionNumber)
self.acceptedExpeditionNumber = try container.decode(Int.self, forKey: .acceptedExpeditionNumber)
if let acceptedExpeditionNumber = try? container.decode(Int.self, forKey: .acceptedExpeditionNumber) {
self.acceptedExpeditionNumber = acceptedExpeditionNumber
} else {
self.acceptedExpeditionNumber = try container.decode(Int.self, forKey: .alterKeyForAcceptedExpeditionNumber)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Model.swift
//
//
// Created by 戴藏龙 on 2023/5/2.
//

import Foundation

// MARK: - GeneralDailyNote

/// A struct representing the result of note API
public struct GeneralDailyNote: DecodableFromMiHoYoAPIJSONResult, DailyNote {
// MARK: Lifecycle

public init(from decoder: Decoder) throws {
let decoder = try decoder.singleValueContainer()
self.staminaInformation = try decoder.decode(StaminaInformation.self)
self.expeditionInformation = try decoder.decode(ExpeditionInformation.self)
}

// MARK: Public

/// Stamina information
public var staminaInformation: StaminaInformation
/// Expedition information
public var expeditionInformation: ExpeditionInformation
/// The time when this struct is generated
public let fetchTime: Date = .init()
}

@available(iOS 15.0, *)
extension GeneralDailyNote {
public static func example() -> DailyNote {
let exampleURL = Bundle.module.url(forResource: "daily_note_example", withExtension: "json")!
// swiftlint:disable force_try
let exampleData = try! Data(contentsOf: exampleURL)
return try! GeneralDailyNote.decodeFromMiHoYoAPIJSONResult(
data: exampleData
) as DailyNote
// swiftlint:enable force_try
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// File.swift
//
//
// Created by 戴藏龙 on 2023/7/2.
//

import Foundation

// MARK: - SimulatedUniverseInformation

public struct SimulatedUniverseInformation {
public let currentScore: Int
public let maxScore: Int
}

// MARK: Decodable

extension SimulatedUniverseInformation: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.currentScore = try container.decode(Int.self, forKey: .currentScore)
self.maxScore = try container.decode(Int.self, forKey: .maxScore)
}

enum CodingKeys: String, CodingKey {
case currentScore = "current_rogue_score"
case maxScore = "max_rogue_score"
}
}
Loading