forked from MeloNX/MeloNX
Navigation palette + iOS 26 glass effects
This commit is contained in:
parent
273a31dc14
commit
89ad9b6457
@ -28,6 +28,7 @@
|
|||||||
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||||
|
7CD054B52E253D2F00287A89 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */; };
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@ -196,6 +197,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
7CD054B52E253D2F00287A89 /* SwiftUIIntrospect in Frameworks */,
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
||||||
4549A31C2DD8795900EC8D88 /* CocoaAsyncSocket in Frameworks */,
|
4549A31C2DD8795900EC8D88 /* CocoaAsyncSocket in Frameworks */,
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||||
@ -295,6 +297,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
||||||
4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */,
|
4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */,
|
||||||
|
7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
||||||
@ -387,6 +390,7 @@
|
|||||||
packageReferences = (
|
packageReferences = (
|
||||||
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
||||||
4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */,
|
4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */,
|
||||||
|
7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 56;
|
preferredProjectObjectVersion = 56;
|
||||||
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
||||||
@ -642,7 +646,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = D59DHVRS87;
|
||||||
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = NO;
|
ENABLE_TESTABILITY = NO;
|
||||||
@ -782,6 +786,9 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1041,9 +1048,15 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
@ -1066,7 +1079,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = D59DHVRS87;
|
||||||
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@ -1206,6 +1219,9 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1465,9 +1481,15 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
@ -1682,6 +1704,14 @@
|
|||||||
kind = branch;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/siteline/swiftui-introspect.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.3.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@ -1695,6 +1725,11 @@
|
|||||||
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
||||||
productName = SwiftSVG;
|
productName = SwiftSVG;
|
||||||
};
|
};
|
||||||
|
7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */;
|
||||||
|
productName = SwiftUIIntrospect;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 4E80A9852CD6F54500029585 /* Project object */;
|
rootObject = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "b4a593815773c4e9eedb98cabe88f41620776314bffb6c39d5a41cb743e4d390",
|
"originHash" : "ae170c69ea1ccacb2f3982b1a91b689a09bc4dcc59ed970f1df977bf7c7aed6f",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "cocoaasyncsocket",
|
"identity" : "cocoaasyncsocket",
|
||||||
@ -18,6 +18,15 @@
|
|||||||
"branch" : "master",
|
"branch" : "master",
|
||||||
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftui-introspect",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/siteline/swiftui-introspect.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
|
||||||
|
"version" : "1.3.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 3
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
//
|
||||||
|
// NavigationItemPalette.swift
|
||||||
|
// iTorrent
|
||||||
|
//
|
||||||
|
// Created by Daniil Vinogradov on 14.11.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
@_spi(Advanced) import SwiftUIIntrospect
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
func navigationItemBottomPalette(@ViewBuilder body: () -> (some View)) -> some View {
|
||||||
|
modifier(NavitaionItemBottomPaletteContent(content: body().asController))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NavitaionItemBottomPaletteContent: ViewModifier {
|
||||||
|
let content: UIViewController
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.introspect(.viewController, on: .iOS(.v14...), customize: { viewController in
|
||||||
|
let view = self.content.view!
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
let size = view.systemLayoutSizeFitting(.init(width: viewController.view.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
|
||||||
|
viewController.navigationItem.setBottomPalette(view, height: size.height)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UINavigationItem {
|
||||||
|
func setBottomPalette(_ contentView: UIView?, height: CGFloat = 44) {
|
||||||
|
/// "_setBottomPalette:"
|
||||||
|
let selector = NSSelectorFromBase64String("X3NldEJvdHRvbVBhbGV0dGU6")
|
||||||
|
guard responds(to: selector) else { return }
|
||||||
|
perform(selector, with: Self.makeNavigationItemPalette(with: contentView, height: height))
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeNavigationItemPalette(with contentView: UIView?, height: CGFloat) -> UIView? {
|
||||||
|
guard let contentView else { return nil }
|
||||||
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
let contentViewHolder = UIView(frame: .init(x: 0, y: 0, width: 0, height: height))
|
||||||
|
contentViewHolder.autoresizingMask = [.flexibleHeight]
|
||||||
|
contentViewHolder.addSubview(contentView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
contentViewHolder.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||||
|
contentViewHolder.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||||
|
contentView.trailingAnchor.constraint(equalTo: contentViewHolder.trailingAnchor),
|
||||||
|
contentView.bottomAnchor.constraint(equalTo: contentViewHolder.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
/// "_UINavigationBarPalette"
|
||||||
|
guard let paletteClass = NSClassFromBase64String("X1VJTmF2aWdhdGlvbkJhclBhbGV0dGU=") as? UIView.Type
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
/// "alloc"
|
||||||
|
/// "initWithContentView:"
|
||||||
|
guard let palette = paletteClass.perform(NSSelectorFromBase64String("YWxsb2M="))
|
||||||
|
.takeUnretainedValue()
|
||||||
|
.perform(NSSelectorFromBase64String("aW5pdFdpdGhDb250ZW50Vmlldzo="), with: contentViewHolder)
|
||||||
|
.takeUnretainedValue() as? UIView
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
palette.preservesSuperviewLayoutMargins = true
|
||||||
|
return palette
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NSSelectorFromBase64String(_ base64String: String) -> Selector {
|
||||||
|
NSSelectorFromString(String(base64: base64String))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NSClassFromBase64String(_ aBase64ClassName: String) -> AnyClass? {
|
||||||
|
NSClassFromString(String(base64: aBase64ClassName))
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
init(base64: String) {
|
||||||
|
self.init(data: Data(base64Encoded: base64)!, encoding: .utf8)!
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// UIKitSwiftUIInarop.swift
|
||||||
|
// iTorrent
|
||||||
|
//
|
||||||
|
// Created by Daniil Vinogradov on 01/11/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct GenericControllerView: UIViewControllerRepresentable {
|
||||||
|
let viewController: UIViewController
|
||||||
|
typealias UIViewControllerType = UIViewController
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> UIViewController {
|
||||||
|
viewController
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { /* Ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
@MainActor
|
||||||
|
var asController: UIHostingController<Self> {
|
||||||
|
let vc = UIHostingController<Self>(rootView: self)
|
||||||
|
if #available(iOS 16.4, *) {
|
||||||
|
vc.safeAreaRegions = []
|
||||||
|
}
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIViewController {
|
||||||
|
var asView: some View {
|
||||||
|
GenericControllerView(viewController: self)
|
||||||
|
}
|
||||||
|
}
|
@ -96,9 +96,15 @@ struct GameInfoSheet: View {
|
|||||||
.navigationTitle(game.titleName)
|
.navigationTitle(game.titleName)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("Dismiss") {
|
if #available(iOS 26, *) {
|
||||||
presentationMode.wrappedValue.dismiss()
|
Button(role: .close) {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button("Dismiss") {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,28 +78,38 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
ZStack {
|
Group {
|
||||||
// Background color
|
// Game list
|
||||||
Color(UIColor.systemBackground)
|
if Ryujinx.shared.games.isEmpty {
|
||||||
.ignoresSafeArea()
|
EmptyGameLibraryView(isSelectingGameFile: $isSelectingGameFile)
|
||||||
|
} else {
|
||||||
VStack(spacing: 0) {
|
gameListView
|
||||||
// Header with stats
|
.animation(.easeInOut(duration: 0.3), value: searchText)
|
||||||
if !Ryujinx.shared.games.isEmpty {
|
}
|
||||||
GameLibraryHeader(
|
}
|
||||||
totalGames: Ryujinx.shared.games.count,
|
.navigationItemBottomPalette {
|
||||||
recentGames: realRecentGames.count,
|
if !Ryujinx.shared.games.isEmpty {
|
||||||
firmwareVersion: firmwareversion
|
GameLibraryHeader(
|
||||||
)
|
totalGames: Ryujinx.shared.games.count,
|
||||||
}
|
recentGames: realRecentGames.count,
|
||||||
|
firmwareVersion: firmwareversion
|
||||||
// Game list
|
)
|
||||||
if Ryujinx.shared.games.isEmpty {
|
.overlay(Group {
|
||||||
EmptyGameLibraryView(isSelectingGameFile: $isSelectingGameFile)
|
if ryujinx.jitenabled {
|
||||||
} else {
|
VStack {
|
||||||
gameListView
|
HStack {
|
||||||
.animation(.easeInOut(duration: 0.3), value: searchText)
|
Spacer()
|
||||||
}
|
Circle()
|
||||||
|
.frame(width: 12, height: 12)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.foregroundColor(Color.green)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Game Library")
|
.navigationTitle("Game Library")
|
||||||
@ -164,27 +174,11 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(Group {
|
|
||||||
if ryujinx.jitenabled {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Circle()
|
|
||||||
.frame(width: 12, height: 12)
|
|
||||||
.padding(.horizontal, 8)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
.foregroundColor(Color.green)
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onChange(of: startemu) { game in
|
.onChange(of: startemu) { game in
|
||||||
guard let game else { return }
|
guard let game else { return }
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
// .searchable(text: $searchText, placement: .toolbar, prompt: "Search games or developers")
|
.searchable(text: $searchText, placement: .toolbar, prompt: "Search games or developers")
|
||||||
.onChange(of: searchText) { _ in
|
.onChange(of: searchText) { _ in
|
||||||
isSearching = !searchText.isEmpty
|
isSearching = !searchText.isEmpty
|
||||||
}
|
}
|
||||||
@ -654,7 +648,7 @@ struct GameLibraryHeader: View {
|
|||||||
// Stats cards
|
// Stats cards
|
||||||
StatCard(
|
StatCard(
|
||||||
icon: "gamecontroller.fill",
|
icon: "gamecontroller.fill",
|
||||||
title: "Total Games",
|
title: "Games",
|
||||||
value: "\(totalGames)",
|
value: "\(totalGames)",
|
||||||
color: .blue
|
color: .blue
|
||||||
)
|
)
|
||||||
@ -674,8 +668,7 @@ struct GameLibraryHeader: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 8)
|
.padding(.bottom, 8)
|
||||||
.padding(.bottom, 4)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,7 +677,9 @@ struct StatCard: View {
|
|||||||
let title: String
|
let title: String
|
||||||
let value: String
|
let value: String
|
||||||
let color: Color
|
let color: Color
|
||||||
|
|
||||||
|
@Namespace var statCardIdNamespace
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
@ -700,11 +695,24 @@ struct StatCard: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(color.opacity(0.1))
|
.apply {
|
||||||
.cornerRadius(10)
|
if #available(iOS 26, *) {
|
||||||
|
$0
|
||||||
|
.glassEffect(.regular.tint(color.opacity(0.05)), in: RoundedRectangle(cornerRadius: 16))
|
||||||
|
.glassEffectID("StatCardID", in: statCardIdNamespace)
|
||||||
|
} else {
|
||||||
|
$0
|
||||||
|
.background(color.opacity(0.1))
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Game Card View
|
// MARK: - Game Card View
|
||||||
struct GameCardView: View {
|
struct GameCardView: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@ -881,7 +889,9 @@ struct GameListRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
if $settingsManager.config.wrappedValue.contains(where: { $0.key == game.titleId }) {
|
if $settingsManager.config.wrappedValue.contains(where: { $0.key == game.titleId }) {
|
||||||
Image(systemName: "gearshape.circle")
|
Image(systemName: "gearshape.circle")
|
||||||
.resizable()
|
.resizable()
|
||||||
@ -889,8 +899,7 @@ struct GameListRow: View {
|
|||||||
.foregroundStyle(.blue)
|
.foregroundStyle(.blue)
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
// Compatibility badges
|
// Compatibility badges
|
||||||
|
@ -168,50 +168,44 @@ struct PerGameSettingsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
ZStack {
|
// Settings content
|
||||||
Color(UIColor.systemBackground)
|
ScrollView {
|
||||||
.ignoresSafeArea()
|
VStack(spacing: 24) {
|
||||||
|
switch selectedCategory {
|
||||||
VStack(spacing: 0) {
|
case .graphics:
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
graphicsSettings
|
||||||
HStack(spacing: 12) {
|
.padding(.top)
|
||||||
ForEach(PerSettingsCategory.allCases, id: \.id) { category in
|
case .system:
|
||||||
CategoryButton(
|
systemSettings
|
||||||
title: category.rawValue,
|
.padding(.top)
|
||||||
icon: category.icon,
|
case .advanced:
|
||||||
isSelected: selectedCategory == category
|
advancedSettings
|
||||||
) {
|
.padding(.top)
|
||||||
selectedCategory = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
// Settings content
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: 24) {
|
|
||||||
switch selectedCategory {
|
|
||||||
case .graphics:
|
|
||||||
graphicsSettings
|
|
||||||
.padding(.top)
|
|
||||||
case .system:
|
|
||||||
systemSettings
|
|
||||||
.padding(.top)
|
|
||||||
case .advanced:
|
|
||||||
advancedSettings
|
|
||||||
.padding(.top)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: 50)
|
|
||||||
}
|
|
||||||
.padding(.bottom)
|
|
||||||
}
|
}
|
||||||
.scrollDismissesKeyboardIfAvailable()
|
|
||||||
|
Spacer(minLength: 50)
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
.background(Color(uiColor: .systemGroupedBackground))
|
||||||
|
.scrollDismissesKeyboardIfAvailable()
|
||||||
|
.navigationItemBottomPalette {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
ForEach(PerSettingsCategory.allCases, id: \.id) { category in
|
||||||
|
CategoryButton(
|
||||||
|
title: category.rawValue,
|
||||||
|
icon: category.icon,
|
||||||
|
isSelected: selectedCategory == category
|
||||||
|
) {
|
||||||
|
selectedCategory = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.defaultScrollAnchorIsAvailable(.center)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
|
@ -469,58 +469,32 @@ struct SettingsViewNew: View {
|
|||||||
|
|
||||||
var iOSSettings: some View {
|
var iOSSettings: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
ZStack {
|
// Settings content
|
||||||
// Background color
|
ScrollView {
|
||||||
Color(UIColor.systemBackground)
|
VStack(spacing: 24) {
|
||||||
.ignoresSafeArea()
|
deviceInfoCard
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
// Category selector
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
ForEach(SettingsCategory.allCases, id: \.id) { category in
|
|
||||||
CategoryButton(
|
|
||||||
title: category.rawValue,
|
|
||||||
icon: category.icon,
|
|
||||||
isSelected: selectedCategory == category
|
|
||||||
) {
|
|
||||||
selectedCategory = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.vertical, 8)
|
.padding(.top)
|
||||||
|
|
||||||
|
switch selectedCategory {
|
||||||
|
case .graphics:
|
||||||
|
graphicsSettings
|
||||||
|
case .input:
|
||||||
|
inputSettings
|
||||||
|
case .system:
|
||||||
|
systemSettings
|
||||||
|
case .advanced:
|
||||||
|
advancedSettings
|
||||||
|
case .misc:
|
||||||
|
miscSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Spacer(minLength: 50)
|
||||||
|
|
||||||
// Settings content
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: 24) {
|
|
||||||
deviceInfoCard
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.top)
|
|
||||||
|
|
||||||
switch selectedCategory {
|
|
||||||
case .graphics:
|
|
||||||
graphicsSettings
|
|
||||||
case .input:
|
|
||||||
inputSettings
|
|
||||||
case .system:
|
|
||||||
systemSettings
|
|
||||||
case .advanced:
|
|
||||||
advancedSettings
|
|
||||||
case .misc:
|
|
||||||
miscSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: 50)
|
|
||||||
}
|
|
||||||
.padding(.bottom)
|
|
||||||
}
|
|
||||||
.scrollDismissesKeyboardIfAvailable()
|
|
||||||
}
|
}
|
||||||
|
.padding(.bottom)
|
||||||
}
|
}
|
||||||
|
.scrollDismissesKeyboardIfAvailable()
|
||||||
|
.background(Color(uiColor: .systemGroupedBackground))
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
// .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
|
// .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
|
||||||
@ -533,6 +507,25 @@ struct SettingsViewNew: View {
|
|||||||
settingsManager.saveSettings()
|
settingsManager.saveSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.navigationItemBottomPalette {
|
||||||
|
// Category selector
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
ForEach(SettingsCategory.allCases, id: \.id) { category in
|
||||||
|
CategoryButton(
|
||||||
|
title: category.rawValue,
|
||||||
|
icon: category.icon,
|
||||||
|
isSelected: selectedCategory == category
|
||||||
|
) {
|
||||||
|
selectedCategory = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.defaultScrollAnchorIsAvailable(.center)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1517,12 +1510,23 @@ struct CategoryButton: View {
|
|||||||
}
|
}
|
||||||
.foregroundColor(isSelected ? .blue : .secondary)
|
.foregroundColor(isSelected ? .blue : .secondary)
|
||||||
.frame(width: 70, height: 56)
|
.frame(width: 70, height: 56)
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(isSelected ? Color.blue.opacity(0.15) : Color.clear)
|
|
||||||
)
|
|
||||||
.animation(.bouncy(duration: 0.3), value: isSelected)
|
.animation(.bouncy(duration: 0.3), value: isSelected)
|
||||||
}
|
}
|
||||||
|
.apply {
|
||||||
|
if #available(iOS 26, *) {
|
||||||
|
if isSelected {
|
||||||
|
$0.glassEffect(.regular.tint(Color.blue.opacity(0.15)), in: RoundedRectangle(cornerRadius: 16))
|
||||||
|
} else {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$0.background(
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.fill(isSelected ? Color.blue.opacity(0.15) : Color.clear)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1561,7 +1565,7 @@ struct SettingsCard<Content: View>: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.fill(colorScheme == .dark ? Color(.systemGray6) : Color.white)
|
.fill(Color(.secondarySystemGroupedBackground))
|
||||||
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
|
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
|
||||||
)
|
)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
@ -1686,3 +1690,14 @@ extension View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this code is used to enable the keyboard to be dismissed when scrolling if available on iOS 16+
|
||||||
|
extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func defaultScrollAnchorIsAvailable(_ anchor: UnitPoint?) -> some View {
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
self.defaultScrollAnchor(anchor)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user