Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .codex/environments/environment.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "MNGA"

[setup]
script = ""

[[actions]]
name = "Run"
icon = "run"
command = "make launch"

[[actions]]
name = "Build"
icon = "tool"
command = "make build"
1 change: 1 addition & 0 deletions app/Shared/Localization/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"Appearance" = "外观";
"Custom Appearance" = "自定义外观";
"Theme Color" = "主题色";
"Prefer High Refresh Rate" = "高刷新率优先";
"Hide Notification Shortcut" = "隐藏通知快捷入口";
"New Short Message" = "发表短消息";
"Send To" = "发送至";
Expand Down
6 changes: 3 additions & 3 deletions app/Shared/MNGAApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ import TipKit
struct MNGAApp: App {
@ObserveInjection var forceRedraw

#if os(iOS)
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#endif
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

@StateObject var prefs = PreferencesStorage()
@StateObject var networkMonitor = NetworkMonitor()

init() {
logger.info("MNGA Init")
logicInitialConfigure()
ProMotionDisplayLink.shared.setEnabled(prefs.forceProMotionDisplayLink)

#if DEBUG
if prefs.debugResetTips, (try? Tips.resetDatastore()) != nil {
Expand Down Expand Up @@ -50,6 +49,7 @@ struct MNGAApp: App {
ContentView()
.onChange(of: prefs.themeColor) { setupColor() }
.onChange(of: prefs.alwaysPortraitOnPhone) { AppInterfaceOrientation.applyCurrentPreference() }
.onChange(of: prefs.forceProMotionDisplayLink) { ProMotionDisplayLink.shared.setEnabled($1) }
.onAppear { setupColor() }
.onAppear { AppInterfaceOrientation.applyCurrentPreference() }
.environment(\.whatsNew, MNGAWhatsNew.environment)
Expand Down
1 change: 1 addition & 0 deletions app/Shared/Storage/PreferencesStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class PreferencesStorage: ObservableObject {
@AppStorage("useInsetGroupedModern") var useInsetGroupedModern = true
@AppStorage("hideMNGAMeta") var hideMNGAMeta = false
@AppStorage("showPlusInTitle") var showPlusInTitle = false
@AppStorage("forceProMotionDisplayLink") var forceProMotionDisplayLink = false
@AppStorage(alwaysPortraitOnPhonePreferenceKey) var alwaysPortraitOnPhone = false

@AppStorage("requestOption") var requestOptionWrapper = WrappedMessage(inner: RequestOption()) {
Expand Down
50 changes: 50 additions & 0 deletions app/Shared/Utilities/ProMotion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// ProMotion.swift
// MNGA
//
// Created by Bugen Zhao on 2026/5/9.
//

import Foundation
import QuartzCore
import UIKit

final class ProMotionDisplayLink: NSObject {
static let shared = ProMotionDisplayLink()

private var displayLink: CADisplayLink?

func setEnabled(_ enabled: Bool) {
if enabled {
start()
} else {
stop()
}
}

func start() {
guard displayLink == nil else { return }

let targetFPS = Float(min(UIScreen.main.maximumFramesPerSecond, 120))
let link = CADisplayLink(target: self, selector: #selector(tick))
link.preferredFrameRateRange = CAFrameRateRange(
minimum: targetFPS,
maximum: targetFPS,
preferred: targetFPS,
)
link.add(to: .main, forMode: .common)
displayLink = link
Comment thread
BugenZhao marked this conversation as resolved.

logger.info("started ProMotion display link at \(targetFPS) FPS")
}

func stop() {
guard let displayLink else { return }
displayLink.invalidate()
self.displayLink = nil

logger.info("stopped ProMotion display link")
}

@objc private func tick(_: CADisplayLink) {}
}
77 changes: 37 additions & 40 deletions app/Shared/Utilities/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,51 @@
//

import Foundation
import UIKit

#if canImport(UIKit)
import UIKit

final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
AppInterfaceOrientation.supportedOrientations
}
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
AppInterfaceOrientation.supportedOrientations
}
}

enum AppInterfaceOrientation {
static var supportedOrientations: UIInterfaceOrientationMask {
guard UIDevice.current.userInterfaceIdiom == .phone else {
return .allButUpsideDown
}
return UserDefaults.standard.bool(forKey: alwaysPortraitOnPhonePreferenceKey) ? .portrait : .allButUpsideDown
enum AppInterfaceOrientation {
static var supportedOrientations: UIInterfaceOrientationMask {
guard UIDevice.current.userInterfaceIdiom == .phone else {
return .allButUpsideDown
}
return UserDefaults.standard.bool(forKey: alwaysPortraitOnPhonePreferenceKey) ? .portrait : .allButUpsideDown
}

@MainActor
static func applyCurrentPreference() {
guard UIDevice.current.userInterfaceIdiom == .phone else { return }
@MainActor
static func applyCurrentPreference() {
guard UIDevice.current.userInterfaceIdiom == .phone else { return }

guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }) ?? UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first
else { return }
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }) ?? UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first
else { return }

windowScene.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
windowScene.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()

let geometryPreferences = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: supportedOrientations)
windowScene.requestGeometryUpdate(geometryPreferences) { error in
logger.warning("failed to update interface orientation: \(error.localizedDescription)")
}
let geometryPreferences = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: supportedOrientations)
windowScene.requestGeometryUpdate(geometryPreferences) { error in
logger.warning("failed to update interface orientation: \(error.localizedDescription)")
}
}

extension UIApplication {
static var myKeyWindow: UIWindow? {
// Get connected scenes
UIApplication.shared.connectedScenes
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap { $0 as? UIWindowScene }?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}

extension UIApplication {
static var myKeyWindow: UIWindow? {
// Get connected scenes
UIApplication.shared.connectedScenes
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap { $0 as? UIWindowScene }?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
#endif
}
7 changes: 7 additions & 0 deletions app/Shared/Views/PreferencesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ struct PreferencesInnerView: View {
Text("Modern").tag(true)
}.disableWithPlusCheck(.customAppearance)

if UIScreen.main.maximumFramesPerSecond > 60 {
Toggle(isOn: $pref.forceProMotionDisplayLink) {
Label("Prefer High Refresh Rate", systemImage: "bolt.fill")
}
.onChange(of: pref.forceProMotionDisplayLink) { ProMotionDisplayLink.shared.setEnabled($1) }
Comment thread
BugenZhao marked this conversation as resolved.
}

if UserInterfaceIdiom.current == .phone {
Toggle(isOn: $pref.alwaysPortraitOnPhone) {
Label("Lock Screen Rotation", systemImage: "iphone")
Expand Down
2 changes: 2 additions & 0 deletions app/iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
Expand Down
Loading