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
8 changes: 4 additions & 4 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
9A4F20EC31E1000100000001 /* ApplePCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4F20EB31E1000100000001 /* ApplePCCBridge.swift */; };
A17D0FF131E1000100000001 /* HandoffBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17D0FF031E1000100000001 /* HandoffBridge.swift */; };
A685F7A8DE8A8339A7CFE48A /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FADD48E0FE94429FDE1912 /* RunnerUITests.swift */; };
AC39B11031E1000100000001 /* NativeBottomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC39B11131E1000100000001 /* NativeBottomTabBar.swift */; };
AC39B11231E1000100000001 /* NativeGlassSurface.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC39B11331E1000100000001 /* NativeGlassSurface.swift */; };
B005C7BA5D2714CB149B4B95 /* MithkaBlue.icon in Resources */ = {isa = PBXBuildFile; fileRef = 0CC7151EAA772074B2F1F159 /* MithkaBlue.icon */; };
B45E6836D8D2D05AB2676E08 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5790DB6BBC8220D67CE86B /* NotificationService.swift */; };
BD06D3B4F53874C98C6D9618 /* tdjson.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF24ACBEB1466595B72E2DD /* tdjson.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -136,7 +136,7 @@
9AE1F80607B45379F5468F27 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
A17D0FF031E1000100000001 /* HandoffBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HandoffBridge.swift; path = ../../apple/HandoffBridge.swift; sourceTree = "<group>"; };
A257B729519AB240FBD5E29F /* MithkaPurple.icon */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.iconcomposer.icon; path = MithkaPurple.icon; sourceTree = "<group>"; };
AC39B11131E1000100000001 /* NativeBottomTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeBottomTabBar.swift; sourceTree = "<group>"; };
AC39B11331E1000100000001 /* NativeGlassSurface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeGlassSurface.swift; sourceTree = "<group>"; };
B02F5C04823E6F1B8B4DC318 /* NotificationCommunicationContent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationCommunicationContent.swift; sourceTree = "<group>"; };
C145FCC43A8CB4B5D827E2D6 /* MithkaPixel.icon */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.iconcomposer.icon; path = MithkaPixel.icon; sourceTree = "<group>"; };
D20D61E8E4A929586A940D51 /* NotificationService.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
Expand Down Expand Up @@ -273,7 +273,7 @@
9A4F20F131E1000100000001 /* NowPlayingBridge.swift */,
9A4F20E931E1000100000001 /* CommunicationNotificationBridge.swift */,
9A4F20EB31E1000100000001 /* ApplePCCBridge.swift */,
AC39B11131E1000100000001 /* NativeBottomTabBar.swift */,
AC39B11331E1000100000001 /* NativeGlassSurface.swift */,
A17D0FF031E1000100000001 /* HandoffBridge.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
Expand Down Expand Up @@ -700,7 +700,7 @@
9A4F20F231E1000100000001 /* NowPlayingBridge.swift in Sources */,
9A4F20EA31E1000100000001 /* CommunicationNotificationBridge.swift in Sources */,
9A4F20EC31E1000100000001 /* ApplePCCBridge.swift in Sources */,
AC39B11031E1000100000001 /* NativeBottomTabBar.swift in Sources */,
AC39B11231E1000100000001 /* NativeGlassSurface.swift in Sources */,
A17D0FF131E1000100000001 /* HandoffBridge.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ import UserNotifications
guard !didRegisterFlutterPlugins else { return }
didRegisterFlutterPlugins = true
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
if let registrar = engineBridge.pluginRegistry.registrar(forPlugin: "NativeBottomTabBar") {
registrar.register(NativeBottomTabBarFactory(registrar: registrar), withId: "mithka/native_bottom_bar")
if let registrar = engineBridge.pluginRegistry.registrar(forPlugin: "NativeGlassSurface") {
registrar.register(NativeGlassSurfaceFactory(registrar: registrar), withId: "mithka/native_glass")
}
let windowGeometryChannel = FlutterMethodChannel(
name: "mithka/window_geometry",
Expand Down
193 changes: 0 additions & 193 deletions ios/Runner/NativeBottomTabBar.swift

This file was deleted.

115 changes: 115 additions & 0 deletions ios/Runner/NativeGlassSurface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Flutter
import UIKit

/// A glass-surface-only platform view behind the app-owned tab row:
///
/// - On iOS 26+ the glass is the public `UIGlassEffect(style: .regular)` inside
/// a `UIVisualEffectView`, styled with `overrideUserInterfaceStyle` and
/// `tintColor` from the active theme.
/// - Before iOS 26 it uses a system material `UIBlurEffect`.
/// - The view draws no tabs, icons, or badges, and disables user interaction so
/// every tap lands on the Flutter tab controls above it.
@MainActor
final class NativeGlassSurfaceFactory: NSObject, FlutterPlatformViewFactory {
private let registrar: FlutterPluginRegistrar

init(registrar: FlutterPluginRegistrar) {
self.registrar = registrar
super.init()
}

func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
FlutterStandardMessageCodec.sharedInstance()
}

func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
NativeGlassSurface(frame: frame, id: viewId, registrar: registrar, arguments: args)
}
}

@MainActor
private final class NativeGlassSurface: NSObject, FlutterPlatformView {
private let host: GlassSurfaceHost
private let channel: FlutterMethodChannel

init(frame: CGRect, id: Int64, registrar: FlutterPluginRegistrar, arguments: Any?) {
host = GlassSurfaceHost(frame: frame)
channel = FlutterMethodChannel(
name: "mithka/native_glass/\(id)", binaryMessenger: registrar.messenger()
)
super.init()
if let configuration = arguments as? [String: Any] { host.apply(configuration) }
channel.setMethodCallHandler { [weak self] call, result in
guard let self, call.method == "update",
let configuration = call.arguments as? [String: Any]
else {
result(FlutterMethodNotImplemented)
return
}
self.host.apply(configuration)
result(nil)
}
}

func view() -> UIView { host }

deinit { channel.setMethodCallHandler(nil) }
}

@MainActor
private final class GlassSurfaceHost: UIView {
private let effectView = UIVisualEffectView(effect: nil)

override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
isOpaque = false
isUserInteractionEnabled = false
effectView.isUserInteractionEnabled = false
effectView.layer.masksToBounds = true
addSubview(effectView)
}

required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

func apply(_ configuration: [String: Any]) {
let dark = configuration["dark"] as? Bool == true
overrideUserInterfaceStyle = dark ? .dark : .light
effectView.overrideUserInterfaceStyle = dark ? .dark : .light
let cornerRadius = (configuration["radius"] as? NSNumber)?.doubleValue ?? 0
effectView.layer.cornerRadius = cornerRadius

if #available(iOS 26.0, *) {
var tint: UIColor?
if let argb = configuration["tint"] as? NSNumber {
let value = argb.uint32Value
tint = UIColor(
red: CGFloat((value >> 16) & 255) / 255,
green: CGFloat((value >> 8) & 255) / 255,
blue: CGFloat(value & 255) / 255,
alpha: CGFloat((value >> 24) & 255) / 255
)
}
if let current = effectView.effect as? UIGlassEffect {
if current.tintColor != tint { current.tintColor = tint }
} else {
let glass = UIGlassEffect(style: .regular)
glass.tintColor = tint
glass.isInteractive = false
effectView.effect = glass
}
} else {
let desiredStyle: UIBlurEffect.Style = dark ? .systemMaterialDark : .systemMaterialLight
effectView.effect = UIBlurEffect(style: desiredStyle)
}
}

override func layoutSubviews() {
super.layoutSubviews()
effectView.frame = bounds
}
}
Loading
Loading