This commit is contained in:
湖北秦九 2025-11-21 09:12:46 +08:00
parent 010ea1cd70
commit 008d5b55b8
101 changed files with 1743 additions and 188 deletions

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

84
FAWidget/FAWidget.swift Normal file
View File

@ -0,0 +1,84 @@
//
// FAWidget.swift
// FAWidget
//
// Created by on 2025/11/10.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), emoji: "😀")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), emoji: "😀")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, emoji: "😀")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let emoji: String
}
struct FAWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .time)
Text("Emoji:")
Text(entry.emoji)
}
}
}
struct FAWidget: Widget {
let kind: String = "FAWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
FAWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
FAWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
//#Preview(as: .systemSmall) {
// FAWidget()
//} timeline: {
// SimpleEntry(date: .now, emoji: "😀")
// SimpleEntry(date: .now, emoji: "🤩")
//}

View File

@ -0,0 +1,17 @@
//
// FAWidgetBundle.swift
// FAWidget
//
// Created by on 2025/11/10.
//
import WidgetKit
import SwiftUI
@main
struct FAWidgetBundle: WidgetBundle {
var body: some Widget {
// FAWidget()
FAWidgetLiveActivity()
}
}

View File

@ -0,0 +1,129 @@
//
// FAWidgetLiveActivity.swift
// FAWidget
//
// Created by on 2025/11/10.
//
import ActivityKit
import WidgetKit
import SwiftUI
struct FAWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: ActivityManagerAttributes.self) { context in
// 🔹 Live Activity UI & Banner
HStack(spacing: 16) {
// 🔹
if let imageData = try? Data(contentsOf: URL(string: context.state.videoCoverPath)!),
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.frame(width: 108, height: 146)
.cornerRadius(8)
.clipped()
} else {
Image("logo100")
.frame(width: 100, height: 100)
.cornerRadius(8)
.clipped()
}
// 🔹
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text("Playing")
.font(.caption)
.foregroundColor(.gray)
Spacer()
Image("logo40")
}
Text(context.state.videoTitle)
.font(.headline)
.foregroundColor(.black)
Text("Ep.\(context.state.videoEpisode)")
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
// 🔹
Button(action: { print("Watch Now") }) {
HStack {
Spacer()
Image(systemName: "play.fill")
Text("Watch Now")
Spacer()
}
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(
LinearGradient(colors: [Color.red, Color.orange], startPoint: .leading, endPoint: .trailing)
)
.foregroundColor(.white)
.cornerRadius(20)
}
}
}
.padding()
.background(RoundedRectangle(cornerRadius: 8).fill(Color.white))
.activityBackgroundTint(Color.white)
.activitySystemActionForegroundColor(.primary)
.widgetURL(URL(string: "fableonapp://liveActivity?short_play_id=\(context.state.videoId)"))
} dynamicIsland: { context in
DynamicIsland {
// 🔹 Expanded UI
DynamicIslandExpandedRegion(.leading) {
VStack (alignment: .center) {
Spacer()
Image("logo40")
.resizable()
.frame(width: 40, height: 40)
.cornerRadius(5)
Spacer()
}
}
DynamicIslandExpandedRegion(.center) {
VStack (alignment: .center) {
Spacer()
Text(context.state.videoTitle)
.font(.headline)
.foregroundColor(.primary)
Spacer()
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack (alignment: .center) {
Spacer()
Text("Ep.\(context.state.videoEpisode)")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
}
}
} compactLeading: {
Image("logo40")
} compactTrailing: {
Text("Ep.\(context.state.videoEpisode)")
.font(.caption2)
} minimal: {
Image("logo40")
}
.widgetURL(URL(string: "fableonapp://liveActivity?short_play_id=\(context.state.videoId)"))
.keylineTint(Color.red)
}
}
}

11
FAWidget/Info.plist Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.hn.qinjiu.fableon</string>
</array>
</dict>
</plist>

View File

@ -227,6 +227,21 @@
03E9A74E2EB5E0F7000D1067 /* FALanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A74D2EB5E0F7000D1067 /* FALanguageModel.swift */; }; 03E9A74E2EB5E0F7000D1067 /* FALanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A74D2EB5E0F7000D1067 /* FALanguageModel.swift */; };
03E9A7502EB5EAC0000D1067 /* FALanguageDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A74F2EB5EAC0000D1067 /* FALanguageDataModel.swift */; }; 03E9A7502EB5EAC0000D1067 /* FALanguageDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A74F2EB5EAC0000D1067 /* FALanguageDataModel.swift */; };
03E9A7522EB83A58000D1067 /* FAAppStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7512EB83A58000D1067 /* FAAppStartViewController.swift */; }; 03E9A7522EB83A58000D1067 /* FAAppStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7512EB83A58000D1067 /* FAAppStartViewController.swift */; };
03E9A75E2EC19101000D1067 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 03E9A7572EC19101000D1067 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
03E9A7682EC19110000D1067 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7652EC19110000D1067 /* NotificationService.swift */; };
03E9A76F2EC1950F000D1067 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03E9A76E2EC1950F000D1067 /* WidgetKit.framework */; };
03E9A7712EC1950F000D1067 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03E9A7702EC1950F000D1067 /* SwiftUI.framework */; };
03E9A77E2EC19510000D1067 /* FAWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 03E9A76D2EC1950F000D1067 /* FAWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
03E9A7892EC19516000D1067 /* FAWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7842EC19516000D1067 /* FAWidget.swift */; };
03E9A78A2EC19516000D1067 /* FAWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7852EC19516000D1067 /* FAWidgetBundle.swift */; };
03E9A78B2EC19516000D1067 /* FAWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7862EC19516000D1067 /* FAWidgetLiveActivity.swift */; };
03E9A78C2EC19516000D1067 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03E9A7832EC19516000D1067 /* Assets.xcassets */; };
03E9A7902EC1B007000D1067 /* FAActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A78F2EC1B007000D1067 /* FAActivityManager.swift */; };
03E9A7912EC1B007000D1067 /* FAActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A78F2EC1B007000D1067 /* FAActivityManager.swift */; };
03E9A7942EC1B24E000D1067 /* UIImage+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7932EC1B24E000D1067 /* UIImage+FAAdd.swift */; };
03E9A7972EC2C7DF000D1067 /* FAHomeNewTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7962EC2C7DF000D1067 /* FAHomeNewTransformer.swift */; };
03E9A79A2EC2C8FD000D1067 /* NSNumber+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A7992EC2C8F3000D1067 /* NSNumber+FAAdd.swift */; };
03E9A79C2EC31AD0000D1067 /* FAFeedbackCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E9A79B2EC31AD0000D1067 /* FAFeedbackCountModel.swift */; };
B86XD3O90WO2R4725L084287 /* Pods_Fableon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */; }; B86XD3O90WO2R4725L084287 /* Pods_Fableon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */; };
F3019606DA7P36H41G408X13 /* ZStreamCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38E33739F391364D0151P7Z /* ZStreamCell.swift */; }; F3019606DA7P36H41G408X13 /* ZStreamCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38E33739F391364D0151P7Z /* ZStreamCell.swift */; };
F30470W590T8274E1642349G /* CControlCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32MR5F8X6Q3HSZ560BD0159 /* CControlCell.swift */; }; F30470W590T8274E1642349G /* CControlCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32MR5F8X6Q3HSZ560BD0159 /* CControlCell.swift */; };
@ -333,6 +348,38 @@
F3ZT3I4VAGB5405FWL36UW12 /* ZFGEtworkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3342A2631Z4ZPCT74M73CK1 /* ZFGEtworkCell.swift */; }; F3ZT3I4VAGB5405FWL36UW12 /* ZFGEtworkCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3342A2631Z4ZPCT74M73CK1 /* ZFGEtworkCell.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
03E9A75C2EC19101000D1067 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F3LK276P33H73Y39H9X6VTDI /* Project object */;
proxyType = 1;
remoteGlobalIDString = 03E9A7562EC19101000D1067;
remoteInfo = NotificationService;
};
03E9A77C2EC19510000D1067 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = F3LK276P33H73Y39H9X6VTDI /* Project object */;
proxyType = 1;
remoteGlobalIDString = 03E9A76C2EC1950F000D1067;
remoteInfo = FAWidgetExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
03E9A7632EC19101000D1067 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
03E9A75E2EC19101000D1067 /* NotificationService.appex in Embed Foundation Extensions */,
03E9A77E2EC19510000D1067 /* FAWidgetExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
031FDEAB2EAF05FB00F4CAC7 /* FAStoreVipCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAStoreVipCell.swift; sourceTree = "<group>"; }; 031FDEAB2EAF05FB00F4CAC7 /* FAStoreVipCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAStoreVipCell.swift; sourceTree = "<group>"; };
031FDEAD2EB093B100F4CAC7 /* FABuyRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FABuyRecordsModel.swift; sourceTree = "<group>"; }; 031FDEAD2EB093B100F4CAC7 /* FABuyRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FABuyRecordsModel.swift; sourceTree = "<group>"; };
@ -552,6 +599,23 @@
03E9A74D2EB5E0F7000D1067 /* FALanguageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALanguageModel.swift; sourceTree = "<group>"; }; 03E9A74D2EB5E0F7000D1067 /* FALanguageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALanguageModel.swift; sourceTree = "<group>"; };
03E9A74F2EB5EAC0000D1067 /* FALanguageDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALanguageDataModel.swift; sourceTree = "<group>"; }; 03E9A74F2EB5EAC0000D1067 /* FALanguageDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALanguageDataModel.swift; sourceTree = "<group>"; };
03E9A7512EB83A58000D1067 /* FAAppStartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAppStartViewController.swift; sourceTree = "<group>"; }; 03E9A7512EB83A58000D1067 /* FAAppStartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAppStartViewController.swift; sourceTree = "<group>"; };
03E9A7572EC19101000D1067 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
03E9A7642EC19110000D1067 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
03E9A7652EC19110000D1067 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
03E9A76D2EC1950F000D1067 /* FAWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FAWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
03E9A76E2EC1950F000D1067 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
03E9A7702EC1950F000D1067 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
03E9A7832EC19516000D1067 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
03E9A7842EC19516000D1067 /* FAWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAWidget.swift; sourceTree = "<group>"; };
03E9A7852EC19516000D1067 /* FAWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAWidgetBundle.swift; sourceTree = "<group>"; };
03E9A7862EC19516000D1067 /* FAWidgetLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAWidgetLiveActivity.swift; sourceTree = "<group>"; };
03E9A7872EC19516000D1067 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
03E9A78F2EC1B007000D1067 /* FAActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAActivityManager.swift; sourceTree = "<group>"; };
03E9A7922EC1B0BC000D1067 /* FAWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FAWidgetExtension.entitlements; sourceTree = "<group>"; };
03E9A7932EC1B24E000D1067 /* UIImage+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+FAAdd.swift"; sourceTree = "<group>"; };
03E9A7962EC2C7DF000D1067 /* FAHomeNewTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeNewTransformer.swift; sourceTree = "<group>"; };
03E9A7992EC2C8F3000D1067 /* NSNumber+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+FAAdd.swift"; sourceTree = "<group>"; };
03E9A79B2EC31AD0000D1067 /* FAFeedbackCountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAFeedbackCountModel.swift; sourceTree = "<group>"; };
19196I43BR665O55RD205171 /* Pods-Fableon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.debug.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.debug.xcconfig"; sourceTree = "<group>"; }; 19196I43BR665O55RD205171 /* Pods-Fableon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.debug.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.debug.xcconfig"; sourceTree = "<group>"; };
C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Fableon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Fableon.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DCD59738B6J31K33W4Z524S0 /* Pods-Fableon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.release.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.release.xcconfig"; sourceTree = "<group>"; }; DCD59738B6J31K33W4Z524S0 /* Pods-Fableon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.release.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.release.xcconfig"; sourceTree = "<group>"; };
@ -663,6 +727,22 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
03E9A7542EC19101000D1067 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
03E9A76A2EC1950F000D1067 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
03E9A7712EC1950F000D1067 /* SwiftUI.framework in Frameworks */,
03E9A76F2EC1950F000D1067 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F37182X6857153T873504S9J /* Frameworks */ = { F37182X6857153T873504S9J /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -809,6 +889,8 @@
03E239772EAA1A29004A8CEC /* UIStackView+FAAdd.swift */, 03E239772EAA1A29004A8CEC /* UIStackView+FAAdd.swift */,
03E239782EAA1A29004A8CEC /* UIView+FAAdd.swift */, 03E239782EAA1A29004A8CEC /* UIView+FAAdd.swift */,
03E239792EAA1A29004A8CEC /* UserDefaults+FAAdd.swift */, 03E239792EAA1A29004A8CEC /* UserDefaults+FAAdd.swift */,
03E9A7932EC1B24E000D1067 /* UIImage+FAAdd.swift */,
03E9A7992EC2C8F3000D1067 /* NSNumber+FAAdd.swift */,
); );
path = Extension; path = Extension;
sourceTree = "<group>"; sourceTree = "<group>";
@ -897,6 +979,7 @@
03E239BD2EAA1A4E004A8CEC /* FAHomeItem.swift */, 03E239BD2EAA1A4E004A8CEC /* FAHomeItem.swift */,
03E239BE2EAA1A4E004A8CEC /* FAHomeModuleItem.swift */, 03E239BE2EAA1A4E004A8CEC /* FAHomeModuleItem.swift */,
03E239BF2EAA1A4E004A8CEC /* FAPopularModel.swift */, 03E239BF2EAA1A4E004A8CEC /* FAPopularModel.swift */,
03E9A7962EC2C7DF000D1067 /* FAHomeNewTransformer.swift */,
); );
path = M; path = M;
sourceTree = "<group>"; sourceTree = "<group>";
@ -988,6 +1071,7 @@
children = ( children = (
03E239ED2EAA1A4E004A8CEC /* FAMeItemModel.swift */, 03E239ED2EAA1A4E004A8CEC /* FAMeItemModel.swift */,
03E9A7412EB4A603000D1067 /* FAVersionUpdateModel.swift */, 03E9A7412EB4A603000D1067 /* FAVersionUpdateModel.swift */,
03E9A79B2EC31AD0000D1067 /* FAFeedbackCountModel.swift */,
); );
path = M; path = M;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1212,6 +1296,7 @@
03E23A962EAA1A65004A8CEC /* Libs */ = { 03E23A962EAA1A65004A8CEC /* Libs */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
03E9A78E2EC1AFEA000D1067 /* ActivityManager */,
031FDEE22EB3487700F4CAC7 /* Alert */, 031FDEE22EB3487700F4CAC7 /* Alert */,
039CE6122EAB0DE1007B5EED /* FAIap */, 039CE6122EAB0DE1007B5EED /* FAIap */,
039CE60F2EAB0D2D007B5EED /* JXIAPManager */, 039CE60F2EAB0D2D007B5EED /* JXIAPManager */,
@ -1302,10 +1387,41 @@
path = WaterfallFlowLayout; path = WaterfallFlowLayout;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
03E9A7662EC19110000D1067 /* NotificationService */ = {
isa = PBXGroup;
children = (
03E9A7642EC19110000D1067 /* Info.plist */,
03E9A7652EC19110000D1067 /* NotificationService.swift */,
);
path = NotificationService;
sourceTree = "<group>";
};
03E9A7882EC19516000D1067 /* FAWidget */ = {
isa = PBXGroup;
children = (
03E9A7832EC19516000D1067 /* Assets.xcassets */,
03E9A7842EC19516000D1067 /* FAWidget.swift */,
03E9A7852EC19516000D1067 /* FAWidgetBundle.swift */,
03E9A7862EC19516000D1067 /* FAWidgetLiveActivity.swift */,
03E9A7872EC19516000D1067 /* Info.plist */,
);
path = FAWidget;
sourceTree = "<group>";
};
03E9A78E2EC1AFEA000D1067 /* ActivityManager */ = {
isa = PBXGroup;
children = (
03E9A78F2EC1B007000D1067 /* FAActivityManager.swift */,
);
path = ActivityManager;
sourceTree = "<group>";
};
612P1QES0606A2642109V515 /* Frameworks */ = { 612P1QES0606A2642109V515 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */, C4THBP0A1283PFXW440071Q5 /* Pods_Fableon.framework */,
03E9A76E2EC1950F000D1067 /* WidgetKit.framework */,
03E9A7702EC1950F000D1067 /* SwiftUI.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1343,7 +1459,10 @@
F31ABI705806054356280I22 = { F31ABI705806054356280I22 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
03E9A7922EC1B0BC000D1067 /* FAWidgetExtension.entitlements */,
F3YR32SS1P8731297KD665X5 /* Fableon */, F3YR32SS1P8731297KD665X5 /* Fableon */,
03E9A7662EC19110000D1067 /* NotificationService */,
03E9A7882EC19516000D1067 /* FAWidget */,
F3B536I6734E05L319J6654P /* Products */, F3B536I6734E05L319J6654P /* Products */,
A9370738344P8616G77589I7 /* Pods */, A9370738344P8616G77589I7 /* Pods */,
612P1QES0606A2642109V515 /* Frameworks */, 612P1QES0606A2642109V515 /* Frameworks */,
@ -1634,6 +1753,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
F36676EN936M89B15P51177W /* Fableon.app */, F36676EN936M89B15P51177W /* Fableon.app */,
03E9A7572EC19101000D1067 /* NotificationService.appex */,
03E9A76D2EC1950F000D1067 /* FAWidgetExtension.appex */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1788,6 +1909,40 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
03E9A7562EC19101000D1067 /* NotificationService */ = {
isa = PBXNativeTarget;
buildConfigurationList = 03E9A7602EC19101000D1067 /* Build configuration list for PBXNativeTarget "NotificationService" */;
buildPhases = (
03E9A7532EC19101000D1067 /* Sources */,
03E9A7542EC19101000D1067 /* Frameworks */,
03E9A7552EC19101000D1067 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = NotificationService;
productName = NotificationService;
productReference = 03E9A7572EC19101000D1067 /* NotificationService.appex */;
productType = "com.apple.product-type.app-extension";
};
03E9A76C2EC1950F000D1067 /* FAWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 03E9A7802EC19510000D1067 /* Build configuration list for PBXNativeTarget "FAWidgetExtension" */;
buildPhases = (
03E9A7692EC1950F000D1067 /* Sources */,
03E9A76A2EC1950F000D1067 /* Frameworks */,
03E9A76B2EC1950F000D1067 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FAWidgetExtension;
productName = FAWidgetExtension;
productReference = 03E9A76D2EC1950F000D1067 /* FAWidgetExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
F3F6B3Q1BI331E859340M109 /* Fableon */ = { F3F6B3Q1BI331E859340M109 /* Fableon */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = F3511CV60KB1225F8K46932G /* Build configuration list for PBXNativeTarget "Fableon" */; buildConfigurationList = F3511CV60KB1225F8K46932G /* Build configuration list for PBXNativeTarget "Fableon" */;
@ -1797,10 +1952,13 @@
F37182X6857153T873504S9J /* Frameworks */, F37182X6857153T873504S9J /* Frameworks */,
F3T938414J234X46539JR019 /* Resources */, F3T938414J234X46539JR019 /* Resources */,
4809W21R638Z15866LWB2041 /* [CP] Embed Pods Frameworks */, 4809W21R638Z15866LWB2041 /* [CP] Embed Pods Frameworks */,
03E9A7632EC19101000D1067 /* Embed Foundation Extensions */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
03E9A75D2EC19101000D1067 /* PBXTargetDependency */,
03E9A77D2EC19510000D1067 /* PBXTargetDependency */,
); );
name = Fableon; name = Fableon;
productName = Fableon; productName = Fableon;
@ -1814,9 +1972,15 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640; LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1640; LastUpgradeCheck = 1640;
TargetAttributes = { TargetAttributes = {
03E9A7562EC19101000D1067 = {
CreatedOnToolsVersion = 26.0.1;
};
03E9A76C2EC1950F000D1067 = {
CreatedOnToolsVersion = 26.0.1;
};
F3F6B3Q1BI331E859340M109 = { F3F6B3Q1BI331E859340M109 = {
CreatedOnToolsVersion = 16.4; CreatedOnToolsVersion = 16.4;
LastSwiftMigration = 1640; LastSwiftMigration = 1640;
@ -1842,11 +2006,28 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
F3F6B3Q1BI331E859340M109 /* Fableon */, F3F6B3Q1BI331E859340M109 /* Fableon */,
03E9A7562EC19101000D1067 /* NotificationService */,
03E9A76C2EC1950F000D1067 /* FAWidgetExtension */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
03E9A7552EC19101000D1067 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
03E9A76B2EC1950F000D1067 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03E9A78C2EC19516000D1067 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F3T938414J234X46539JR019 /* Resources */ = { F3T938414J234X46539JR019 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1937,12 +2118,32 @@
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
03E9A7532EC19101000D1067 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03E9A7682EC19110000D1067 /* NotificationService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
03E9A7692EC1950F000D1067 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03E9A7892EC19516000D1067 /* FAWidget.swift in Sources */,
03E9A78A2EC19516000D1067 /* FAWidgetBundle.swift in Sources */,
03E9A7912EC1B007000D1067 /* FAActivityManager.swift in Sources */,
03E9A78B2EC19516000D1067 /* FAWidgetLiveActivity.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F3A707N33187J9XN3E985764 /* Sources */ = { F3A707N33187J9XN3E985764 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
F39C9Y266RT4H0109L9T1807 /* LKVRecommendedHomeView.swift in Sources */, F39C9Y266RT4H0109L9T1807 /* LKVRecommendedHomeView.swift in Sources */,
F37S2W333F5881I12F59EXJ0 /* KCFAlignment.swift in Sources */, F37S2W333F5881I12F59EXJ0 /* KCFAlignment.swift in Sources */,
03E9A79C2EC31AD0000D1067 /* FAFeedbackCountModel.swift in Sources */,
F34Y5K0497T0B7D95Q1F4304 /* VRResult.swift in Sources */, F34Y5K0497T0B7D95Q1F4304 /* VRResult.swift in Sources */,
F3Q1B8O8164TYQ9724T34V29 /* OACAbleView.swift in Sources */, F3Q1B8O8164TYQ9724T34V29 /* OACAbleView.swift in Sources */,
F36MZ39RQ295569BV1915908 /* FOMptyView.swift in Sources */, F36MZ39RQ295569BV1915908 /* FOMptyView.swift in Sources */,
@ -1984,6 +2185,7 @@
F3362Y34741Z8AE0YP4S2Y54 /* YORecommended.swift in Sources */, F3362Y34741Z8AE0YP4S2Y54 /* YORecommended.swift in Sources */,
03E239632EAA1945004A8CEC /* AppDelegate+FAConfig.swift in Sources */, 03E239632EAA1945004A8CEC /* AppDelegate+FAConfig.swift in Sources */,
03E239642EAA1945004A8CEC /* AppDelegate.swift in Sources */, 03E239642EAA1945004A8CEC /* AppDelegate.swift in Sources */,
03E9A7972EC2C7DF000D1067 /* FAHomeNewTransformer.swift in Sources */,
03E239652EAA1945004A8CEC /* SceneDelegate.swift in Sources */, 03E239652EAA1945004A8CEC /* SceneDelegate.swift in Sources */,
039CE6092EAA2F71007B5EED /* FAAdjustStateManager.swift in Sources */, 039CE6092EAA2F71007B5EED /* FAAdjustStateManager.swift in Sources */,
F333U95746V7VK13QI9275B3 /* UMenuTransformerCell.swift in Sources */, F333U95746V7VK13QI9275B3 /* UMenuTransformerCell.swift in Sources */,
@ -2089,6 +2291,7 @@
03E23A5C2EAA1A4E004A8CEC /* FAGenresListViewController.swift in Sources */, 03E23A5C2EAA1A4E004A8CEC /* FAGenresListViewController.swift in Sources */,
03E23A5D2EAA1A4E004A8CEC /* FARecommendViewModel.swift in Sources */, 03E23A5D2EAA1A4E004A8CEC /* FARecommendViewModel.swift in Sources */,
03E23A5E2EAA1A4E004A8CEC /* FAHomeMustSeeContentView.swift in Sources */, 03E23A5E2EAA1A4E004A8CEC /* FAHomeMustSeeContentView.swift in Sources */,
03E9A7902EC1B007000D1067 /* FAActivityManager.swift in Sources */,
03E23A5F2EAA1A4E004A8CEC /* FAEpSelectorCell.swift in Sources */, 03E23A5F2EAA1A4E004A8CEC /* FAEpSelectorCell.swift in Sources */,
031FDEEC2EB35DF600F4CAC7 /* FACoinsPackAlert.swift in Sources */, 031FDEEC2EB35DF600F4CAC7 /* FACoinsPackAlert.swift in Sources */,
03E23A602EAA1A4E004A8CEC /* FASearchResultView.swift in Sources */, 03E23A602EAA1A4E004A8CEC /* FASearchResultView.swift in Sources */,
@ -2111,6 +2314,7 @@
03E23A6E2EAA1A4E004A8CEC /* FARankingListCell.swift in Sources */, 03E23A6E2EAA1A4E004A8CEC /* FARankingListCell.swift in Sources */,
031FDED22EB2F69200F4CAC7 /* FALoginView.swift in Sources */, 031FDED22EB2F69200F4CAC7 /* FALoginView.swift in Sources */,
03E23A6F2EAA1A4E004A8CEC /* FAHomeBannerCell.swift in Sources */, 03E23A6F2EAA1A4E004A8CEC /* FAHomeBannerCell.swift in Sources */,
03E9A79A2EC2C8FD000D1067 /* NSNumber+FAAdd.swift in Sources */,
03E23A702EAA1A4E004A8CEC /* FAGenresCell.swift in Sources */, 03E23A702EAA1A4E004A8CEC /* FAGenresCell.swift in Sources */,
03E23A712EAA1A4E004A8CEC /* FAHomeSectionTitleView.swift in Sources */, 03E23A712EAA1A4E004A8CEC /* FAHomeSectionTitleView.swift in Sources */,
03E23A722EAA1A4E004A8CEC /* FASettingViewController.swift in Sources */, 03E23A722EAA1A4E004A8CEC /* FASettingViewController.swift in Sources */,
@ -2206,6 +2410,7 @@
03E239AF2EAA1A29004A8CEC /* FAAPI.swift in Sources */, 03E239AF2EAA1A29004A8CEC /* FAAPI.swift in Sources */,
031FDECA2EB1F8F200F4CAC7 /* FACoinsPackReceiveModel.swift in Sources */, 031FDECA2EB1F8F200F4CAC7 /* FACoinsPackReceiveModel.swift in Sources */,
03E239B02EAA1A29004A8CEC /* FAWebView.swift in Sources */, 03E239B02EAA1A29004A8CEC /* FAWebView.swift in Sources */,
03E9A7942EC1B24E000D1067 /* UIImage+FAAdd.swift in Sources */,
039CE61C2EAB0F29007B5EED /* FAIapOrderModel.swift in Sources */, 039CE61C2EAB0F29007B5EED /* FAIapOrderModel.swift in Sources */,
03E9A7522EB83A58000D1067 /* FAAppStartViewController.swift in Sources */, 03E9A7522EB83A58000D1067 /* FAAppStartViewController.swift in Sources */,
03E239B12EAA1A29004A8CEC /* FANetworkMonitor.swift in Sources */, 03E239B12EAA1A29004A8CEC /* FANetworkMonitor.swift in Sources */,
@ -2232,6 +2437,19 @@
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
03E9A75D2EC19101000D1067 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 03E9A7562EC19101000D1067 /* NotificationService */;
targetProxy = 03E9A75C2EC19101000D1067 /* PBXContainerItemProxy */;
};
03E9A77D2EC19510000D1067 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 03E9A76C2EC1950F000D1067 /* FAWidgetExtension */;
targetProxy = 03E9A77C2EC19510000D1067 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
F3A0557617AU32W3L218F159 /* Localizable.strings */ = { F3A0557617AU32W3L218F159 /* Localizable.strings */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -2252,6 +2470,132 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
03E9A7612EC19101000D1067 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6XALB8RSYF;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
03E9A7622EC19101000D1067 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6XALB8RSYF;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NotificationService/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotificationService;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
03E9A7812EC19510000D1067 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = FAWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6XALB8RSYF;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FAWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FAWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon.FAWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
03E9A7822EC19510000D1067 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = FAWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6XALB8RSYF;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FAWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FAWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon.FAWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
F33W2S46100182427H2605Y0 /* Release */ = { F33W2S46100182427H2605Y0 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@ -2391,6 +2735,7 @@
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_NSUserTrackingUsageDescription = "We will use your advertising identifier (IDFA) to provide a personalized advertising experience."; INFOPLIST_KEY_NSUserTrackingUsageDescription = "We will use your advertising identifier (IDFA) to provide a personalized advertising experience.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
@ -2402,7 +2747,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1; MARKETING_VERSION = 1.0.4;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon; PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -2432,6 +2777,7 @@
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_NSCameraUsageDescription = "The APP needs to access your album to provide screenshots for feedback.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback."; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "The APP needs to access your album to provide screenshots for feedback.";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
INFOPLIST_KEY_NSUserTrackingUsageDescription = "We will use your advertising identifier (IDFA) to provide a personalized advertising experience."; INFOPLIST_KEY_NSUserTrackingUsageDescription = "We will use your advertising identifier (IDFA) to provide a personalized advertising experience.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
@ -2443,7 +2789,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1; MARKETING_VERSION = 1.0.4;
PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon; PRODUCT_BUNDLE_IDENTIFIER = com.hn.qinjiu.fableon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -2458,6 +2804,24 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
03E9A7602EC19101000D1067 /* Build configuration list for PBXNativeTarget "NotificationService" */ = {
isa = XCConfigurationList;
buildConfigurations = (
03E9A7612EC19101000D1067 /* Debug */,
03E9A7622EC19101000D1067 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
03E9A7802EC19510000D1067 /* Build configuration list for PBXNativeTarget "FAWidgetExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
03E9A7812EC19510000D1067 /* Debug */,
03E9A7822EC19510000D1067 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F3511CV60KB1225F8K46932G /* Build configuration list for PBXNativeTarget "Fableon" */ = { F3511CV60KB1225F8K46932G /* Build configuration list for PBXNativeTarget "Fableon" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@ -42,7 +42,8 @@ extension SceneDelegate {
guard FANetworkMonitor.manager.isReachable == true, guard FANetworkMonitor.manager.isReachable == true,
manager.isOpenApp, manager.isOpenApp,
manager.idfaAuthorizationFinish //idfa manager.idfaAuthorizationFinish, //idfa
manager.upgradeAlertFinish
else { else {
if let webpageURL = webpageURL { if let webpageURL = webpageURL {
manager.webpageURL = webpageURL manager.webpageURL = webpageURL

View File

@ -22,16 +22,16 @@ extension SceneDelegate {
center.requestAuthorization(options: [.badge, .sound, .alert]) { grant, error in center.requestAuthorization(options: [.badge, .sound, .alert]) { grant, error in
if !grant { if !grant {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.showApnsAlert() self.showApnsAlert()
} }
} else {
FAAdjustStateManager.manager.apnsFinish()
} }
FAAdjustStateManager.manager.apnsAuthorizationFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
FATool.requestIDFAAuthorization(nil)
FAStatAPI.uploadApnsAuthorizationStatus() FAStatAPI.uploadApnsAuthorizationStatus()
} }
UIApplication.shared.registerForRemoteNotifications() UIApplication.shared.registerForRemoteNotifications()

View File

@ -8,6 +8,7 @@
import UIKit import UIKit
import YYText import YYText
import MJRefresh import MJRefresh
import Kingfisher
class SceneDelegate: UIResponder, UIWindowSceneDelegate { class SceneDelegate: UIResponder, UIWindowSceneDelegate {
@ -62,6 +63,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidEnterBackground(_ scene: UIScene) { func sceneDidEnterBackground(_ scene: UIScene) {
FAAdjustStateManager.manager.allowOpenMessage = true FAAdjustStateManager.manager.allowOpenMessage = true
if #available(iOS 16.1, *) {
startWidgeLiveActivity()
}
} }
@ -123,10 +127,18 @@ extension SceneDelegate {
} }
@objc private func networkStatusDidChangeNotification() { @objc private func networkStatusDidChangeNotification() {
let localizedData = FALanguageManager.manager.languageData ?? [:]
if FANetworkMonitor.manager.isReachable == true { if FANetworkMonitor.manager.isReachable == true {
handleOnLine()
FATool.requestIDFAAuthorization(nil) FATool.requestIDFAAuthorization(nil)
self.retryHandleOpenAppMessage() self.retryHandleOpenAppMessage()
FAIapManager.manager.preloadingProducts() FAIapManager.manager.preloadingProducts()
///
if localizedData.isEmpty {
self.startApp()
}
} }
} }
@ -134,4 +146,35 @@ extension SceneDelegate {
MJRefreshConfig.default.languageCode = FALanguageManager.manager.mjLanguageKey MJRefreshConfig.default.languageCode = FALanguageManager.manager.mjLanguageKey
setTabBarController() setTabBarController()
} }
@available(iOS 16.1, *)
private func startWidgeLiveActivity() {
guard let model = FATool.widgetLiveActivityModel, let imageUrl = URL(string: model.image_url ?? "") else { return }
KingfisherManager.shared.retrieveImage(with: imageUrl) { result in
switch result {
case .success(let imageResult):
let image = imageResult.image
guard let data = image.compressImageSize(toByte: 9 * 1024) else { return }
guard let filePath = FAActivityManager.coverFileUrl else { return }
do {
try data.write(to: filePath, options: .atomic)
FAActivityManager.liveActivity(with: model.name ?? "",
videoId: model.short_play_id ?? "",
videoEpisode: model.current_episode ?? "")
} catch {
}
default:
break
}
}
}
} }

View File

@ -12,6 +12,11 @@
<array> <array>
<string>applinks:fableon.go.link</string> <string>applinks:fableon.go.link</string>
<string>applinks:kuzt.adj.st</string> <string>applinks:kuzt.adj.st</string>
<string>applinks:www.hbqinjiu.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.hn.qinjiu.fableon</string>
</array> </array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array/> <array/>

View File

@ -12,6 +12,10 @@ class FANavigationController: UINavigationController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
if #available(iOS 26.0, *) {
self.interactiveContentPopGestureRecognizer?.isEnabled = false
}
fd_fullscreenPopGestureRecognizer.isEnabled = true fd_fullscreenPopGestureRecognizer.isEnabled = true
} }

View File

@ -0,0 +1,25 @@
//
// NSNumber+FAAdd.swift
// Fableon
//
// Created by on 2025/11/11.
//
import UIKit
extension NSNumber {
func toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String {
let formatter = NumberFormatter()
formatter.minimumIntegerDigits = 1
formatter.maximumFractionDigits = maximumFractionDigits
if let minimumFractionDigits = minimumFractionDigits {
formatter.minimumFractionDigits = minimumFractionDigits
}
if let roundingMode = roundingMode {
formatter.roundingMode = roundingMode
}
formatter.numberStyle = .none
return formatter.string(from: self) ?? "0"
}
}

View File

@ -0,0 +1,68 @@
//
// UIImage+FAAdd.swift
// Fableon
//
// Created by on 2025/11/10.
//
import UIKit
extension UIImage {
///
static func getCompressRate(imageSize: CGFloat, targetSize: CGFloat) -> CGFloat {
var rate = Int(imageSize / targetSize)
rate = (rate == 0) ? 1 : rate
//
let maxCompressRate: CGFloat = 0.8
let minCompressRate: CGFloat = 0.2
//
var compressRate = 0.8 / CGFloat(rate)
//
compressRate = min(max(compressRate, minCompressRate), maxCompressRate)
return compressRate
}
/// JPEG
func compressImageSize(toByte maxLength: Int) -> Data? {
let image = self
//
guard var data = image.jpegData(compressionQuality: 1.0) else { return nil }
if data.count < maxLength {
return data
}
var compressRate = UIImage.getCompressRate(imageSize: CGFloat(data.count), targetSize: CGFloat(maxLength))
data = image.jpegData(compressionQuality: compressRate) ?? data
if data.count < maxLength {
return data
}
//
var resultImage = UIImage(data: data) ?? image
var lastDataLength = 0
while data.count > maxLength && data.count != lastDataLength {
lastDataLength = data.count
let ratio = CGFloat(maxLength) / CGFloat(data.count)
let newWidth = max(Int(resultImage.size.width * sqrt(ratio)), 10)
let newHeight = max(Int(resultImage.size.height * sqrt(ratio)), 10)
let newSize = CGSize(width: newWidth, height: newHeight)
if newSize.width < 10 || newSize.height < 10 { break }
//
UIGraphicsBeginImageContext(newSize)
resultImage.draw(in: CGRect(origin: .zero, size: newSize))
resultImage = UIGraphicsGetImageFromCurrentImageContext() ?? resultImage
UIGraphicsEndImageContext()
data = resultImage.jpegData(compressionQuality: compressRate) ?? data
}
return data
}
}

View File

@ -18,12 +18,11 @@ extension UIScrollView {
} }
func fa_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) { func fa_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
let footer = MJRefreshAutoNormalFooter(refreshingBlock: { self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: {
block?() block?()
}) })
footer.ignoredScrollViewContentInsetBottom = insetBottom
self.mj_footer = footer self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom
} }
@ -54,17 +53,17 @@ extension UIScrollView {
} }
func fa_updateNoMoreDataState(_ hasNextPage: Bool?) { func fa_updateNoMoreDataState(_ hasNextPage: Bool?) {
if hasNextPage == false { // if hasNextPage == false {
self.fa_endRefreshingWithNoMoreData() // self.fa_endRefreshingWithNoMoreData()
} else { // } else {
self.fa_resetNoMoreData() // self.fa_resetNoMoreData()
} // }
//
if self.mj_totalDataCount() == 0 { // if self.mj_totalDataCount() == 0 {
self.mj_footer?.isHidden = true // self.mj_footer?.isHidden = true
} else { // } else {
self.mj_footer?.isHidden = false // self.mj_footer?.isHidden = false
} // }
} }
} }

View File

@ -65,6 +65,9 @@ struct FAAPI {
] ]
FANetworkManager.manager.request(FABaseURL + "/myHistorys", method: .get, parameters: parameters, isToast: false) { (response: FANetworkManager.Response<FANetworkManager.List<FAShortPlayModel>>) in FANetworkManager.manager.request(FABaseURL + "/myHistorys", method: .get, parameters: parameters, isToast: false) { (response: FANetworkManager.Response<FANetworkManager.List<FAShortPlayModel>>) in
if page == 1, let model = response.data?.list?.first {
FATool.widgetLiveActivityModel = model
}
completer?(response.data) completer?(response.data)
} }
} }
@ -103,6 +106,7 @@ struct FAAPI {
parameters: parameters, parameters: parameters,
isLoding: true) { (response: FANetworkManager.Response<FAShortDetailModel>) in isLoding: true) { (response: FANetworkManager.Response<FAShortDetailModel>) in
if response.isSuccess { if response.isSuccess {
FAToast.show(text: "fableo_success".localized)
success?() success?()
NotificationCenter.default.post(name: FAAPI.updateShortCollectStateNotification, object: nil, userInfo: [ NotificationCenter.default.post(name: FAAPI.updateShortCollectStateNotification, object: nil, userInfo: [
"state" : isCollect, "state" : isCollect,
@ -301,6 +305,19 @@ struct FAAPI {
} }
} }
///
static func requestFeedbackRedCount(completer: ((_ model: FAFeedbackCountModel?) -> Void)?) {
FANetworkManager.manager.request(FABaseURL + "/noticeNum",
method: .post,
parameters: nil,
isLoding: false,
isToast: true) { (response: FANetworkManager.Response<FAFeedbackCountModel>) in
completer?(response.data)
}
}
} }

View File

@ -70,6 +70,10 @@ class FANetworkManager {
response.code = -1 response.code = -1
completion?(response) completion?(response)
} else { } else {
if code == 402, isToast {
FAToast.show(text: "fableon_kick_out_login".localized)
}
self.requestUserToken { self.requestUserToken {
if FALogin.manager.token != nil { if FALogin.manager.token != nil {
FALogin.manager.requestUserInfo(completer: nil) FALogin.manager.requestUserInfo(completer: nil)
@ -173,8 +177,10 @@ extension FANetworkManager {
} }
private var headers: HTTPHeaders { private var headers: HTTPHeaders {
let token = FALogin.manager.token?.token ?? "" let token = FALogin.manager.token?.token ?? ""
let dic = [ var dic = [
"authorization" : token, "authorization" : token,
"system-version" : UIDevice.current.systemVersion, "system-version" : UIDevice.current.systemVersion,
"lang-key" : FALanguageManager.manager.currentLanguageKey, "lang-key" : FALanguageManager.manager.currentLanguageKey,
@ -187,8 +193,11 @@ extension FANetworkManager {
"idfa" : ASIdentifierManager.shared().advertisingIdentifier.uuidString, "idfa" : ASIdentifierManager.shared().advertisingIdentifier.uuidString,
"device-id" : FADeviceIDManager.shared.id, //id "device-id" : FADeviceIDManager.shared.id, //id
"device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "", "device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "",
"product-prefix" : FAIapManager.IAPPrefix "product-prefix" : FAIapManager.IAPPrefix,
] ]
#if DEBUG
dic["security"] = "false"
#endif
return HTTPHeaders(dic) return HTTPHeaders(dic)
} }
} }

View File

@ -74,7 +74,7 @@ extension FABaseWebViewController {
case kFAWebMessageAccountDeletionFinish: case kFAWebMessageAccountDeletionFinish:
self.navigationController?.popToRootViewController(animated: true) self.navigationController?.popToRootViewController(animated: true)
NotificationCenter.default.post(name: FALogin.loginStatusChangeNotification, object: nil)
default: default:
break break

View File

@ -30,12 +30,13 @@ class FAGenresListViewController: FAViewController {
let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dataSource = self collectionView.dataSource = self
collectionView.ly_emptyView = FAEmpty.fa_emptyView()
collectionView.contentInset = .init(top: 20, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) collectionView.contentInset = .init(top: 20, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
collectionView.register(UINib(nibName: "FAGenresListCell", bundle: nil), forCellWithReuseIdentifier: "cell") collectionView.register(UINib(nibName: "FAGenresListCell", bundle: nil), forCellWithReuseIdentifier: "cell")
collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)
} }
collectionView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in collectionView.fa_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return collectionView return collectionView

View File

@ -31,6 +31,7 @@ class FAGenresViewController: FAViewController {
let collectionView = FACollectionView(frame: .zero, collectionViewLayout: self.collectionViewLayout) let collectionView = FACollectionView(frame: .zero, collectionViewLayout: self.collectionViewLayout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dataSource = self collectionView.dataSource = self
collectionView.ly_emptyView = FAEmpty.fa_emptyView()
collectionView.contentInset = .init(top: 24, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) collectionView.contentInset = .init(top: 24, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
collectionView.register(UINib(nibName: "FAGenresCell", bundle: nil), forCellWithReuseIdentifier: "cell") collectionView.register(UINib(nibName: "FAGenresCell", bundle: nil), forCellWithReuseIdentifier: "cell")
return collectionView return collectionView
@ -38,7 +39,7 @@ class FAGenresViewController: FAViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.title = "Genres".localized self.title = "fableon_genres".localized
fa_setupLayout() fa_setupLayout()

View File

@ -8,13 +8,21 @@
import UIKit import UIKit
import SwiftUI import SwiftUI
import SnapKit import SnapKit
import LYEmptyView
class FAHomeViewController: FAViewController { class FAHomeViewController: FAViewController {
private var viewModel = FAHomeViewModel() private var viewModel = FAHomeViewModel()
private lazy var notNetworkEmptyView: LYEmptyView = {
let view = FAEmpty.fa_notNetworkEmptyView { [weak self] in
self?.requestAllData(completer: nil)
self?.fa_setupLayout()
}
view.autoShowEmptyView = false
return view
}()
private lazy var cvLayout: FAWaterfallFlowLayout = { private lazy var cvLayout: FAWaterfallFlowLayout = {
let layout = FAWaterfallFlowLayout() let layout = FAWaterfallFlowLayout()
@ -80,6 +88,7 @@ class FAHomeViewController: FAViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(loginStatusChangeNotification), name: FALogin.loginStatusChangeNotification, object: nil)
fa_setupLayout() fa_setupLayout()
requestAllData(completer: nil) requestAllData(completer: nil)
@ -107,12 +116,27 @@ class FAHomeViewController: FAViewController {
if self.viewModel.dataArr.isEmpty, FANetworkMonitor.manager.isReachable == true { if self.viewModel.dataArr.isEmpty, FANetworkMonitor.manager.isReachable == true {
requestAllData(completer: nil) requestAllData(completer: nil)
} }
self.fa_setupLayout()
}
@objc private func loginStatusChangeNotification() {
requestAllData(completer: nil)
} }
} }
extension FAHomeViewController { extension FAHomeViewController {
private func fa_setupLayout() { private func fa_setupLayout() {
if FANetworkMonitor.manager.isReachable == false, collectionView.superview == nil {
view.ly_emptyView = notNetworkEmptyView
view.ly_showEmpty()
return
}
if collectionView.superview != nil {
return
}
view.ly_hideEmpty()
view.addSubview(titleView) view.addSubview(titleView)
view.addSubview(searchButton) view.addSubview(searchButton)
view.addSubview(collectionView) view.addSubview(collectionView)
@ -182,7 +206,7 @@ extension FAHomeViewController: UICollectionViewDelegate, UICollectionViewDataSo
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader { if kind == UICollectionView.elementKindSectionHeader {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FAHomeSectionTitleView", for: indexPath) let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FAHomeSectionTitleView", for: indexPath) as! FAHomeSectionTitleView
return view return view
} }
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "footer", for: indexPath) return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "footer", for: indexPath)
@ -295,6 +319,8 @@ extension FAHomeViewController {
if let playHistory = self.viewModel.playHistory { if let playHistory = self.viewModel.playHistory {
self.playHistoryView.model = playHistory self.playHistoryView.model = playHistory
self.playHistoryView.isHidden = false self.playHistoryView.isHidden = false
} else {
self.playHistoryView.isHidden = true
} }
} }
} }

View File

@ -29,6 +29,7 @@ class FAPopularListViewController: FAViewController {
collectionView.delegate = self collectionView.delegate = self
collectionView.dataSource = self collectionView.dataSource = self
collectionView.contentInset = .init(top: 20, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) collectionView.contentInset = .init(top: 20, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
collectionView.ly_emptyView = FAEmpty.fa_emptyView()
collectionView.register(UINib(nibName: "FAGenresListCell", bundle: nil), forCellWithReuseIdentifier: "cell") collectionView.register(UINib(nibName: "FAGenresListCell", bundle: nil), forCellWithReuseIdentifier: "cell")
collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)

View File

@ -36,6 +36,7 @@ class FARankingListViewController: FAViewController {
collectionView.dataSource = self collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false collectionView.showsHorizontalScrollIndicator = false
collectionView.ly_emptyView = FAEmpty.fa_emptyView()
collectionView.contentInset = .init(top: 15, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) collectionView.contentInset = .init(top: 15, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
collectionView.register(UINib(nibName: "FARankingListCell", bundle: nil), forCellWithReuseIdentifier: "cell") collectionView.register(UINib(nibName: "FARankingListCell", bundle: nil), forCellWithReuseIdentifier: "cell")
return collectionView return collectionView
@ -43,7 +44,7 @@ class FARankingListViewController: FAViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.title = "Rankings".localized self.title = "fableon_rabkings".localized
fa_setupLayout() fa_setupLayout()

View File

@ -13,4 +13,10 @@ struct FACategoryModel: SmartCodable {
var id: String? var id: String?
var category_name: String? var category_name: String?
var short_play_list: [FAShortPlayModel]? var short_play_list: [FAShortPlayModel]?
static func mappingForKey() -> [SmartKeyTransformer]? {
return [
CodingKeys.category_name <--- ["category_name", "name"],
]
}
} }

View File

@ -0,0 +1,17 @@
//
// FAHomeNewTransformer.swift
// Fableon
//
// Created by on 2025/11/11.
//
import UIKit
import FSPagerView
class FAHomeNewTransformer: FSPagerViewTransformer {
override func proposedInteritemSpacing() -> CGFloat {
return -50
}
}

View File

@ -61,7 +61,7 @@ struct FAHomeMustSeeView: View {
} }
HStack { HStack {
Text("selection".localized) Text("fableon_selection".localized)
.font(Font.font(size: 12, weight: .medium)) .font(Font.font(size: 12, weight: .medium))
.foregroundStyle(textColor) .foregroundStyle(textColor)
.padding(.leading, 8) .padding(.leading, 8)

View File

@ -14,7 +14,7 @@ struct FAHomeNewView: View {
@ObservedObject var viewModel: FAHomeViewModel @ObservedObject var viewModel: FAHomeViewModel
@State private var transformer: FSPagerViewTransformer = { @State private var transformer: FSPagerViewTransformer = {
let transformer = FSPagerViewTransformer(type: .overlap) let transformer = FAHomeNewTransformer(type: .overlap)
transformer.minimumScale = 0.9 transformer.minimumScale = 0.9
transformer.minimumAlpha = 1 transformer.minimumAlpha = 1
return transformer return transformer

View File

@ -14,14 +14,22 @@ class FAGenresCell: UICollectionViewCell {
var list = model?.short_play_list var list = model?.short_play_list
let firstModel = list?.removeFirst() let firstModel = list?.removeFirst()
hotCountLabel.text = "\(firstModel?.watch_total ?? 0)" let count = firstModel?.watch_total ?? 0
var countStr = "\(count)"
if count > 1000 {
countStr = NSNumber(value: CGFloat(count) / 1000).toString(maximumFractionDigits: 1) + "k"
}
hotCountLabel.text = countStr
nameLabel.text = model?.category_name nameLabel.text = model?.category_name
coverImageView.fa_setImage(firstModel?.image_url) coverImageView.fa_setImage(firstModel?.image_url)
list?.enumerated().forEach { list?.enumerated().forEach {
let i = $0 let i = $0
let imageView = smallImageViewArr[i] let viewCount = smallImageViewArr.count
if i > viewCount { return }
let imageView = smallImageViewArr[viewCount - i - 1]
imageView.fa_setImage($1.image_url) imageView.fa_setImage($1.image_url)
} }
} }

View File

@ -12,7 +12,11 @@ class FAHomeBannerCell: FSPagerViewCell {
var model: FAShortPlayModel? { var model: FAShortPlayModel? {
didSet { didSet {
coverImageView.fa_setImage(model?.horizontally_img) if let image = model?.horizontally_img, image.count > 0 {
coverImageView.fa_setImage(image)
} else {
coverImageView.fa_setImage(model?.image_url)
}
} }
} }

View File

@ -26,6 +26,7 @@ class FAHomeBannerContentCell: UICollectionViewCell {
let view = FSPagerView() let view = FSPagerView()
view.automaticSlidingInterval = 5
view.itemSize = .init(width: 282, height: 146) view.itemSize = .init(width: 282, height: 146)
view.transformer = transformer view.transformer = transformer
view.delegate = self view.delegate = self

View File

@ -9,9 +9,12 @@ import UIKit
class FAHomeSectionTitleView: UICollectionReusableView { class FAHomeSectionTitleView: UICollectionReusableView {
@IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
// Initialization code self.titleLabel.text = "fableon_recommended_for_you".localized
} }
} }

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/> <device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
<capability name="Named colors" minToolsVersion="9.0"/> <capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -11,7 +11,7 @@
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="U6b-Vx-4bR"> <collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="U6b-Vx-4bR" customClass="FAHomeSectionTitleView" customModule="Fableon" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="555" height="123"/> <rect key="frame" x="0.0" y="0.0" width="555" height="123"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
@ -27,6 +27,9 @@
<constraint firstItem="8Zn-D3-Qbm" firstAttribute="leading" secondItem="U6b-Vx-4bR" secondAttribute="leading" constant="16" id="EqE-jT-c4H"/> <constraint firstItem="8Zn-D3-Qbm" firstAttribute="leading" secondItem="U6b-Vx-4bR" secondAttribute="leading" constant="16" id="EqE-jT-c4H"/>
<constraint firstAttribute="top" secondItem="8Zn-D3-Qbm" secondAttribute="top" id="WgU-9Y-14J"/> <constraint firstAttribute="top" secondItem="8Zn-D3-Qbm" secondAttribute="top" id="WgU-9Y-14J"/>
</constraints> </constraints>
<connections>
<outlet property="titleLabel" destination="8Zn-D3-Qbm" id="ApY-aQ-yPU"/>
</connections>
<point key="canvasLocation" x="318.32061068702291" y="66.549295774647888"/> <point key="canvasLocation" x="318.32061068702291" y="66.549295774647888"/>
</collectionReusableView> </collectionReusableView>
</objects> </objects>

View File

@ -14,7 +14,14 @@ class FARankingListCell: UICollectionViewCell {
coverImageView.fa_setImage(model?.image_url) coverImageView.fa_setImage(model?.image_url)
nameLabel.text = model?.name nameLabel.text = model?.name
epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)") epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)")
countLabel.text = "\(model?.watch_total ?? 0)"
let count = model?.watch_total ?? 0
var countStr = "\(count)"
if count > 1000 {
countStr = NSNumber(value: CGFloat(count) / 1000).toString(maximumFractionDigits: 1) + "k"
}
countLabel.text = countStr
} }
} }

View File

@ -14,8 +14,14 @@ class FASearchRecommendCell: UICollectionViewCell {
didSet { didSet {
coverImageView.fa_setImage(model?.image_url) coverImageView.fa_setImage(model?.image_url)
titleLabel.text = model?.name titleLabel.text = model?.name
countLabel.text = "\(model?.watch_total ?? 0)"
epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)") epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)")
let count = model?.watch_total ?? 0
var countStr = "\(count)"
if count > 1000 {
countStr = NSNumber(value: CGFloat(count) / 1000).toString(maximumFractionDigits: 1) + "k"
}
countLabel.text = countStr
} }
} }

View File

@ -19,7 +19,7 @@ class FASearchResultCell: UICollectionViewCell {
titleLabel.text = model?.name titleLabel.text = model?.name
coverImageView.fa_setImage(model?.image_url) coverImageView.fa_setImage(model?.image_url)
if let category = model?.category?.first, !category.isEmpty { if let category = model?.categoryList?.first?.category_name, !category.isEmpty {
categoryView.isHidden = false categoryView.isHidden = false
categoryLabel.text = category categoryLabel.text = category
} else { } else {

View File

@ -41,10 +41,10 @@ class FAHomeViewModel: ObservableObject {
$0.title = "fableon_popular".localized $0.title = "fableon_popular".localized
popularItem = $0 popularItem = $0
} else if $0.module_key == .week_ranking { } else if $0.module_key == .week_ranking {
$0.title = "Rankings".localized $0.title = "fableon_rabkings".localized
rankingsItem = $0 rankingsItem = $0
} else if $0.module_key == .cagetory_recommand, genresItem == nil { } else if $0.module_key == .cagetory_recommand, genresItem == nil {
$0.title = "Genres".localized $0.title = "fableon_genres".localized
genresItem = $0 genresItem = $0
} else if $0.module_key == .new_recommand { } else if $0.module_key == .new_recommand {
$0.title = "fableon_new".localized $0.title = "fableon_new".localized
@ -109,6 +109,8 @@ class FAHomeViewModel: ObservableObject {
guard let self = self else { return } guard let self = self else { return }
if let model = listModel?.list?.first { if let model = listModel?.list?.first {
self.playHistory = model self.playHistory = model
} else {
self.playHistory = nil
} }
completer?() completer?()
} }

View File

@ -101,6 +101,8 @@ class FACoinPackViewController: FAViewController {
return label return label
}() }()
private weak var recommandView: UIView?
private lazy var tipTextLabel: UILabel = { private lazy var tipTextLabel: UILabel = {
let att = NSMutableAttributedString(string: "coins_pack_tips".localized) let att = NSMutableAttributedString(string: "coins_pack_tips".localized)
att.yy_lineSpacing = 3 att.yy_lineSpacing = 3
@ -210,7 +212,34 @@ extension FACoinPackViewController {
FAStoreAPI.requestReceiveCoinsPackCoins(id: id) { [weak self] finish in FAStoreAPI.requestReceiveCoinsPackCoins(id: id) { [weak self] finish in
guard let self = self else { return } guard let self = self else { return }
self.requestCoinsPackData() self.requestCoinsPackData()
if finish {
self.showVideoRecommand()
}
} }
} }
///
private func showVideoRecommand() {
guard self.recommandView == nil else { return }
FAAPI.requestDetailRecommendVideo { [weak self] list in
guard let self = self else { return }
guard let list = list else { return }
guard self.recommandView == nil else { return }
let view = FADetailRecommendView()
view.dataArr = list
view.didSelectedVideo = { [weak self] model in
guard let self = self else { return }
let vc = FAPlayerDetailViewController()
vc.shortPlayId = model.short_play_id
self.navigationController?.pushViewController(vc, animated: true)
}
view.show(in: FATool.keyWindow)
self.recommandView = view
}
}
} }

View File

@ -9,11 +9,59 @@ import UIKit
class FAFeedbackViewController: FAAppWebViewController { class FAFeedbackViewController: FAAppWebViewController {
private lazy var rightButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "Frame 2085663258"), for: .normal)
button.addTarget(self, action: #selector(handleRightBarButton), for: .touchUpInside)
return button
}()
private lazy var redView: UIView = {
let view = UIView()
view.backgroundColor = .FF_313_B
view.layer.cornerRadius = 8
view.isHidden = true
return view
}()
private lazy var redLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .bold)
label.textColor = .FFFFFF
return label
}()
override func viewDidLoad() { override func viewDidLoad() {
self.webUrl = kFAFeedBackHomeWebUrl self.webUrl = kFAFeedBackHomeWebUrl
super.viewDidLoad() super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Frame 2085663258"), style: .plain, target: self, action: #selector(handleRightBarButton)) // self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Frame 2085663258"), style: .plain, target: self, action: #selector(handleRightBarButton))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightButton)
rightButton.addSubview(redView)
redView.addSubview(redLabel)
redView.snp.makeConstraints { make in
make.height.equalTo(16)
make.width.greaterThanOrEqualTo(16)
make.top.equalToSuperview().offset(-8)
make.right.equalToSuperview().offset(8)
}
redLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
// make.left.equalToSuperview().offset(5)
make.left.greaterThanOrEqualToSuperview().offset(3)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.requestRedCount()
} }
@ -23,6 +71,22 @@ class FAFeedbackViewController: FAAppWebViewController {
vc.webUrl = kFAFeedBackListWebUrl vc.webUrl = kFAFeedBackListWebUrl
self.navigationController?.pushViewController(vc, animated: true) self.navigationController?.pushViewController(vc, animated: true)
}
}
extension FAFeedbackViewController {
private func requestRedCount() {
FAAPI.requestFeedbackRedCount { [weak self] model in
guard let self = self else { return }
if let count = model?.feedback_notice_num, count > 0 {
self.redView.isHidden = false
self.redLabel.text = "\(count)"
} else {
self.redView.isHidden = true
}
}
} }

View File

@ -12,22 +12,7 @@ class FAMeListViewController: FAViewController, JXPagingViewListViewDelegate {
var scrollCallback: ((_ : UIScrollView) -> Void)? var scrollCallback: ((_ : UIScrollView) -> Void)?
private lazy var dataArr: [FAMeItemModel] = { private lazy var dataArr: [FAMeItemModel] = []
let arr = [
FAMeItemModel(type: .language, name: "fableon_language".localized, icon: UIImage(named: "icon_language")),
FAMeItemModel(type: .feedback, name: "fableon_feedback".localized, icon: UIImage(named: "icon_feedback")),
FAMeItemModel(type: .about, name: "fableon_about_us".localized, icon: UIImage(named: "icon_about")),
FAMeItemModel(type: .privacyPolicy, name: "fableon_privacy_policy".localized, icon: UIImage(named: "icon_privacy")),
FAMeItemModel(type: .userAgreement, name: "fableon_user_agreement".localized, icon: UIImage(named: "icon_user")),
// FAMeItemModel(type: .visitWebsite, name: "fableon_visit_website".localized, icon: UIImage(named: "icon_visit")),
FAMeItemModel(type: .setting, name: "fableon_settings".localized, icon: UIImage(named: "icon_setting"))
]
return arr
}()
private lazy var tableViewHeaderView: FAMeTableViewHeaderView = { private lazy var tableViewHeaderView: FAMeTableViewHeaderView = {
let view = FAMeTableViewHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 124)) let view = FAMeTableViewHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 124))
@ -49,7 +34,6 @@ class FAMeListViewController: FAViewController, JXPagingViewListViewDelegate {
tableView.rowHeight = 56 tableView.rowHeight = 56
tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32) tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32)
tableView.register(UINib(nibName: "FAMeCell", bundle: nil), forCellReuseIdentifier: "cell") tableView.register(UINib(nibName: "FAMeCell", bundle: nil), forCellReuseIdentifier: "cell")
// tableView.addObserver(self, forKeyPath: "contentSize", context: nil)
return tableView return tableView
}() }()
@ -61,7 +45,7 @@ class FAMeListViewController: FAViewController, JXPagingViewListViewDelegate {
super.viewDidLoad() super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: FALogin.userInfoUpdateNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: FALogin.userInfoUpdateNotification, object: nil)
setDataArr()
self.bgView.isHidden = true self.bgView.isHidden = true
fa_setupLayout() fa_setupLayout()
@ -70,6 +54,7 @@ class FAMeListViewController: FAViewController, JXPagingViewListViewDelegate {
@objc private func userInfoUpdateNotification() { @objc private func userInfoUpdateNotification() {
tableViewHeaderView.userInfo = FALogin.manager.userInfo tableViewHeaderView.userInfo = FALogin.manager.userInfo
setDataArr()
} }
func listScrollView() -> UIScrollView { func listScrollView() -> UIScrollView {
@ -159,3 +144,27 @@ extension FAMeListViewController: UITableViewDelegate, UITableViewDataSource {
} }
} }
extension FAMeListViewController {
private func setDataArr() {
var arr = [
FAMeItemModel(type: .language, name: "fableon_language".localized, icon: UIImage(named: "icon_language")),
FAMeItemModel(type: .feedback, name: "fableon_feedback".localized, icon: UIImage(named: "icon_feedback")),
FAMeItemModel(type: .about, name: "fableon_about_us".localized, icon: UIImage(named: "icon_about")),
FAMeItemModel(type: .privacyPolicy, name: "fableon_privacy_policy".localized, icon: UIImage(named: "icon_privacy")),
FAMeItemModel(type: .userAgreement, name: "fableon_user_agreement".localized, icon: UIImage(named: "icon_user")),
]
if FALogin.manager.isLogin {
arr.append(
FAMeItemModel(type: .setting, name: "fableon_settings".localized, icon: UIImage(named: "icon_setting"))
)
}
self.dataArr = arr
self.tableView.reloadData()
}
}

View File

@ -0,0 +1,15 @@
//
// FAFeedbackCountModel.swift
// Fableon
//
// Created by on 2025/11/11.
//
import UIKit
import SmartCodable
struct FAFeedbackCountModel: SmartCodable {
var feedback_notice_num: Int?
}

View File

@ -104,7 +104,7 @@ class FACoinPackHeaderView: UIView {
.foregroundColor : UIColor.FFFFFF .foregroundColor : UIColor.FFFFFF
])) ]))
let countAtt = AttributedString(" \(0)".localized, attributes: AttributeContainer([ let countAtt = AttributedString(" \(self.model?.receive_coins ?? 0)".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .bold), .font : UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor : UIColor.FFFFFF .foregroundColor : UIColor.FFFFFF
])) ]))

View File

@ -24,7 +24,8 @@ class FACoinsPackClaimListCell: UICollectionViewCell {
titleAtt.yy_color = .FFFFFF titleAtt.yy_color = .FFFFFF
titleAtt.yy_font = .font(ofSize: 14, weight: .bold) titleAtt.yy_font = .font(ofSize: 14, weight: .bold)
let dayAtt = NSMutableAttributedString(string: " (Day \(model?.day_text ?? ""))") let day = "fableon_d".localized
let dayAtt = NSMutableAttributedString(string: " (\(day) \(model?.day_text ?? ""))")
dayAtt.yy_color = ._20_A_1_FF dayAtt.yy_color = ._20_A_1_FF
dayAtt.yy_font = .font(ofSize: 14, weight: .regular) dayAtt.yy_font = .font(ofSize: 14, weight: .regular)
titleAtt.append(dayAtt) titleAtt.append(dayAtt)

View File

@ -97,6 +97,24 @@ class FAMeHeaderView: UIView {
return button return button
}() }()
private lazy var topUpButton: UIButton = {
let button = FAGradientButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
let vc = FAWalletViewController()
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}))
button.fa_colors = [UIColor.BEDFFF.cgColor, UIColor._52_A_2_F_1.cgColor]
button.fa_locations = [0, 1]
button.fa_startPoint = .init(x: 0, y: 0.5)
button.fa_endPoint = .init(x: 1, y: 0.5)
button.layer.cornerRadius = 18
button.layer.masksToBounds = true
button.setTitle("GO", for: .normal)
button.setTitleColor(._000000, for: .normal)
button.titleLabel?.font = .font(ofSize: 14, weight: .semibold)
return button
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
@ -118,6 +136,7 @@ extension FAMeHeaderView {
addSubview(bonusCoinsView) addSubview(bonusCoinsView)
addSubview(coinPackButton) addSubview(coinPackButton)
addSubview(loginButton) addSubview(loginButton)
addSubview(topUpButton)
avatarImageView.snp.makeConstraints { make in avatarImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16) make.left.equalToSuperview().offset(16)
@ -159,6 +178,13 @@ extension FAMeHeaderView {
make.width.equalTo(76) make.width.equalTo(76)
make.height.equalTo(28) make.height.equalTo(28)
} }
topUpButton.snp.makeConstraints { make in
make.centerY.equalTo(coinsView)
make.right.equalToSuperview().offset(-16)
make.width.equalTo(84)
make.height.equalTo(36)
}
} }
} }

View File

@ -11,18 +11,18 @@ class FAMeTableViewHeaderView: UIView {
var userInfo: FAUserInfo? { var userInfo: FAUserInfo? {
didSet { didSet {
subtitleLabel.text = "vip_tip_01".localized
if userInfo?.is_vip == true { if userInfo?.is_vip == true {
subscribeView.isHidden = true subscribeView.isHidden = true
expirationView.isHidden = false expirationView.isHidden = false
vipImageView.image = UIImage(named: "vip_image_02") vipImageView.image = UIImage(named: "vip_image_02")
titleLabel.text = "fableon_activated".localized titleLabel.text = "fableon_activated".localized
subtitleLabel.text = "vip_tip_01".localized
} else { } else {
subscribeView.isHidden = false subscribeView.isHidden = false
expirationView.isHidden = true expirationView.isHidden = true
vipImageView.image = UIImage(named: "vip_image_01") vipImageView.image = UIImage(named: "vip_image_01")
titleLabel.text = "fableon_not_activated".localized titleLabel.text = "fableon_not_activated".localized
subtitleLabel.text = "vip_tip_02".localized // subtitleLabel.text = "vip_tip_02".localized
} }

View File

@ -49,7 +49,10 @@ class FASettingFooterView: UIView {
let view = FALogoutAlert() let view = FALogoutAlert()
view.clickHighlightButton = { view.clickHighlightButton = {
FALogin.manager.logout(completer: nil) FALogin.manager.logout { [weak self] finish in
guard let self = self else { return }
self.viewController?.navigationController?.popToRootViewController(animated: true)
}
} }
view.show() view.show()
} }

View File

@ -21,6 +21,8 @@ class FACollectViewController: FAViewController {
} }
} }
private var needUpdate = false
private lazy var collectionViewLayout: UICollectionViewFlowLayout = { private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let width = floor((UIScreen.width - 16 - 32) / 3) let width = floor((UIScreen.width - 16 - 32) / 3)
@ -67,11 +69,17 @@ class FACollectViewController: FAViewController {
return item return item
}() }()
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.edgesForExtendedLayout = .top self.edgesForExtendedLayout = .top
self.title = "fableon_collect".localized self.title = "fableon_collect".localized
NotificationCenter.default.addObserver(self, selector: #selector(loginStatusChangeNotification), name: FALogin.loginStatusChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: FAAPI.updateShortCollectStateNotification, object: nil)
self.navigationItem.rightBarButtonItems = [historyButton, spaceButton, editBarButton] self.navigationItem.rightBarButtonItems = [historyButton, spaceButton, editBarButton]
@ -87,6 +95,14 @@ class FACollectViewController: FAViewController {
self.navigationController?.setNavigationBarHidden(false, animated: true) self.navigationController?.setNavigationBarHidden(false, animated: true)
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if needUpdate {
needUpdate = false
requestDataArr(page: 1, completer: nil)
}
}
override func viewDidDisappear(_ animated: Bool) { override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated) super.viewDidDisappear(animated)
self.fa_isEditing = false self.fa_isEditing = false
@ -138,6 +154,13 @@ class FACollectViewController: FAViewController {
} }
} }
@objc private func loginStatusChangeNotification() {
requestDataArr(page: 1, completer: nil)
}
@objc private func updateShortCollectStateNotification() {
needUpdate = true
}
} }
extension FACollectViewController { extension FACollectViewController {

View File

@ -31,7 +31,7 @@ class FAHistoryViewController: FAViewController {
guard let self = self else { return } guard let self = self else { return }
self.handleHeaderRefresh(nil) self.handleHeaderRefresh(nil)
} }
collectionView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in collectionView.fa_addRefreshFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return collectionView return collectionView

View File

@ -22,6 +22,7 @@ class FAShortPlayModel: NSObject, Identifiable, SmartCodable {
var episode_total: Int? var episode_total: Int?
var horizontally_img: String? var horizontally_img: String?
var category: [String]? var category: [String]?
var categoryList: [FACategoryModel]?
var short_play_id: String? var short_play_id: String?
var short_play_video_id: String? var short_play_video_id: String?
var video_info: FAVideoInfoModel? var video_info: FAVideoInfoModel?

View File

@ -21,6 +21,7 @@ class FADetailRecommendCell: FSPagerViewCell {
private lazy var player: JXPlayer = { private lazy var player: JXPlayer = {
let player = JXPlayer(controlView: nil) let player = JXPlayer(controlView: nil)
player.playerView = self.playerView player.playerView = self.playerView
player.isLoop = true
return player return player
}() }()

View File

@ -16,6 +16,7 @@ class FADetailRecommendView: FABaseAlert {
var dataArr: [FAShortPlayModel] = [] { var dataArr: [FAShortPlayModel] = [] {
didSet { didSet {
self.pagerView.reloadData() self.pagerView.reloadData()
pageControl.numberOfPages = dataArr.count
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
self?.updateCurrentData() self?.updateCurrentData()
@ -68,6 +69,15 @@ class FADetailRecommendView: FABaseAlert {
return view return view
}() }()
private lazy var pageControl: UIPageControl = {
let view = UIPageControl()
view.isUserInteractionEnabled = false
view.pageIndicatorTintColor = .FFFFFF.withAlphaComponent(0.6)
view.currentPageIndicatorTintColor = .FFFFFF
view.numberOfPages = 0
return view
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
contentWidth = UIScreen.width contentWidth = UIScreen.width
@ -96,6 +106,7 @@ class FADetailRecommendView: FABaseAlert {
guard let cell = self.pagerView.cellForItem(at: self.pagerView.currentIndex) as? FADetailRecommendCell else { return } guard let cell = self.pagerView.cellForItem(at: self.pagerView.currentIndex) as? FADetailRecommendCell else { return }
self.currentCell = cell self.currentCell = cell
self.pageControl.currentPage = self.pagerView.currentIndex
// let model = cell.model // let model = cell.model
// self.videoNameLabel.text = model?.name // self.videoNameLabel.text = model?.name
} }
@ -109,6 +120,7 @@ extension FADetailRecommendView {
contentView.addSubview(bgView) contentView.addSubview(bgView)
contentView.addSubview(cancelButton) contentView.addSubview(cancelButton)
bgView.addSubview(pagerView) bgView.addSubview(pagerView)
bgView.addSubview(pageControl)
titleLabel.snp.makeConstraints { make in titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview() make.top.equalToSuperview()
@ -131,6 +143,11 @@ extension FADetailRecommendView {
make.height.equalTo(267) make.height.equalTo(267)
make.center.equalToSuperview() make.center.equalToSuperview()
} }
pageControl.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(pagerView.snp.bottom).offset(10)
}
} }
} }
@ -158,3 +175,13 @@ extension FADetailRecommendView: FSPagerViewDelegate, FSPagerViewDataSource {
} }
} }
extension FADetailRecommendView {
class PageControl: UIPageControl{
override func size(forNumberOfPages pageCount: Int) -> CGSize {
return .init(width: 4, height: 4)
}
}
}

View File

@ -192,9 +192,8 @@ extension FAEpSelectorView: UICollectionViewDelegate, UICollectionViewDataSource
return return
} }
self.didSelected?(indexPath.row) self.dismiss(animated: true) { [weak self] in
self.dismiss(animated: true) { self?.didSelected?(indexPath.row)
} }
} }

View File

@ -71,7 +71,7 @@ class FAOldVideoRechargeView: FAPanModalContentView {
private lazy var scrollView: FAScrollView = { private lazy var scrollView: FAScrollView = {
let scrollView = FAScrollView() let scrollView = FAScrollView()
scrollView.clipsToBounds = false // scrollView.clipsToBounds = false
return scrollView return scrollView
}() }()
@ -148,7 +148,7 @@ class FAOldVideoRechargeView: FAPanModalContentView {
let label = UILabel() let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium) label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .FFFFFF label.textColor = .FFFFFF
label.text = "store_tips_title".localized label.text = "fableon_tips".localized
return label return label
}() }()

View File

@ -32,6 +32,8 @@ class FAPlayerDetailCell: JXPlayerListCell {
didSet { didSet {
let controlView = self.controlView as? FAPlayerDetailControlView let controlView = self.controlView as? FAPlayerDetailControlView
controlView?.shortModel = shortModel controlView?.shortModel = shortModel
self.player.coverImageView?.fa_setImage(shortModel?.image_url)
} }
} }

View File

@ -58,6 +58,12 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
} }
} }
override var isLoading: Bool {
didSet {
progressView.isLoading = isLoading
}
}
private lazy var epButton: UIHostingController<FAPlayerEpUIButton> = { private lazy var epButton: UIHostingController<FAPlayerEpUIButton> = {
let view = FAPlayerEpUIButton() let view = FAPlayerEpUIButton()
let hc = UIHostingController(rootView: view) let hc = UIHostingController(rootView: view)
@ -126,7 +132,15 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id
let isCollect = !(self.shortModel?.is_collect ?? false) let isCollect = !(self.shortModel?.is_collect ?? false)
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil) if isCollect {
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil)
} else {
let alert = FARemoveCollectAlert()
alert.clickHighlightButton = {
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil)
}
alert.show()
}
}), for: .touchUpInside) }), for: .touchUpInside)

View File

@ -152,7 +152,7 @@ class FAPlayerProgressView: UIView {
if self.isPaning { if self.isPaning {
progress = self.panProgress progress = self.panProgress
} }
let currentProgressWidth = progressWidth * progress
/// ///
let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2) let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2)
@ -165,6 +165,12 @@ class FAPlayerProgressView: UIView {
context.addPath(currentPath.cgPath) context.addPath(currentPath.cgPath)
context.setFillColor(currentProgress.cgColor) context.setFillColor(currentProgress.cgColor)
context.fillPath() context.fillPath()
///
let path = UIBezierPath(arcCenter: .init(x: currentProgressWidth + progressX, y: progressY + lineWidth / 2), radius: 3, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
context.addPath(path.cgPath)
context.setFillColor(currentProgress.cgColor)
context.fillPath()
} }

View File

@ -78,13 +78,13 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
self.viewModel.currentCell?.pause() self.viewModel.currentCell?.pause()
} }
override var previousVideoUrl: String? { // override var previousVideoUrl: String? {
return self.fa_viewModel.previousEpisode?.video_url // return self.fa_viewModel.previousEpisode?.video_url
} // }
//
override var nextVideoUrl: String? { // override var nextVideoUrl: String? {
return self.fa_viewModel.nextEpisode?.video_url // return self.fa_viewModel.nextEpisode?.video_url
} // }
override func play() { override func play() {
let videoInfo = self.viewModel.currentCell?.model as? FAVideoInfoModel let videoInfo = self.viewModel.currentCell?.model as? FAVideoInfoModel
@ -92,6 +92,11 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
if videoInfo?.is_lock != true { if videoInfo?.is_lock != true {
super.play() super.play()
FAAPI.requestCreatePlayHistory(videoId: videoInfo?.short_play_video_id, shortPlayId: videoInfo?.short_play_id) FAAPI.requestCreatePlayHistory(videoId: videoInfo?.short_play_video_id, shortPlayId: videoInfo?.short_play_id)
if let cell = viewModel.currentCell as? FAPlayerDetailCell, let model = cell.shortModel {
FATool.widgetLiveActivityModel = model
FATool.widgetLiveActivityModel?.current_episode = videoInfo?.episode
}
return return
} }
self.pause() self.pause()
@ -114,7 +119,7 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
@objc private func handleBackButton() { @objc private func handleBackButton() {
self.pause() self.pause()
if !self.fa_viewModel.recommandDataArr.isEmpty { if !self.fa_viewModel.recommandDataArr.isEmpty, self.fa_viewModel.isShowRecommand {
let view = FADetailRecommendView() let view = FADetailRecommendView()
view.dataArr = self.fa_viewModel.recommandDataArr view.dataArr = self.fa_viewModel.recommandDataArr
view.clickCloseButton = { [weak self] in view.clickCloseButton = { [weak self] in
@ -126,6 +131,8 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
self.requestDetailList() self.requestDetailList()
} }
view.show(in: FATool.keyWindow) view.show(in: FATool.keyWindow)
} else {
self.handleNavigationBack()
} }
// self.handleNavigationBack() // self.handleNavigationBack()

View File

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import JXPlayer import JXPlayer
import YYText
//@MainActor //@MainActor
class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject { class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
@ -14,6 +15,9 @@ class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
private(set) var dataArr: [FAShortDetailModel] = [] private(set) var dataArr: [FAShortDetailModel] = []
private(set) var recommandDataArr: [FAShortPlayModel] = [] private(set) var recommandDataArr: [FAShortPlayModel] = []
///
private(set) var isShowRecommand = false
private var recommandTimer: Timer?
var shortPlayId: String = "" var shortPlayId: String = ""
@ -43,6 +47,11 @@ class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
func requestDetailData(indexPath: IndexPath? = nil, completer: ((_ code: Int) -> Void)?) { func requestDetailData(indexPath: IndexPath? = nil, completer: ((_ code: Int) -> Void)?) {
isShowRecommand = false
recommandTimer?.invalidate()
recommandTimer = nil
recommandTimer = Timer.scheduledTimer(timeInterval: 6, target: YYTextWeakProxy(target: self), selector: #selector(handleRecommandTimer), userInfo: nil, repeats: false)
FAAPI.requestShortDetailData(shortPlayId: shortPlayId) { [weak self] model, code, msg in FAAPI.requestShortDetailData(shortPlayId: shortPlayId) { [weak self] model, code, msg in
guard let self = self else { return } guard let self = self else { return }
if let model = model { if let model = model {
@ -99,6 +108,18 @@ class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
FAAPI.requestUploadPlayTime(shortPlayId: shortPlayId, videoId: videoId, seconds: Int(time) * 1000) FAAPI.requestUploadPlayTime(shortPlayId: shortPlayId, videoId: videoId, seconds: Int(time) * 1000)
} }
override func playFinish(player: any JXPlayerCell) {
super.playFinish(player: player)
let videoInfo = self.currentCell?.model as? FAVideoInfoModel
guard let shortPlayId = videoInfo?.short_play_id, let videoId = videoInfo?.short_play_video_id else { return }
FAAPI.requestUploadPlayTime(shortPlayId: shortPlayId, videoId: videoId, seconds: 0)
}
@objc private func handleRecommandTimer() {
self.isShowRecommand = true
}
} }
extension FAShortDetailViewModel { extension FAShortDetailViewModel {
@ -119,6 +140,7 @@ extension FAShortDetailViewModel {
func handleUnlockVideo() { func handleUnlockVideo() {
unlockVideo { [weak self] finish in unlockVideo { [weak self] finish in
if finish { if finish {
FAToast.show(text: "fableo_success".localized)
self?.playerListVC?.reloadData { self?.playerListVC?.reloadData {
self?.playerListVC?.play() self?.playerListVC?.play()
} }
@ -166,6 +188,10 @@ extension FAShortDetailViewModel {
} }
private func _openOldRechargeView(_ model: FAPayDateModel, _ videoInfo: FAVideoInfoModel) { private func _openOldRechargeView(_ model: FAPayDateModel, _ videoInfo: FAVideoInfoModel) {
FAStatAPI.requestEventStat(orderCode: nil, shortPlayId: videoInfo.short_play_id, videoId: videoInfo.short_play_video_id, eventKey: .payTemplateDialog, errorMsg: nil, otherParamenters: [
"event_name" : "pay open"
])
let view = FAOldVideoRechargeView() let view = FAOldVideoRechargeView()
view.model = model view.model = model
view.videoInfo = videoInfo view.videoInfo = videoInfo

View File

@ -7,6 +7,7 @@
import UIKit import UIKit
import JXPlayer import JXPlayer
import LYEmptyView
class FARecommendViewController: JXPlayerListViewController { class FARecommendViewController: JXPlayerListViewController {
@ -22,15 +23,37 @@ class FARecommendViewController: JXPlayerListViewController {
return self.viewModel as! FARecommendViewModel return self.viewModel as! FARecommendViewModel
} }
private lazy var notNetworkEmptyView: LYEmptyView = {
let view = FAEmpty.fa_notNetworkEmptyView { [weak self] in
self?.fa_viewModel.requestDataArr(page: 1)
self?.fa_setupLayout()
}
view.autoShowEmptyView = false
return view
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(loginStatusChangeNotification), name: FALogin.loginStatusChangeNotification, object: nil)
self.register(FARecommendPlayerCell.self, forCellWithReuseIdentifier: "cell") self.register(FARecommendPlayerCell.self, forCellWithReuseIdentifier: "cell")
self.collectionView.fa_addRefreshHeader { [weak self] in
self?.fa_viewModel.requestDataArr(page: 1) {
self?.collectionView.fa_endHeaderRefreshing()
}
}
self.collectionView.isHidden = true
self.delegate = self self.delegate = self
self.dataSource = self self.dataSource = self
self.fa_viewModel.requestDataArr(page: 1) self.fa_viewModel.requestDataArr(page: 1)
fa_setupLayout()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -43,12 +66,39 @@ class FARecommendViewController: JXPlayerListViewController {
self.viewModel.currentCell?.pause() self.viewModel.currentCell?.pause()
} }
override var previousVideoUrl: String? { // override var previousVideoUrl: String? {
return self.fa_viewModel.previousEpisode?.video_url // return self.fa_viewModel.previousEpisode?.video_url
// }
//
// override var nextVideoUrl: String? {
// return self.fa_viewModel.nextEpisode?.video_url
// }
@objc private func loginStatusChangeNotification() {
self.fa_viewModel.requestDataArr(page: 1)
} }
override var nextVideoUrl: String? { @objc private func networkStatusDidChangeNotification() {
return self.fa_viewModel.nextEpisode?.video_url if FANetworkMonitor.manager.isReachable == true, self.fa_viewModel.dataArr.isEmpty {
self.fa_viewModel.requestDataArr(page: 1)
}
fa_setupLayout()
}
}
extension FARecommendViewController {
private func fa_setupLayout() {
if FANetworkMonitor.manager.isReachable == false, self.collectionView.isHidden {
view.ly_emptyView = notNetworkEmptyView
view.ly_showEmpty()
return
}
if !self.collectionView.isHidden {
return
}
view.ly_hideEmpty()
self.collectionView.isHidden = false
} }
} }
@ -69,5 +119,7 @@ extension FARecommendViewController: JXPlayerListViewControllerDataSource {
//MARK: JXPlayerListViewControllerDelegate //MARK: JXPlayerListViewControllerDelegate
extension FARecommendViewController: JXPlayerListViewControllerDelegate { extension FARecommendViewController: JXPlayerListViewControllerDelegate {
func jx_playerViewControllerLoadMoreData(playerViewController: JXPlayerListViewController) {
self.fa_viewModel.requestDataArr(page: self.fa_viewModel.page + 1)
}
} }

View File

@ -21,6 +21,7 @@ class FARecommendPlayerCell: JXPlayerListCell {
let videoInfo = model?.video_info let videoInfo = model?.video_info
self.player.setPlayUrl(url: videoInfo?.video_url ?? "") self.player.setPlayUrl(url: videoInfo?.video_url ?? "")
self.player.coverImageView?.fa_setImage(model?.image_url)
} }
} }

View File

@ -57,12 +57,12 @@ class FARecommendPlayerControlView: JXPlayerListControlView {
} }
} }
private lazy var epButton: UIHostingController<FAPlayerEpUIButton> = { // private lazy var epButton: UIHostingController<FAPlayerEpUIButton> = {
let view = FAPlayerEpUIButton() // let view = FAPlayerEpUIButton()
let hc = UIHostingController(rootView: view) // let hc = UIHostingController(rootView: view)
hc.view.backgroundColor = .clear // hc.view.backgroundColor = .clear
return hc // return hc
}() // }()
private lazy var progressView: FAPlayerProgressView = { private lazy var progressView: FAPlayerProgressView = {
let view = FAPlayerProgressView() let view = FAPlayerProgressView()
@ -79,6 +79,9 @@ class FARecommendPlayerControlView: JXPlayerListControlView {
label.font = .font(ofSize: 12, weight: .regular); label.font = .font(ofSize: 12, weight: .regular);
label.textColor = UIColor(named: .color_FFFFFF)!.withAlphaComponent(0.8) label.textColor = UIColor(named: .color_FFFFFF)!.withAlphaComponent(0.8)
label.numberOfLines = 2 label.numberOfLines = 2
label.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(handleEp))
label.addGestureRecognizer(tap)
return label return label
}() }()
@ -125,7 +128,15 @@ class FARecommendPlayerControlView: JXPlayerListControlView {
let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id
let isCollect = !(self.shortModel?.is_collect ?? false) let isCollect = !(self.shortModel?.is_collect ?? false)
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil) if isCollect {
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil)
} else {
let alert = FARemoveCollectAlert()
alert.clickHighlightButton = {
FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil)
}
alert.show()
}
}), for: .touchUpInside) }), for: .touchUpInside)
@ -154,17 +165,20 @@ class FARecommendPlayerControlView: JXPlayerListControlView {
} }
private func updateEp() { private func updateEp() {
// let model = self.model as? FAVideoInfoModel // let model = self.model as? FAShortPlayModel
let model = self.model as? FAShortPlayModel // let videoInfo = model?.video_info
let videoInfo = model?.video_info //
//
// let text = "fableon_episode_set".localizedReplace(text: videoInfo?.episode ?? "") + "/" + "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)")
// var view = FAPlayerEpUIButton(text: text)
// view.clickHandle = { [weak self] in
// self?.fa_viewModel?.pushPlayerDetail(self?.shortModel)
// }
// epButton.rootView = view
}
@objc private func handleEp() {
let text = "fableon_episode_set".localizedReplace(text: videoInfo?.episode ?? "") + "/" + "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)") self.fa_viewModel?.pushPlayerDetail(self.shortModel)
var view = FAPlayerEpUIButton(text: text)
view.clickHandle = { [weak self] in
self?.fa_viewModel?.pushPlayerDetail(self?.shortModel)
}
epButton.rootView = view
} }
private func updateProgress() { private func updateProgress() {
@ -191,7 +205,7 @@ extension FARecommendPlayerControlView {
private func fa_setupLayout() { private func fa_setupLayout() {
addSubview(epButton.view) // addSubview(epButton.view)
addSubview(progressView) addSubview(progressView)
addSubview(textLabel) addSubview(textLabel)
addSubview(shortNameLabel) addSubview(shortNameLabel)
@ -199,16 +213,17 @@ extension FARecommendPlayerControlView {
addSubview(collectButton) addSubview(collectButton)
epButton.view.snp.makeConstraints { make in // epButton.view.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16) // make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview() // make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-10) // make.bottom.equalToSuperview().offset(-10)
} // }
progressView.snp.makeConstraints { make in progressView.snp.makeConstraints { make in
make.left.equalToSuperview() make.left.equalToSuperview()
make.centerX.equalToSuperview() make.centerX.equalToSuperview()
make.bottom.equalTo(epButton.view.snp.top).offset(-8) // make.bottom.equalTo(epButton.view.snp.top).offset(-8)
make.bottom.equalToSuperview().offset(-1)
} }
textLabel.snp.makeConstraints { make in textLabel.snp.makeConstraints { make in

View File

@ -11,7 +11,7 @@ import JXPlayer
class FARecommendViewModel: JXPlayerListViewModel { class FARecommendViewModel: JXPlayerListViewModel {
private(set) var dataArr: [FAShortPlayModel] = [] private(set) var dataArr: [FAShortPlayModel] = []
private(set) lazy var page = 1
var previousEpisode: FAVideoInfoModel? { var previousEpisode: FAVideoInfoModel? {
@ -65,6 +65,7 @@ extension FARecommendViewModel {
FAAPI.requestRecommendVideo(page: page) { [weak self] listModel in FAAPI.requestRecommendVideo(page: page) { [weak self] listModel in
guard let self = self else { return } guard let self = self else { return }
if let listModel = listModel, let list = listModel.list { if let listModel = listModel, let list = listModel.list {
self.page = page
if page == 1 { if page == 1 {
self.playerListVC?.clearData() self.playerListVC?.clearData()
self.dataArr = list self.dataArr = list

View File

@ -25,7 +25,7 @@ class FACoinRecordViewController: FAViewController {
tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)
} }
tableView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in tableView.fa_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return tableView return tableView
@ -65,11 +65,7 @@ extension FACoinRecordViewController: UITableViewDelegate, UITableViewDataSource
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = self.dataArr[indexPath.row] let model = self.dataArr[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FAOrderRecordCell let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FAOrderRecordCell
if model.type == "send" { cell.titleLabel.text = model.type
cell.titleLabel.text = "fableo_bonus_coins".localized
} else {
cell.titleLabel.text = "fableo_recharge_coins".localized
}
cell.dateLabel.text = model.created_at cell.dateLabel.text = model.created_at
cell.countLabel.text = "+\(model.value ?? "0")" cell.countLabel.text = "+\(model.value ?? "0")"
return cell return cell

View File

@ -25,7 +25,7 @@ class FAConsumptionRecordsViewController: FAViewController {
tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)
} }
tableView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in tableView.fa_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return tableView return tableView

View File

@ -25,7 +25,7 @@ class FARewardCoinsViewController: FAViewController {
tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)
} }
tableView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in tableView.fa_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return tableView return tableView

View File

@ -73,7 +73,7 @@ class FAStoreViewController: FAViewController {
let label = UILabel() let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium) label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .FFFFFF label.textColor = .FFFFFF
label.text = "store_tips_title".localized label.text = "fableon_tips".localized
return label return label
}() }()
@ -130,21 +130,21 @@ class FAStoreViewController: FAViewController {
self.stackView.fa_removeAllArrangedSubview() self.stackView.fa_removeAllArrangedSubview()
guard let model = self.payDataModel else { return } guard let model = self.payDataModel else { return }
if model.pay_mode == 1 { // if model.pay_mode == 1 {
self.addCoinsView() // self.addCoinsView()
} else { // } else {
if let sort = model.sort, sort.count > 0 { // }
sort.forEach { if let sort = model.sort, sort.count > 0 {
if $0 == .vip { sort.forEach {
self.addVipView() if $0 == .vip {
} else if $0 == .coin { self.addVipView()
self.addCoinsView() } else if $0 == .coin {
} self.addCoinsView()
} }
} else {
self.addVipView()
self.addCoinsView()
} }
} else {
self.addVipView()
self.addCoinsView()
} }
self.stackView.addArrangedSubview(tipView) self.stackView.addArrangedSubview(tipView)
} }
@ -154,18 +154,18 @@ class FAStoreViewController: FAViewController {
var newList: [FAPayItem] = [] var newList: [FAPayItem] = []
if model.show_type == 1, model.pay_mode == 1 { // if model.show_type == 1, model.pay_mode == 1 {
if let list = model.list_coins, list.count > 0 { // if let list = model.list_coins, list.count > 0 {
list.forEach { // list.forEach {
if $0.buy_type == .subCoins { // if $0.buy_type == .subCoins {
newList.append($0) // newList.append($0)
} // }
} // }
} // }
} else { // } else {
if let list = model.list_coins, list.count > 0 { // }
newList = list if let list = model.list_coins, list.count > 0 {
} newList = list
} }
if newList.count > 0 { if newList.count > 0 {
coinsView.setDataArr(newList) coinsView.setDataArr(newList)

View File

@ -25,7 +25,7 @@ class FAVipRecordViewController: FAViewController {
tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in tableView.fa_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil) self?.handleHeaderRefresh(nil)
} }
tableView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in tableView.fa_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil) self?.handleFooterRefresh(nil)
} }
return tableView return tableView

View File

@ -38,7 +38,7 @@ class FACoinPackConfirmItem2View: FACoinPackConfirmItemView {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.titleLabel.text = "How Do I Receive Coins" self.titleLabel.text = "fableon_coin_bag_buy_tip_title".localized
fa_setupLayout() fa_setupLayout()
} }

View File

@ -13,7 +13,12 @@ class FAStoreCoinsPackCell: FAStoreCoinsCell {
override var model: FAPayItem? { override var model: FAPayItem? {
didSet { didSet {
coinsLabel.text = "\(model?.ext_info?.max_total_coins ?? 0)" coinsLabel.text = "\(model?.ext_info?.max_total_coins ?? 0)"
ratioLabel.text = "\(model?.ext_info?.receive_coins_rate ?? "0%")" if let text = model?.ext_info?.receive_coins_rate, !text.isEmpty {
ratioLabel.text = text
ratioBgView.isHidden = false
} else {
ratioBgView.isHidden = true
}
priceView.setNeedsUpdateConfiguration() priceView.setNeedsUpdateConfiguration()
} }
@ -124,9 +129,9 @@ class FAStoreCoinsPackCell: FAStoreCoinsCell {
var subtitle = AttributedString("\(currency)\(oldPrice)", attributes: AttributeContainer([ var subtitle = AttributedString("\(currency)\(oldPrice)", attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .regular), .font : UIFont.font(ofSize: 12, weight: .regular),
.foregroundColor : UIColor._636_F_7_B.withAlphaComponent(0.2), .foregroundColor : UIColor._636_F_7_B.withAlphaComponent(0.05),
.strikethroughStyle: NSUnderlineStyle.single.rawValue, .strikethroughStyle: NSUnderlineStyle.double.rawValue,
.strikethroughColor: UIColor._636_F_7_B.withAlphaComponent(0.2) .strikethroughColor: UIColor._636_F_7_B.withAlphaComponent(0.05)
])) ]))
button.configuration?.attributedSubtitle = subtitle button.configuration?.attributedSubtitle = subtitle

View File

@ -110,7 +110,9 @@ extension FAStoreCoinsView {
addSubview(collectionView) addSubview(collectionView)
collectionView.snp.makeConstraints { make in collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview() // make.edges.equalToSuperview()
make.top.equalToSuperview().offset(8)
make.left.right.bottom.equalToSuperview()
make.height.equalTo(1) make.height.equalTo(1)
} }
} }
@ -190,6 +192,8 @@ extension FAStoreCoinsView: UICollectionViewDelegate, UICollectionViewDataSource
if model.buy_type == .subCoins { if model.buy_type == .subCoins {
let view = FACoinPackConfirmView() let view = FACoinPackConfirmView()
view.shortPlayId = self.shortPlayId
view.videoId = self.videoId
view.model = model view.model = model
view.buyFinishHandle = { [weak self] in view.buyFinishHandle = { [weak self] in
guard let self = self else { return } guard let self = self else { return }

View File

@ -12,7 +12,7 @@ class FAStoreVipCell: UICollectionViewCell {
var item: FAPayItem? { var item: FAPayItem? {
didSet { didSet {
nameLabel.text = item?.title nameLabel.text = item?.brief
desLabel.text = item?.fa_description desLabel.text = item?.fa_description
// coinTimeLabel.text = item?.auto_sub // coinTimeLabel.text = item?.auto_sub
@ -43,7 +43,7 @@ class FAStoreVipCell: UICollectionViewCell {
let oldPriceStr = NSMutableAttributedString(string: oldPrice) let oldPriceStr = NSMutableAttributedString(string: oldPrice)
oldPriceStr.yy_strikethroughColor = discountLabel.textColor oldPriceStr.yy_strikethroughColor = discountLabel.textColor
oldPriceStr.yy_strikethroughStyle = .single oldPriceStr.yy_strikethroughStyle = .double
discountLabel.attributedText = oldPriceStr discountLabel.attributedText = oldPriceStr
discountLabel.isHidden = false discountLabel.isHidden = false
@ -156,7 +156,7 @@ class FAStoreVipCell: UICollectionViewCell {
private lazy var discountLabel: UILabel = { private lazy var discountLabel: UILabel = {
let label = UILabel() let label = UILabel()
label.font = .font(ofSize: 16, weight: .medium) label.font = .font(ofSize: 16, weight: .medium)
label.textColor = .FFFFFF.withAlphaComponent(0.4) label.textColor = .FFFFFF.withAlphaComponent(0.03)
return label return label
}() }()

View File

@ -20,16 +20,18 @@ class FAStoreVipView: UIView {
var buyFinishHandle: (() -> Void)? var buyFinishHandle: (() -> Void)?
private lazy var collectionViewLayout: UICollectionViewCompositionalLayout = { private lazy var collectionViewLayout: UICollectionViewCompositionalLayout = {
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))) let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(110)), subitems: [item]) let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(110)), subitems: [item])
group.interItemSpacing = .fixed(14)
let layoutSection = NSCollectionLayoutSection(group: group) let layoutSection = NSCollectionLayoutSection(group: group)
layoutSection.interGroupSpacing = 14
layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16) layoutSection.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
let config = UICollectionViewCompositionalLayoutConfiguration() let config = UICollectionViewCompositionalLayoutConfiguration()
let layout = UICollectionViewCompositionalLayout(section: layoutSection) let layout = UICollectionViewCompositionalLayout(section: layoutSection)

View File

@ -0,0 +1,62 @@
//
// FAActivityManager.swift
// Fableon
//
// Created by on 2025/11/10.
//
import UIKit
import ActivityKit
struct ActivityManagerAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var videoTitle: String
var videoCoverPath: String
var videoEpisode:String
var videoId: String
}
// Fixed non-changing properties about your activity go here!
var name: String
}
@available(iOS 16.1, *)
class FAActivityManager {
static var coverFileUrl: URL? {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.hn.qinjiu.fableon")
let fileURL = sharedContainerURL?.appendingPathComponent("cover.png")
return fileURL
}
@objc public static func liveActivity(with videoTitle: String, videoId: String, videoEpisode: String) {
let fileURL = self.coverFileUrl?.absoluteString ?? ""
let contentState = ActivityManagerAttributes.ContentState(videoTitle: videoTitle,
videoCoverPath: fileURL,
videoEpisode: videoEpisode,
videoId:videoId)
do {
if Activity<ActivityManagerAttributes>.activities.count == 0 {
let attributes = ActivityManagerAttributes(name: "live activity")
let activity = try Activity<ActivityManagerAttributes>.request(attributes: attributes,
contentState: contentState,
pushType: nil)
print("Requested a pizza delivery Live Activity \(activity.id)")
} else {
Task {
for activity in Activity<ActivityManagerAttributes>.activities {
await activity.update(using: contentState)
}
}
}
} catch (let error) {
print("Error requesting pizza delivery Live Activity \(error.localizedDescription)")
}
}
}

View File

@ -20,5 +20,14 @@ class FAAdjustStateManager {
var idfaAuthorizationFinish = false var idfaAuthorizationFinish = false
var apnsAuthorizationFinish = false private(set) var apnsAuthorizationFinish = false
var upgradeAlertFinish = false
func apnsFinish() {
self.apnsAuthorizationFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
FATool.requestIDFAAuthorization(nil)
}
} }

View File

@ -58,6 +58,12 @@ class FAApnsAlert: FABaseAlert {
override func handleHighlightButton() { override func handleHighlightButton() {
super.handleHighlightButton() super.handleHighlightButton()
FATool.openApnsSetting() FATool.openApnsSetting()
FAAdjustStateManager.manager.apnsFinish()
}
override func dismiss() {
super.dismiss()
FAAdjustStateManager.manager.apnsFinish()
} }
} }

View File

@ -56,7 +56,7 @@ class FAUpdatesAlert: FABaseAlert {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
highlightButton.layer.cornerRadius = 18 highlightButton.layer.cornerRadius = 18
highlightButton.setTitle("fableon_update_now", for: .normal) highlightButton.setTitle("fableon_update_now".localized, for: .normal)
fa_setupLayout() fa_setupLayout()
} }
@ -74,6 +74,12 @@ class FAUpdatesAlert: FABaseAlert {
application.open(url) application.open(url)
} }
} }
override func dismiss() {
super.dismiss()
FAAdjustStateManager.manager.upgradeAlertFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
}
} }
extension FAUpdatesAlert { extension FAUpdatesAlert {

View File

@ -132,6 +132,7 @@ extension FAVipRetainAlert {
var payItem: FAPayItem? { var payItem: FAPayItem? {
didSet { didSet {
nameLabel.text = payItem?.getVipTitle() nameLabel.text = payItem?.getVipTitle()
textLabel.text = "vip_tip_01".localized
button.setNeedsUpdateConfiguration() button.setNeedsUpdateConfiguration()
} }
@ -215,9 +216,9 @@ extension FAVipRetainAlert {
button.configuration?.attributedSubtitle = AttributedString("\(currency)\(oldPrice)", attributes: AttributeContainer([ button.configuration?.attributedSubtitle = AttributedString("\(currency)\(oldPrice)", attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .regular), .font : UIFont.font(ofSize: 12, weight: .regular),
.foregroundColor : UIColor._000000.withAlphaComponent(0.5), .foregroundColor : UIColor._000000.withAlphaComponent(0.05),
.strikethroughStyle: NSUnderlineStyle.single.rawValue, .strikethroughStyle: NSUnderlineStyle.double.rawValue,
.strikethroughColor: UIColor._000000.withAlphaComponent(0.5) .strikethroughColor: UIColor._000000.withAlphaComponent(0.05)
])) ]))
} else { } else {
@ -239,7 +240,7 @@ extension FAVipRetainAlert {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
nameLabel.text = "Weekly VIP" nameLabel.text = "Weekly VIP"
textLabel.text = "Unlimited access to all series"
addSubview(bgView) addSubview(bgView)
bgView.addSubview(bgIconImageView4) bgView.addSubview(bgIconImageView4)

View File

@ -24,4 +24,33 @@ struct FAEmpty {
return view! return view!
} }
static func fa_notNetworkEmptyView(clickBlock: (() -> Void)?) -> LYEmptyView {
let view = LYEmptyView.emptyActionView(with: UIImage(named: "__startap"), titleStr: "No connection", detailStr: "Please check your network", btnTitleStr: "Try again") {
clickBlock?()
}
view?.titleLabFont = .font(ofSize: 17, weight: .bold)
view?.titleLabTextColor = .FFFFFF
view?.detailLabFont = .font(ofSize: 15, weight: .regular)
view?.detailLabTextColor = .E_5_E_5_E_5
view?.actionBtnHeight = 32
view?.actionBtnHorizontalMargin = 26
view?.actionBtnCornerRadius = 16
view?.actionBtnBorderWidth = 1
view?.actionBtnBorderColor = .FFFFFF
view?.actionBtnBackGroundColor = .clear
view?.actionBtnTitleColor = .FFFFFF
view?.actionBtnFont = .font(ofSize: 14, weight: .medium)
view?.contentViewOffset = -20
view?.titleLabMargin = 30
view?.detailLabMargin = 2
view?.actionBtnMargin = 20
return view!
}
} }

View File

@ -77,6 +77,7 @@ class FALogin: NSObject {
FAStatAPI.requestStatOnLine() FAStatAPI.requestStatOnLine()
completer?(true) completer?(true)
NotificationCenter.default.post(name: FALogin.userInfoUpdateNotification, object: nil) NotificationCenter.default.post(name: FALogin.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: FALogin.loginStatusChangeNotification, object: nil)
} }
} }
@ -93,6 +94,7 @@ class FALogin: NSObject {
FAStatAPI.requestStatOnLine() FAStatAPI.requestStatOnLine()
completer?(true) completer?(true)
NotificationCenter.default.post(name: FALogin.userInfoUpdateNotification, object: nil) NotificationCenter.default.post(name: FALogin.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: FALogin.loginStatusChangeNotification, object: nil)
} else { } else {
completer?(false) completer?(false)
} }
@ -129,5 +131,7 @@ extension FALogin {
/// ///
@objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "FALogin.userInfoUpdateNotification") @objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "FALogin.userInfoUpdateNotification")
///
@objc static let loginStatusChangeNotification = NSNotification.Name(rawValue: "FALogin.loginStatusChangeNotification")
} }

View File

@ -32,6 +32,9 @@ class FATool {
return keyWindow?.rootViewController return keyWindow?.rootViewController
} }
///
static var widgetLiveActivityModel: FAShortPlayModel?
/// ///
static func getLanuchViewController() -> UIViewController? { static func getLanuchViewController() -> UIViewController? {
let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil) let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
@ -102,18 +105,31 @@ extension FATool {
//MARK: //MARK:
extension FATool { extension FATool {
static weak var updateAlert: UIView?
static func checkUpdates() { static func checkUpdates() {
guard self.self.updateAlert == nil else { return }
FAAPI.requestVersionUpdateData { model in FAAPI.requestVersionUpdateData { model in
guard let model = model else { return } guard let model = model else {
FAAdjustStateManager.manager.upgradeAlertFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
return
}
self.showUpdatesAlert(model) self.showUpdatesAlert(model)
} }
} }
static func showUpdatesAlert(_ model: FAVersionUpdateModel) { static func showUpdatesAlert(_ model: FAVersionUpdateModel) {
guard self.self.updateAlert == nil else { return }
#if !DEBUG #if !DEBUG
if model.force != true { if model.force != true {
if let date = UserDefaults.standard.object(forKey: kFAVersionUpdateAlertDefaultsKey) as? Date { if let date = UserDefaults.standard.object(forKey: kFAVersionUpdateAlertDefaultsKey) as? Date {
if date.fa_isToday { return } if date.fa_isToday {
FAAdjustStateManager.manager.upgradeAlertFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
return
}
} }
UserDefaults.standard.set(Date(), forKey: kFAVersionUpdateAlertDefaultsKey) UserDefaults.standard.set(Date(), forKey: kFAVersionUpdateAlertDefaultsKey)
} }
@ -121,9 +137,17 @@ extension FATool {
if model.canUpdate() { if model.canUpdate() {
FAStatAPI.requestEventStat(orderCode: nil, shortPlayId: nil, videoId: nil, eventKey: .forceUpdate, errorMsg: nil, otherParamenters: [
"is_force" : model.force == true ? "true" : "false"
])
let view = FAUpdatesAlert() let view = FAUpdatesAlert()
view.model = model view.model = model
view.show() view.show()
self.updateAlert = view
} else {
FAAdjustStateManager.manager.upgradeAlertFinish = true
FATool.sceneDelegate?.retryHandleOpenAppMessage()
} }
} }

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x3B",
"green" : "0x31",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -5,12 +5,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "Thalire@2x.png", "filename" : "Fableon@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "Thalire@3x.png", "filename" : "Fableon@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "__startap@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "__startap@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -46,5 +46,12 @@
</array> </array>
</dict> </dict>
</dict> </dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UIDesignRequiresCompatibility</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -91,6 +91,13 @@
"fableon_editor's_picks" = "Editor's Picks"; "fableon_editor's_picks" = "Editor's Picks";
"fableon_home_new" = "New Releases"; "fableon_home_new" = "New Releases";
"fableon_watch" = "Watch"; "fableon_watch" = "Watch";
"fableon_top_up" = "Top Up";
"fableon_selection" = "selection";
"fableon_coin_bag_buy_tip_title" = "How Do I Receive Coins";
"fableon_d" = "Day";
"fableon_kick_out_login" = "Your account is already logged in on another device~";
"fableon_rabkings" = "Rankings";
"fableon_recommended_for_you" = "Recommended for you";
"remove_collect_alert_title" = "Remove from Favorites?"; "remove_collect_alert_title" = "Remove from Favorites?";
"remove_collect_alert_text" = "This drama will be removed from your favorites."; "remove_collect_alert_text" = "This drama will be removed from your favorites.";
@ -107,6 +114,7 @@
"network_error_01" = "The service is abnormal. Check the network."; "network_error_01" = "The service is abnormal. Check the network.";
"buy_fail_toast_01" = "Purchase failed, please try again later!"; "buy_fail_toast_01" = "Purchase failed, please try again later!";
"buy_fail_toast_02" = "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series"; "buy_fail_toast_02" = "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series";
@ -130,4 +138,4 @@
"coins_pack_tips_title" = "Subscription Rules"; "coins_pack_tips_title" = "Subscription Rules";
"coins_pack_tips" = "1.Up to 2 subscriptions can be active at once.<br>2.Coins are delivered instantly upon purchase.<br>3.Daily bonus coins available from the next day.<br>4.All coins will be revoked when the subscription expires, including both initial and daily coins."; "coins_pack_tips" = "1.Coins are delivered instantly upon purchase.<br>2.Daily bonus coins available from the next day.<br>3.All coins will be revoked when the subscription expires, including both initial and daily coins.";

View File

@ -169,7 +169,7 @@ withUnsafeMutablePointer(to: &writeclearWatchdog) { pointer in
$0.title = "fableon_popular".localized $0.title = "fableon_popular".localized
popularItem = $0 popularItem = $0
} else if $0.module_key == .week_ranking { } else if $0.module_key == .week_ranking {
$0.title = "Rankings".localized $0.title = "fableon_rabkings".localized
rankingsItem = $0 rankingsItem = $0
} else if $0.module_key == .cagetory_recommand, genresItem == nil { } else if $0.module_key == .cagetory_recommand, genresItem == nil {
$0.title = "veloriaDetailPath".localized $0.title = "veloriaDetailPath".localized

View File

@ -74,7 +74,7 @@ var deceleratingMax: Double = 0.0
} }
HStack { HStack {
Text("selection".localized) Text("fableon_selection".localized)
.font(Font.font(size: 12, weight: .medium)) .font(Font.font(size: 12, weight: .medium))
.foregroundStyle(textColor) .foregroundStyle(textColor)
.padding(.leading, 8) .padding(.leading, 8)

View File

@ -16,8 +16,14 @@ var bottomProgressDict: [String: Any]?
didSet { didSet {
coverImageView.fa_setImage(model?.image_url) coverImageView.fa_setImage(model?.image_url)
titleLabel.text = model?.name titleLabel.text = model?.name
countLabel.text = "\(model?.watch_total ?? 0)"
epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)") epLabel.text = "fableon_episode_set".localizedReplace(text: "\(model?.episode_total ?? 0)")
let count = model?.watch_total ?? 0
var countStr = "\(count)"
if count > 1000 {
countStr = NSNumber(value: CGFloat(count) / 1000).toString(maximumFractionDigits: 1) + "k"
}
countLabel.text = countStr
} }
} }

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,69 @@
//
// NotificationService.swift
// NotificationService
//
// Created by on 2025/11/10.
//
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
contentHandler(request.content)
return
}
if let options = request.content.userInfo["fcm_options"] as? [String : Any],
let imageURLString = options["image"] as? String,
let imageURL = URL(string: imageURLString) {
downloadImage(from: imageURL) { attachment in
if let attachment = attachment {
bestAttemptContent.attachments = [attachment]
}
contentHandler(bestAttemptContent)
}
} else {
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func downloadImage(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
let task = URLSession.shared.downloadTask(with: url) { (downloadedUrl, _, error) in
guard let downloadedUrl = downloadedUrl else {
completion(nil)
return
}
let tmpDir = FileManager.default.temporaryDirectory
let tmpFile = tmpDir.appendingPathComponent(url.lastPathComponent)
do {
try FileManager.default.moveItem(at: downloadedUrl, to: tmpFile)
let attachment = try UNNotificationAttachment(identifier: UUID().uuidString, url: tmpFile, options: nil)
completion(attachment)
} catch {
completion(nil)
}
}
task.resume()
}
}

View File

@ -20,7 +20,7 @@ target 'Fableon' do
pod 'YYText' pod 'YYText'
pod 'Alamofire' pod 'Alamofire'
pod 'SmartCodable' pod 'SmartCodable'
pod 'JXPlayer', '~> 0.1.5' pod 'JXPlayer', '~> 0.1.8'
pod 'Kingfisher' pod 'Kingfisher'
pod 'SnapKit' pod 'SnapKit'
pod 'FSPagerView' pod 'FSPagerView'

View File

@ -50,9 +50,9 @@ PODS:
- IQTextView (1.0.5): - IQTextView (1.0.5):
- IQKeyboardToolbar/Placeholderable - IQKeyboardToolbar/Placeholderable
- JXPagingView/Paging (2.1.3) - JXPagingView/Paging (2.1.3)
- JXPlayer (0.1.5): - JXPlayer (0.1.8):
- SJMediaCacheServer - SJMediaCacheServer (= 2.1.6)
- SJVideoPlayer - SJVideoPlayer (= 3.4.3)
- JXSegmentedView (1.4.1) - JXSegmentedView (1.4.1)
- Kingfisher (8.5.0) - Kingfisher (8.5.0)
- LYEmptyView (1.3.1) - LYEmptyView (1.3.1)
@ -86,10 +86,10 @@ PODS:
- SJUIKit/ObserverHelper - SJUIKit/ObserverHelper
- SJUIKit/Queues - SJUIKit/Queues
- SJUIKit/SQLite3 - SJUIKit/SQLite3
- SJMediaCacheServer (2.1.7): - SJMediaCacheServer (2.1.6):
- SJMediaCacheServer/Core (= 2.1.7) - SJMediaCacheServer/Core (= 2.1.6)
- SJUIKit/SQLite3 - SJUIKit/SQLite3
- SJMediaCacheServer/Core (2.1.7): - SJMediaCacheServer/Core (2.1.6):
- SJUIKit/SQLite3 - SJUIKit/SQLite3
- SJUIKit/AttributesFactory (0.0.0.59): - SJUIKit/AttributesFactory (0.0.0.59):
- SJUIKit/AttributesFactory/Deprecated (= 0.0.0.59) - SJUIKit/AttributesFactory/Deprecated (= 0.0.0.59)
@ -148,7 +148,7 @@ DEPENDENCIES:
- HWPanModal - HWPanModal
- IQKeyboardManagerSwift - IQKeyboardManagerSwift
- JXPagingView/Paging - JXPagingView/Paging
- JXPlayer (~> 0.1.5) - JXPlayer (~> 0.1.8)
- JXSegmentedView - JXSegmentedView
- Kingfisher - Kingfisher
- LYEmptyView - LYEmptyView
@ -215,14 +215,14 @@ SPEC CHECKSUMS:
IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559 IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559
IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7 IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7
JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
JXPlayer: 0e0cd9a2ee406ebb3051d00c760cd5fc599bf09b JXPlayer: 8ca57987b506419cb51a8c84e6196c25043634ca
JXSegmentedView: cd73555ce2134d1656db2cb383ba9c2f36fb5078 JXSegmentedView: cd73555ce2134d1656db2cb383ba9c2f36fb5078
Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c
LYEmptyView: b6d418cfa38b78df0cf243f9a9c25ccbdc399922 LYEmptyView: b6d418cfa38b78df0cf243f9a9c25ccbdc399922
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
SJBaseVideoPlayer: b3122de12225b27b71bd9a8a1f08f4dcf2f4e5ec SJBaseVideoPlayer: b3122de12225b27b71bd9a8a1f08f4dcf2f4e5ec
SJMediaCacheServer: 0ca17fcdd5340fe1d2ad03110e511376f290d924 SJMediaCacheServer: f6cd08ff32f5c6fc18ff06e676b42d6c17ce4cf1
SJUIKit: a40111941e408cc17d4c1c23495aa92999e814b0 SJUIKit: a40111941e408cc17d4c1c23495aa92999e814b0
SJVideoPlayer: 4f09814f58522e0975cb2dccfda925f6c8643467 SJVideoPlayer: 4f09814f58522e0975cb2dccfda925f6c8643467
SmartCodable: 545dd052990fe7e80085463b79a1a5e462ee29ff SmartCodable: 545dd052990fe7e80085463b79a1a5e462ee29ff
@ -234,6 +234,6 @@ SPEC CHECKSUMS:
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
ZLPhotoBrowser: d5928f08485c90a0b349a3a1e804e82c83ccf193 ZLPhotoBrowser: d5928f08485c90a0b349a3a1e804e82c83ccf193
PODFILE CHECKSUM: ee93369942c41a1babdca4fd27a29e21997fc357 PODFILE CHECKSUM: 0d6c1bc4222cac60ce66cfe1a22456b0a9cc1685
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

Some files were not shown because too many files have changed in this diff Show More