forked from MeloNX/MeloNX
Settings screen implemented
This commit is contained in:
parent
68ba8868cc
commit
b7a338f8e3
@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import melo.nxmodel.Ryujinx
|
import melo.nxmodel.Ryujinx
|
||||||
@ -73,11 +74,14 @@ open class MainActivity: SDLActivity {
|
|||||||
PresentationRootView(ComposeContext())
|
PresentationRootView(ComposeContext())
|
||||||
SideEffect { saveableStateHolder.removeState(true) }
|
SideEffect { saveableStateHolder.removeState(true) }
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
Text(
|
Text(
|
||||||
"FPS: ${fps?.value ?: 0 }"
|
"FPS: ${fps?.value ?: 0}",
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 24.sp
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (startGameConfig?.value != null) {
|
if (startGameConfig?.value != null) {
|
||||||
runSimulator(GameState.shared.startGameConfig!!)
|
runSimulator(GameState.shared.startGameConfig!!)
|
||||||
|
@ -15,7 +15,7 @@ public enum AspectRatio: String, Codable, CaseIterable {
|
|||||||
case fixed32x9 = "Fixed32x9"
|
case fixed32x9 = "Fixed32x9"
|
||||||
case stretched = "Stretched"
|
case stretched = "Stretched"
|
||||||
|
|
||||||
var displayName: String {
|
public var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .fixed4x3: return "4:3"
|
case .fixed4x3: return "4:3"
|
||||||
case .fixed16x9: return "16:9 (Default)"
|
case .fixed16x9: return "16:9 (Default)"
|
||||||
|
@ -27,7 +27,7 @@ public enum SystemLanguage: String, Codable, CaseIterable {
|
|||||||
case traditionalChinese = "TraditionalChinese"
|
case traditionalChinese = "TraditionalChinese"
|
||||||
case brazilianPortuguese = "BrazilianPortuguese"
|
case brazilianPortuguese = "BrazilianPortuguese"
|
||||||
|
|
||||||
var displayName: String {
|
public var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .japanese: return "Japanese"
|
case .japanese: return "Japanese"
|
||||||
case .americanEnglish: return "American English"
|
case .americanEnglish: return "American English"
|
||||||
|
@ -16,7 +16,7 @@ public enum SystemRegionCode: String, Codable, CaseIterable {
|
|||||||
case korea = "Korea"
|
case korea = "Korea"
|
||||||
case taiwan = "Taiwan"
|
case taiwan = "Taiwan"
|
||||||
|
|
||||||
var displayName: String {
|
public var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .japan: return "Japan"
|
case .japan: return "Japan"
|
||||||
case .usa: return "United States"
|
case .usa: return "United States"
|
||||||
|
@ -387,3 +387,47 @@ public extension Ryujinx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension Ryujinx.Configuration {
|
||||||
|
func saveSettings() {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
|
||||||
|
print("Saving Settings")
|
||||||
|
#else
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = .prettyPrinted
|
||||||
|
let data = try encoder.encode(self)
|
||||||
|
let jsonString = String(data: data, encoding: .utf8)
|
||||||
|
UserDefaults.standard.set(jsonString, forKey: "config")
|
||||||
|
} catch {
|
||||||
|
print("Failed to save settings: \(error)")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Ryujinx {
|
||||||
|
// Original loadSettings function assumed to exist
|
||||||
|
static func loadSettings() -> Ryujinx.Configuration {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
print("Running on Simulator")
|
||||||
|
|
||||||
|
return Ryujinx.Configuration(gamepath: "")
|
||||||
|
#else
|
||||||
|
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
||||||
|
let data = jsonString.data(using: .utf8)
|
||||||
|
else {
|
||||||
|
return Ryujinx.Configuration(gamepath: "")
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let configs = try decoder.decode(Ryujinx.Configuration.self, from: data)
|
||||||
|
return configs
|
||||||
|
} catch {
|
||||||
|
print("Failed to load settings: \(error)")
|
||||||
|
return Ryujinx.Configuration(gamepath: "")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,16 +18,10 @@ struct ContentView: View {
|
|||||||
@State var viewModel = ViewModel()
|
@State var viewModel = ViewModel()
|
||||||
@State var appearance = ""
|
@State var appearance = ""
|
||||||
|
|
||||||
@State private var config: Ryujinx.Configuration
|
@State private var config = Ryujinx.loadSettings()
|
||||||
@State private var game: Game?
|
@State private var game: Game?
|
||||||
@State private var isLoading = true
|
@State private var isLoading = true
|
||||||
|
|
||||||
init() {
|
|
||||||
// let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
|
|
||||||
let defaultConfig = Ryujinx.Configuration(gamepath: "")
|
|
||||||
_config = State(initialValue: defaultConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if game != nil {
|
if game != nil {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
@ -39,16 +33,14 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TabView(selection: $tab) {
|
TabView(selection: $tab) {
|
||||||
NavigationStack {
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
GamesView(startemu: $game)
|
GamesView(startemu: $game)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.tabItem { Label("Games", systemImage: "house.fill") }
|
.tabItem { Label("Games", systemImage: "house.fill") }
|
||||||
.tag(ContentTab.games)
|
.tag(ContentTab.games)
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
SettingsView(appearance: $appearance)
|
SettingsView(appearance: $appearance, config: $config)
|
||||||
.navigationTitle("Settings")
|
.navigationTitle("Settings")
|
||||||
}
|
}
|
||||||
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
|
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
|
||||||
|
@ -13,13 +13,6 @@ struct GamesView: View {
|
|||||||
@State var firmwareversion = "0"
|
@State var firmwareversion = "0"
|
||||||
@State var searchQuery: String = ""
|
@State var searchQuery: String = ""
|
||||||
|
|
||||||
// let allGames: [Game] = [
|
|
||||||
// .init(title: "\(DemoLib.instance.demo_number())", developer: "Pesesda"),
|
|
||||||
// .init(title: "\(Ryujinx.shared.testMessage)", developer: "Bethesda"),
|
|
||||||
// .init(title: "Skyrim", developer: "Bethesda"),
|
|
||||||
// .init(title: "Naruto", developer: "Anime"),
|
|
||||||
// .init(title: "Burnour", developer: "Paradise")
|
|
||||||
// ]
|
|
||||||
var allGames: Binding<[Game]> {
|
var allGames: Binding<[Game]> {
|
||||||
Binding(
|
Binding(
|
||||||
get: { Ryujinx.shared.games },
|
get: { Ryujinx.shared.games },
|
||||||
|
@ -5,17 +5,286 @@
|
|||||||
// Created by Даниил Виноградов on 02.03.2025.
|
// Created by Даниил Виноградов on 02.03.2025.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import MeloNXModel
|
import MeloNXModel
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
|
@AppStorage("oldWindowCode") private var windowCode: Bool = false
|
||||||
|
@AppStorage("portal") private var gamepo = false
|
||||||
|
|
||||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
@Environment(ViewModel.self) var viewModel: ViewModel
|
||||||
@Binding var appearance: String
|
@Binding var appearance: String
|
||||||
|
@Binding var config: Ryujinx.Configuration
|
||||||
|
|
||||||
|
@State private var showResolutionInfo = false
|
||||||
|
@State private var showAnisotropicInfo = false
|
||||||
|
@State private var showControllerInfo = false
|
||||||
|
@State private var searchQuery = ""
|
||||||
|
|
||||||
|
private var memoryManagerModes = [
|
||||||
|
("HostMapped", "Host (fast)"),
|
||||||
|
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
|
||||||
|
("SoftwarePageTable", "Software (slow)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
init(appearance: Binding<String>, config: Binding<Ryujinx.Configuration>) {
|
||||||
|
_appearance = appearance
|
||||||
|
_config = config
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@Bindable var viewModel = viewModel
|
@Bindable var viewModel = viewModel
|
||||||
Form {
|
List {
|
||||||
TextField("Name", text: $viewModel.name)
|
// Graphics & Performance
|
||||||
|
Section {
|
||||||
|
Picker(selection: $config.aspectRatio) {
|
||||||
|
ForEach(AspectRatio.allCases, id: \.self) { ratio in
|
||||||
|
Text(ratio.displayName).tag(ratio)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Aspect Ratio", iconName: "rectangle.expand.vertical")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disableShaderCache) {
|
||||||
|
labelWithIcon("Shader Cache", iconName: "memorychip")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablevsync) {
|
||||||
|
labelWithIcon("Disable VSync", iconName: "arrow.triangle.2.circlepath")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.enableTextureRecompression) {
|
||||||
|
labelWithIcon("Texture Recompression", iconName: "rectangle.compress.vertical")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disableDockedMode) {
|
||||||
|
labelWithIcon("Docked Mode", iconName: "dock.rectangle")
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.macroHLE) {
|
||||||
|
labelWithIcon("Macro HLE", iconName: "gearshape")
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
labelWithIcon("Resolution Scale", iconName: "magnifyingglass")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
showResolutionInfo = !showResolutionInfo
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
// .symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
// .help("Learn more about Resolution Scale")
|
||||||
|
.alert("Resolution Scale", isPresented: $showResolutionInfo, presenting: "Adjust the internal rendering resolution. Higher values improve visuals but may reduce performance.", actions: { _ in
|
||||||
|
Button(role: .cancel) {} label: {
|
||||||
|
Text("OK")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $config.resscale, in: 0.1 ... 3.0, step: 0.102) {
|
||||||
|
Text("Resolution Scale")
|
||||||
|
}
|
||||||
|
// minimumValueLabel: {
|
||||||
|
// Text("0.1x")
|
||||||
|
// .font(.footnote)
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// } maximumValueLabel: {
|
||||||
|
// Text("3.0x")
|
||||||
|
// .font(.footnote)
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// }
|
||||||
|
Text("\(config.resscale, specifier: "%.2f")x")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
// .padding(.vertical, 8)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
labelWithIcon("Max Anisotropic Scale", iconName: "magnifyingglass")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
showAnisotropicInfo = !showAnisotropicInfo
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
// .symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
// .help("Learn more about Max Anisotropic Scale")
|
||||||
|
.alert("Max Anisotripic Scale", isPresented: $showAnisotropicInfo, presenting: "Adjust the internal Anisotropic resolution. Higher values improve visuals but may reduce performance. Default at 0 lets game decide.", actions: { _ in
|
||||||
|
Button(role: .cancel) {} label: {
|
||||||
|
Text("OK")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $config.maxAnisotropy, in: 0.0 ... 16.0, step: 0.1) {
|
||||||
|
Text("Resolution Scale")
|
||||||
|
}
|
||||||
|
// minimumValueLabel: {
|
||||||
|
// Text("0x")
|
||||||
|
// .font(.footnote)
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// } maximumValueLabel: {
|
||||||
|
// Text("16.0x")
|
||||||
|
// .font(.footnote)
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// }
|
||||||
|
Text("\(config.maxAnisotropy, specifier: "%.2f")x")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
// .padding(.vertical, 8)
|
||||||
|
|
||||||
|
// Toggle(isOn: $performacehud) {
|
||||||
|
// labelWithIcon("Performance Overlay", iconName: "speedometer")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
} header: {
|
||||||
|
Text("Graphics & Performance")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
// .headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Fine-tune graphics and performance to suit your device and preferences.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language and Region Settings
|
||||||
|
Section {
|
||||||
|
Picker(selection: $config.language) {
|
||||||
|
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
|
||||||
|
Text(ratio.displayName).tag(ratio)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Language", iconName: "character.bubble")
|
||||||
|
}
|
||||||
|
|
||||||
|
Picker(selection: $config.regioncode) {
|
||||||
|
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
|
||||||
|
Text(ratio.displayName).tag(ratio)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Region", iconName: "globe")
|
||||||
|
}
|
||||||
|
|
||||||
|
// globe
|
||||||
|
} header: {
|
||||||
|
Text("Language and Region Settings")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
// .headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Configure the System Language and the Region.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU Mode
|
||||||
|
Section {
|
||||||
|
// if filteredMemoryModes.isEmpty {
|
||||||
|
// Text("No matches for \"\(searchText)\"")
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// } else {
|
||||||
|
Picker(selection: $config.memoryManagerMode) {
|
||||||
|
ForEach(memoryManagerModes, id: \.0) { key, displayName in
|
||||||
|
Text(displayName).tag(key)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Memory Manager Mode", iconName: "gearshape")
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablePTC) {
|
||||||
|
labelWithIcon("Disable PTC", iconName: "cpu")
|
||||||
|
}.tint(.blue)
|
||||||
|
} header: {
|
||||||
|
Text("CPU")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
// .headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Select how memory is managed. 'Host (fast)' is best for most users.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $config.expandRam) {
|
||||||
|
labelWithIcon("Expand Guest Ram (6GB)", iconName: "exclamationmark.bubble")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.ignoreMissingServices) {
|
||||||
|
labelWithIcon("Ignore Missing Services", iconName: "waveform.path")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
} header: {
|
||||||
|
Text("Hacks")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
// .headerProminence(.increased)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $windowCode) {
|
||||||
|
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
// DisclosureGroup {
|
||||||
|
// Toggle(isOn: $mVKPreFillBuffer) {
|
||||||
|
// labelWithIcon("MVK: Pre-Fill Metal Command Buffers", iconName: "gearshape")
|
||||||
|
// }.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.dfsIntegrityChecks) {
|
||||||
|
labelWithIcon("Disable FS Integrity Checks", iconName: "checkmark.shield")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
// HStack {
|
||||||
|
// labelWithIcon("Page Size", iconName: "textformat.size")
|
||||||
|
// Spacer()
|
||||||
|
// Text("\(String(Int(getpagesize())))")
|
||||||
|
// .foregroundColor(.secondary)
|
||||||
|
// }
|
||||||
|
|
||||||
|
TextField("Additional Arguments", text: Binding(
|
||||||
|
get: {
|
||||||
|
config.additionalArgs.joined(separator: " ")
|
||||||
|
},
|
||||||
|
set: { newValue in
|
||||||
|
config.additionalArgs = newValue
|
||||||
|
.split(separator: ",")
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.textInputAutocapitalization(nil)
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
// Ryujinx.shared.removeFirmware()
|
||||||
|
} label: {
|
||||||
|
Text("Remove Firmware")
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// } label: {
|
||||||
|
// Text("Advanced Options")
|
||||||
|
// }
|
||||||
|
} header: {
|
||||||
|
Text("Advanced")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
// .headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). \n \n\(gamepo ? "the cake is a lie" : "")")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
// TextField("Name", text: $viewModel.name)
|
||||||
Picker("Appearance", selection: $appearance) {
|
Picker("Appearance", selection: $appearance) {
|
||||||
Text("System").tag("")
|
Text("System").tag("")
|
||||||
Text("Light").tag("light")
|
Text("Light").tag("light")
|
||||||
@ -30,14 +299,33 @@ struct SettingsView : View {
|
|||||||
Text(verbatim: "💙")
|
Text(verbatim: "💙")
|
||||||
#endif
|
#endif
|
||||||
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
|
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
|
||||||
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
|
||||||
|
{
|
||||||
Text("Version \(version) (\(buildNumber))")
|
Text("Version \(version) (\(buildNumber))")
|
||||||
.foregroundStyle(.gray)
|
.foregroundStyle(.gray)
|
||||||
}
|
}
|
||||||
Text("Powered by [Skip](https://skip.tools)")
|
Text("Powered by [Skip](https://skip.tools)")
|
||||||
}
|
}
|
||||||
.foregroundStyle(.gray)
|
.foregroundStyle(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: config) { _, newState in
|
||||||
|
newState.saveSettings()
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.large)
|
||||||
|
// .searchable(text: $searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
@ViewBuilder
|
||||||
|
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
if !iconName.isEmpty {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
// .symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.blue)
|
||||||
|
}
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
.font(.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user