Skip and docs
76
pedi-foods/.gitignore
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
## User settings
|
||||
|
||||
# vi
|
||||
.*.swp
|
||||
.*.swo
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# gradle properties
|
||||
local.properties
|
||||
.gradle/
|
||||
.android/
|
||||
.kotlin/
|
||||
Android/app/keystore.jks
|
||||
Android/app/keystore.properties
|
||||
|
||||
xcodebuild*.log
|
||||
|
||||
default.profraw
|
||||
*.mobileprovision
|
||||
*.cer
|
||||
*.p12
|
||||
*.p12.password
|
||||
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
.swiftpm
|
||||
.build/
|
||||
build/
|
||||
DerivedData/
|
||||
xcuserdata/
|
||||
xcodebuild*.log
|
||||
.idea/
|
||||
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
Packages/
|
||||
Package.pins
|
||||
Package.resolved
|
||||
#*.xcodeproj
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# fastlane
|
||||
|
||||
**/fastlane/apikey.json
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/README.md
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/test_output
|
||||
91
pedi-foods/Android/app/build.gradle.kts
Normal file
@@ -0,0 +1,91 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.android.application)
|
||||
id("skip-build-plugin")
|
||||
}
|
||||
|
||||
skip {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(libs.versions.jvm.get().toString())
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = group as String
|
||||
compileSdk = libs.versions.android.sdk.compile.get().toInt()
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
|
||||
targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
|
||||
}
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols.add("**/*.so")
|
||||
pickFirsts.add("**/*.so")
|
||||
// this option will compress JNI .so files
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.sdk.min.get().toInt()
|
||||
targetSdk = libs.versions.android.sdk.compile.get().toInt()
|
||||
// skip.tools.skip-build-plugin will automatically use Skip.env properties for:
|
||||
// applicationId = ANDROID_APPLICATION_ID ?? PRODUCT_BUNDLE_IDENTIFIER
|
||||
// versionCode = CURRENT_PROJECT_VERSION
|
||||
// versionName = MARKETING_VERSION
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
lint {
|
||||
disable.add("Instantiatable")
|
||||
disable.add("MissingPermission")
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
// default signing configuration tries to load from keystore.properties
|
||||
// see: https://skip.tools/docs/deployment/#export-signing
|
||||
signingConfigs {
|
||||
val keystorePropertiesFile = file("keystore.properties")
|
||||
create("release") {
|
||||
if (keystorePropertiesFile.isFile) {
|
||||
val keystoreProperties = Properties()
|
||||
keystoreProperties.load(keystorePropertiesFile.inputStream())
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
} else {
|
||||
// when there is no keystore.properties file, fall back to signing with debug config
|
||||
keyAlias = signingConfigs.getByName("debug").keyAlias
|
||||
keyPassword = signingConfigs.getByName("debug").keyPassword
|
||||
storeFile = signingConfigs.getByName("debug").storeFile
|
||||
storePassword = signingConfigs.getByName("debug").storePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
isDebuggable = false // can be set to true for debugging release build, but needs to be false when uploading to store
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
10
pedi-foods/Android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
-keeppackagenames **
|
||||
-keep class skip.** { *; }
|
||||
-keep class tools.skip.** { *; }
|
||||
-keep class kotlin.jvm.functions.** {*;}
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn java.awt.**
|
||||
-keep class * implements com.sun.jna.** { *; }
|
||||
-keep class * implements skip.bridge.** { *; }
|
||||
-keep class **._ModuleBundleAccessor_* { *; }
|
||||
-keep class pedi.foods.** { *; }
|
||||
30
pedi-foods/Android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This AndroidManifest.xml template was generated by Skip -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- example permissions for using device location -->
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> -->
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> -->
|
||||
|
||||
<!-- permissions needed for using the internet or an embedded WebKit browser -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
|
||||
<application
|
||||
android:label="${PRODUCT_NAME}"
|
||||
android:name=".AndroidAppMain"
|
||||
android:supportsRtl="true"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
167
pedi-foods/Android/app/src/main/kotlin/Main.kt
Normal file
@@ -0,0 +1,167 @@
|
||||
package pedi.foods
|
||||
|
||||
import skip.lib.*
|
||||
import skip.model.*
|
||||
import skip.foundation.*
|
||||
import skip.ui.*
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.graphics.Color as AndroidColor
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.core.app.ActivityCompat
|
||||
|
||||
internal val logger: SkipLogger = SkipLogger(subsystem = "pedi.foods", category = "PediFoods")
|
||||
|
||||
private typealias AppRootView = PediFoodsRootView
|
||||
private typealias AppDelegate = PediFoodsAppDelegate
|
||||
|
||||
/// AndroidAppMain is the `android.app.Application` entry point, and must match `application android:name` in the AndroidMainfest.xml file.
|
||||
open class AndroidAppMain: Application {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
logger.info("starting app")
|
||||
ProcessInfo.launch(applicationContext)
|
||||
AppDelegate.shared.onInit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
/// AndroidAppMain is initial `androidx.appcompat.app.AppCompatActivity`, and must match `activity android:name` in the AndroidMainfest.xml file.
|
||||
open class MainActivity: AppCompatActivity {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
logger.info("starting activity")
|
||||
UIApplication.launch(this)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
saveableStateHolder.SaveableStateProvider(true) {
|
||||
PresentationRootView(ComposeContext())
|
||||
SideEffect { saveableStateHolder.removeState(true) }
|
||||
}
|
||||
}
|
||||
|
||||
AppDelegate.shared.onLaunch()
|
||||
|
||||
// Example of requesting permissions on startup.
|
||||
// These must match the permissions in the AndroidManifest.xml file.
|
||||
//let permissions = listOf(
|
||||
// Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
// Manifest.permission.ACCESS_FINE_LOCATION
|
||||
// Manifest.permission.CAMERA,
|
||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
//)
|
||||
//let requestTag = 1
|
||||
//ActivityCompat.requestPermissions(self, permissions.toTypedArray(), requestTag)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
logger.info("onStart")
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
AppDelegate.shared.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
AppDelegate.shared.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
AppDelegate.shared.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
AppDelegate.shared.onLowMemory()
|
||||
}
|
||||
|
||||
override fun onRestart() {
|
||||
logger.info("onRestart")
|
||||
super.onRestart()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: android.os.Bundle): Unit = super.onSaveInstanceState(outState)
|
||||
|
||||
override fun onRestoreInstanceState(bundle: android.os.Bundle) {
|
||||
// Usually you restore your state in onCreate(). It is possible to restore it in onRestoreInstanceState() as well, but not very common. (onRestoreInstanceState() is called after onStart(), whereas onCreate() is called before onStart().
|
||||
logger.info("onRestoreInstanceState")
|
||||
super.onRestoreInstanceState(bundle)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: kotlin.Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
logger.info("onRequestPermissionsResult: ${requestCode}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SyncSystemBarsWithTheme() {
|
||||
val dark = MaterialTheme.colorScheme.background.luminance() < 0.5f
|
||||
|
||||
val transparent = AndroidColor.TRANSPARENT
|
||||
val style = if (dark) {
|
||||
SystemBarStyle.dark(transparent)
|
||||
} else {
|
||||
SystemBarStyle.light(transparent, transparent)
|
||||
}
|
||||
|
||||
val activity = LocalContext.current as? ComponentActivity
|
||||
DisposableEffect(style) {
|
||||
activity?.enableEdgeToEdge(
|
||||
statusBarStyle = style,
|
||||
navigationBarStyle = style
|
||||
)
|
||||
onDispose { }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PresentationRootView(context: ComposeContext) {
|
||||
val colorScheme = if (isSystemInDarkTheme()) ColorScheme.dark else ColorScheme.light
|
||||
PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
|
||||
SyncSystemBarsWithTheme()
|
||||
val contentContext = ctx.content()
|
||||
Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
AppRootView().Compose(context = contentContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
BIN
pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 565 B |
|
After Width: | Height: | Size: 565 B |
BIN
pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
11
pedi-foods/Android/fastlane/Appfile
Normal file
@@ -0,0 +1,11 @@
|
||||
# This file contains the app distribution configuration
|
||||
# for the Android half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
# Load the shared Skip.env properties with the app info
|
||||
require('dotenv')
|
||||
Dotenv.load('../../Skip.env')
|
||||
package_name(ENV['PRODUCT_BUNDLE_IDENTIFIER'].sub("-", "_"))
|
||||
|
||||
# Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
json_key_file("fastlane/apikey.json")
|
||||
51
pedi-foods/Android/fastlane/Fastfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# for the Android half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
# Load the shared Skip.env properties with the app info
|
||||
require('dotenv')
|
||||
Dotenv.load '../../Skip.env'
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
# use the Homebrew gradle rather than expecting a local gradlew
|
||||
gradle_bin = (ENV['HOMEBREW_PREFIX'] ? ENV['HOMEBREW_PREFIX'] : "/opt/homebrew") + "/bin/gradle"
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
desc "Build Skip Android App"
|
||||
lane :build do |options|
|
||||
build_config = (options[:release] ? "Release" : "Debug")
|
||||
gradle(
|
||||
task: "build${build_config}",
|
||||
gradle_path: gradle_bin,
|
||||
flags: "--warning-mode none -x lint"
|
||||
)
|
||||
end
|
||||
|
||||
desc "Test Skip Android App"
|
||||
lane :test do
|
||||
gradle(
|
||||
task: "test",
|
||||
gradle_path: gradle_bin
|
||||
)
|
||||
end
|
||||
|
||||
desc "Assemble Skip Android App"
|
||||
lane :assemble do
|
||||
gradle(
|
||||
gradle_path: gradle_bin,
|
||||
task: "bundleRelease"
|
||||
)
|
||||
# sh "your_script.sh"
|
||||
end
|
||||
|
||||
desc "Deploy Skip Android App to Google Play"
|
||||
lane :release do
|
||||
|
||||
assemble
|
||||
|
||||
upload_to_play_store(
|
||||
aab: '../.build/Android/app/outputs/bundle/release/app-release.aab'
|
||||
)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
@@ -0,0 +1 @@
|
||||
PediFoods
|
||||
3
pedi-foods/Android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4g
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
1
pedi-foods/Android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
25
pedi-foods/Android/settings.gradle.kts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This gradle project is part of a conventional Skip app project.
|
||||
pluginManagement {
|
||||
// Initialize the Skip plugin folder and perform a pre-build for non-Xcode builds
|
||||
val pluginPath = File.createTempFile("skip-plugin-path", ".tmp")
|
||||
|
||||
// overriding outputs for an Android IDE can be done by un-commenting and setting the Xcode path:
|
||||
//System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-HASH/Build/Products/Debug-iphonesimulator")
|
||||
|
||||
val skipPluginResult = providers.exec {
|
||||
commandLine("/bin/sh", "-c", "skip plugin --prebuild --package-path '${settings.rootDir.parent}' --plugin-ref '${pluginPath.absolutePath}'")
|
||||
environment("PATH", "${System.getenv("PATH")}:/opt/homebrew/bin")
|
||||
}
|
||||
val skipPluginOutput = skipPluginResult.standardOutput.asText.get()
|
||||
print(skipPluginOutput)
|
||||
val skipPluginError = skipPluginResult.standardError.asText.get()
|
||||
print(skipPluginError)
|
||||
|
||||
includeBuild(pluginPath.readText()) {
|
||||
name = "skip-plugins"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("skip-plugin") apply true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 897 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-83.5@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon~ios-marketing.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
pedi-foods/Darwin/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
pedi-foods/Darwin/Entitlements.plist
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
8
pedi-foods/Darwin/Info.plist
Normal file
@@ -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>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
58
pedi-foods/Darwin/PediFoods.xcconfig
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "../Skip.env"
|
||||
|
||||
// Set the action that will be executed as part of the Xcode Run Script phase
|
||||
// Setting to "launch" will build and run the app in the first open Android emulator or device
|
||||
// Setting to "build" will just run gradle build, but will not launch the app
|
||||
// Setting to "none" will completely disable the build and launch of the Android app
|
||||
SKIP_ACTION = launch
|
||||
//SKIP_ACTION = build
|
||||
//SKIP_ACTION = none
|
||||
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
||||
|
||||
INFOPLIST_FILE = Info.plist
|
||||
GENERATE_INFOPLIST_FILE = YES
|
||||
|
||||
// The user-visible name of the app (localizable)
|
||||
//INFOPLIST_KEY_CFBundleDisplayName = App Name
|
||||
//INFOPLIST_KEY_LSApplicationCategoryType = public.app-category.utilities
|
||||
//INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This app uses your location to …"
|
||||
|
||||
// iOS-specific Info.plist property keys
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0
|
||||
SUPPORTS_MACCATALYST = NO
|
||||
|
||||
// iPhone + iPad
|
||||
TARGETED_DEVICE_FAMILY = 1,2
|
||||
|
||||
// iPhone only
|
||||
// TARGETED_DEVICE_FAMILY = 1
|
||||
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
|
||||
// the name of the product module; this can be anything, but cannot conflict with any Swift module names
|
||||
PRODUCT_MODULE_NAME = $(PRODUCT_NAME:c99extidentifier)App
|
||||
|
||||
// On-device testing may need to override the bundle ID
|
||||
// PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=iphoneos*] = cool.beans.BundleIdentifer
|
||||
|
||||
SDKROOT = auto
|
||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
|
||||
SWIFT_VERSION = 6
|
||||
|
||||
// Development team ID for on-device testing
|
||||
CODE_SIGNING_REQUIRED = NO
|
||||
CODE_SIGN_STYLE = Automatic
|
||||
CODE_SIGN_ENTITLEMENTS = Entitlements.plist
|
||||
//CODE_SIGNING_IDENTITY = -
|
||||
//DEVELOPMENT_TEAM =
|
||||
289
pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,289 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; };
|
||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
|
||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
|
||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4900101C2BACEA710000DE33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
493609562A6B7EAE00C401E2 /* PediFoods */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = PediFoods; path = ..; sourceTree = "<group>"; };
|
||||
496BDBEB2B89A47800C09264 /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/PediFoods/Resources/Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = PediFoods.xcconfig; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
499AB9082B0581F4005E8330 /* plugins */ = {isa = PBXFileReference; lastKnownFileType = folder; name = plugins; path = ../../Intermediates.noindex/BuildToolPluginIntermediates; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
|
||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
499CD43C2AC5B799001AE8D8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
496BDBEC2B89A47800C09264 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
496BDBEB2B89A47800C09264 /* PediFoods.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49AB54462B066A7E007B79B2 /* SkipStone */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
499AB9082B0581F4005E8330 /* plugins */,
|
||||
);
|
||||
name = SkipStone;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49F90C1F2A52156200F06D93 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
496EB72F2A6AE4DE00C1253C /* README.md */,
|
||||
496EB72F2A6AE4DE00C1253A /* Skip.env */,
|
||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */,
|
||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
|
||||
493609562A6B7EAE00C401E2 /* PediFoods */,
|
||||
49F90C2A2A52156200F06D93 /* App */,
|
||||
49AB54462B066A7E007B79B2 /* SkipStone */,
|
||||
496BDBEC2B89A47800C09264 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49F90C2A2A52156200F06D93 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49F90C2B2A52156200F06D93 /* Main.swift */,
|
||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */,
|
||||
49F90C312A52156300F06D93 /* Entitlements.plist */,
|
||||
4900101C2BACEA710000DE33 /* Info.plist */,
|
||||
);
|
||||
name = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
499CD4382AC5B799001AE8D8 /* PediFoods App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */;
|
||||
buildPhases = (
|
||||
499CD43A2AC5B799001AE8D8 /* Sources */,
|
||||
499CD43C2AC5B799001AE8D8 /* Frameworks */,
|
||||
499CD43E2AC5B799001AE8D8 /* Resources */,
|
||||
499CD4452AC5B869001AE8D8 /* Run skip gradle */,
|
||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "PediFoods App";
|
||||
packageProductDependencies = (
|
||||
491F27812DA55B72004926EE /* PediFoods */,
|
||||
);
|
||||
productName = App;
|
||||
productReference = 496BDBEB2B89A47800C09264 /* PediFoods.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
49F90C202A52156200F06D93 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1430;
|
||||
LastUpgradeCheck = 1630;
|
||||
};
|
||||
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
es,
|
||||
ja,
|
||||
"zh-Hans",
|
||||
);
|
||||
mainGroup = 49F90C1F2A52156200F06D93;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 496BDBEC2B89A47800C09264 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
499CD4382AC5B799001AE8D8 /* PediFoods App */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
499CD43E2AC5B799001AE8D8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
|
||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
499CD4452AC5B869001AE8D8 /* Run skip gradle */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run skip gradle";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/sh -e";
|
||||
shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" = \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" = \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelif [ \"${SKIP_ACTION}\" = \"none\" ]; then\n echo \"note: skipping skip due to SKIP_ACTION none\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Release:${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
499CD43A2AC5B799001AE8D8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
499CD4422AC5B799001AE8D8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
||||
buildSettings = {
|
||||
ENABLE_PREVIEWS = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
499CD4432AC5B799001AE8D8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
||||
buildSettings = {
|
||||
ENABLE_PREVIEWS = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
49F90C4B2A52156300F06D93 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
49F90C4C2A52156300F06D93 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
499CD4422AC5B799001AE8D8 /* Debug */,
|
||||
499CD4432AC5B799001AE8D8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
49F90C4B2A52156300F06D93 /* Debug */,
|
||||
49F90C4C2A52156300F06D93 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
491F27812DA55B72004926EE /* PediFoods */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PediFoods;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 49F90C202A52156200F06D93 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1630"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
86
pedi-foods/Darwin/Sources/Main.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
import SwiftUI
|
||||
import PediFoods
|
||||
|
||||
private typealias AppRootView = PediFoodsRootView
|
||||
private typealias AppDelegate = PediFoodsAppDelegate
|
||||
|
||||
/// The entry point to the app simply loads the App implementation from SPM module.
|
||||
@main struct AppMain: App {
|
||||
@AppDelegateAdaptor(AppMainDelegate.self) var appDelegate
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
AppRootView()
|
||||
}
|
||||
.onChange(of: scenePhase) { oldPhase, newPhase in
|
||||
switch newPhase {
|
||||
case .active:
|
||||
AppDelegate.shared.onResume()
|
||||
case .inactive:
|
||||
AppDelegate.shared.onPause()
|
||||
case .background:
|
||||
AppDelegate.shared.onStop()
|
||||
@unknown default:
|
||||
print("unknown app phase: \(newPhase)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
|
||||
typealias AppMainDelegateBase = UIApplicationDelegate
|
||||
typealias AppType = UIApplication
|
||||
#elseif canImport(AppKit)
|
||||
typealias AppDelegateAdaptor = NSApplicationDelegateAdaptor
|
||||
typealias AppMainDelegateBase = NSApplicationDelegate
|
||||
typealias AppType = NSApplication
|
||||
#endif
|
||||
|
||||
@MainActor final class AppMainDelegate: NSObject, AppMainDelegateBase {
|
||||
let application = AppType.shared
|
||||
|
||||
#if canImport(UIKit)
|
||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
AppDelegate.shared.onInit()
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
AppDelegate.shared.onLaunch()
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
|
||||
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
|
||||
AppDelegate.shared.onLowMemory()
|
||||
}
|
||||
|
||||
// support for SkipNotify.fetchNotificationToken()
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken"), object: application, userInfo: ["deviceToken": deviceToken])
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError"), object: application, userInfo: ["error": error])
|
||||
}
|
||||
#elseif canImport(AppKit)
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
AppDelegate.shared.onInit()
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
AppDelegate.shared.onLaunch()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: Notification) {
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
5
pedi-foods/Darwin/fastlane/AppStore.xcconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
// Additional properties included by the Fastfile build_app
|
||||
|
||||
// This file can be used to override various properties from Skip.env
|
||||
//PRODUCT_BUNDLE_IDENTIFIER =
|
||||
//DEVELOPMENT_TEAM =
|
||||
8
pedi-foods/Darwin/fastlane/Appfile
Normal file
@@ -0,0 +1,8 @@
|
||||
# For more information about the Appfile, see:
|
||||
# https://docs.fastlane.tools/advanced/#appfile
|
||||
|
||||
require('dotenv')
|
||||
Dotenv.load '../../Skip.env'
|
||||
app_identifier(ENV['ANDROID_APPLICATION_ID'] || ENV['PRODUCT_BUNDLE_IDENTIFIER'])
|
||||
|
||||
# apple_id("my@email")
|
||||
27
pedi-foods/Darwin/fastlane/Deliverfile
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
copyright "#{Time.now.year}"
|
||||
|
||||
force(true) # Skip HTML report verification
|
||||
automatic_release(true)
|
||||
skip_screenshots(false)
|
||||
precheck_include_in_app_purchases(false)
|
||||
|
||||
#skip_binary_upload(true)
|
||||
submit_for_review(true)
|
||||
|
||||
submission_information({
|
||||
add_id_info_serves_ads: false,
|
||||
add_id_info_uses_idfa: false,
|
||||
add_id_info_tracks_install: false,
|
||||
add_id_info_tracks_action: false,
|
||||
add_id_info_limits_tracking: false,
|
||||
content_rights_has_rights: false,
|
||||
content_rights_contains_third_party_content: false,
|
||||
export_compliance_contains_third_party_cryptography: false,
|
||||
export_compliance_encryption_updated: false,
|
||||
export_compliance_platform: 'ios',
|
||||
export_compliance_compliance_required: false,
|
||||
export_compliance_uses_encryption: false,
|
||||
export_compliance_is_exempt: false,
|
||||
export_compliance_contains_proprietary_cryptography: false
|
||||
})
|
||||
40
pedi-foods/Darwin/fastlane/Fastfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# for the iOS half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
lane :assemble do |options|
|
||||
# only build the iOS side of the app
|
||||
ENV["SKIP_ZERO"] = "true"
|
||||
build_app(
|
||||
scheme: "PediFoods App",
|
||||
sdk: "iphoneos",
|
||||
xcconfig: "fastlane/AppStore.xcconfig",
|
||||
xcargs: "-skipPackagePluginValidation -skipMacroValidation",
|
||||
derived_data_path: "../.build/Darwin/DerivedData",
|
||||
output_directory: "../.build/fastlane/Darwin",
|
||||
skip_archive: ENV["FASTLANE_SKIP_ARCHIVE"] == "YES",
|
||||
skip_codesigning: ENV["FASTLANE_SKIP_CODESIGNING"] == "YES"
|
||||
)
|
||||
end
|
||||
|
||||
lane :release do |options|
|
||||
desc "Build and release app"
|
||||
|
||||
# see https://docs.fastlane.tools/uploading-app-privacy-details/
|
||||
#upload_app_privacy_details_to_app_store(json_path: "fastlane/app_privacy_details.json")
|
||||
|
||||
# if you have an apikey.json file (https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api), fastlane can automatically fetch certificates and the ASC authentication information
|
||||
#get_certificates(api_key_path: "fastlane/apikey.json")
|
||||
get_provisioning_profile(api_key_path: "fastlane/apikey.json")
|
||||
|
||||
assemble
|
||||
|
||||
upload_to_app_store(
|
||||
api_key_path: "fastlane/apikey.json",
|
||||
app_rating_config_path: "fastlane/metadata/rating.json",
|
||||
release_notes: { default: "Fixes and improvements." }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"data_protections": [
|
||||
"DATA_NOT_COLLECTED"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/keywords.txt
Normal file
@@ -0,0 +1 @@
|
||||
app,key,words
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/privacy/
|
||||
@@ -0,0 +1 @@
|
||||
Bug fixes and performance improvements.
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/app/
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/subtitle.txt
Normal file
@@ -0,0 +1 @@
|
||||
A new Skip app
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/support/
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
PediFoods
|
||||
@@ -0,0 +1 @@
|
||||
New features and better performance.
|
||||
18
pedi-foods/Darwin/fastlane/metadata/rating.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingSimulated": "NONE",
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"koreaAgeRatingOverride": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE",
|
||||
"gambling": false,
|
||||
"lootBox": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
21
pedi-foods/Package.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
// swift-tools-version: 6.1
|
||||
// This is a Skip (https://skip.tools) package.
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "pedi-foods",
|
||||
defaultLocalization: "en",
|
||||
platforms: [.iOS(.v17), .macOS(.v14)],
|
||||
products: [
|
||||
.library(name: "PediFoods", type: .dynamic, targets: ["PediFoods"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://source.skip.tools/skip.git", from: "1.7.0"),
|
||||
.package(url: "https://source.skip.tools/skip-fuse-ui.git", from: "1.0.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "PediFoods", dependencies: [
|
||||
.product(name: "SkipFuseUI", package: "skip-fuse-ui")
|
||||
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
|
||||
]
|
||||
)
|
||||
7
pedi-foods/Project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Darwin/PediFoods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
60
pedi-foods/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# PediFoods
|
||||
|
||||
This is a [Skip](https://skip.tools) dual-platform app project.
|
||||
|
||||
|
||||
<!-- TODO: add iOS screenshots to fastlane metadata
|
||||
## iPhone Screenshots
|
||||
|
||||
<img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/1_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/2_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/3_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/4_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/5_en-US.png" style="width: 18%" />
|
||||
-->
|
||||
|
||||
<!-- TODO: add Android screenshots to fastlane metadata
|
||||
## Android Screenshots
|
||||
|
||||
<img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png" style="width: 18%" />
|
||||
-->
|
||||
|
||||
## Building
|
||||
|
||||
This project is both a stand-alone Swift Package Manager module,
|
||||
as well as an Xcode project that builds and translates the project
|
||||
into a Kotlin Gradle project for Android using the skipstone plugin.
|
||||
|
||||
Building the module requires that Skip be installed using
|
||||
[Homebrew](https://brew.sh) with `brew install skiptools/skip/skip`.
|
||||
|
||||
This will also install the necessary Skip prerequisites:
|
||||
Kotlin, Gradle, and the Android build tools.
|
||||
|
||||
Installation prerequisites can be confirmed by running
|
||||
`skip checkup`. The project can be validated with `skip verify`.
|
||||
|
||||
## Running
|
||||
|
||||
Xcode and Android Studio must be downloaded and installed in order to
|
||||
run the app in the iOS simulator / Android emulator.
|
||||
An Android emulator must already be running, which can be launched from
|
||||
Android Studio's Device Manager.
|
||||
|
||||
The project can be opened and run in Xcode from
|
||||
`Project.xcworkspace`, which also enabled parallel
|
||||
development of any Skip libary dependencies.
|
||||
|
||||
To run both the Swift and Kotlin apps simultaneously,
|
||||
launch the "PediFoods App" target from Xcode.
|
||||
A build phases runs the "Launch Android APK" script that
|
||||
will deploy the Skip app to a running Android emulator or connected device.
|
||||
Logging output for the iOS app can be viewed in the Xcode console, and in
|
||||
Android Studio's logcat tab for the transpiled Kotlin app, or
|
||||
using `adb logcat` from a terminal.
|
||||
|
||||
## Testing
|
||||
|
||||
The module can be tested using the standard `swift test` command
|
||||
or by running the test target for the macOS destination in Xcode,
|
||||
which will run the Swift tests as well as the transpiled
|
||||
Kotlin JUnit tests in the Robolectric Android simulation environment.
|
||||
|
||||
Parity testing can be performed with `skip test`,
|
||||
which will output a table of the test results for both platforms.
|
||||
23
pedi-foods/Skip.env
Normal file
@@ -0,0 +1,23 @@
|
||||
// The configuration file for your Skip App (https://skip.tools).
|
||||
// Properties specified here are shared between
|
||||
// Darwin/PediFoods.xcconfig and Android/settings.gradle.kts
|
||||
// and will be included in the app's metadata files
|
||||
// Info.plist and AndroidManifest.xml
|
||||
|
||||
// PRODUCT_NAME is the default title of the app, which must match the app's Swift module name
|
||||
PRODUCT_NAME = PediFoods
|
||||
|
||||
// PRODUCT_BUNDLE_IDENTIFIER is the unique id for both the iOS and Android app
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app
|
||||
|
||||
// The semantic version of the app
|
||||
MARKETING_VERSION = 0.0.1
|
||||
|
||||
// The build number specifying the internal app version
|
||||
CURRENT_PROJECT_VERSION = 1
|
||||
|
||||
// The package name for the Android entry point, referenced by the AndroidManifest.xml
|
||||
ANDROID_PACKAGE_NAME = pedi.foods
|
||||
|
||||
// If your Android appId is different from the iOS Bundle Identifer, specify it here
|
||||
// ANDROID_APPLICATION_ID = com.br.pedifoods.app
|
||||
177
pedi-foods/Sources/PediFoods/ContentView.swift
Normal file
@@ -0,0 +1,177 @@
|
||||
import SwiftUI
|
||||
|
||||
enum ContentTab: String, Hashable {
|
||||
case welcome, home, settings
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@AppStorage("tab") var tab = ContentTab.welcome
|
||||
@AppStorage("name") var welcomeName = "Skipper"
|
||||
@AppStorage("appearance") var appearance = ""
|
||||
@State var viewModel = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $tab) {
|
||||
NavigationStack {
|
||||
WelcomeView(welcomeName: $welcomeName)
|
||||
}
|
||||
.tabItem { Label("Welcome", systemImage: "heart.fill") }
|
||||
.tag(ContentTab.welcome)
|
||||
|
||||
NavigationStack {
|
||||
ItemListView()
|
||||
.navigationTitle(Text("\(viewModel.items.count) Items"))
|
||||
}
|
||||
.tabItem { Label("Home", systemImage: "house.fill") }
|
||||
.tag(ContentTab.home)
|
||||
|
||||
NavigationStack {
|
||||
SettingsView(appearance: $appearance, welcomeName: $welcomeName)
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
|
||||
.tag(ContentTab.settings)
|
||||
}
|
||||
.environment(viewModel)
|
||||
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeView : View {
|
||||
@State var heartBeating = false
|
||||
@Binding var welcomeName: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("Hello [\(welcomeName)](https://skip.tools)!")
|
||||
.padding()
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundStyle(.red)
|
||||
.scaleEffect(heartBeating ? 1.5 : 1.0)
|
||||
.animation(.easeInOut(duration: 1).repeatForever(), value: heartBeating)
|
||||
.task { heartBeating = true }
|
||||
}
|
||||
.font(.largeTitle)
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemListView : View {
|
||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.items) { item in
|
||||
NavigationLink(value: item) {
|
||||
Label {
|
||||
Text(item.itemTitle)
|
||||
} icon: {
|
||||
if item.favorite {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundStyle(.yellow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDelete { offsets in
|
||||
viewModel.items.remove(atOffsets: offsets)
|
||||
}
|
||||
.onMove { fromOffsets, toOffset in
|
||||
viewModel.items.move(fromOffsets: fromOffsets, toOffset: toOffset)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Item.self) { item in
|
||||
ItemView(item: item)
|
||||
.navigationTitle(item.itemTitle)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItemGroup {
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.items.insert(Item(), at: 0)
|
||||
}
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemView : View {
|
||||
@State var item: Item
|
||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Title", text: $item.title)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Toggle("Favorite", isOn: $item.favorite)
|
||||
DatePicker("Date", selection: $item.date)
|
||||
Text("Notes").font(.title3)
|
||||
TextEditor(text: $item.notes)
|
||||
.border(Color.secondary, width: 1.0)
|
||||
}
|
||||
.navigationBarBackButtonHidden()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
viewModel.save(item: item)
|
||||
dismiss()
|
||||
}
|
||||
.disabled(!viewModel.isUpdated(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView : View {
|
||||
@Binding var appearance: String
|
||||
@Binding var welcomeName: String
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Name", text: $welcomeName)
|
||||
Picker("Appearance", selection: $appearance) {
|
||||
Text("System").tag("")
|
||||
Text("Light").tag("light")
|
||||
Text("Dark").tag("dark")
|
||||
}
|
||||
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
|
||||
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
||||
Text("Version \(version) (\(buildNumber))")
|
||||
}
|
||||
HStack {
|
||||
PlatformHeartView()
|
||||
Text("Powered by [Skip](https://skip.tools)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view that shows a blue heart on iOS and a green heart on Android.
|
||||
struct PlatformHeartView : View {
|
||||
var body: some View {
|
||||
#if os(Android)
|
||||
ComposeView {
|
||||
HeartComposer()
|
||||
}
|
||||
#else
|
||||
Text(verbatim: "💙")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if SKIP
|
||||
/// Use a ContentComposer to integrate Compose content. This code will be transpiled to Kotlin.
|
||||
struct HeartComposer : ContentComposer {
|
||||
@Composable func Compose(context: ComposeContext) {
|
||||
androidx.compose.material3.Text("💚", modifier: context.modifier)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
59
pedi-foods/Sources/PediFoods/PediFoodsApp.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import SkipFuse
|
||||
import SwiftUI
|
||||
|
||||
/// A logger for the PediFoods module.
|
||||
let logger: Logger = Logger(subsystem: "com.br.pedifoods.app", category: "PediFoods")
|
||||
|
||||
/// The shared top-level view for the app, loaded from the platform-specific App delegates below.
|
||||
///
|
||||
/// The default implementation merely loads the `ContentView` for the app and logs a message.
|
||||
/* SKIP @bridge */public struct PediFoodsRootView : View {
|
||||
/* SKIP @bridge */public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ContentView()
|
||||
.task {
|
||||
logger.info("Skip app logs are viewable in the Xcode console for iOS; Android logs can be viewed in Studio or using adb logcat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global application delegate functions.
|
||||
///
|
||||
/// These functions can update a shared observable object to communicate app state changes to interested views.
|
||||
/* SKIP @bridge */public final class PediFoodsAppDelegate : Sendable {
|
||||
/* SKIP @bridge */public static let shared = PediFoodsAppDelegate()
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onInit() {
|
||||
logger.debug("onInit")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onLaunch() {
|
||||
logger.debug("onLaunch")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onResume() {
|
||||
logger.debug("onResume")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onPause() {
|
||||
logger.debug("onPause")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onStop() {
|
||||
logger.debug("onStop")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onDestroy() {
|
||||
logger.debug("onDestroy")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onLowMemory() {
|
||||
logger.debug("onLowMemory")
|
||||
}
|
||||
}
|
||||
563
pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings
Normal file
@@ -0,0 +1,563 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"%lld Items" : {
|
||||
"comment" : "Header title for a list that contains the number of items",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld elementos"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld éléments"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "アイテム数 %lld"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld 个条目"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Add" : {
|
||||
"comment" : "Button in items list that will cause a new item to be added",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Añadir"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ajouter"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "追加"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "添加"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Appearance" : {
|
||||
"comment" : "Settings select label for the interface style of the controls (light, dark, or default)",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Apariencia"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Apparence"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "外観"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "外观"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cancel" : {
|
||||
"comment" : "Button title indicating that the operation should be cancelled",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cancelar"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Annuler"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "キャンセル"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dark" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in dark mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Oscuro"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sombre"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ダーク"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "深色"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Date" : {
|
||||
"comment" : "Item editor form label for the Date field",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fecha"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Date"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "日付"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "日期"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Favorite" : {
|
||||
"comment" : "Item editor title label for marking the item as a favorite",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Favorito"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Favori"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "お気に入り"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "收藏"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hello [%@](https://skip.tools)!" : {
|
||||
"comment" : "Welcome tab contents",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "¡Hola [%@](https://skip.tools)!"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bonjour [%@](https://skip.tools)!"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "こんにちは [%@](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "你好 [%@](https://skip.tools)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Home" : {
|
||||
"comment" : "Tab bar item title for the Home tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inicio"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accueil"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ホーム"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "首页"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Light" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in light mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Claro"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Clair"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "明るい"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "浅色"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Name" : {
|
||||
"comment" : "Placeholder title for the Name field in a form",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nombre"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nom"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "名前"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "姓名"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Notes" : {
|
||||
"comment" : "Item editor form label for the Notes field",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notas"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notes"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ノート"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔记"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Powered by [Skip](https://skip.tools)" : {
|
||||
"comment" : "Link markdown text for the Powered by… label",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Impulsado por [Skip](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fonctionnalités offertes par [Skip](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "[Skip](https://skip.tools) を使って動かしています"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "由[Skip](https://skip.tools)提供支持"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Save" : {
|
||||
"comment" : "Button title indicating that the current contents should be saved",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Guardar"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Enregistrer"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "保存"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "保存"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Settings" : {
|
||||
"comment" : "Tab bar item title for the Settings tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Configuración"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Paramètres"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "設定"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "设置"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"System" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in the default system mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sistema"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Système"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "システム"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "系统"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Title" : {
|
||||
"comment" : "Label for the item editor form indicating the title of the item",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Título"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Titre"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "タイトル"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "标题"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Version %@ (%@)" : {
|
||||
"comment" : "Settings label showing the current version of the app",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Version %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Versión %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Version %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "バージョン %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "版本 %1$@ (%2$@)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Welcome" : {
|
||||
"comment" : "Tab bar item title for the Welcome tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bienvenido"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bienvenue"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ようこそ"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "欢迎"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
pedi-foods/Sources/PediFoods/Skip/skip.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Configuration file for https://skip.tools project
|
||||
#
|
||||
# Kotlin dependencies and Gradle build options for this module can be configured here
|
||||
#build:
|
||||
# contents:
|
||||
# - block: 'dependencies'
|
||||
# contents:
|
||||
# - 'implementation("androidx.compose.runtime:runtime")'
|
||||
|
||||
# this is a natively-compiled Skip Fuse module
|
||||
skip:
|
||||
mode: 'native'
|
||||
93
pedi-foods/Sources/PediFoods/ViewModel.swift
Normal file
@@ -0,0 +1,93 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import SkipFuse
|
||||
|
||||
/// The Observable ViewModel used by the application.
|
||||
@Observable public class ViewModel {
|
||||
var items: [Item] = loadItems() {
|
||||
didSet { saveItems() }
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func clear() {
|
||||
items.removeAll()
|
||||
}
|
||||
|
||||
func isUpdated(_ item: Item) -> Bool {
|
||||
item != items.first { i in
|
||||
i.id == item.id
|
||||
}
|
||||
}
|
||||
|
||||
func save(item: Item) {
|
||||
items = items.map { i in
|
||||
i.id == item.id ? item : i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An individual item held by the ViewModel
|
||||
struct Item : Identifiable, Hashable, Codable {
|
||||
let id: UUID
|
||||
var date: Date
|
||||
var favorite: Bool
|
||||
var title: String
|
||||
var notes: String
|
||||
|
||||
init(id: UUID = UUID(), date: Date = .now, favorite: Bool = false, title: String = "", notes: String = "") {
|
||||
self.id = id
|
||||
self.date = date
|
||||
self.favorite = favorite
|
||||
self.title = title
|
||||
self.notes = notes
|
||||
}
|
||||
|
||||
var itemTitle: String {
|
||||
!title.isEmpty ? title : dateString
|
||||
}
|
||||
|
||||
var dateString: String {
|
||||
date.formatted(date: .complete, time: .omitted)
|
||||
}
|
||||
|
||||
var dateTimeString: String {
|
||||
date.formatted(date: .abbreviated, time: .shortened)
|
||||
}
|
||||
}
|
||||
|
||||
/// Utilities for defaulting and persising the items in the list
|
||||
extension ViewModel {
|
||||
private static let savePath = URL.applicationSupportDirectory.appendingPathComponent("appdata.json")
|
||||
|
||||
fileprivate static func loadItems() -> [Item] {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try Data(contentsOf: savePath)
|
||||
defer {
|
||||
let end = Date.now
|
||||
logger.info("loaded \(data.count) bytes from \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
}
|
||||
return try JSONDecoder().decode([Item].self, from: data)
|
||||
} catch {
|
||||
// perhaps the first launch, or the data could not be read
|
||||
logger.warning("failed to load data from \(Self.savePath), using defaultItems: \(error)")
|
||||
let defaultItems = (1...365).map { Date(timeIntervalSinceNow: Double($0 * 60 * 60 * 24 * -1)) }
|
||||
return defaultItems.map({ Item(date: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func saveItems() {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try JSONEncoder().encode(items)
|
||||
try FileManager.default.createDirectory(at: URL.applicationSupportDirectory, withIntermediateDirectories: true)
|
||||
try data.write(to: Self.savePath)
|
||||
let end = Date.now
|
||||
logger.info("saved \(data.count) bytes to \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
} catch {
|
||||
logger.error("error saving data: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||