summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaindropsSys <contact@minteck.org>2023-08-14 21:26:07 +0200
committerRaindropsSys <contact@minteck.org>2023-08-14 21:26:07 +0200
commit04c6aef158f46f7aac3e71c7100cea952fed87e6 (patch)
tree478d3738ef56cba622041dd97f4f445068e57dff
downloadponyplay-04c6aef158f46f7aac3e71c7100cea952fed87e6.tar.gz
ponyplay-04c6aef158f46f7aac3e71c7100cea952fed87e6.tar.bz2
ponyplay-04c6aef158f46f7aac3e71c7100cea952fed87e6.zip
Initial commit
-rw-r--r--android/.gitignore15
-rw-r--r--android/.idea/.gitignore3
-rw-r--r--android/.idea/.name1
-rw-r--r--android/.idea/compiler.xml6
-rw-r--r--android/.idea/deploymentTargetDropDown.xml10
-rw-r--r--android/.idea/discord.xml7
-rw-r--r--android/.idea/gradle.xml19
-rw-r--r--android/.idea/kotlinc.xml6
-rw-r--r--android/.idea/migrations.xml10
-rw-r--r--android/.idea/misc.xml9
-rw-r--r--android/app/.gitignore1
-rw-r--r--android/app/build.gradle.kts52
-rw-r--r--android/app/proguard-rules.pro21
-rw-r--r--android/app/release/app-release.apkbin0 -> 10367217 bytes
-rw-r--r--android/app/release/output-metadata.json20
-rw-r--r--android/app/src/main/AndroidManifest.xml42
-rw-r--r--android/app/src/main/ic_app-playstore.pngbin0 -> 299685 bytes
-rw-r--r--android/app/src/main/ic_launcher-playstore.pngbin0 -> 12412 bytes
-rw-r--r--android/app/src/main/java/dev/equestria/ponyplay/JavaScriptExtensions.kt63
-rw-r--r--android/app/src/main/java/dev/equestria/ponyplay/MainActivity.kt237
-rw-r--r--android/app/src/main/res/drawable/ic_launcher_foreground.xml15
-rw-r--r--android/app/src/main/res/layout/activity_main.xml36
-rw-r--r--android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--android/app/src/main/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1990 bytes
-rw-r--r--android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webpbin0 -> 7200 bytes
-rw-r--r--android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 1990 bytes
-rw-r--r--android/app/src/main/res/mipmap-mdpi/ic_launcher.webpbin0 -> 1254 bytes
-rw-r--r--android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webpbin0 -> 4284 bytes
-rw-r--r--android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1254 bytes
-rw-r--r--android/app/src/main/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 2870 bytes
-rw-r--r--android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webpbin0 -> 10682 bytes
-rw-r--r--android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 2870 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 4464 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webpbin0 -> 19274 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 4464 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 6432 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webpbin0 -> 30736 bytes
-rw-r--r--android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 6432 bytes
-rw-r--r--android/app/src/main/res/values/colors.xml5
-rw-r--r--android/app/src/main/res/values/ic_launcher_background.xml4
-rw-r--r--android/app/src/main/res/values/strings.xml3
-rw-r--r--android/app/src/main/res/values/themes.xml10
-rw-r--r--android/app/src/main/res/xml/backup_rules.xml13
-rw-r--r--android/app/src/main/res/xml/data_extraction_rules.xml19
-rw-r--r--android/build.gradle.kts7
-rw-r--r--android/gradle.properties23
-rw-r--r--android/gradle/libs.versions.toml28
-rw-r--r--android/gradle/wrapper/gradle-wrapper.jarbin0 -> 59203 bytes
-rw-r--r--android/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xandroid/gradlew185
-rw-r--r--android/gradlew.bat89
-rw-r--r--android/logo.pngbin0 -> 101290 bytes
-rw-r--r--android/settings.gradle.kts19
-rw-r--r--android/themed.svg30
-rw-r--r--web/.idea/.gitignore8
-rw-r--r--web/.idea/deployment.xml16
-rw-r--r--web/.idea/discord.xml7
-rw-r--r--web/.idea/modules.xml8
-rw-r--r--web/.idea/php.xml13
-rw-r--r--web/.idea/web.iml8
-rw-r--r--web/TODO.md22
-rw-r--r--web/alarms/Awaken.oggbin0 -> 266352 bytes
-rw-r--r--web/alarms/Bright_morning.oggbin0 -> 159900 bytes
-rw-r--r--web/alarms/Fresh_start.oggbin0 -> 374126 bytes
-rw-r--r--web/alarms/Your_new_adventure.oggbin0 -> 310018 bytes
-rw-r--r--web/app.html310
-rw-r--r--web/apps/alarms.html51
-rw-r--r--web/apps/settings.html51
-rw-r--r--web/apps/settings.js35
-rw-r--r--web/availabilitycheck.txt1
-rwxr-xr-xweb/fonts/google.ttfbin0 -> 109128 bytes
-rw-r--r--web/fonts/mlp.ttfbin0 -> 60976 bytes
-rw-r--r--web/icons/alarms.svg1
-rw-r--r--web/icons/close.svg1
-rw-r--r--web/icons/face.svg1
-rw-r--r--web/icons/installer.svg1
-rw-r--r--web/icons/ponypush.svg21
-rw-r--r--web/icons/settings.svg1
-rw-r--r--web/icons/system.svg1
-rw-r--r--web/icons/weather.svg1
-rw-r--r--web/settings.json72
-rw-r--r--web/src/alarms.js78
-rw-r--r--web/src/ambienteq.js103
-rw-r--r--web/src/appmanager.js28
-rw-r--r--web/src/apptheme.js12
-rw-r--r--web/src/color.js119
-rw-r--r--web/src/loader.js7
-rw-r--r--web/src/notification.js33
-rw-r--r--web/src/scroll.js111
-rw-r--r--web/src/sleep.js57
-rw-r--r--web/src/theme.js63
-rw-r--r--web/src/timedate.js16
-rw-r--r--web/src/weather.js57
-rw-r--r--web/test.html41
-rw-r--r--web/weather/01d.pngbin0 -> 79341 bytes
-rw-r--r--web/weather/01n.pngbin0 -> 81441 bytes
-rw-r--r--web/weather/02d.pngbin0 -> 148598 bytes
-rw-r--r--web/weather/02n.pngbin0 -> 132277 bytes
-rw-r--r--web/weather/03d.pngbin0 -> 94389 bytes
-rw-r--r--web/weather/03n.pngbin0 -> 92151 bytes
-rw-r--r--web/weather/04d.pngbin0 -> 84329 bytes
-rw-r--r--web/weather/04n.pngbin0 -> 84329 bytes
-rw-r--r--web/weather/09d.pngbin0 -> 101738 bytes
-rw-r--r--web/weather/09n.pngbin0 -> 101738 bytes
-rw-r--r--web/weather/10d.pngbin0 -> 117184 bytes
-rw-r--r--web/weather/10n.pngbin0 -> 84765 bytes
-rw-r--r--web/weather/11d.pngbin0 -> 136073 bytes
-rw-r--r--web/weather/11n.pngbin0 -> 126451 bytes
-rw-r--r--web/weather/13d.pngbin0 -> 111600 bytes
-rw-r--r--web/weather/13n.pngbin0 -> 111600 bytes
-rw-r--r--web/weather/202d.pngbin0 -> 135364 bytes
-rw-r--r--web/weather/202n.pngbin0 -> 135364 bytes
-rw-r--r--web/weather/211d.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/211n.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/212d.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/212n.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/221d.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/221n.pngbin0 -> 111000 bytes
-rw-r--r--web/weather/232d.pngbin0 -> 135364 bytes
-rw-r--r--web/weather/232n.pngbin0 -> 135364 bytes
-rw-r--r--web/weather/300d.pngbin0 -> 98278 bytes
-rw-r--r--web/weather/300n.pngbin0 -> 98278 bytes
-rw-r--r--web/weather/500d.pngbin0 -> 98278 bytes
-rw-r--r--web/weather/500n.pngbin0 -> 98278 bytes
-rw-r--r--web/weather/50d.pngbin0 -> 98587 bytes
-rw-r--r--web/weather/50n.pngbin0 -> 98587 bytes
-rw-r--r--web/weather/511d.pngbin0 -> 123850 bytes
-rw-r--r--web/weather/511n.pngbin0 -> 123850 bytes
-rw-r--r--web/weather/615d.pngbin0 -> 123850 bytes
-rw-r--r--web/weather/615n.pngbin0 -> 123850 bytes
-rw-r--r--web/weather/616d.pngbin0 -> 115965 bytes
-rw-r--r--web/weather/616n.pngbin0 -> 115965 bytes
-rw-r--r--web/weather/804d.pngbin0 -> 82536 bytes
-rw-r--r--web/weather/804n.pngbin0 -> 82536 bytes
-rw-r--r--web/weather/list.json1
136 files changed, 2380 insertions, 0 deletions
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/android/.idea/.gitignore b/android/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/android/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/android/.idea/.name b/android/.idea/.name
new file mode 100644
index 0000000..b778484
--- /dev/null
+++ b/android/.idea/.name
@@ -0,0 +1 @@
+App Additions \ No newline at end of file
diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/android/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <bytecodeTargetLevel target="17" />
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/deploymentTargetDropDown.xml b/android/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..0c0c338
--- /dev/null
+++ b/android/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="deploymentTargetDropDown">
+ <value>
+ <entry key="app">
+ <State />
+ </entry>
+ </value>
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/discord.xml b/android/.idea/discord.xml
new file mode 100644
index 0000000..cf77f1e
--- /dev/null
+++ b/android/.idea/discord.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="DiscordProjectSettings">
+ <option name="show" value="DISABLE" />
+ <option name="description" value="" />
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml
new file mode 100644
index 0000000..0897082
--- /dev/null
+++ b/android/.idea/gradle.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GradleMigrationSettings" migrationVersion="1" />
+ <component name="GradleSettings">
+ <option name="linkedExternalProjectsSettings">
+ <GradleProjectSettings>
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
+ <option name="modules">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ <option value="$PROJECT_DIR$/app" />
+ </set>
+ </option>
+ <option name="resolveExternalAnnotations" value="false" />
+ </GradleProjectSettings>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/kotlinc.xml b/android/.idea/kotlinc.xml
new file mode 100644
index 0000000..217e5c5
--- /dev/null
+++ b/android/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="KotlinJpsPluginSettings">
+ <option name="version" value="1.8.21" />
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/migrations.xml b/android/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/android/.idea/migrations.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectMigrations">
+ <option name="MigrateToGradleLocalJavaHome">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ </set>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/android/.idea/misc.xml
@@ -0,0 +1,9 @@
+<project version="4">
+ <component name="ExternalStorageConfigurationManager" enabled="true" />
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+</project> \ No newline at end of file
diff --git a/android/app/.gitignore b/android/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/android/app/.gitignore
@@ -0,0 +1 @@
+/build \ No newline at end of file
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 0000000..ab64399
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,52 @@
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.kotlinAndroid)
+}
+
+android {
+ namespace = "dev.equestria.ponyplay"
+ compileSdk = 33
+
+ defaultConfig {
+ applicationId = "dev.equestria.ponyplay"
+ minSdk = 33
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ buildFeatures {
+ buildConfig = true
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+ implementation(libs.okhttp)
+ implementation(libs.volley)
+ implementation(libs.core.ktx)
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ implementation(libs.constraintlayout)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.ext.junit)
+ androidTestImplementation(libs.espresso.core)
+} \ No newline at end of file
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile \ No newline at end of file
diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk
new file mode 100644
index 0000000..9a04caf
--- /dev/null
+++ b/android/app/release/app-release.apk
Binary files differ
diff --git a/android/app/release/output-metadata.json b/android/app/release/output-metadata.json
new file mode 100644
index 0000000..8f082d7
--- /dev/null
+++ b/android/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "dev.equestria.phoneadditions",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 5,
+ "versionName": "2.1.0-phone",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+} \ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..26dc29c
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-feature
+ android:name="android.hardware.camera"
+ android:required="false" />
+
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS"
+ tools:ignore="ProtectedPermissions" />
+
+ <application
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:icon="@mipmap/ic_launcher"
+ android:hardwareAccelerated="true"
+ android:label="@string/app_name"
+ android:supportsRtl="false"
+ android:theme="@style/Theme.Ponyplay"
+ tools:targetApi="31">
+ <activity
+ android:name=".MainActivity"
+ android:label="Ponyplay"
+ android:icon="@mipmap/ic_launcher"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/android/app/src/main/ic_app-playstore.png b/android/app/src/main/ic_app-playstore.png
new file mode 100644
index 0000000..395a1d5
--- /dev/null
+++ b/android/app/src/main/ic_app-playstore.png
Binary files differ
diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..476b975
--- /dev/null
+++ b/android/app/src/main/ic_launcher-playstore.png
Binary files differ
diff --git a/android/app/src/main/java/dev/equestria/ponyplay/JavaScriptExtensions.kt b/android/app/src/main/java/dev/equestria/ponyplay/JavaScriptExtensions.kt
new file mode 100644
index 0000000..4ea2d95
--- /dev/null
+++ b/android/app/src/main/java/dev/equestria/ponyplay/JavaScriptExtensions.kt
@@ -0,0 +1,63 @@
+package dev.equestria.ponyplay
+
+import android.content.Intent
+import android.provider.Settings
+import android.webkit.JavascriptInterface
+import android.webkit.WebView
+
+
+class JavaScriptExtensions(originalActivity: MainActivity) {
+ private val activity: MainActivity = originalActivity
+ private var lightSensorValue: Int = 0
+
+ @JavascriptInterface
+ fun getSystemBrightness(): Int {
+ return Settings.System.getInt(
+ activity.baseContext.contentResolver,
+ Settings.System.SCREEN_BRIGHTNESS)
+ }
+
+ @JavascriptInterface
+ fun getBrightness(): Float {
+ return activity.lightSensorValue
+ }
+
+ @JavascriptInterface
+ fun setBrightness(brightness: Int): Int {
+ Settings.System.putInt(
+ activity.baseContext.contentResolver,
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
+ )
+
+ Settings.System.putInt(
+ activity.baseContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, brightness
+ )
+
+ return Settings.System.getInt(
+ activity.baseContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS
+ )
+ }
+
+ @JavascriptInterface
+ fun reloadWebView() {
+ val wv = activity.findViewById<WebView>(R.id.webview)
+ wv.reload()
+ }
+
+ @JavascriptInterface
+ fun startApp(url: String) {
+ val i = Intent()
+ i.setAction("startApp")
+ i.putExtra("url", url)
+ activity.sendBroadcast(i)
+ }
+
+ @JavascriptInterface
+ fun closeApp() {
+ val i = Intent()
+ i.setAction("closeApp")
+ activity.sendBroadcast(i)
+ }
+}
+
diff --git a/android/app/src/main/java/dev/equestria/ponyplay/MainActivity.kt b/android/app/src/main/java/dev/equestria/ponyplay/MainActivity.kt
new file mode 100644
index 0000000..9d7b663
--- /dev/null
+++ b/android/app/src/main/java/dev/equestria/ponyplay/MainActivity.kt
@@ -0,0 +1,237 @@
+package dev.equestria.ponyplay
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ActivityInfo
+import android.graphics.Color
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.os.Bundle
+import android.util.Log
+import android.view.Display
+import android.view.View
+import android.view.WindowManager
+import android.webkit.GeolocationPermissions
+import android.webkit.PermissionRequest
+import android.webkit.WebChromeClient
+import android.webkit.WebSettings
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import com.android.volley.Request
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import okhttp3.*
+import kotlin.system.exitProcess
+
+
+class MainActivity : SensorEventListener, Activity() {
+ private lateinit var mSensorManager: SensorManager
+ private lateinit var mLightSensor: Sensor
+ private lateinit var broadcastReceiver: BroadcastReceiver
+ var lightSensorValue: Float = 0f
+ val mainAppURL: String = "https://ponyplay-raindrops.equestria.dev:20443/app.html"
+
+ override fun onSensorChanged(sensorEvent: SensorEvent) {
+ if (sensorEvent.sensor.type == Sensor.TYPE_LIGHT) {
+ Log.d("SensorTest", "" + sensorEvent.values[0])
+ lightSensorValue = sensorEvent.values[0]
+ }
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onBackPressed() {
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, i: Int) {}
+
+ fun showError() {
+ MaterialAlertDialogBuilder(this).setCancelable(false)
+ .setTitle("Error")
+ .setMessage("Unable to communicate with your local Ponyplay Station.")
+ .setNegativeButton("Quit") { _, _ ->
+ this.moveTaskToBack(true)
+ exitProcess(0)
+ }.show()
+ }
+
+ override fun onDestroy() {
+ unregisterReceiver(broadcastReceiver)
+ super.onDestroy()
+ }
+
+ override fun onResume() {
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+
+ super.onResume()
+
+ mSensorManager.registerListener(this, mLightSensor, SensorManager.SENSOR_DELAY_NORMAL)
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+
+ super.onWindowFocusChanged(hasFocus)
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ //
+ // getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
+ setContentView(R.layout.activity_main)
+
+ val intentFilter = IntentFilter()
+ intentFilter.addAction("startApp")
+ intentFilter.addAction("closeApp")
+
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == "startApp") {
+ val av = findViewById<WebView>(R.id.appview)
+ av.visibility = View.VISIBLE
+
+ val webSettings = av.settings
+ webSettings.javaScriptEnabled = true
+ webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
+ webSettings.domStorageEnabled = true
+ webSettings.allowFileAccess = true
+ webSettings.allowContentAccess = true
+ webSettings.safeBrowsingEnabled = false
+
+ webSettings.allowUniversalAccessFromFileURLs = true
+ webSettings.allowFileAccessFromFileURLs = true
+
+ webSettings.mediaPlaybackRequiresUserGesture = false
+ webSettings.userAgentString = "Mozilla/5.0 (Linux; Ponyplay; main) AppleWebKit/" + webSettings.userAgentString.split("AppleWebKit/")[1]
+
+ av.webViewClient = object : WebViewClient() {
+ @Deprecated("Deprecated in Java")
+ override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
+ av.loadUrl(url)
+ return true
+ }
+ }
+
+ av.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+
+ intent.getStringExtra("url")?.let { av.loadUrl(it) }
+ } else if (intent.action == "closeApp") {
+ val av = findViewById<WebView>(R.id.appview)
+ av.visibility = View.GONE
+ av.loadUrl("about:blank")
+ }
+ }
+ }
+
+ registerReceiver(broadcastReceiver, intentFilter, RECEIVER_NOT_EXPORTED)
+
+ mSensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
+ mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
+
+ window.statusBarColor = Color.TRANSPARENT
+ window.navigationBarColor = Color.TRANSPARENT
+
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_FULLSCREEN)
+ this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+
+ val wv = findViewById<WebView>(R.id.webview)
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest = JsonObjectRequest(
+ Request.Method.GET, "https://ponyplay-raindrops.equestria.dev:20443/availabilitycheck.txt", null,
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+
+ if (response.getString("status") == "OK") {
+ wv.clearCache(true)
+ wv.clearHistory()
+
+ val webSettings = wv.settings
+ webSettings.javaScriptEnabled = true
+ webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
+ webSettings.domStorageEnabled = true
+ webSettings.allowFileAccess = true
+ webSettings.allowContentAccess = true
+ webSettings.safeBrowsingEnabled = false
+
+ webSettings.allowUniversalAccessFromFileURLs = true
+ webSettings.allowFileAccessFromFileURLs = true
+
+ webSettings.mediaPlaybackRequiresUserGesture = false
+ webSettings.userAgentString = "Mozilla/5.0 (Linux; Ponyplay; main) AppleWebKit/" + webSettings.userAgentString.split("AppleWebKit/")[1]
+
+ wv.loadUrl(mainAppURL)
+ wv.addJavascriptInterface(JavaScriptExtensions(this), "engine")
+ wv.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+
+ wv.webViewClient = object : WebViewClient() {
+ @Deprecated("Deprecated in Java")
+ override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
+ wv.loadUrl(url)
+ return true
+ }
+ }
+
+ wv.webChromeClient = object : WebChromeClient() {
+ override fun onPermissionRequest(request: PermissionRequest) {
+ Log.d("WebView", "onPermissionRequest")
+ runOnUiThread {
+ Log.d("WebView", request.origin.toString())
+ Log.d("WebView", "GRANTED")
+ request.grant(request.resources)
+ }
+ }
+
+ override fun onGeolocationPermissionsShowPrompt(
+ origin: String?,
+ callback: GeolocationPermissions.Callback
+ ) {
+ callback.invoke(origin, true, false)
+ }
+ }
+
+ WebView.setWebContentsDebuggingEnabled(false)
+ } else {
+ showError()
+ }
+ },
+
+ { error ->
+ showError()
+ Log.e("HTTPRequest", "Request error: ${error.localizedMessage}")
+ })
+
+ volleyQueue.add(jsonObjectRequest)
+ }
+} \ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..9ea0ceb
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#000000">
+ <group android:scaleX="0.58"
+ android:scaleY="0.58"
+ android:translateX="5.04"
+ android:translateY="5.04">
+ <path
+ android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"
+ android:fillColor="#FF000000"/>
+ </group>
+</vector> \ No newline at end of file
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..32c5989
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/Base.Theme.Ponyplay"
+ android:configChanges="orientation"
+ android:screenOrientation="landscape"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:id="@+id/linearLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <WebView
+ android:id="@+id/webview"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="10"
+ android:hardwareAccelerated="true">
+
+ </WebView>
+
+ <WebView
+ android:id="@+id/appview"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="90"
+ android:visibility="gone" />
+
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -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="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..7353dbd
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -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="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..b30f614
--- /dev/null
+++ b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..fbfe3aa
--- /dev/null
+++ b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b30f614
--- /dev/null
+++ b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..ec87dd6
--- /dev/null
+++ b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..9668e99
--- /dev/null
+++ b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ec87dd6
--- /dev/null
+++ b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..9b84016
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..edc6bd5
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9b84016
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..737647c
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..d4a60ea
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..737647c
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..5c74df5
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..71545d9
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..5c74df5
--- /dev/null
+++ b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="ic_launcher_background">#FFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..69a66f4
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">Equestria.dev Ponyplay</string>
+</resources> \ No newline at end of file
diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..12e97e4
--- /dev/null
+++ b/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,10 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Base.Theme.Ponyplay" parent="Theme.Material3.Dark.NoActionBar">
+ <!-- Customize your light theme here. -->
+ <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+
+ <style name="Theme.Ponyplay" parent="Base.Theme.Ponyplay" />
+</resources> \ No newline at end of file
diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+ <!--
+ <include domain="sharedpref" path="."/>
+ <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content> \ No newline at end of file
diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample data extraction rules file; uncomment and customize as necessary.
+ See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+ for details.
+-->
+<data-extraction-rules>
+ <cloud-backup>
+ <!-- TODO: Use <include> and <exclude> to control what is backed up.
+ <include .../>
+ <exclude .../>
+ -->
+ </cloud-backup>
+ <!--
+ <device-transfer>
+ <include .../>
+ <exclude .../>
+ </device-transfer>
+ -->
+</data-extraction-rules> \ No newline at end of file
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 0000000..20d87a7
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,7 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+ alias(libs.plugins.androidApplication) apply false
+ alias(libs.plugins.kotlinAndroid) apply false
+}
+true // Needed to make the Suppress annotation work for the plugins block \ No newline at end of file
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..3c5031e
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true \ No newline at end of file
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
new file mode 100644
index 0000000..0235b33
--- /dev/null
+++ b/android/gradle/libs.versions.toml
@@ -0,0 +1,28 @@
+[versions]
+agp = "8.2.0-alpha14"
+kotlin = "1.8.21"
+core-ktx = "1.10.1"
+junit = "4.13.2"
+androidx-test-ext-junit = "1.1.5"
+espresso-core = "3.5.1"
+appcompat = "1.6.1"
+material = "1.9.0"
+constraintlayout = "2.1.4"
+okhttp = "4.10.0"
+volley = "1.2.1"
+
+[libraries]
+core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
+volley = { module = "com.android.volley:volley", version.ref = "volley" }
+
+[plugins]
+androidApplication = { id = "com.android.application", version.ref = "agp" }
+kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..da3c56c
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Aug 09 22:22:46 CEST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/android/gradlew b/android/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/logo.png b/android/logo.png
new file mode 100644
index 0000000..81a5e45
--- /dev/null
+++ b/android/logo.png
Binary files differ
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 0000000..bb6fd36
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -0,0 +1,19 @@
+import java.net.URI
+
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "App Additions"
+include(":app")
diff --git a/android/themed.svg b/android/themed.svg
new file mode 100644
index 0000000..f7a7634
--- /dev/null
+++ b/android/themed.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 139.7 139.7" style="enable-background:new 0 0 139.7 139.7;" xml:space="preserve">
+<style type="text/css">
+ .st0{opacity:0.25;enable-background:new ;}
+ .st1{opacity:0.5;enable-background:new ;}
+</style>
+<path class="st0" d="M85.9,29.8C78.5,30.6,71.8,34,71.1,40c-0.2,2-1.5,10.8-3.6,11.6c-10.6,3.8-19.8,20-19.9,26.8
+ c-2,15.3-3.6,20.9,1.2,31L68.9,79c2.2-5.6,5.3-10.6,7-16.5c0.5-1.9,1-4.5,1.9-5.8c0.9-1.5,2.4-2.5,4.2-4.5c1.2-1.3,3-4.2,4.5-6.6
+ c2.9-4.9,3.9-7.1,5.6-10.3C92.3,33.4,90.2,29.3,85.9,29.8z M60,72.6c-1,1.7-2.9,5.1-3.9,6.7c-0.5,0.9-0.9,2.2-1.2,3.2
+ c-0.3-1.2-0.3-3,0.6-4.6l0,0c0.7-0.9,2-2.7,3-3.9c2.1-2.9,8.2-12,9.5-12.8C66.8,62.4,61.9,69.5,60,72.6z"/>
+<path d="M56.3,75c-3.1,3.4-3.5,10-2.4,14.3C57.7,76,66,65.7,72.3,57.1C66.5,59.4,61.1,69.5,56.3,75z M60,72.6
+ c-1,1.7-2.9,5.1-3.9,6.7c-0.5,0.9-0.9,2.2-1.2,3.2c-0.3-1.2-0.3-3,0.6-4.6l0,0c0.7-0.9,2-2.7,3-3.9c2.1-2.9,8.2-12,9.5-12.8
+ C66.8,62.4,61.9,69.5,60,72.6z"/>
+<path d="M72.3,57.1L72.3,57.1L72.3,57.1z"/>
+<path id="path4677" d="M81.9,35c0.1,0.9,0,1.1,0.3,1.4c1.2,0.1,3-0.8,4.2-1.2c-0.6,0.9-2.1,2.1-3.5,3.3c-0.2,0.5-0.1,0.8-0.1,1.2
+ c-1-0.1-2.1,0.3-2.6,0.6c0.4,0.3,1.7,3.5,1.2,5.2c-0.4-0.4-1.7-3.9-2.7-4.2c-0.6,1.5-0.4,5-1.2,6.8c-1-1-1-3.9-0.4-5.5
+ c-1.2,0.4-2.2,1.2-3.4,1.2c1.2-1.5,2.6-2.3,3.9-3.5c0-0.5-0.3-0.6-0.9-0.9c1.2-0.4,2.4-1,2.3-2.7c0.4,0.3,0.8,0.6,1.2,0.7
+ C81.1,36.6,81.6,35.6,81.9,35L81.9,35z"/>
+<path d="M86.7,29.2C80.6,29.4,76.1,30.6,73,34c-2.2,2.6-3.1,6-3.6,9.9c-0.9,6.4-0.9,6.2-5.5,8.9c-4.3,2.6-8.2,7.1-11.2,10.7
+ c-6.5,7.8-11.9,33-4.9,46.9l0,0l2.1-2.3c-6.8-7.7-2.1-28.1,0.3-35.2c2.2-6.6,7.9-13.3,13.7-17.6c6.8-5,6-3.8,7.2-9.7
+ c0.7-3.7,1.5-8,3.6-10.2c2.7-2.9,6.8-5,12.1-4.9L86.7,29.2z"/>
+<path id="path3177" d="M77.6,56.5c4.2-2,11.4-11.3,14.4-21.2c-1.2,1.5-2.9,3.9-4.4,7C84.5,48.7,78.7,54.9,77.6,56.5z"/>
+<path d="M54.5,97.2c-7.9,9.6-6.3,12.7-5.7,12.3c1.8-1.8,4.5-6.6,8.1-11.2c8.9-11.5,18.7-28.7,20.3-40.4
+ C71.5,75.6,60.1,90.2,54.5,97.2z"/>
+<path id="path4334_00000171706154884340887790000012676028719422921878_" class="st1" d="M55.6,77.9c-0.9,1.6-0.9,3.5-0.6,4.6
+ c0.3-1,0.6-2.2,1.2-3.2c0.9-1.6,2.7-5,3.9-6.7c2-3.1,6.8-10.2,8.2-11.5c-1.4,0.8-7.5,9.9-9.5,12.8C57.6,75.2,56.3,77,55.6,77.9
+ L55.6,77.9z"/>
+</svg>
diff --git a/web/.idea/.gitignore b/web/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/web/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/web/.idea/deployment.xml b/web/.idea/deployment.xml
new file mode 100644
index 0000000..924f11d
--- /dev/null
+++ b/web/.idea/deployment.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="PublishConfigData" autoUpload="Always" serverName="maretimebay" confirmBeforeDeletion="false" autoUploadExternalChanges="true">
+ <option name="confirmBeforeDeletion" value="false" />
+ <serverData>
+ <paths name="maretimebay">
+ <serverdata>
+ <mappings>
+ <mapping deploy="/opt/ponyplay" local="$PROJECT_DIR$" web="/" />
+ </mappings>
+ </serverdata>
+ </paths>
+ </serverData>
+ <option name="myAutoUpload" value="ALWAYS" />
+ </component>
+</project> \ No newline at end of file
diff --git a/web/.idea/discord.xml b/web/.idea/discord.xml
new file mode 100644
index 0000000..cf77f1e
--- /dev/null
+++ b/web/.idea/discord.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="DiscordProjectSettings">
+ <option name="show" value="DISABLE" />
+ <option name="description" value="" />
+ </component>
+</project> \ No newline at end of file
diff --git a/web/.idea/modules.xml b/web/.idea/modules.xml
new file mode 100644
index 0000000..f589ca3
--- /dev/null
+++ b/web/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/web.iml" filepath="$PROJECT_DIR$/.idea/web.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/web/.idea/php.xml b/web/.idea/php.xml
new file mode 100644
index 0000000..88cd1bc
--- /dev/null
+++ b/web/.idea/php.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="MessDetectorOptionsConfiguration">
+ <option name="transferred" value="true" />
+ </component>
+ <component name="PHPCSFixerOptionsConfiguration">
+ <option name="transferred" value="true" />
+ </component>
+ <component name="PHPCodeSnifferOptionsConfiguration">
+ <option name="highlightLevel" value="WARNING" />
+ <option name="transferred" value="true" />
+ </component>
+</project> \ No newline at end of file
diff --git a/web/.idea/web.iml b/web/.idea/web.iml
new file mode 100644
index 0000000..c956989
--- /dev/null
+++ b/web/.idea/web.iml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/web/TODO.md b/web/TODO.md
new file mode 100644
index 0000000..d3ad99d
--- /dev/null
+++ b/web/TODO.md
@@ -0,0 +1,22 @@
+# Ponyplay roadmap
+
+* [x] <s>Time and date</s>
+* [x] <s>Sleep mode</s>
+* [x] <s>Ambient EQ</s>
+* [x] <s>Material You</s>
+* [ ] Offline mode
+* [x] <s>Notifications</s>
+* [ ] Alarms
+* [ ] Ponypush
+ * [ ] High priority notifications take up the entire screen
+* [ ] Face unlock
+* [ ] Settings
+ * [ ] Volume
+ * [ ] Brightness
+ * [ ] System
+* [ ] Weather app
+* [ ] Applications
+ * [ ] Installer
+ * [ ] Marehood
+ * [ ] Apple Music
+ * [ ] YouTube \ No newline at end of file
diff --git a/web/alarms/Awaken.ogg b/web/alarms/Awaken.ogg
new file mode 100644
index 0000000..0667166
--- /dev/null
+++ b/web/alarms/Awaken.ogg
Binary files differ
diff --git a/web/alarms/Bright_morning.ogg b/web/alarms/Bright_morning.ogg
new file mode 100644
index 0000000..4e3ed8c
--- /dev/null
+++ b/web/alarms/Bright_morning.ogg
Binary files differ
diff --git a/web/alarms/Fresh_start.ogg b/web/alarms/Fresh_start.ogg
new file mode 100644
index 0000000..76ecf57
--- /dev/null
+++ b/web/alarms/Fresh_start.ogg
Binary files differ
diff --git a/web/alarms/Your_new_adventure.ogg b/web/alarms/Your_new_adventure.ogg
new file mode 100644
index 0000000..e770ac4
--- /dev/null
+++ b/web/alarms/Your_new_adventure.ogg
Binary files differ
diff --git a/web/app.html b/web/app.html
new file mode 100644
index 0000000..06a55b8
--- /dev/null
+++ b/web/app.html
@@ -0,0 +1,310 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
+ <title>app</title>
+ <style id="dynamic-theme">
+ :root {
+ --text-color: white;
+ --surface-bg: #555;
+ --elevated-bg: #777;
+ --bg: #333;
+ --on-surface: #bbb;
+ --on-elevated: #aaa;
+ }
+ </style>
+ <style>
+ @font-face {
+ src: url("/fonts/google.ttf");
+ font-family: "Google Sans";
+ }
+
+ @font-face {
+ src: url("/fonts/mlp.ttf");
+ font-family: "MLPFindYourSparkle";
+ }
+
+ * {
+ color: var(--text-color);
+ font-family: "Google Sans", sans-serif;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+
+ body {
+ font-size: 2.4vw;
+ }
+
+ #sleep.show {
+ opacity: 1 !important;
+ pointer-events: initial !important;
+ }
+
+ #sleep.inhibited {
+ transition: opacity 200ms !important;
+ }
+
+ .notification {
+ box-shadow: 0 0 .5vw rgba(0, 0, 0, .5);
+ background-color: var(--surface-bg);
+ border-radius: 1.5vw;
+ width: 25vw;
+ padding: 2vw;
+ }
+
+ .notification-header {
+ height: 2.5vw;
+ display: flex;
+ align-items: center;
+ margin-bottom: 1.5vw;
+ }
+
+ .notification-application {
+ font-size: 1.3vw;
+ vertical-align: middle;
+ display: inline-block;
+ margin-left: 1vw;
+ width: 21.5vw;
+ white-space: nowrap;
+ overflow: hidden !important;
+ text-overflow: ellipsis;
+ }
+
+ .notification-icon {
+ height: 2.5vw;
+ vertical-align: middle;
+ width: 2.5vw;
+ border-radius: 999px;
+ background: var(--on-surface);
+ display: inline-block;
+ }
+
+ .notification-title {
+ font-weight: bold;
+ margin-bottom: .5vw;
+ }
+
+ .notification-content {
+ font-size: 1.8vw;
+ }
+
+ .notification-image {
+ max-width: 100%;
+ border-radius: 1.5vw;
+ margin-top: 1.5vw;
+ }
+
+ #container.deployed {
+ transition: top 200ms, opacity 200ms, border-radius 200ms;
+ transition-timing-function: linear;
+ top: 0 !important;
+ opacity: 1 !important;
+ pointer-events: initial !important;
+ border-radius: 0 !important;
+ }
+
+ #container.deployed #bottom-row {
+ transition: border-radius 200ms;
+ border-radius: 0 !important;
+ }
+
+ #container.backing {
+ pointer-events: initial !important;
+ }
+
+ #container.undeploying {
+ transition: top 200ms, opacity 200ms;
+ transition-timing-function: linear;
+ top: -100vh !important;
+ opacity: 0 !important;
+ }
+
+ #content, #notifications {
+ transition: opacity 200ms;
+ }
+
+ #bg, #bg-blur {
+ transition: opacity 200ms;
+ }
+
+ body.deploying #bg, body.deploying #bg-blur {
+ transition: none !important;
+ }
+
+ body.deploying #content, body.deploying #notifications {
+ opacity: 0;
+ }
+
+ #app.show {
+ opacity: 1 !important;
+ pointer-events: initial !important;
+ }
+
+ #app.show-native iframe {
+ display: none;
+ }
+
+ #app.show-native #app-header-icon-container {
+ width: 100vh !important;
+ }
+
+ #app.show-native #app-header {
+ height: 100vh !important;
+ }
+
+ #alarm.show {
+ opacity: 1 !important;
+ pointer-events: initial !important;
+ }
+ </style>
+</head>
+<body style="background-color: var(--bg);">
+ <div style="position: fixed; inset: 0; z-index: 5000; opacity: 0; pointer-events: none;">
+ <canvas id="canvas"></canvas>
+ <img id="photo">
+ <video id="video"></video>
+ <span id="rgb"></span>
+ <span style="width: 2ch; height: 2ch; background-color: black; display: inline-block;" id="sq1"></span>
+ <span style="width: 2ch; height: 2ch; background-color: black; display: inline-block;" id="sq2"></span>
+ </div>
+
+ <div id="app" style="position: fixed; inset: 0; z-index: 200; background-color: var(--bg); transition: opacity 200ms; pointer-events: none; opacity: 0;">
+ <div id="app-header" style="background-color: var(--surface-bg); height: 10vh; display: grid; grid-template-columns: max-content 1fr;">
+ <div id="app-header-icon-container" onclick="closeApp();" style="width: 10vh; text-align: center; justify-content: center; align-items: center; display: flex;">
+ <img id="app-header-icon">
+ </div>
+ <div style="text-align: center; justify-content: center; align-items: center; display: flex;" id="app-header-title"></div>
+ </div>
+
+ <iframe id="app-frame" src="about:blank" style="border: none; width: 100%; height: 90vh;"></iframe>
+ </div>
+
+ <div id="error" style="background-color: blue; color: white; font-family: monospace !important; font-size: 14px; z-index: 99999; position: fixed; inset: 0; display: none;">
+ <pre id="error-message" style="font-family: monospace !important; font-size: 14px; margin: 0 !important;">-</pre>
+ </div>
+ <pre id="warnings" style="font-family: monospace !important; font-size: 14px; z-index: 99998; position: fixed; top: 0; left: 0; pointer-events: none; color: red; margin: 0 !important;"></pre>
+ <script>
+ function addWarning(warning) {
+ document.getElementById("warnings").innerText += "\n" + warning;
+ document.getElementById("warnings").innerText = document.getElementById("warnings").innerText.trim();
+ }
+
+ if (navigator.userAgent.includes("(Linux; Ponyplay; ")) {
+ document.getElementById("warnings").style.display = "none";
+ } else {
+ addWarning("UNSUPPORTED SETUP -- The application is running outside of the Ponyplay engine. You will get NO SUPPORT.");
+ }
+
+ window.onerror = (_1, _2, _3, _4, error) => {
+ document.getElementById("error").style.display = "";
+ document.getElementById("error-message").innerText = error.stack + "\n\n----\n\n" + JSON.stringify(window.weather, null, 2);
+ return false;
+ }
+
+ window.onunhandledrejection = (e) => {
+ document.getElementById("error").style.display = "";
+ document.getElementById("error-message").innerText = (e.reason?.stack ?? e.reason) + "\n\n----\n\n" + JSON.stringify(window.weather, null, 2);
+ return false;
+ }
+ </script>
+
+ <div id="container" style="position: fixed; left: 0; background-color: var(--surface-bg); z-index: 50; top: -100vh; right: 0; height: 100vh; pointer-events: none; border-bottom-left-radius: 2vw; border-bottom-right-radius: 2vw; display: grid; grid-template-rows: 80vh 20vh;">
+ <div id="top-row">
+ <div id="no-apps" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; opacity: .5;">You don't have any applications .c.</div>
+ </div>
+ <div id="bottom-row" style="background-color: var(--elevated-bg); border-bottom-left-radius: 2vw; border-bottom-right-radius: 2vw; display: grid; grid-template-columns: repeat(6, 1fr);">
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;">
+ <div>
+ <div id="app-1-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-1-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-1-name" style="font-size: 1.6vw; margin-top: .5vw;">Installer</div>
+ </div>
+ </div>
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;" onclick="openApp('Alarms', '/apps/alarms.html', true);">
+ <div>
+ <div id="app-2-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-2-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-2-name" style="font-size: 1.6vw; margin-top: .5vw;">Alarms</div>
+ </div>
+ </div>
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;">
+ <div>
+ <div id="app-3-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-3-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-3-name" style="font-size: 1.6vw; margin-top: .5vw;">Face unlock</div>
+ </div>
+ </div>
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;">
+ <div>
+ <div id="app-4-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-4-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-4-name" style="font-size: 1.6vw; margin-top: .5vw;">Ponypush</div>
+ </div>
+ </div>
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;">
+ <div>
+ <div id="app-5-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-5-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-5-name" style="font-size: 1.6vw; margin-top: .5vw;">Weather</div>
+ </div>
+ </div>
+ <div style="display: flex; align-items: center; justify-content: center; text-align: center;" onclick="openApp('Configuration', '/apps/settings.html', true);">
+ <div>
+ <div id="app-6-icon" style="display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto; background-color: var(--on-elevated); width: 7.5vh; height: 7.5vh; border-radius: 999px;">
+ <img id="app-6-icon-img" style="width: 5vh; height: 5vh;">
+ </div>
+ <div id="app-6-name" style="font-size: 1.6vw; margin-top: .5vw;">Configuration</div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="sleep" style="z-index: 1000; color: white !important; background-color: black; position: fixed; inset: 0; opacity: 0; pointer-events: none; transition: opacity 2000ms; display: flex; align-items: center; justify-content: center;" onclick="startInhibitSleep();">
+ <div id="time-sleep" style="color: white !important; font-size: 32vw; margin-top: 16vw; font-family: MLPFindYourSparkle, 'Google Sans', sans-serif; display: inline-block; pointer-events: none;">--:--</div>
+ </div>
+
+ <div id="alarm" style="z-index: 2000; color: black !important; background-color: white; position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background-image: radial-gradient(circle, rgba(255,210,121,1) 0%, rgba(255,255,255,1) 50%); opacity: 0; transition: opacity 200ms; pointer-events: none;" onclick="stopAlarm();">
+ <div>
+ <div id="time-alarm" style="color: black !important; font-size: 32vw; margin-top: 16vw; font-family: MLPFindYourSparkle, 'Google Sans', sans-serif; display: block; pointer-events: none;">--:--</div>
+ <div style="color: black !important; font-size: 2.4vw; position: absolute; text-align: center; width: 100%; left: 0; margin-top: -8vw;">Tap to dismiss alarm</div>
+ </div>
+ </div>
+
+ <div id="bg" style="position: fixed; inset: 0; background-position: center; background-size: cover; z-index: 5;"></div>
+ <div id="bg-blur" style="position: fixed; inset: 0; background-position: center; background-size: cover; z-index: 5;"></div>
+ <div id="swipe-zone" style="position: fixed; inset: 0; background-image: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.25) 100%); z-index: 10;"></div>
+ <div id="filter" style="background-color: white; position: fixed; inset: 0; z-index: 500; opacity: .1; transition: background-color 2000ms;pointer-events: none;"></div>
+ <div style="position: fixed; bottom: 3vw; left: 4.5vw; z-index: 20; text-shadow: 0 0 .5vw rgba(0, 0, 0, .5);" id="content">
+ <div id="date">---, -- ---</div>
+ <div id="time" style="font-size: 8vw; margin-top: 4vw; font-family: MLPFindYourSparkle, 'Google Sans', sans-serif; display: inline-block;" onclick="openApp('Configuration', '/apps/settings.html', false);">--:--</div>
+ <div style="display: inline-block; font-size: 3vw; margin-left: 1vw;">
+ <img style="vertical-align: middle; width: 3vw; height: 3vw;" id="weather">
+ <span style="vertical-align: middle;" id="temperature">--°</span>
+ </div>
+ <div id="alarm-coming" style="position: fixed; right: 4.5vw; bottom: 6vw; display: none;">
+ <img id="alarm-coming-icon" style="vertical-align: middle;">
+ <span id="alarm-coming-time" style="vertical-align: middle;">--:--</span>
+ </div>
+ </div>
+
+ <div id="notifications" style="position: fixed; z-index: 3000; right: 2vw; top: 2vw;"></div>
+
+ <script src="/src/appmanager.js"></script>
+ <script src="/src/color.js"></script>
+ <script src="/src/loader.js"></script>
+ <script src="/src/alarms.js"></script>
+ <script src="/src/theme.js"></script>
+ <script src="/src/notification.js"></script>
+ <script src="/src/sleep.js"></script>
+ <script src="/src/weather.js"></script>
+ <script src="/src/timedate.js"></script>
+ <script src="/src/ambienteq.js"></script>
+ <script src="/src/scroll.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/web/apps/alarms.html b/web/apps/alarms.html
new file mode 100644
index 0000000..326dd95
--- /dev/null
+++ b/web/apps/alarms.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
+ <title>app</title>
+ <style id="dynamic-theme">
+ :root {
+ --text-color: white;
+ --surface-bg: #555;
+ --elevated-bg: #777;
+ --bg: #333;
+ --on-surface: #bbb;
+ --on-elevated: #aaa;
+ }
+ </style>
+ <style>
+ @font-face {
+ src: url("/fonts/google.ttf");
+ font-family: "Google Sans";
+ }
+
+ @font-face {
+ src: url("/fonts/mlp.ttf");
+ font-family: "MLPFindYourSparkle";
+ }
+
+ * {
+ color: var(--text-color);
+ font-family: "Google Sans", sans-serif;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+
+ body {
+ font-size: 2.4vw;
+ }
+
+ input::placeholder {
+ color: var(--text-color);
+ opacity: .5;
+ }
+ </style>
+</head>
+<body style="background-color: var(--bg);">
+ <div id="settings" style="border-top: 1px solid var(--on-elevated); border-left: 1px solid var(--on-elevated); border-right: 1px solid var(--on-elevated);"></div>
+
+ <script src="/apps/settings.js"></script>
+ <script src="/src/apptheme.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/web/apps/settings.html b/web/apps/settings.html
new file mode 100644
index 0000000..326dd95
--- /dev/null
+++ b/web/apps/settings.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
+ <title>app</title>
+ <style id="dynamic-theme">
+ :root {
+ --text-color: white;
+ --surface-bg: #555;
+ --elevated-bg: #777;
+ --bg: #333;
+ --on-surface: #bbb;
+ --on-elevated: #aaa;
+ }
+ </style>
+ <style>
+ @font-face {
+ src: url("/fonts/google.ttf");
+ font-family: "Google Sans";
+ }
+
+ @font-face {
+ src: url("/fonts/mlp.ttf");
+ font-family: "MLPFindYourSparkle";
+ }
+
+ * {
+ color: var(--text-color);
+ font-family: "Google Sans", sans-serif;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+
+ body {
+ font-size: 2.4vw;
+ }
+
+ input::placeholder {
+ color: var(--text-color);
+ opacity: .5;
+ }
+ </style>
+</head>
+<body style="background-color: var(--bg);">
+ <div id="settings" style="border-top: 1px solid var(--on-elevated); border-left: 1px solid var(--on-elevated); border-right: 1px solid var(--on-elevated);"></div>
+
+ <script src="/apps/settings.js"></script>
+ <script src="/src/apptheme.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/web/apps/settings.js b/web/apps/settings.js
new file mode 100644
index 0000000..58191f0
--- /dev/null
+++ b/web/apps/settings.js
@@ -0,0 +1,35 @@
+(async () => {
+ window.settings = await (await fetch("/settings.json?_" + Math.random())).json();
+ window.oldSettings = {};
+
+ for (let setting of window.settings) {
+ oldSettings[setting.name] = localStorage.getItem(setting.name);
+ }
+
+ document.getElementById("settings").innerHTML = settings.sort((a, b) => ('' + a.name).localeCompare(b.name)).map(setting => `<div style="display: grid; grid-gap: 1vw; grid-template-columns: 1fr 1fr; border-bottom: 1px solid var(--on-elevated);">
+ <div style="border-right: 1px solid var(--on-elevated); margin-left: 1vw;">${setting.name}</div>
+ <input id="setting--${setting.name}" type="text" value="${setting.secret ? '' : (localStorage.getItem(setting.name) ?? setting.default)}" ${setting.secret ? 'placeholder="(no change)"' : ''} style="border: none; font-size: 2.4vw; outline: none; background: transparent;" spellcheck="false" autocapitalize="off" autocomplete="off" onchange="updateSetting('${setting.name}');">
+ </div>`).join("");
+})();
+
+function updateSetting(name) {
+ if (window.settings.filter(i => i.name === name)[0].secret) {
+ if (document.getElementById("setting--" + name).value.trim() !== "") {
+ localStorage.setItem(name, document.getElementById("setting--" + name).value.trim());
+ } else {
+ if (window.oldSettings[name]) {
+ localStorage.setItem(name, window.oldSettings[name]);
+ } else {
+ localStorage.setItem(name, window.settings.filter(i => i.name === name)[0].default);
+ document.getElementById("setting--" + name).value = window.settings.filter(i => i.name === name)[0].default;
+ }
+ }
+ } else {
+ if (document.getElementById("setting--" + name).value.trim() !== "") {
+ localStorage.setItem(name, document.getElementById("setting--" + name).value.trim());
+ } else {
+ localStorage.setItem(name, window.settings.filter(i => i.name === name)[0].default);
+ document.getElementById("setting--" + name).value = window.settings.filter(i => i.name === name)[0].default;
+ }
+ }
+} \ No newline at end of file
diff --git a/web/availabilitycheck.txt b/web/availabilitycheck.txt
new file mode 100644
index 0000000..6340169
--- /dev/null
+++ b/web/availabilitycheck.txt
@@ -0,0 +1 @@
+{"status": "OK"}
diff --git a/web/fonts/google.ttf b/web/fonts/google.ttf
new file mode 100755
index 0000000..e2c69c3
--- /dev/null
+++ b/web/fonts/google.ttf
Binary files differ
diff --git a/web/fonts/mlp.ttf b/web/fonts/mlp.ttf
new file mode 100644
index 0000000..ced52e8
--- /dev/null
+++ b/web/fonts/mlp.ttf
Binary files differ
diff --git a/web/icons/alarms.svg b/web/icons/alarms.svg
new file mode 100644
index 0000000..44a7485
--- /dev/null
+++ b/web/icons/alarms.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M479-82q-74 0-139.5-28t-114-76.5q-48.5-48.5-77-114T120-440.733q0-74.733 28.5-140T225.5-695q48.5-49 114-77T479-800q74 0 139.5 28T733-695q49 49 77 114.267t28 140Q838-366 810-300.5t-77 114Q684-138 618.5-110T479-82Zm0-357Zm121 161 42-42-130-130v-190h-60v214l148 148ZM214-867l42 42L92-667l-42-42 164-158Zm530 0 164 158-42 42-164-158 42-42ZM479.043-142Q604-142 691-229.043t87-212Q778-566 690.957-653t-212-87Q354-740 267-652.957t-87 212Q180-316 267.043-229t212 87Z"/></svg> \ No newline at end of file
diff --git a/web/icons/close.svg b/web/icons/close.svg
new file mode 100644
index 0000000..ded3e2a
--- /dev/null
+++ b/web/icons/close.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z"/></svg> \ No newline at end of file
diff --git a/web/icons/face.svg b/web/icons/face.svg
new file mode 100644
index 0000000..a0f8315
--- /dev/null
+++ b/web/icons/face.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M222-255q63-44 125-67.5T480-346q71 0 133.5 23.5T739-255q44-54 62.5-109T820-480q0-145-97.5-242.5T480-820q-145 0-242.5 97.5T140-480q0 61 19 116t63 109Zm257.814-195Q422-450 382.5-489.686q-39.5-39.686-39.5-97.5t39.686-97.314q39.686-39.5 97.5-39.5t97.314 39.686q39.5 39.686 39.5 97.5T577.314-489.5q-39.686 39.5-97.5 39.5Zm.654 370Q398-80 325-111.5q-73-31.5-127.5-86t-86-127.266Q80-397.532 80-480.266T111.5-635.5q31.5-72.5 86-127t127.266-86q72.766-31.5 155.5-31.5T635.5-848.5q72.5 31.5 127 86t86 127.032q31.5 72.532 31.5 155T848.5-325q-31.5 73-86 127.5t-127.032 86q-72.532 31.5-155 31.5ZM480-140q55 0 107.5-16T691-212q-51-36-104-55t-107-19q-54 0-107 19t-104 55q51 40 103.5 56T480-140Zm0-370q34 0 55.5-21.5T557-587q0-34-21.5-55.5T480-664q-34 0-55.5 21.5T403-587q0 34 21.5 55.5T480-510Zm0-77Zm0 374Z"/></svg> \ No newline at end of file
diff --git a/web/icons/installer.svg b/web/icons/installer.svg
new file mode 100644
index 0000000..eb90940
--- /dev/null
+++ b/web/icons/installer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M480-313 287-506l43-43 120 120v-371h60v371l120-120 43 43-193 193ZM220-160q-24 0-42-18t-18-42v-143h60v143h520v-143h60v143q0 24-18 42t-42 18H220Z"/></svg> \ No newline at end of file
diff --git a/web/icons/ponypush.svg b/web/icons/ponypush.svg
new file mode 100644
index 0000000..03221aa
--- /dev/null
+++ b/web/icons/ponypush.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1"
+ id="svg8" inkscape:export-filename="/home/pheckel/Code/ntfy-android/assets/launcher_full_bg.png" inkscape:export-xdpi="260.10001" inkscape:export-ydpi="260.10001" inkscape:version="1.1.1 (3bf5ae0, 2021-09-20)" sodipodi:docname="main-list-icon.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 141.7 141.7"
+ style="enable-background:new 0 0 141.7 141.7;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;stroke:#777777;stroke-width:12;stroke-miterlimit:10;}
+</style>
+<sodipodi:namedview bordercolor="#666666" borderopacity="1.0" fit-margin-bottom="0" fit-margin-left="0" fit-margin-right="0" fit-margin-top="0" id="base" inkscape:current-layer="layer1" inkscape:cx="42.40716" inkscape:cy="53.189293" inkscape:document-units="mm" inkscape:guide-bbox="true" inkscape:measure-end="0,0" inkscape:measure-start="0,0" inkscape:pagecheckerboard="0" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:rotation="-1" inkscape:snap-text-baseline="true" inkscape:window-height="1025" inkscape:window-maximized="1" inkscape:window-width="1863" inkscape:window-x="57" inkscape:window-y="27" inkscape:zoom="2.6887315" pagecolor="#ffffff" showgrid="false" showguides="false">
+ <sodipodi:guide id="guide1770" orientation="1,0" position="9.8690703,86.715698"></sodipodi:guide>
+ <sodipodi:guide id="guide1772" orientation="1,0" position="39.661132,81.074874"></sodipodi:guide>
+ <sodipodi:guide id="guide1774" orientation="0,-1" position="9.8690703,58.786381"></sodipodi:guide>
+ <sodipodi:guide id="guide1776" orientation="0,-1" position="-2.6121775,28.943566"></sodipodi:guide>
+ <sodipodi:guide id="guide4020" orientation="1,0" position="14.686182,55.195651"></sodipodi:guide>
+ <sodipodi:guide id="guide4022" orientation="1,0" position="34.626283,58.786381"></sodipodi:guide>
+ <sodipodi:guide id="guide4024" orientation="0,-1" position="12.398156,51.002016"></sodipodi:guide>
+ <sodipodi:guide id="guide4026" orientation="0,-1" position="11.073267,36.978591"></sodipodi:guide>
+</sodipodi:namedview>
+<path class="st0" d="M13.2,46.1c0,0,58.1-28.7,111.3-20.8c0,0,32.2,49-18.6,91.9c0,0-37.6-14.6-92.7,3.8"/>
+</svg>
diff --git a/web/icons/settings.svg b/web/icons/settings.svg
new file mode 100644
index 0000000..dc0d1ec
--- /dev/null
+++ b/web/icons/settings.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M427-120v-225h60v83h353v60H487v82h-60Zm-307-82v-60h247v60H120Zm187-166v-82H120v-60h187v-84h60v226h-60Zm120-82v-60h413v60H427Zm166-165v-225h60v82h187v60H653v83h-60Zm-473-83v-60h413v60H120Z"/></svg> \ No newline at end of file
diff --git a/web/icons/system.svg b/web/icons/system.svg
new file mode 100644
index 0000000..23ace3d
--- /dev/null
+++ b/web/icons/system.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M480-200q-99 0-169.5-13.5T240-246v-34h-95q-26.145 0-44.072-19.5Q83-319 85-345l31-360q2-23 18.808-39 16.807-16 40.192-16h610q23.385 0 40.192 16Q842-728 844-705l31 360q2 26-15.928 45.5Q841.145-280 815-280h-95v34q0 19-70.5 32.5T480-200ZM145-340h670l-30-360H175l-30 360Zm335-180Z"/></svg> \ No newline at end of file
diff --git a/web/icons/weather.svg b/web/icons/weather.svg
new file mode 100644
index 0000000..2175d03
--- /dev/null
+++ b/web/icons/weather.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M450-770v-150h60v150h-60Zm256 106-42-42 106-107 42 43-106 106Zm64 214v-60h150v60H770ZM450-40v-150h60v150h-60ZM253-665 148-770l42-42 106 106-43 41Zm518 517L664-254l41-41 108 104-42 43ZM40-450v-60h150v60H40Zm151 302-43-42 105-105 22 20 22 21-106 106Zm289-92q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-60q75 0 127.5-52.5T660-480q0-75-52.5-127.5T480-660q-75 0-127.5 52.5T300-480q0 75 52.5 127.5T480-300Zm0-180Z"/></svg> \ No newline at end of file
diff --git a/web/settings.json b/web/settings.json
new file mode 100644
index 0000000..036ef4e
--- /dev/null
+++ b/web/settings.json
@@ -0,0 +1,72 @@
+[
+ {
+ "name": "weather_owm_key",
+ "secret": true,
+ "default": ""
+ },
+ {
+ "name": "sleep_trigger_brightness",
+ "secret": false,
+ "default": "8"
+ },
+ {
+ "name": "sleep_inhibit_duration",
+ "secret": false,
+ "default": "30000"
+ },
+ {
+ "name": "sleep_minimum_opacity",
+ "secret": false,
+ "default": "0.15"
+ },
+ {
+ "name": "ambienteq_brightness_add",
+ "secret": false,
+ "default": "15"
+ },
+ {
+ "name": "ambienteq_add_whilesleep",
+ "secret": false,
+ "default": "false"
+ },
+ {
+ "name": "ambienteq_brightness_enable",
+ "secret": false,
+ "default": "true"
+ },
+ {
+ "name": "ambienteq_filter_enable",
+ "secret": false,
+ "default": "true"
+ },
+ {
+ "name": "notification_duration",
+ "secret": false,
+ "default": "10000"
+ },
+ {
+ "name": "sleep_disable_wakelock",
+ "secret": false,
+ "default": "false"
+ },
+ {
+ "name": "ui_deploy_blur",
+ "secret": false,
+ "default": "true"
+ },
+ {
+ "name": "ui_deploy_trigger",
+ "secret": false,
+ "default": "50"
+ },
+ {
+ "name": "weather_refresh_interval",
+ "secret": false,
+ "default": "60000"
+ },
+ {
+ "name": "weather_iplocation_fallback",
+ "secret": false,
+ "default": "true"
+ }
+] \ No newline at end of file
diff --git a/web/src/alarms.js b/web/src/alarms.js
new file mode 100644
index 0000000..7edaf1a
--- /dev/null
+++ b/web/src/alarms.js
@@ -0,0 +1,78 @@
+window.alarmShown = false;
+
+(async () => {
+ window.alarmRingtones = [
+ new Audio(URL.createObjectURL(new Blob([await (await fetch("/alarms/Awaken.ogg")).arrayBuffer()], { type: "audio/ogg" }))),
+ new Audio(URL.createObjectURL(new Blob([await (await fetch("/alarms/Bright_morning.ogg")).arrayBuffer()], { type: "audio/ogg" }))),
+ new Audio(URL.createObjectURL(new Blob([await (await fetch("/alarms/Fresh_start.ogg")).arrayBuffer()], { type: "audio/ogg" }))),
+ new Audio(URL.createObjectURL(new Blob([await (await fetch("/alarms/Your_new_adventure.ogg")).arrayBuffer()], { type: "audio/ogg" })))
+ ];
+
+ for (let alarm of window.alarmRingtones) {
+ alarm.loop = true;
+ }
+})();
+
+window.loadingItems.push(() => {
+ // TODO: Actually load alarms
+ window.alarms = [
+ {
+ enabled: true,
+ time: new Date(new Date().getTime() + 30000).getTime() - new Date(new Date().toISOString().split("T")[0]).getTime(),
+ repeat: 0b1111111,
+ alarm: 3
+ },
+ {
+ enabled: true,
+ time: new Date(new Date().getTime() + 60000).getTime() - new Date(new Date().toISOString().split("T")[0]).getTime(),
+ repeat: 0b1111100,
+ alarm: 2
+ }
+ ];
+
+ refreshAlarms();
+
+ setInterval(() => {
+ let list = window.alarms.filter(i => i.enabled).sort((a, b) => a.time - b.time);
+ let alarm = 0;
+
+ if (!list[alarm] || !list[alarm].time) return;
+
+ while (new Date().getTime() - (list[alarm].time + new Date(new Date().toISOString().split("T")[0]).getTime()) > 1000) {
+ alarm++;
+ }
+
+ if (new Date().getTime() - (list[alarm].time + new Date(new Date().toISOString().split("T")[0]).getTime()) > 0 && new Date().getTime() - (list[alarm].time + new Date(new Date().toISOString().split("T")[0]).getTime()) <= 1000) {
+ startAlarm(list[alarm].alarm ?? 3);
+ list[alarm].enabled = false;
+ refreshAlarms();
+ }
+ }, 1000);
+});
+
+function startAlarm(ringtone) {
+ window.alarmShown = true;
+ document.getElementById("alarm").classList.add("show");
+ window.alarmRingtones[ringtone].play();
+}
+
+function stopAlarm() {
+ window.alarmShown = false;
+ document.getElementById("alarm").classList.remove("show");
+
+ for (let alarm of window.alarmRingtones) {
+ alarm.pause();
+ alarm.currentTime = 0;
+ }
+}
+
+function refreshAlarms() {
+ if (window.alarms.filter(i => i.enabled).length > 0) {
+ let nextAlarmTime = alarms.filter(i => i.enabled).sort((a, b) => a.time - b.time)[0].time + new Date(new Date().toISOString().split("T")[0]).getTime();
+ document.getElementById("alarm-coming-time").innerText = fixed(new Date(nextAlarmTime).getHours(), 2) + ":" + fixed(new Date(nextAlarmTime).getMinutes(), 2);
+ document.getElementById("alarm-coming").style.display = "";
+ } else {
+ document.getElementById("alarm-coming-time").innerText = "--:--";
+ document.getElementById("alarm-coming").style.display = "none";
+ }
+} \ No newline at end of file
diff --git a/web/src/ambienteq.js b/web/src/ambienteq.js
new file mode 100644
index 0000000..81acb65
--- /dev/null
+++ b/web/src/ambienteq.js
@@ -0,0 +1,103 @@
+window.loadingItems.push(() => {
+ const width = 320;
+ let height = 0;
+ let streaming = false;
+
+ let video = null;
+ let canvas = null;
+ let photo = null;
+
+ function startup() {
+ video = document.getElementById("video");
+ canvas = document.getElementById("canvas");
+ photo = document.getElementById("photo");
+
+ if (localStorage.getItem("ambienteq_filter_enable") === "false") {
+ document.getElementById("filter").style.display = "none";
+ return;
+ }
+
+ navigator.mediaDevices
+ .getUserMedia({ video: true, audio: false })
+ .then((stream) => {
+ video.srcObject = stream;
+ video.play();
+ })
+ .catch(async (err) => {
+ console.error(`An error occurred: ${err}`);
+ document.getElementById("filter").style.display = "none";
+ addWarning("[CAMERA_ERROR] Unable to use camera, ambient lighting will be unavailable. See console for details.");
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access camera", "Face unlock and Ambient Brightness will not work", null, true);
+ });
+
+ video.addEventListener(
+ "canplay",
+ (ev) => {
+ if (!streaming) {
+ height = video.videoHeight / (video.videoWidth / width);
+
+ video.setAttribute("width", width);
+ video.setAttribute("height", height);
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+ streaming = true;
+ }
+ },
+ false,
+ );
+
+ clearphoto();
+ }
+
+ function clearphoto() {
+ const context = canvas.getContext("2d");
+ context.fillStyle = "#AAA";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+
+ const data = canvas.toDataURL("image/png");
+ photo.setAttribute("src", data);
+ }
+
+ function takepicture() {
+ const context = canvas.getContext("2d");
+
+ if (width && height) {
+ canvas.width = width;
+ canvas.height = height;
+ context.drawImage(video, 0, 0, width, height);
+
+ const data = canvas.toDataURL("image/png");
+ photo.setAttribute("src", data);
+ } else {
+ clearphoto();
+ }
+ }
+
+ window.addEventListener("load", startup, false);
+
+ setInterval(() => {
+ takepicture();
+
+ if (window.engine && localStorage.getItem("ambienteq_brightness_enable") === "true") {
+ if (!window.sleeping || localStorage.getItem("ambienteq_add_whilesleep") === "true") {
+ if (Math.abs(window.engine.getBrightness() - window.engine.getSystemBrightness()) > 10) window.engine.setBrightness(window.engine.getBrightness() + parseInt(localStorage.getItem("ambianteq_brightness_add")));
+ } else {
+ if (Math.abs(window.engine.getBrightness() - window.engine.getSystemBrightness()) > 10) window.engine.setBrightness(window.engine.getBrightness());
+ }
+ }
+ }, 2000);
+
+ document.getElementById("photo").onload = () => {
+ let rgb = Object.values(getAverageRGB(document.getElementById("photo")));
+ document.getElementById("rgb").innerText = rgb2hue(rgb[0], rgb[1], rgb[2]).toFixed(2);
+ document.getElementById("sq1").style.backgroundColor = "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
+ document.getElementById("sq2").style.backgroundColor = document.getElementById("filter").style.backgroundColor = "hsl(" + rgb2hue(rgb[0], rgb[1], rgb[2]) + "deg, 100%, 50%)";
+
+ let opacity = (rgb.reduce((a, b) => a + b) / rgb.length) / 255;
+ if (opacity < parseFloat(localStorage.getItem("sleep_minimum_opacity"))) opacity = parseFloat(localStorage.getItem("sleep_minimum_opacity"));
+
+ document.getElementById("time-sleep").style.opacity = opacity.toString();
+ }
+
+ startup();
+}); \ No newline at end of file
diff --git a/web/src/appmanager.js b/web/src/appmanager.js
new file mode 100644
index 0000000..3f0b027
--- /dev/null
+++ b/web/src/appmanager.js
@@ -0,0 +1,28 @@
+window.appManagerCloseTimeout = null;
+window.appOpen = false;
+
+function closeApp() {
+ appOpen = false;
+ document.getElementById("app").classList.remove("show-native");
+ document.getElementById("app").classList.remove("show");
+
+ if (window.engine) window.engine.closeApp();
+
+ window.appManagerCloseTimeout = setTimeout(() => {
+ document.getElementById("app-header-title").innerText = "";
+ document.getElementById("app-frame").src = "about:blank";
+ }, 250);
+}
+
+function openApp(title, url, external) {
+ appOpen = true;
+ document.getElementById("app-header-title").innerText = title;
+ document.getElementById("app").classList.add("show");
+
+ if (window.engine && external) {
+ document.getElementById("app").classList.add("show-native");
+ window.engine.startApp(url);
+ } else {
+ document.getElementById("app-frame").src = url;
+ }
+} \ No newline at end of file
diff --git a/web/src/apptheme.js b/web/src/apptheme.js
new file mode 100644
index 0000000..205f74b
--- /dev/null
+++ b/web/src/apptheme.js
@@ -0,0 +1,12 @@
+window.dynamicColor = window.parent.dynamicColor;
+
+document.getElementById("dynamic-theme").innerText = `
+:root {
+ --text-color: #${window.dynamicColor.text};
+ --surface-bg: #${window.dynamicColor.surface};
+ --elevated-bg: #${window.dynamicColor.elevated};
+ --bg: #${window.dynamicColor.bg};
+ --on-surface: #${window.dynamicColor.onSurface};
+ --on-elevated: #${window.dynamicColor.onElevated};
+}
+`; \ No newline at end of file
diff --git a/web/src/color.js b/web/src/color.js
new file mode 100644
index 0000000..322b0ed
--- /dev/null
+++ b/web/src/color.js
@@ -0,0 +1,119 @@
+function getAverageRGB(imgEl) {
+ let blockSize = 5,
+ defaultRGB = {r:0,g:0,b:0},
+ canvas = document.createElement('canvas'),
+ context = canvas.getContext && canvas.getContext('2d'),
+ data, width, height,
+ i = -4,
+ length,
+ rgb = {r:0,g:0,b:0},
+ count = 0;
+
+ if (!context) {
+ return null;
+ }
+
+ height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
+ width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
+
+ context.drawImage(imgEl, 0, 0);
+
+ try {
+ data = context.getImageData(0, 0, width, height);
+ } catch(e) {
+ console.error(e);
+ return null;
+ }
+
+ length = data.data.length;
+
+ while ( (i += blockSize * 4) < length ) {
+ ++count;
+ rgb.r += data.data[i];
+ rgb.g += data.data[i+1];
+ rgb.b += data.data[i+2];
+ }
+
+ rgb.r = ~~(rgb.r/count);
+ rgb.g = ~~(rgb.g/count);
+ rgb.b = ~~(rgb.b/count);
+
+ return rgb;
+}
+
+function rgb2hue(r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+
+ let max = Math.max(r, g, b);
+ let min = Math.min(r, g, b);
+ let c = max - min;
+ let hue;
+ let segment;
+ let shift;
+
+ if (c === 0) {
+ hue = 0;
+ } else {
+ switch(max) {
+ case r:
+ segment = (g - b) / c;
+ shift = 0 / 60;
+ if (segment < 0) {
+ shift = 360 / 60;
+ }
+ hue = segment + shift;
+ break;
+ case g:
+ segment = (b - r) / c;
+ shift = 120 / 60;
+ hue = segment + shift;
+ break;
+ case b:
+ segment = (r - g) / c;
+ shift = 240 / 60;
+ hue = segment + shift;
+ break;
+ }
+ }
+
+ return hue * 60;
+}
+
+function hslToRgb(h, s, l) {
+ let r, g, b;
+
+ if (s === 0){
+ r = g = b = l;
+ } else {
+ let hue2rgb = function hue2rgb(p, q, t) {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1/6) return p + (q - p) * 6 * t;
+ if (t < 1/2) return q;
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ }
+
+ let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ let p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3);
+ }
+
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+}
+
+function rgbToHex(r, g, b) {
+ let rh = r.toString(16);
+ let gh = g.toString(16);
+ let bh = b.toString(16);
+
+ if (rh.length < 2) rh = "0" + rh;
+ if (gh.length < 2) gh = "0" + gh;
+ if (bh.length < 2) bh = "0" + bh;
+
+ return rh + gh + bh;
+} \ No newline at end of file
diff --git a/web/src/loader.js b/web/src/loader.js
new file mode 100644
index 0000000..a227a3b
--- /dev/null
+++ b/web/src/loader.js
@@ -0,0 +1,7 @@
+window.loadingItems = [];
+
+function load() {
+ for (let item of loadingItems) {
+ item();
+ }
+} \ No newline at end of file
diff --git a/web/src/notification.js b/web/src/notification.js
new file mode 100644
index 0000000..1a7841a
--- /dev/null
+++ b/web/src/notification.js
@@ -0,0 +1,33 @@
+function sendNotification(app, icon, title, description, image, persistent) {
+ let id = crypto.randomUUID();
+
+ document.getElementById("notifications").innerHTML = `
+ <div class="notification" id="notification-${id}" style="margin-bottom: 1.5vw; transition: opacity 500ms; opacity: 0;">
+ <div class="notification-header">
+ <img class="notification-icon" ${icon ? `src="${icon}"` : ""}>
+ <span class="notification-application">${app ?? "System"}</span>
+ </div>
+ <div class="notification-content">
+ ${title ? `<div class="notification-title">${title}</div>` : ""}
+ ${description ? `<div class="notification-text">${description}</div>` : ""}
+ ${image ? `<img alt="Image." src="${image}" class="notification-image">` : ""}
+ </div>
+ </div>
+ ` + document.getElementById("notifications").innerHTML;
+
+ setTimeout(() => {
+ document.getElementById("notification-" + id).style.opacity = "1";
+
+ if (!persistent) {
+ setTimeout(() => {
+ document.getElementById("notification-" + id).style.opacity = "0";
+
+ setTimeout(() => {
+ document.getElementById("notification-" + id).outerHTML = "";
+ }, 500);
+ }, parseInt(localStorage.getItem("notification_duration")));
+ }
+ }, 100);
+
+ return id;
+} \ No newline at end of file
diff --git a/web/src/scroll.js b/web/src/scroll.js
new file mode 100644
index 0000000..8ea3bb8
--- /dev/null
+++ b/web/src/scroll.js
@@ -0,0 +1,111 @@
+let tracking = false;
+let startY = 0;
+
+document.getElementById("swipe-zone").onmousedown = document.getElementById("swipe-zone").ontouchstart = (event) => {
+ tracking = true;
+ startY = (event.touches ? event.touches[0].clientY : event.clientY);
+
+ document.getElementById("container").classList.remove("backing");
+ document.getElementById("container").classList.remove("undeploying");
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.backdropFilter = "";
+}
+
+document.getElementById("swipe-zone").onmouseup = document.getElementById("swipe-zone").ontouchcancel = document.getElementById("swipe-zone").ontouchend = document.getElementById("swipe-zone").onmouseleave = (event) => {
+ tracking = false;
+
+ if (parseInt(document.getElementById("container").style.top.split("vh")[0]) > -(parseInt(localStorage.getItem("ui_deploy_trigger")))) {
+ document.getElementById("container").style.top = "0vh";
+ document.getElementById("container").classList.add("deployed");
+ } else {
+ document.getElementById("container").style.top = "-100vh";
+ document.getElementById("container").classList.add("undeploying");
+ document.body.classList.remove("deploying");
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.backdropFilter = "";
+ }
+}
+
+document.getElementById("swipe-zone").onmousemove = document.getElementById("swipe-zone").ontouchmove = (event) => {
+ if (tracking) {
+ let triggerHeight = window.innerHeight / 2;
+
+ let difference = (event.touches ? event.touches[0].clientY : event.clientY) - startY;
+ if (difference < 0) difference = 0;
+
+ let percentage = difference / triggerHeight;
+ if (percentage > 1) percentage = 1;
+
+ console.log((percentage * 100) + "vh", difference, triggerHeight);
+ document.getElementById("container").style.top = -(100 - (percentage * 100)) + "vh";
+
+ if (percentage > .05) {
+ let scaleSubtracter = percentage / 2;
+ let blurSubtracter = (percentage / 4) * 100;
+
+ document.body.classList.add("deploying");
+ document.getElementById("bg").style.opacity = (1 - scaleSubtracter).toString();
+ if (localStorage.getItem("ui_deploy_blur") === "true") document.getElementById("bg-blur").style.backdropFilter = "blur(" + blurSubtracter + "px)";
+ } else {
+ document.body.classList.remove("deploying");
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.transform = "";
+ }
+ }
+}
+
+document.getElementById("container").onmousedown = document.getElementById("container").ontouchstart = (event) => {
+ tracking = true;
+ startY = (event.touches ? event.touches[0].clientY : event.clientY);
+
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.backdropFilter = "";
+}
+
+document.getElementById("container").onmouseup = document.getElementById("container").onmouseleave = document.getElementById("container").ontouchend = document.getElementById("container").ontouchcancel = (event) => {
+ tracking = false;
+ document.getElementById("container").classList.remove("backing");
+
+ if (parseInt(document.getElementById("container").style.top.split("vh")[0]) > -(parseInt(localStorage.getItem("ui_deploy_trigger")))) {
+ document.getElementById("container").style.top = "0vh";
+ document.getElementById("container").classList.add("deployed");
+ } else {
+ document.getElementById("container").style.top = "-100vh";
+ document.getElementById("container").classList.add("undeploying");
+ document.body.classList.remove("deploying");
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.backdropFilter = "";
+ }
+}
+
+document.getElementById("container").onmousemove = document.getElementById("container").ontouchmove = (event) => {
+ if (tracking) {
+ document.getElementById("container").classList.remove("deployed");
+ document.getElementById("container").classList.add("backing");
+
+ let triggerHeight = window.innerHeight / 2;
+
+ let difference = startY - (event.touches ? event.touches[0].clientY : event.clientY);
+ if (difference < 0) difference = 0;
+
+ let percentage = difference / triggerHeight;
+ if (percentage > 1) percentage = 1;
+ percentage = 1 - percentage;
+
+ console.log((percentage * 100) + "vh", -(100 - (percentage * 100)) + "vh", difference, triggerHeight);
+ document.getElementById("container").style.top = -(100 - (percentage * 100)) + "vh";
+
+ if (percentage > .05) {
+ let scaleSubtracter = percentage / 2;
+ let blurSubtracter = (percentage / 4) * 100;
+
+ document.body.classList.add("deploying");
+ document.getElementById("bg").style.opacity = (1 - scaleSubtracter).toString();
+ if (localStorage.getItem("ui_deploy_blur") === "true") document.getElementById("bg-blur").style.backdropFilter = "blur(" + blurSubtracter + "px)";
+ } else {
+ document.body.classList.remove("deploying");
+ document.getElementById("bg").style.opacity = "";
+ document.getElementById("bg-blur").style.transform = "";
+ }
+ }
+} \ No newline at end of file
diff --git a/web/src/sleep.js b/web/src/sleep.js
new file mode 100644
index 0000000..43f6f47
--- /dev/null
+++ b/web/src/sleep.js
@@ -0,0 +1,57 @@
+window.lastInteraction = new Date().getTime();
+window.inhibitSleep = false;
+window.sleeping = false;
+
+document.body.onmousemove = document.body.onclick = document.body.ontouchmove = document.body.ontouchstart = () => {
+ window.lastInteraction = new Date().getTime();
+}
+
+setInterval(() => {
+ if (new Date().getTime() - window.lastInteraction > parseInt(localStorage.getItem("sleep_trigger_brightness"))) {
+ window.inhibitSleep = false;
+ document.getElementById("sleep").classList.remove("inhibited");
+ }
+});
+
+function startInhibitSleep() {
+ window.sleeping = false;
+ document.getElementById("sleep").classList.add("inhibited");
+ document.getElementById("sleep").classList.remove("show");
+ window.inhibitSleep = true;
+}
+
+window.loadingItems.push(() => {
+ let hasBrightness = false;
+ let brightnessError = "INTERNAL_ERROR";
+
+ if (window.engine && window.engine.getBrightness && window.engine.getSystemBrightness && window.engine.setBrightness) {
+ try {
+ window.engine.getBrightness();
+ hasBrightness = true;
+ } catch (e) {
+ brightnessError = "INVALID_VALUE";
+ }
+ } else if (window.engine) {
+ brightnessError = "INVALID_NATIVE_INTEGRATION";
+ } else {
+ brightnessError = "NATIVE_INTEGRATION_NOTFOUND";
+ }
+
+ if (!hasBrightness) {
+ addWarning("[" + brightnessError + "] Unable to use light sensor, sleep mode and auto-brightness will be unavailable.");
+
+ (async () => {
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access light sensor", "Your display will not adjust its content based on ambient light", null, true);
+ })();
+ }
+
+ setInterval(() => {
+ if (hasBrightness && window.engine.getBrightness() < parseInt(localStorage.getItem("sleep_trigger_brightness")) && !window.inhibitSleep && !(window.appOpen || localStorage.getItem("sleep_disable_wakelock") === "true" || window.alarmShown)) {
+ window.sleeping = true;
+ document.getElementById("sleep").classList.add("show");
+ } else {
+ window.sleeping = false;
+ document.getElementById("sleep").classList.remove("show");
+ }
+ });
+}); \ No newline at end of file
diff --git a/web/src/theme.js b/web/src/theme.js
new file mode 100644
index 0000000..d2d15b9
--- /dev/null
+++ b/web/src/theme.js
@@ -0,0 +1,63 @@
+window.loaded = false;
+
+function setBackground(url) {
+ const img = document.createElement("img");
+
+ img.onload = () => {
+ document.getElementById("bg").style.backgroundImage = 'url("' + url.replaceAll('"', '\\"') + '")';
+
+ let rgb = getAverageRGB(img);
+ let hue = rgb2hue(rgb.r, rgb.g, rgb.b);
+
+ window.dynamicColor = {
+ _raw: {...rgb, hue: hue},
+ text: rgbToHex(...hslToRgb(hue / 360, 0.35, 0.85)),
+ surface: rgbToHex(...hslToRgb(hue / 360, 0.35, 0.25)),
+ bg: rgbToHex(...hslToRgb(hue / 360, 0.35, 0.15)),
+ elevated: rgbToHex(...hslToRgb(hue / 360, 0.35, 0.35)),
+ onSurface: rgbToHex(...hslToRgb(hue / 360, 0.45, 0.75)),
+ onElevated: rgbToHex(...hslToRgb(hue / 360, 0.45, 0.65))
+ }
+
+ document.getElementById("dynamic-theme").innerText = `
+ :root {
+ --text-color: #${window.dynamicColor.text};
+ --surface-bg: #${window.dynamicColor.surface};
+ --elevated-bg: #${window.dynamicColor.elevated};
+ --bg: #${window.dynamicColor.bg};
+ --on-surface: #${window.dynamicColor.onSurface};
+ --on-elevated: #${window.dynamicColor.onElevated};
+ }
+ `;
+
+ (async () => {
+ document.getElementById("app-header-icon").src = await renderIcon("close", false);
+ document.getElementById("alarm-coming-icon").src = await renderIcon("alarms", false);
+ document.getElementById("app-6-icon-img").src = await renderIcon("settings", true);
+ document.getElementById("app-1-icon-img").src = await renderIcon("installer", true);
+ document.getElementById("app-2-icon-img").src = await renderIcon("alarms", true);
+ document.getElementById("app-3-icon-img").src = await renderIcon("face", true);
+ document.getElementById("app-5-icon-img").src = await renderIcon("weather", true);
+ document.getElementById("app-4-icon-img").src = await renderIcon("ponypush", true);
+
+ if (!window.loaded) {
+ let defaults = await (await fetch("/settings.json?_" + Math.random())).json();
+
+ for (let setting of defaults) {
+ if (!localStorage.getItem(setting.name)) localStorage.setItem(setting.name, setting.default);
+ }
+
+ load();
+ window.loaded = true;
+ }
+ })();
+ }
+
+ img.src = url;
+}
+
+async function renderIcon(icon, surfaced) {
+ return "data:image/svg+xml;base64," + btoa((await (await fetch("/icons/" + icon + ".svg")).text()).replace('width="48">', 'width="48" fill="#' + (surfaced ? window.dynamicColor.surface : window.dynamicColor.text) + '">').replace('#777777', '#' + (surfaced ? window.dynamicColor.surface : window.dynamicColor.text)));
+}
+
+setBackground("/img/2023/8/12/3181099.jpg"); \ No newline at end of file
diff --git a/web/src/timedate.js b/web/src/timedate.js
new file mode 100644
index 0000000..5930727
--- /dev/null
+++ b/web/src/timedate.js
@@ -0,0 +1,16 @@
+function fixed(number, digits) {
+ number = Math.round(number);
+ return "0".repeat(digits).substring(0, digits - number.toString().length) + number.toString();
+}
+
+document.getElementById("time").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+document.getElementById("time-sleep").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+document.getElementById("time-alarm").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+document.getElementById("date").innerText = (new Date().toDateString().split(" "))[0] + ", " + (new Date().toDateString().split(" "))[2] + " " + (new Date().toDateString().split(" "))[1];
+
+setInterval(() => {
+ document.getElementById("time").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+ document.getElementById("time-sleep").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+ document.getElementById("time-alarm").innerText = fixed(new Date().getHours(), 2) + ":" + fixed(new Date().getMinutes(), 2);
+ document.getElementById("date").innerText = (new Date().toDateString().split(" "))[0] + ", " + (new Date().toDateString().split(" "))[2] + " " + (new Date().toDateString().split(" "))[1];
+}, 1000); \ No newline at end of file
diff --git a/web/src/weather.js b/web/src/weather.js
new file mode 100644
index 0000000..666ac4c
--- /dev/null
+++ b/web/src/weather.js
@@ -0,0 +1,57 @@
+window.loadingItems.push(() => {
+ window.latitude = 0;
+ window.longitude = 0;
+
+ navigator.geolocation.getCurrentPosition(async (pos) => {
+ window.latitude = pos.coords.latitude;
+ window.longitude = pos.coords.longitude;
+
+ setInterval(() => {
+ refreshWeather();
+ }, parseInt(localStorage.getItem("weather_refresh_interval")));
+
+ refreshWeather();
+ }, async (error) => {
+ switch(error.code) {
+ case error.PERMISSION_DENIED:
+ addWarning("[GEO_PERMISSION_DENIED] Permission to use location was denied, weather will be unavailable.");
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access location", "Permission to access your location was denied", null, true);
+ break;
+ case error.POSITION_UNAVAILABLE:
+ addWarning("[GEO_POSITION_UNAVAILABLE] Gathering a location failed, weather will be unavailable.");
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access location", "Ponyplay was unable to figure out your physical position", null, true);
+ break;
+ case error.TIMEOUT:
+ addWarning("[GEO_TIMEOUT] Gathering a location timed out, weather will be unavailable.");
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access location", "Ponyplay was unable to access your location in time", null, true);
+ break;
+ default:
+ addWarning("[GEO_UNKNOWN_ERROR] An unknown error occurred while gathering a location, weather will be unavailable.");
+ if (navigator.userAgent.includes("Ponyplay/")) sendNotification("Ponyplay System", await renderIcon("system", true), "Unable to access location", "An unknown error occurred while gathering your location", null, true);
+ break;
+ }
+
+ if (localStorage.getItem("weather_iplocation_fallback") === "true") {
+ let location = (await (await fetch("https://ipinfo.io/json")).json())['loc'].split(",").map(i => parseFloat(i));
+
+ window.latitude = location[0];
+ window.longitude = location[1];
+
+ setInterval(() => {
+ refreshWeather();
+ }, parseInt(localStorage.getItem("weather_refresh_interval")));
+
+ refreshWeather();
+ }
+ });
+
+ async function refreshWeather() {
+ try {
+ window.weather = await (await fetch("https://api.openweathermap.org/data/2.5/weather?lat=" + window.latitude + "&lon=" + window.longitude + "&appid=" + localStorage.getItem("openweathermap_api_key") + "&units=metric")).json();
+ window.weatherIcons = await (await fetch("/weather/list.json")).json();
+
+ document.getElementById("temperature").innerText = Math.round(window.weather['main']['temp']) + "°";
+ document.getElementById("weather").src = "/weather/" + (window.weatherIcons.includes(window.weather.weather[0].id + window.weather.weather[0].icon.substring(2, 3) + ".png") ? window.weather.weather[0].id + window.weather.weather[0].icon.substring(2, 3) + ".png" : window.weather.weather[0].icon + ".png");
+ } catch (e) {}
+ }
+}); \ No newline at end of file
diff --git a/web/test.html b/web/test.html
new file mode 100644
index 0000000..c7b57e3
--- /dev/null
+++ b/web/test.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>app</title>
+</head>
+<body>
+ <h1 id="message">JavaScript</h1>
+ <p>
+ <textarea id="input" autocomplete="off" autocapitalize="off"></textarea>
+ </p>
+ <button onclick="run();">Run</button>
+ <hr>
+ <pre id="output"></pre>
+ <hr>
+ <video autoplay id="preview" src="/test.webm" controls style="width: 500px; height: 200px;"></video>
+
+ <script>
+ function run() {
+ try {
+ document.getElementById("output").innerText = eval(document.getElementById("input").value);
+ } catch (e) {
+ document.getElementById("output").innerText = e.stack;
+ }
+ }
+
+ /*navigator.mediaDevices.getUserMedia({ video: {} })
+ .then(function(mediaStream) {
+ let video = document.querySelector('video');
+ video.srcObject = mediaStream;
+ })
+ .catch(function(err) { document.getElementById("message").innerText = err.name + ": " + err.message; });
+
+ setInterval(() => {
+ if (document.querySelector('video').paused) {
+ document.querySelector('video').play();
+ }
+ })*/
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/web/weather/01d.png b/web/weather/01d.png
new file mode 100644
index 0000000..22fd67f
--- /dev/null
+++ b/web/weather/01d.png
Binary files differ
diff --git a/web/weather/01n.png b/web/weather/01n.png
new file mode 100644
index 0000000..289d923
--- /dev/null
+++ b/web/weather/01n.png
Binary files differ
diff --git a/web/weather/02d.png b/web/weather/02d.png
new file mode 100644
index 0000000..3aa2038
--- /dev/null
+++ b/web/weather/02d.png
Binary files differ
diff --git a/web/weather/02n.png b/web/weather/02n.png
new file mode 100644
index 0000000..3a785aa
--- /dev/null
+++ b/web/weather/02n.png
Binary files differ
diff --git a/web/weather/03d.png b/web/weather/03d.png
new file mode 100644
index 0000000..6371747
--- /dev/null
+++ b/web/weather/03d.png
Binary files differ
diff --git a/web/weather/03n.png b/web/weather/03n.png
new file mode 100644
index 0000000..19b80d7
--- /dev/null
+++ b/web/weather/03n.png
Binary files differ
diff --git a/web/weather/04d.png b/web/weather/04d.png
new file mode 100644
index 0000000..20d9245
--- /dev/null
+++ b/web/weather/04d.png
Binary files differ
diff --git a/web/weather/04n.png b/web/weather/04n.png
new file mode 100644
index 0000000..20d9245
--- /dev/null
+++ b/web/weather/04n.png
Binary files differ
diff --git a/web/weather/09d.png b/web/weather/09d.png
new file mode 100644
index 0000000..c3daa01
--- /dev/null
+++ b/web/weather/09d.png
Binary files differ
diff --git a/web/weather/09n.png b/web/weather/09n.png
new file mode 100644
index 0000000..c3daa01
--- /dev/null
+++ b/web/weather/09n.png
Binary files differ
diff --git a/web/weather/10d.png b/web/weather/10d.png
new file mode 100644
index 0000000..2bb071a
--- /dev/null
+++ b/web/weather/10d.png
Binary files differ
diff --git a/web/weather/10n.png b/web/weather/10n.png
new file mode 100644
index 0000000..8073e99
--- /dev/null
+++ b/web/weather/10n.png
Binary files differ
diff --git a/web/weather/11d.png b/web/weather/11d.png
new file mode 100644
index 0000000..a5784a9
--- /dev/null
+++ b/web/weather/11d.png
Binary files differ
diff --git a/web/weather/11n.png b/web/weather/11n.png
new file mode 100644
index 0000000..acc1dbb
--- /dev/null
+++ b/web/weather/11n.png
Binary files differ
diff --git a/web/weather/13d.png b/web/weather/13d.png
new file mode 100644
index 0000000..d3d1ba6
--- /dev/null
+++ b/web/weather/13d.png
Binary files differ
diff --git a/web/weather/13n.png b/web/weather/13n.png
new file mode 100644
index 0000000..d3d1ba6
--- /dev/null
+++ b/web/weather/13n.png
Binary files differ
diff --git a/web/weather/202d.png b/web/weather/202d.png
new file mode 100644
index 0000000..bcdc1e0
--- /dev/null
+++ b/web/weather/202d.png
Binary files differ
diff --git a/web/weather/202n.png b/web/weather/202n.png
new file mode 100644
index 0000000..bcdc1e0
--- /dev/null
+++ b/web/weather/202n.png
Binary files differ
diff --git a/web/weather/211d.png b/web/weather/211d.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/211d.png
Binary files differ
diff --git a/web/weather/211n.png b/web/weather/211n.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/211n.png
Binary files differ
diff --git a/web/weather/212d.png b/web/weather/212d.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/212d.png
Binary files differ
diff --git a/web/weather/212n.png b/web/weather/212n.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/212n.png
Binary files differ
diff --git a/web/weather/221d.png b/web/weather/221d.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/221d.png
Binary files differ
diff --git a/web/weather/221n.png b/web/weather/221n.png
new file mode 100644
index 0000000..15a15e4
--- /dev/null
+++ b/web/weather/221n.png
Binary files differ
diff --git a/web/weather/232d.png b/web/weather/232d.png
new file mode 100644
index 0000000..bcdc1e0
--- /dev/null
+++ b/web/weather/232d.png
Binary files differ
diff --git a/web/weather/232n.png b/web/weather/232n.png
new file mode 100644
index 0000000..bcdc1e0
--- /dev/null
+++ b/web/weather/232n.png
Binary files differ
diff --git a/web/weather/300d.png b/web/weather/300d.png
new file mode 100644
index 0000000..75853f6
--- /dev/null
+++ b/web/weather/300d.png
Binary files differ
diff --git a/web/weather/300n.png b/web/weather/300n.png
new file mode 100644
index 0000000..75853f6
--- /dev/null
+++ b/web/weather/300n.png
Binary files differ
diff --git a/web/weather/500d.png b/web/weather/500d.png
new file mode 100644
index 0000000..75853f6
--- /dev/null
+++ b/web/weather/500d.png
Binary files differ
diff --git a/web/weather/500n.png b/web/weather/500n.png
new file mode 100644
index 0000000..75853f6
--- /dev/null
+++ b/web/weather/500n.png
Binary files differ
diff --git a/web/weather/50d.png b/web/weather/50d.png
new file mode 100644
index 0000000..fde3800
--- /dev/null
+++ b/web/weather/50d.png
Binary files differ
diff --git a/web/weather/50n.png b/web/weather/50n.png
new file mode 100644
index 0000000..fde3800
--- /dev/null
+++ b/web/weather/50n.png
Binary files differ
diff --git a/web/weather/511d.png b/web/weather/511d.png
new file mode 100644
index 0000000..c21fbd6
--- /dev/null
+++ b/web/weather/511d.png
Binary files differ
diff --git a/web/weather/511n.png b/web/weather/511n.png
new file mode 100644
index 0000000..c21fbd6
--- /dev/null
+++ b/web/weather/511n.png
Binary files differ
diff --git a/web/weather/615d.png b/web/weather/615d.png
new file mode 100644
index 0000000..c21fbd6
--- /dev/null
+++ b/web/weather/615d.png
Binary files differ
diff --git a/web/weather/615n.png b/web/weather/615n.png
new file mode 100644
index 0000000..c21fbd6
--- /dev/null
+++ b/web/weather/615n.png
Binary files differ
diff --git a/web/weather/616d.png b/web/weather/616d.png
new file mode 100644
index 0000000..cbc921f
--- /dev/null
+++ b/web/weather/616d.png
Binary files differ
diff --git a/web/weather/616n.png b/web/weather/616n.png
new file mode 100644
index 0000000..cbc921f
--- /dev/null
+++ b/web/weather/616n.png
Binary files differ
diff --git a/web/weather/804d.png b/web/weather/804d.png
new file mode 100644
index 0000000..8c8c451
--- /dev/null
+++ b/web/weather/804d.png
Binary files differ
diff --git a/web/weather/804n.png b/web/weather/804n.png
new file mode 100644
index 0000000..8c8c451
--- /dev/null
+++ b/web/weather/804n.png
Binary files differ
diff --git a/web/weather/list.json b/web/weather/list.json
new file mode 100644
index 0000000..4ac36da
--- /dev/null
+++ b/web/weather/list.json
@@ -0,0 +1 @@
+["01d.png","01n.png","02d.png","02n.png","03d.png","03n.png","04d.png","04n.png","09d.png","09n.png","10d.png","10n.png","11d.png","11n.png","13d.png","13n.png","202d.png","202n.png","211d.png","211n.png","212d.png","212n.png","221d.png","221n.png","232d.png","232n.png","300d.png","300n.png","500d.png","500n.png","50d.png","50n.png","511d.png","511n.png","615d.png","615n.png","616d.png","616n.png","804d.png","804n.png"] \ No newline at end of file