Convert SatsPrice to a Skip multiplatform app
64
Android/app/build.gradle.kts
Normal file
@@ -0,0 +1,64 @@
|
||||
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 {
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = libs.versions.jvm.get().toString()
|
||||
}
|
||||
packaging {
|
||||
jniLibs.keepDebugSymbols.add("**/*.so")
|
||||
}
|
||||
|
||||
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 = PRODUCT_BUNDLE_IDENTIFIER
|
||||
// versionCode = CURRENT_PROJECT_VERSION
|
||||
// versionName = MARKETING_VERSION
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
// default signing configuration tries to load from keystore.properties
|
||||
signingConfigs {
|
||||
val keystorePropertiesFile = file("keystore.properties")
|
||||
if (keystorePropertiesFile.isFile) {
|
||||
create("release") {
|
||||
val keystoreProperties = Properties()
|
||||
keystoreProperties.load(keystorePropertiesFile.inputStream())
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("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")
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
-keeppackagenames **
|
||||
-keep class skip.** { *; }
|
||||
-keep class com.sun.jna.Pointer { *; }
|
||||
-keep class sats.price.** { *; }
|
||||
30
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>
|
||||
BIN
Android/app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
125
Android/app/src/main/kotlin/sats/price/Main.kt
Normal file
@@ -0,0 +1,125 @@
|
||||
package sats.price
|
||||
|
||||
import skip.lib.*
|
||||
import skip.model.*
|
||||
import skip.foundation.*
|
||||
import skip.ui.*
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.compose.setContent
|
||||
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.saveable.rememberSaveableStateHolder
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.app.ActivityCompat
|
||||
|
||||
internal val logger: SkipLogger = SkipLogger(subsystem = "sats.price", category = "SatsPrice")
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// 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 onSaveInstanceState(bundle: android.os.Bundle): Unit = super.onSaveInstanceState(bundle)
|
||||
|
||||
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 onRestart() {
|
||||
logger.info("onRestart")
|
||||
super.onRestart()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
logger.info("onStart")
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
logger.info("onResume")
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
logger.info("onPause")
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
logger.info("onStop")
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
logger.info("onDestroy")
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
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 PresentationRootView(context: ComposeContext) {
|
||||
val colorScheme = if (isSystemInDarkTheme()) ColorScheme.dark else ColorScheme.light
|
||||
PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
|
||||
val contentContext = ctx.content()
|
||||
Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
RootView().Compose(context = contentContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
BIN
Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
11
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'])
|
||||
|
||||
# Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
json_key_file("fastlane/apikey.json")
|
||||
49
Android/fastlane/Fastfile
Normal file
@@ -0,0 +1,49 @@
|
||||
# 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!
|
||||
1
Android/fastlane/metadata/android/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
SatsPrice
|
||||
3
Android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4g
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
2
Android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#Sat Aug 31 09:41:39 EEST 2024
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
46
Android/settings.gradle.kts
Normal file
@@ -0,0 +1,46 @@
|
||||
// This gradle project is part of a conventional Skip app project.
|
||||
// It invokes the shared build skip plugin logic, which included as part of the skip-unit buildSrc
|
||||
// When built from Android Studio, it uses the BUILT_PRODUCTS_DIR folder to share the same build outputs as Xcode, otherwise it uses SwiftPM's .build/ folder
|
||||
pluginManagement {
|
||||
// local override of BUILT_PRODUCTS_DIR
|
||||
if (System.getenv("BUILT_PRODUCTS_DIR") == null) {
|
||||
//System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-aqywrhrzhkbvfseiqgxuufbdwdft/Build/Products/Debug-iphonesimulator")
|
||||
}
|
||||
|
||||
// the source for the plugin is linked as part of the SkipUnit transpilation
|
||||
val skipOutput = System.getenv("BUILT_PRODUCTS_DIR") ?: System.getProperty("BUILT_PRODUCTS_DIR")
|
||||
|
||||
val outputExt = if (skipOutput != null) ".output" else "" // Xcode saves output in package-name.output; SPM has no suffix
|
||||
val skipOutputs: File = if (skipOutput != null) {
|
||||
// BUILT_PRODUCTS_DIR is set when building from Xcode, in which case we will use Xcode's DerivedData plugin output
|
||||
file(skipOutput).resolve("../../../SourcePackages/plugins/")
|
||||
} else {
|
||||
exec {
|
||||
// create transpiled Kotlin and generate Gradle projects from SwiftPM modules
|
||||
commandLine("swift", "build")
|
||||
workingDir = file("..")
|
||||
}
|
||||
// SPM output folder is a peer of the parent Package.swift
|
||||
rootDir.resolve("../.build/plugins/outputs/")
|
||||
}
|
||||
|
||||
// load the Skip plugin (part of the skip-unit project), which handles configuring the Android project
|
||||
// because this path is a symlink, we need to use the canonical path or gradle will mis-interpret it as a different build source
|
||||
var pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/skipstone/buildSrc/").canonicalFile
|
||||
if (!pluginSource.isDirectory) {
|
||||
// check new SwiftPM6 plugin "destination" folder for command-line builds
|
||||
pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/destination/skipstone/buildSrc/").canonicalFile
|
||||
}
|
||||
|
||||
if (!pluginSource.isDirectory) {
|
||||
throw GradleException("Missing expected Skip output folder: ${pluginSource}. Run `swift build` in the root folder to create, or specify Xcode environment BUILT_PRODUCTS_DIR.")
|
||||
}
|
||||
includeBuild(pluginSource.path) {
|
||||
name = "skip-plugins"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("skip-plugin") apply true
|
||||
}
|
||||
|
||||