Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iOS): Paint safe areas on iOS mobile #928

Merged
merged 12 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation project(':capacitor-keyboard')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-splash-screen')
implementation project(':capacitor-status-bar')
}


Expand Down
3 changes: 3 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ project(':capacitor-screen-orientation').projectDir = new File('../node_modules/

include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')

include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
4 changes: 4 additions & 0 deletions ios/App/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
56524E4F2CE8119000C237C2 /* PluginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56524E4E2CE8119000C237C2 /* PluginViewController.swift */; };
567DF60B2D0784C1003BA7AC /* PluginSafeAreasColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DF60A2D0784C1003BA7AC /* PluginSafeAreasColor.swift */; };
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
/* End PBXBuildFile section */

Expand All @@ -29,6 +30,7 @@
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
56524E4E2CE8119000C237C2 /* PluginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginViewController.swift; sourceTree = "<group>"; };
567DF60A2D0784C1003BA7AC /* PluginSafeAreasColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PluginSafeAreasColor.swift; path = App/PluginSafeAreasColor.swift; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -57,6 +59,7 @@
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
567DF60A2D0784C1003BA7AC /* PluginSafeAreasColor.swift */,
56524E4E2CE8119000C237C2 /* PluginViewController.swift */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
Expand Down Expand Up @@ -213,6 +216,7 @@
buildActionMask = 2147483647;
files = (
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
567DF60B2D0784C1003BA7AC /* PluginSafeAreasColor.swift in Sources */,
56524E4F2CE8119000C237C2 /* PluginViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
72 changes: 72 additions & 0 deletions ios/App/App/PluginSafeAreasColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Capacitor
import UIKit

@objc(SafeAreasColorPlugin)
public class SafeAreasColorPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "SafeAreasColorPlugin"
public let jsName = "SafeAreasColor"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "changeSafeAreasColorOniOS", returnType: CAPPluginReturnPromise)
]

@objc func changeSafeAreasColorOniOS(_ call: CAPPluginCall) {
let value = call.getString("color") ?? ""
guard let uiColor = UIColor(hex: value) else {
call.reject("Invalid color format")
return
}

DispatchQueue.main.async {
if #available(iOS 16.0, *) {
// Find the active window in iOS 13 or later
if let window = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.flatMap({ $0.windows })
.first(where: { $0.isKeyWindow }) {

window.backgroundColor = uiColor // Set the background color
call.resolve(["value": "Color set successfully"])
} else {
call.reject("No active window found")
}
} else {
call.reject("Earlier versions than iOS 16 are not supported")
}
}

call.resolve(["value": value])
}
}

extension UIColor {
convenience init?(hex: String) {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

if hexSanitized.hasPrefix("#") {
hexSanitized.remove(at: hexSanitized.startIndex)
}

var rgb: UInt64 = 0
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }

let length = hexSanitized.count
switch length {
case 6: // RGB
self.init(
red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgb & 0x0000FF) / 255.0,
alpha: 1.0
)
case 8: // RGBA
self.init(
red: CGFloat((rgb & 0xFF000000) >> 24) / 255.0,
green: CGFloat((rgb & 0x00FF0000) >> 16) / 255.0,
blue: CGFloat((rgb & 0x0000FF00) >> 8) / 255.0,
alpha: CGFloat(rgb & 0x000000FF) / 255.0
)
default:
return nil
}
}
}
14 changes: 8 additions & 6 deletions ios/App/PluginViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import UIKit
import Capacitor

class PluginViewController: CAPBridgeViewController {
override open func capacitorDidLoad() {
bridge?.registerPluginInstance(SafeAreasColorPlugin())
}

override open func viewDidLoad() {
super.viewDidLoad()

Expand Down Expand Up @@ -35,17 +39,15 @@ class PluginViewController: CAPBridgeViewController {
var leftPadding: CGFloat = 0
var rightPadding: CGFloat = 0

if #available(iOS 13.0, *) {
if #available(iOS 16.0, *) {
let window = view.window ?? UIApplication.shared.windows.first { $0.isKeyWindow }
topPadding = window?.safeAreaInsets.top ?? 0
bottomPadding = window?.safeAreaInsets.bottom ?? 0
leftPadding = window?.safeAreaInsets.left ?? 0
rightPadding = window?.safeAreaInsets.right ?? 0
} else {
topPadding = UIApplication.shared.statusBarFrame.height
}

webView.frame.origin = CGPoint(x: leftPadding, y: topPadding)
webView.frame.size = CGSize(width: UIScreen.main.bounds.width - leftPadding - rightPadding, height: UIScreen.main.bounds.height - topPadding - bottomPadding)
webView.frame.origin = CGPoint(x: leftPadding, y: topPadding)
webView.frame.size = CGSize(width: UIScreen.main.bounds.width - leftPadding - rightPadding, height: UIScreen.main.bounds.height - topPadding - bottomPadding)
}
}
}
1 change: 1 addition & 0 deletions ios/App/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def capacitor_pods
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorScreenOrientation', :path => '../../node_modules/@capacitor/screen-orientation'
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
end

target 'App' do
Expand Down
12 changes: 9 additions & 3 deletions ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ PODS:
- Capacitor
- CapacitorScreenOrientation (6.0.3):
- Capacitor
- CapacitorSplashScreen (6.0.2):
- CapacitorSplashScreen (6.0.3):
- Capacitor
- CapacitorStatusBar (6.0.2):
- Capacitor

DEPENDENCIES:
Expand All @@ -27,6 +29,7 @@ DEPENDENCIES:
- "CapacitorKeyboard (from `../../node_modules/@capacitor/keyboard`)"
- "CapacitorScreenOrientation (from `../../node_modules/@capacitor/screen-orientation`)"
- "CapacitorSplashScreen (from `../../node_modules/@capacitor/splash-screen`)"
- "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)"

EXTERNAL SOURCES:
Capacitor:
Expand All @@ -47,6 +50,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/screen-orientation"
CapacitorSplashScreen:
:path: "../../node_modules/@capacitor/splash-screen"
CapacitorStatusBar:
:path: "../../node_modules/@capacitor/status-bar"

SPEC CHECKSUMS:
Capacitor: 1f3c7b9802d958cd8c4eb63895fff85dff2e1eea
Expand All @@ -57,8 +62,9 @@ SPEC CHECKSUMS:
CapacitorFilesystem: c832a3f6d4870c3872688e782ae8e33665e6ecbf
CapacitorKeyboard: 460c6f9ec5e52c84f2742d5ce2e67bbc7ab0ebb0
CapacitorScreenOrientation: 3bb823f5d265190301cdc5d58a568a287d98972a
CapacitorSplashScreen: 250df9ef8014fac5c7c1fd231f0f8b1d8f0b5624
CapacitorSplashScreen: 68893659d77b5f82d753b3a70475082845e3039c
CapacitorStatusBar: 3b9ac7d0684770522c532d1158a1434512ab1477

PODFILE CHECKSUM: 0bfaa008b5f31bb57606a8c6259197a6af507ba4
PODFILE CHECKSUM: 97c46b79f9ec807c302bf24e1511e3e277306740

COCOAPODS: 1.16.2
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@capacitor/keyboard": "^6.0.3",
"@capacitor/screen-orientation": "^6.0.3",
"@capacitor/splash-screen": "^6.0.2",
"@capacitor/status-bar": "^6.0.2",
"@dicebear/collection": "^9.0.1",
"@dicebear/core": "^9.0.1",
"@dicebear/identicon": "^9.0.1",
Expand Down
11 changes: 11 additions & 0 deletions src/lib/plugins/safeAreaColorAndroid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { log } from "$lib/utils/Logger"
import { registerPlugin } from "@capacitor/core"
import { StatusBar, Style } from "@capacitor/status-bar"

interface ISafeAreaColorPlugin {
setStatusBarColor(options: { color: string }): Promise<{ value: string }>
Expand All @@ -11,6 +12,16 @@ const SafeAreaColorPlugin = registerPlugin<ISafeAreaColorPlugin>("SafeAreaColorP
async function setStatusBarColor(color: string) {
try {
log.info("Calling native android function to change status bar color")
if (color.toLowerCase() === "white") {
color = "#FFFFFF"
log.info(`Converted color "white" to hexadecimal: ${color}`)
await StatusBar.setStyle({ style: Style.Light })
log.debug("Change status bar style to light")
} else {
await StatusBar.setStyle({ style: Style.Dark })
log.debug("Change status bar style to dark")
}

await SafeAreaColorPlugin.setStatusBarColor({ color })
} catch (error) {
log.error("Error setting status bar color:", error)
Expand Down
34 changes: 34 additions & 0 deletions src/lib/plugins/safeAreaColoriOS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { log } from "$lib/utils/Logger"
import { registerPlugin } from "@capacitor/core"
import { StatusBar, Style } from "@capacitor/status-bar"

interface ISafeAreasColorPlugin {
changeSafeAreasColorOniOS(options: { color: string }): Promise<{ value: string }>
}

const Echo = registerPlugin<ISafeAreasColorPlugin>("SafeAreasColor")

async function setNewSafeAreasColorOniOS(color: string) {
try {
log.info("Calling native iOS function to change status bar color")

// Check if the color is 'white' and convert to hex
if (color.toLowerCase() === "white") {
color = "#FFFFFF"
log.info(`Converted color "white" to hexadecimal: ${color}`)
await StatusBar.setStyle({ style: Style.Light })
log.debug("Change status bar style to light")
} else {
await StatusBar.setStyle({ style: Style.Dark })
log.debug("Change status bar style to dark")
}

await Echo.changeSafeAreasColorOniOS({ color })
} catch (error) {
log.error("Error trying to call swift function:", error)
}
}

export function changeSafeAreaColorsOniOS(color: string) {
setNewSafeAreasColorOniOS(color)
}
8 changes: 8 additions & 0 deletions src/lib/utils/Mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ export function isAndroid(): boolean {
}
return platform === "android"
}

export function isiOSMobile(): boolean {
if (platform === null) {
log.warn("Platform info not yet loaded. Assuming 'false'.")
return false
}
return platform === "ios"
}
12 changes: 8 additions & 4 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
import Market from "$lib/components/market/Market.svelte"
import { swipe } from "$lib/components/ui/Swipe"
import { ScreenOrientation } from "@capacitor/screen-orientation"
import { fetchDeviceInfo, isAndroid, isAndroidOriOS } from "$lib/utils/Mobile"
import { fetchDeviceInfo, isAndroid, isAndroidOriOS, isiOSMobile } from "$lib/utils/Mobile"
import { changeSafeAreaColorsOnAndroid } from "$lib/plugins/safeAreaColorAndroid"
import { changeSafeAreaColorsOniOS } from "$lib/plugins/safeAreaColoriOS"

log.debug("Initializing app, layout routes page.")

Expand Down Expand Up @@ -230,11 +231,14 @@

function changeSafeAreaColors() {
setTimeout(() => {
const rootStyles = getComputedStyle(document.documentElement)
let mainBgColor = rootStyles.getPropertyValue("--background").trim()
if (isAndroid()) {
const rootStyles = getComputedStyle(document.documentElement)
let mainBgColor = rootStyles.getPropertyValue("--background").trim()
changeSafeAreaColorsOnAndroid(mainBgColor)
}
if (isiOSMobile()) {
changeSafeAreaColorsOniOS(mainBgColor)
}
}, 1000)
}

Expand Down Expand Up @@ -289,7 +293,7 @@

onMount(async () => {
await fetchDeviceInfo()
if (await isAndroidOriOS()) {
if (isAndroidOriOS()) {
lockOrientation()
}

Expand Down
Loading