Skip to content

Commit

Permalink
load app settings and add quickapp switch feature
Browse files Browse the repository at this point in the history
  • Loading branch information
lukesthl committed Jan 14, 2024
1 parent 9251397 commit 17446f2
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 41 deletions.
3 changes: 2 additions & 1 deletion apps/expo/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ function RootLayoutNav() {
console.log("App has come to the foreground!");
} else if (appState.current.match(/active/) && nextAppState === "background") {
clearShortcutListener();
void ShortCutPayload.clear();
// void ShortCutPayload.clear();
console.log("this would clear the shortcut listener");
console.log("App has come to the background!");
}
appState.current = nextAppState;
Expand Down
14 changes: 9 additions & 5 deletions apps/expo/components/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export const Container = ({
paddingTop={insets.top}
paddingBottom={tabBarHeight ?? insets.bottom}
flex={1}
minWidth={600}
maxWidth={600}
marginHorizontal="auto"
$gtSm={{
minWidth: 600,
marginHorizontal: "auto",
maxWidth: 600,
}}
{...(viewProps as React.ComponentProps<typeof ScrollView>)}
>
<View flex={1} paddingBottom={tabBarHeight ?? insets.bottom}>
Expand All @@ -35,8 +37,10 @@ export const Container = ({
paddingTop={insets.top}
paddingBottom={tabBarHeight ?? insets.bottom}
flex={1}
marginHorizontal="auto"
maxWidth={600}
$gtSm={{
marginHorizontal: "auto",
maxWidth: 600,
}}
{...viewProps}
>
<View flex={1} paddingBottom={tabBarHeight ?? insets.bottom}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
207 changes: 172 additions & 35 deletions apps/expo/targets/intents/DigitalBreak.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import AppIntents
import CommonCrypto
import Foundation
import UIKit

enum MyError: Error {
case runtimeError(String)
struct AppInfo: Decodable {
struct ItemSettings: Decodable {
let breakDurationSeconds: Int
let quickAppSwitchDurationMinutes: Int
let dailyTimeSpentMinutes: Int
}

let name: String
let active: Bool
let iconKey: String
let id: String
let settings: ItemSettings
let key: String
}
struct Manifest: Decodable {
let apps: String
}

struct AppIntentPayload: Codable {
var openedApp: String?
}

typealias AppInfoList = [String: AppInfo]

struct DigitalBreak: AppIntent {
static var title: LocalizedStringResource = "Check Digital Break"

Expand All @@ -21,10 +44,8 @@ struct DigitalBreak: AppIntent {
Summary("Digital Break when \(\.$appPrompt) opens")
}

@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
func getStorageDirAndFile(fileName: String) -> (URL, URL) {
let RCTStorageDirectory = "RCTAsyncLocalStorage_V1"
let RCTManifestFileName = "appintent.json"

let fileManager = FileManager.default

Expand All @@ -37,48 +58,164 @@ struct DigitalBreak: AppIntent {

let storageDirectory = mySupportDirectory.appendingPathComponent(RCTStorageDirectory)

let storageFile = storageDirectory.appendingPathComponent(RCTManifestFileName)
let storageFile = storageDirectory.appendingPathComponent(fileName)
return (storageDirectory, storageFile)
}

var dict = [] as? [String: String]
do {
func MD5(string: String) -> String {
let digestLength = Int(CC_MD5_DIGEST_LENGTH)
let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLength)

let stringFromFile = try String(contentsOf: storageFile, encoding: .utf8)
let strBytes = string.cString(using: .utf8)
let strLen = CC_LONG(string.lengthOfBytes(using: .utf8))

CC_MD5(strBytes, strLen, result)

var hash = ""
for i in 0..<digestLength {
hash += String(format: "%02x", result[i])
}

let data = stringFromFile.data(using: .utf8, allowLossyConversion: false)
result.deallocate()

let json = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json)
dict = json as? [String: String]
return hash
}

func getAppIntentPayload() -> AppIntentPayload? {
let RCTStorageDirectory = "RCTAsyncLocalStorage_V1"
let fileManager = FileManager.default

guard
let appSupportDirectory = fileManager.urls(
for: .applicationSupportDirectory, in: .userDomainMask
).first
else {
print("Couldn't find application support directory.")
return nil
}

let mySupportDirectory = appSupportDirectory.appendingPathComponent(
Bundle.main.bundleIdentifier!)
let storageDirectory = mySupportDirectory.appendingPathComponent(RCTStorageDirectory)

let appIntentFile = storageDirectory.appendingPathComponent("appintent.json")
do {
let data = try Data(contentsOf: appIntentFile)
let decoder = JSONDecoder()
let appIntentPayload = try decoder.decode(AppIntentPayload.self, from: data)
return appIntentPayload
} catch {
print("Failed to read manifest, creating new..")
print("Error: \(error)")
}
return nil
}

func fetchAppItem(with name: String) -> AppInfo? {
let RCTStorageDirectory = "RCTAsyncLocalStorage_V1"
let fileManager = FileManager.default

guard
let appSupportDirectory = fileManager.urls(
for: .applicationSupportDirectory, in: .userDomainMask
).first
else {
print("Couldn't find application support directory.")
return nil
}
if dict == nil {
dict = [:]

let mySupportDirectory = appSupportDirectory.appendingPathComponent(
Bundle.main.bundleIdentifier!)
let storageDirectory = mySupportDirectory.appendingPathComponent(RCTStorageDirectory)

let manifestFile = storageDirectory.appendingPathComponent("manifest.json")
let appsFile = storageDirectory.appendingPathComponent(MD5(string: "apps"))
let decoder = JSONDecoder()

do {
if let data = try? Data(contentsOf: manifestFile),
let manifest = try? decoder.decode(Manifest.self, from: data),
let appsData = manifest.apps.data(using: .utf8),
let apps = try? decoder.decode([AppInfo].self, from: appsData)
{
return apps.first(where: { $0.name == name })
}

if let data = try? Data(contentsOf: appsFile),
let apps = try? decoder.decode([AppInfo].self, from: data)
{
return apps.first(where: { $0.name == name })
}
} catch {
print("Error while fetching apps: \(error)")
}
let redirectToApp = true
if dict!["openedApp"] != nil && dict!["openedApp"]!.contains("_app-reopen") {
dict!["openedApp"] = nil
} else {
let app = appPrompt
dict!["openedApp"] = "\(app!)_\(Date().timeIntervalSince1970)_break-start"

return nil
}

@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
var appIntentPayload = getAppIntentPayload()
var appInfo = fetchAppItem(with: appPrompt!)
var canOpenApp = true
do {
if appIntentPayload?.openedApp != nil && appInfo != nil {
canOpenApp = appInfo!.active
if canOpenApp,
let payload = appIntentPayload,
let openedApp = payload.openedApp,
!openedApp.contains("_app-reopen")
{
let lastOpen = appIntentPayload?.openedApp?.split(separator: "_")
let lastOpenTime = Double(lastOpen![1])!
let quickAppSwitchDurationAsTimestamp =
Double(appInfo!.settings.quickAppSwitchDurationMinutes * 60)
let timeSinceLastOpen = Date().timeIntervalSince1970 - lastOpenTime
let timeSinceLastOpenMinutes = timeSinceLastOpen / 60
print("timeSinceLastOpen", timeSinceLastOpenMinutes)
canOpenApp =
Double(appInfo?.settings.quickAppSwitchDurationMinutes ?? 0)
< Double(
timeSinceLastOpenMinutes)
}
}
} catch {
print("Error while fetching apps: \(error)")
}
if dict != nil {
do {
let stringified = asString(dict: dict!)
if !fileManager.fileExists(atPath: storageDirectory.path) {
try fileManager.createDirectory(
atPath: storageDirectory.path, withIntermediateDirectories: true, attributes: nil)
if canOpenApp {
if let payload = appIntentPayload,
let openedApp = payload.openedApp,
openedApp.contains("_app-reopen")
{
appIntentPayload?.openedApp = nil
print("dont open app because it was just reopened")
} else {
let app = appPrompt
appIntentPayload = AppIntentPayload(
openedApp: "\(app!)_\(Date().timeIntervalSince1970)_break-start")
print("open app")
}
if appIntentPayload != nil {
let (storageDirectory, storageFile) = getStorageDirAndFile(fileName: "appintent.json")
let fileManager = FileManager.default
do {
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(appIntentPayload)
if !fileManager.fileExists(atPath: storageDirectory.path) {
try fileManager.createDirectory(
atPath: storageDirectory.path, withIntermediateDirectories: true, attributes: nil)
}
let stringified = String(data: jsonData, encoding: .utf8)!
try stringified.write(to: storageFile, atomically: true, encoding: .utf8)
} catch {
print(error)
}
try stringified.write(to: storageFile, atomically: true, encoding: .utf8)
let input = try String(contentsOf: storageFile)
print(input)
} catch {
print(error)
}
} else {
print("dont open app because settings disallow it")
}
print("Should open app: \(dict!["openedApp"] != nil)")

return .result(
value: dict!["openedApp"] != nil)
value: canOpenApp && appIntentPayload?.openedApp != nil)
}
func asString(dict: [String: String]) -> String {
do {
Expand Down

0 comments on commit 17446f2

Please sign in to comment.