Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07884c2728 | |||
| cd24d122c3 | |||
| 347eaefbf5 | |||
| e373c7f337 | |||
| ad7f1c7481 | |||
| 9dd8990303 |
@@ -1,58 +0,0 @@
|
||||
# stims
|
||||
|
||||
An Android app that keeps your screen awake while specific apps are in the foreground.
|
||||
|
||||
## What it does
|
||||
|
||||
Stims lets you select apps that should prevent your screen from turning off. When a "stimmed" app is active in the foreground, the screen stays on at full brightness. When you switch to a different app, the screen resumes normal sleep behavior.
|
||||
|
||||
---
|
||||
|
||||
## Permissions
|
||||
|
||||
Stims requires **Usage Access** permission to detect which app is in the foreground so it can keep the phone awake.
|
||||
|
||||
On first launch the app will redirect you to the system settings screen. To enable it:
|
||||
|
||||
1. Go to **Settings → Apps → Special app access → Usage access**
|
||||
2. Find **Stims** in the list and toggle it on
|
||||
3. Return to the app
|
||||
|
||||
Without this permission the background service cannot detect foreground apps and the screen will not be kept awake.
|
||||
|
||||
---
|
||||
|
||||
# Building from source
|
||||
|
||||
**Requirements**
|
||||
|
||||
- Android Studio or the Android SDK command-line tools
|
||||
- JDK 8+
|
||||
|
||||
**Steps**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/acidburnmonkey/stims.git
|
||||
cd stims
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
The APK will be output to:
|
||||
|
||||
```
|
||||
app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
For a release build:
|
||||
|
||||
```bash
|
||||
./gradlew assembleRelease
|
||||
# output: app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Minimum:** Android 7.0 (Nougat, API 24)
|
||||
- **Target:** Android 14 (API 34)
|
||||
@@ -1,71 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'acidburn.stims'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "acidburn.stims"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.5.8'
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||
implementation platform('androidx.compose:compose-bom:2023.10.01')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.compose.material:material-icons-extended'
|
||||
|
||||
// Added for XML theme support
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01')
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Required to query all installed apps on Android 11+ -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Stims">
|
||||
|
||||
<service
|
||||
android:name=".StimsService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Monitoring apps to keep screen on" />
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,297 +0,0 @@
|
||||
package acidburn.stims
|
||||
|
||||
import android.app.AppOpsManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import acidburn.stims.ui.theme.StimsTheme
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class AppInfo(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val icon: Drawable? = null
|
||||
)
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var prefs: SharedPreferences
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
prefs = getSharedPreferences("stims_prefs", Context.MODE_PRIVATE)
|
||||
|
||||
setContent {
|
||||
StimsTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
AppListScreen(prefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!hasUsageStatsPermission(this)) {
|
||||
Toast.makeText(this, "Please enable Usage Stats permission", Toast.LENGTH_LONG).show()
|
||||
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasUsageStatsPermission(context: Context): Boolean {
|
||||
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
||||
val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.packageName)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), context.packageName)
|
||||
}
|
||||
return mode == AppOpsManager.MODE_ALLOWED
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppListScreen(prefs: SharedPreferences) {
|
||||
val context = LocalContext.current
|
||||
val packageManager = context.packageManager
|
||||
|
||||
var allApps by remember { mutableStateOf<List<AppInfo>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
||||
val stimmedPackages = remember {
|
||||
mutableStateListOf<String>().apply {
|
||||
addAll(prefs.getStringSet("stimmed_apps", emptySet()) ?: emptySet())
|
||||
}
|
||||
}
|
||||
|
||||
val tempSelected = remember { mutableStateListOf<String>() }
|
||||
|
||||
LaunchedEffect(stimmedPackages.toList()) {
|
||||
prefs.edit().putStringSet("stimmed_apps", stimmedPackages.toSet()).apply()
|
||||
|
||||
val intent = Intent(context, StimsService::class.java).apply {
|
||||
putStringArrayListExtra("selected_packages", ArrayList(stimmedPackages))
|
||||
}
|
||||
|
||||
if (stimmedPackages.isEmpty()) {
|
||||
context.stopService(intent)
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
allApps = withContext(Dispatchers.IO) {
|
||||
val intent = Intent(Intent.ACTION_MAIN, null).apply {
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
}
|
||||
val resolveInfos = packageManager.queryIntentActivities(intent, 0)
|
||||
|
||||
resolveInfos.map { resolveInfo ->
|
||||
AppInfo(
|
||||
name = resolveInfo.loadLabel(packageManager).toString(),
|
||||
packageName = resolveInfo.activityInfo.packageName,
|
||||
icon = resolveInfo.loadIcon(packageManager)
|
||||
)
|
||||
}.distinctBy { it.packageName }
|
||||
.sortedBy { it.name }
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
|
||||
val stimList = remember(stimmedPackages.toList(), allApps) {
|
||||
allApps.filter { it.packageName in stimmedPackages }
|
||||
}
|
||||
|
||||
val availableApps = remember(searchQuery, stimmedPackages.toList(), allApps) {
|
||||
allApps.filter { it.packageName !in stimmedPackages &&
|
||||
(searchQuery.isBlank() || it.name.contains(searchQuery, ignoreCase = true) || it.packageName.contains(searchQuery, ignoreCase = true))
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Stims Manager", fontWeight = FontWeight.Bold) }
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
if (isLoading) {
|
||||
Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
Column(modifier = Modifier.padding(padding)) {
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
placeholder = { Text("Search apps to stim...") },
|
||||
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
LazyColumn(modifier = Modifier.weight(1f)) {
|
||||
if (stimList.isNotEmpty()) {
|
||||
item {
|
||||
SectionHeader("STAY AWAKE (STIMMED)")
|
||||
}
|
||||
items(stimList) { app ->
|
||||
StimmedAppItem(app = app) {
|
||||
stimmedPackages.remove(app.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SectionHeader("ALL APPS")
|
||||
}
|
||||
items(availableApps) { app ->
|
||||
SelectableAppItem(
|
||||
app = app,
|
||||
isSelected = app.packageName in tempSelected,
|
||||
onToggle = {
|
||||
if (app.packageName in tempSelected) tempSelected.remove(app.packageName)
|
||||
else tempSelected.add(app.packageName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
stimmedPackages.addAll(tempSelected)
|
||||
tempSelected.clear()
|
||||
Toast.makeText(context, "Apps Stimmed!", Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
enabled = tempSelected.isNotEmpty(),
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
Text("STIM SELECTED APPS", fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SectionHeader(title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelLarge.copy(
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
letterSpacing = 1.2.sp
|
||||
),
|
||||
modifier = Modifier.padding(start = 16.dp, top = 20.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StimmedAppItem(app: AppInfo, onRemove: () -> Unit) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color(0xFFE8F5E9)
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppIcon(app.icon)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(text = app.name, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold)
|
||||
Text(text = "STIMMED", style = MaterialTheme.typography.labelSmall, color = Color(0xFF2E7D32))
|
||||
}
|
||||
IconButton(onClick = onRemove) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Remove", tint = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectableAppItem(app: AppInfo, isSelected: Boolean, onToggle: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onToggle() }
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppIcon(app.icon)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(text = app.name, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(text = app.packageName, style = MaterialTheme.typography.bodySmall, color = Color.Gray)
|
||||
}
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = { onToggle() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppIcon(icon: Drawable?) {
|
||||
if (icon != null) {
|
||||
Image(
|
||||
bitmap = icon.toBitmap().asImageBitmap(),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(44.dp)
|
||||
)
|
||||
} else {
|
||||
Surface(
|
||||
modifier = Modifier.size(44.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
package acidburn.stims
|
||||
|
||||
import android.app.*
|
||||
import android.app.usage.UsageEvents
|
||||
import android.app.usage.UsageStatsManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class StimsService : Service() {
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private var selectedPackages = mutableSetOf<String>()
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val checkInterval = 10000L // Check every 10 seconds
|
||||
|
||||
private val monitorRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
checkForegroundApp()
|
||||
handler.postDelayed(this, checkInterval)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val action = intent?.action
|
||||
val packages = intent?.getStringArrayListExtra("selected_packages")
|
||||
|
||||
if (packages != null) {
|
||||
selectedPackages = packages.toMutableSet()
|
||||
updateNotification()
|
||||
}
|
||||
|
||||
if (action == ACTION_STOP) {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
startForegroundService()
|
||||
handler.removeCallbacks(monitorRunnable)
|
||||
handler.post(monitorRunnable)
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun startForegroundService() {
|
||||
createNotificationChannel()
|
||||
updateNotification()
|
||||
handler.post(monitorRunnable)
|
||||
}
|
||||
|
||||
private fun updateNotification() {
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("Stims Daemon Running")
|
||||
.setContentText("Monitoring ${selectedPackages.size} apps")
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private fun checkForegroundApp() {
|
||||
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
if (!powerManager.isInteractive) {
|
||||
releaseWakeLock()
|
||||
return
|
||||
}
|
||||
|
||||
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
|
||||
val time = System.currentTimeMillis()
|
||||
val events = usageStatsManager.queryEvents(time - 15000, time)
|
||||
val event = UsageEvents.Event()
|
||||
var currentPackage: String? = null
|
||||
|
||||
while (events.hasNextEvent()) {
|
||||
events.getNextEvent(event)
|
||||
if (event.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
|
||||
currentPackage = event.packageName
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPackage != null && selectedPackages.contains(currentPackage)) {
|
||||
acquireWakeLock()
|
||||
} else if (currentPackage != null) {
|
||||
releaseWakeLock()
|
||||
}
|
||||
}
|
||||
|
||||
private fun acquireWakeLock() {
|
||||
if (wakeLock == null || !wakeLock!!.isHeld) {
|
||||
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
@Suppress("DEPRECATION")
|
||||
wakeLock = powerManager.newWakeLock(
|
||||
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
|
||||
"Stims::WakeLock"
|
||||
)
|
||||
wakeLock?.acquire(10 * 60 * 1000L) // 10 min timeout
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseWakeLock() {
|
||||
if (wakeLock?.isHeld == true) {
|
||||
wakeLock?.release()
|
||||
}
|
||||
wakeLock = null
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
if (manager.getNotificationChannel(CHANNEL_ID) == null) {
|
||||
val serviceChannel = NotificationChannel(
|
||||
CHANNEL_ID, "Stims Service", NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
manager.createNotificationChannel(serviceChannel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
handler.removeCallbacks(monitorRunnable)
|
||||
releaseWakeLock()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "StimsServiceChannel"
|
||||
const val NOTIFICATION_ID = 1
|
||||
const val ACTION_STOP = "acidburn.stims.STOP"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package acidburn.stims.ui.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
@Composable
|
||||
fun StimsTheme(content: @Composable () -> Unit) {
|
||||
val darkTheme = isSystemInDarkTheme()
|
||||
val context = LocalContext.current
|
||||
|
||||
val colorScheme = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
if (darkTheme) dynamicDarkColorScheme(context)
|
||||
else dynamicLightColorScheme(context)
|
||||
}
|
||||
darkTheme -> darkColorScheme()
|
||||
else -> lightColorScheme()
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.9 MiB |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/circular_icon" />
|
||||
<foreground android:drawable="@android:color/transparent" />
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/circular_icon" />
|
||||
<foreground android:drawable="@android:color/transparent" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Stims</string>
|
||||
</resources>
|
||||
@@ -1,6 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Stims" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 889 KiB |
@@ -1,389 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="221.7683"
|
||||
height="373.49692"
|
||||
viewBox="0 0 221.7683 373.49692"
|
||||
role="img"
|
||||
aria-label="Cyberpunk caffeine auto-injector icon"
|
||||
version="1.1"
|
||||
id="svg24"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs13">
|
||||
<linearGradient
|
||||
id="bodyGrad"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stop-color="#24163a"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="50%"
|
||||
stop-color="#3b2c65"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset="100%"
|
||||
stop-color="#0b0f1f"
|
||||
id="stop3" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="glassGrad"
|
||||
x1="183.4281"
|
||||
y1="75.396645"
|
||||
x2="441.97247"
|
||||
y2="333.94101"
|
||||
gradientTransform="scale(0.79948761,1.2508011)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0%"
|
||||
stop-color="#ff9a00"
|
||||
id="stop4" />
|
||||
<stop
|
||||
offset="45%"
|
||||
stop-color="#ff5e00"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset="100%"
|
||||
stop-color="#ffbf3c"
|
||||
id="stop6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="needleGrad"
|
||||
x1="260.10269"
|
||||
y1="342.12448"
|
||||
x2="350.93219"
|
||||
y2="342.12448"
|
||||
gradientTransform="scale(1.5138252,0.66057826)"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0%"
|
||||
stop-color="#9cfbff"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="100%"
|
||||
stop-color="#00d1ff"
|
||||
id="stop8" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="ringGrad"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stop-color="#59f3ff"
|
||||
id="stop9" />
|
||||
<stop
|
||||
offset="100%"
|
||||
stop-color="#00a3d9"
|
||||
id="stop10" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="glowCyan"
|
||||
x="-0.75"
|
||||
y="-0.75"
|
||||
width="2.5"
|
||||
height="2.5">
|
||||
<feGaussianBlur
|
||||
stdDeviation="5"
|
||||
result="blur"
|
||||
id="feGaussianBlur10" />
|
||||
<feMerge
|
||||
id="feMerge11">
|
||||
<feMergeNode
|
||||
in="blur"
|
||||
id="feMergeNode10" />
|
||||
<feMergeNode
|
||||
in="SourceGraphic"
|
||||
id="feMergeNode11" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
<filter
|
||||
id="glowOrange"
|
||||
x="-0.28296598"
|
||||
y="-0.84744828"
|
||||
width="1.565932"
|
||||
height="2.6948966">
|
||||
<feGaussianBlur
|
||||
stdDeviation="7"
|
||||
result="blur"
|
||||
id="feGaussianBlur11" />
|
||||
<feMerge
|
||||
id="feMerge13">
|
||||
<feMergeNode
|
||||
in="blur"
|
||||
id="feMergeNode12" />
|
||||
<feMergeNode
|
||||
in="SourceGraphic"
|
||||
id="feMergeNode13" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
<style
|
||||
id="style13">
|
||||
.outline { stroke:#0ff7ff; stroke-opacity:.55; stroke-width:3; }
|
||||
.metal { fill:url(#bodyGrad); stroke:#7d6efc; stroke-width:3; }
|
||||
.accent { fill:#6a49ff; }
|
||||
.cyan { fill:#40f5ff; }
|
||||
.text { font-family: Arial, Helvetica, sans-serif; font-weight:700; letter-spacing:2px; }
|
||||
</style>
|
||||
<linearGradient
|
||||
xlink:href="#bodyGrad"
|
||||
id="linearGradient24"
|
||||
x1="136.72952"
|
||||
y1="127.53463"
|
||||
x2="231.78215"
|
||||
y2="222.58723"
|
||||
gradientTransform="scale(0.68383178,1.462348)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#bodyGrad"
|
||||
id="linearGradient25"
|
||||
x1="134.45683"
|
||||
y1="173.42926"
|
||||
x2="334.03391"
|
||||
y2="373.00632"
|
||||
gradientTransform="scale(1.067257,0.93698147)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#ringGrad"
|
||||
id="linearGradient26"
|
||||
x1="58.319386"
|
||||
y1="210.38649"
|
||||
x2="58.319386"
|
||||
y2="314.7475"
|
||||
gradientTransform="scale(1.6289609,0.61388827)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#ringGrad"
|
||||
id="linearGradient27"
|
||||
x1="144.26373"
|
||||
y1="210.38649"
|
||||
x2="144.26373"
|
||||
y2="314.7475"
|
||||
gradientTransform="scale(1.6289609,0.61388827)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#bodyGrad"
|
||||
id="linearGradient28"
|
||||
x1="579.89478"
|
||||
y1="113.10458"
|
||||
x2="656.08533"
|
||||
y2="189.29514"
|
||||
gradientTransform="scale(0.59062442,1.6931233)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#bodyGrad"
|
||||
id="linearGradient29"
|
||||
x1="512.72302"
|
||||
y1="157.30843"
|
||||
x2="582.06396"
|
||||
y2="226.64938"
|
||||
gradientTransform="scale(0.74991759,1.3334799)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
transform="matrix(-0.30621193,0.74049952,-0.49004152,-0.46271545,312.88776,84.680243)"
|
||||
id="g24">
|
||||
<!-- rear cap -->
|
||||
<rect
|
||||
x="95"
|
||||
y="188"
|
||||
rx="26"
|
||||
ry="26"
|
||||
width="62"
|
||||
height="136"
|
||||
class="metal"
|
||||
id="rect13"
|
||||
style="fill:url(#linearGradient24)" />
|
||||
<circle
|
||||
cx="124"
|
||||
cy="205"
|
||||
r="9"
|
||||
class="cyan"
|
||||
filter="url(#glowCyan)"
|
||||
id="circle13" />
|
||||
<circle
|
||||
cx="124"
|
||||
cy="307"
|
||||
r="9"
|
||||
class="cyan"
|
||||
filter="url(#glowCyan)"
|
||||
id="circle14" />
|
||||
<!-- body -->
|
||||
<rect
|
||||
x="145"
|
||||
y="164"
|
||||
rx="34"
|
||||
ry="34"
|
||||
width="210"
|
||||
height="184"
|
||||
class="metal"
|
||||
id="rect14"
|
||||
style="fill:url(#linearGradient25)" />
|
||||
<rect
|
||||
x="165"
|
||||
y="182"
|
||||
rx="20"
|
||||
ry="20"
|
||||
width="170"
|
||||
height="148"
|
||||
fill="#11192c"
|
||||
stroke="#3ef4ff"
|
||||
stroke-width="2"
|
||||
id="rect15" />
|
||||
<!-- glowing chamber -->
|
||||
<rect
|
||||
x="184"
|
||||
y="196"
|
||||
rx="16"
|
||||
ry="16"
|
||||
width="132"
|
||||
height="120"
|
||||
fill="url(#glassGrad)"
|
||||
filter="url(#glowOrange)"
|
||||
opacity="0.96"
|
||||
id="rect16"
|
||||
style="fill:url(#glassGrad)" />
|
||||
<rect
|
||||
x="193"
|
||||
y="196"
|
||||
rx="10"
|
||||
ry="10"
|
||||
width="18"
|
||||
height="120"
|
||||
fill="#fff4c2"
|
||||
opacity="0.18"
|
||||
id="rect17" />
|
||||
<text
|
||||
x="250"
|
||||
y="263"
|
||||
text-anchor="middle"
|
||||
class="text"
|
||||
font-size="28px"
|
||||
fill="#fff6d5"
|
||||
filter="url(#glowOrange)"
|
||||
id="text17">CAF</text>
|
||||
<!-- cyan tubing -->
|
||||
<path
|
||||
d="m 214,174 c -26,-34 -54,-34 -68,-1"
|
||||
fill="none"
|
||||
stroke="url(#ringGrad)"
|
||||
stroke-width="9"
|
||||
stroke-linecap="round"
|
||||
filter="url(#glowCyan)"
|
||||
id="path17"
|
||||
style="stroke:url(#linearGradient26)" />
|
||||
<path
|
||||
d="m 286,174 c 26,-34 54,-34 68,-1"
|
||||
fill="none"
|
||||
stroke="url(#ringGrad)"
|
||||
stroke-width="9"
|
||||
stroke-linecap="round"
|
||||
filter="url(#glowCyan)"
|
||||
id="path18"
|
||||
style="stroke:url(#linearGradient27)" />
|
||||
<!-- side nodes -->
|
||||
<circle
|
||||
cx="170"
|
||||
cy="256"
|
||||
r="12"
|
||||
class="accent"
|
||||
id="circle18" />
|
||||
<circle
|
||||
cx="341"
|
||||
cy="256"
|
||||
r="12"
|
||||
class="accent"
|
||||
id="circle19" />
|
||||
<!-- display -->
|
||||
<rect
|
||||
x="205"
|
||||
y="320"
|
||||
rx="10"
|
||||
ry="10"
|
||||
width="90"
|
||||
height="24"
|
||||
fill="#101827"
|
||||
stroke="#3ef4ff"
|
||||
stroke-width="2"
|
||||
id="rect19" />
|
||||
<text
|
||||
x="250"
|
||||
y="338"
|
||||
text-anchor="middle"
|
||||
class="text"
|
||||
font-size="14px"
|
||||
fill="#53fbff"
|
||||
id="text19">250</text>
|
||||
<!-- front collar -->
|
||||
<rect
|
||||
x="344"
|
||||
y="193"
|
||||
rx="20"
|
||||
ry="20"
|
||||
width="42"
|
||||
height="126"
|
||||
class="metal"
|
||||
id="rect20"
|
||||
style="fill:url(#linearGradient28)" />
|
||||
<circle
|
||||
cx="365"
|
||||
cy="207"
|
||||
r="8"
|
||||
class="cyan"
|
||||
filter="url(#glowCyan)"
|
||||
id="circle20" />
|
||||
<circle
|
||||
cx="365"
|
||||
cy="305"
|
||||
r="8"
|
||||
class="cyan"
|
||||
filter="url(#glowCyan)"
|
||||
id="circle21" />
|
||||
<!-- injector tip -->
|
||||
<path
|
||||
d="m 386,232 49,-20 v 88 l -49,-20 z"
|
||||
fill="url(#bodyGrad)"
|
||||
stroke="#7d6efc"
|
||||
stroke-width="3"
|
||||
id="path21"
|
||||
style="fill:url(#linearGradient29)" />
|
||||
<path
|
||||
d="m 435,244 55,12 -55,12 z"
|
||||
fill="url(#needleGrad)"
|
||||
filter="url(#glowCyan)"
|
||||
id="path22"
|
||||
style="fill:url(#needleGrad)" />
|
||||
<path
|
||||
d="m 490,252 16,4 -16,4 z"
|
||||
fill="#d8fdff"
|
||||
id="path23" />
|
||||
<!-- little rings -->
|
||||
<rect
|
||||
x="156"
|
||||
y="176"
|
||||
rx="6"
|
||||
ry="6"
|
||||
width="12"
|
||||
height="160"
|
||||
fill="#1af2ff"
|
||||
opacity="0.35"
|
||||
id="rect23" />
|
||||
<rect
|
||||
x="328"
|
||||
y="176"
|
||||
rx="6"
|
||||
ry="6"
|
||||
width="12"
|
||||
height="160"
|
||||
fill="#1af2ff"
|
||||
opacity="0.2"
|
||||
id="rect24" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
@@ -1,18 +0,0 @@
|
||||
// Top-level build file
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
Stims lets you select apps that should prevent your screen from turning off. When a stimmed app is active in the foreground,
|
||||
the screen stays on at full brightness. When you switch to a different app, the screen resumes normal sleep behavior.
|
||||
|
||||
Select apps to stim from the list, tap "Stim Selected Apps", and a background service will monitor the foreground
|
||||
app and hold a wake lock whenever one of your chosen apps is active.
|
||||
|
||||
Requires Usage Access permission to detect which app is in the foreground.
|
||||
@@ -1 +0,0 @@
|
||||
Keep your screen awake while specific apps are in the foreground.
|
||||
@@ -1,3 +0,0 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
@@ -1,6 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -1,249 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# 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 ;; #(
|
||||
MSYS* | 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
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
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
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# 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"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -1,92 +0,0 @@
|
||||
@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=.
|
||||
@rem This is normally unused
|
||||
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% equ 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% equ 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!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,565 @@
|
||||
:root {
|
||||
--bg: #070707;
|
||||
--surface: #0e0e0e;
|
||||
--border: #1c1c1c;
|
||||
--border-mid: #282828;
|
||||
--amber: #e89a0a;
|
||||
--amber-bright: #ffb020;
|
||||
--text: #f5f0e8;
|
||||
--text-dim: #c8c2b8;
|
||||
--text-muted: #9a9490;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Azeret Mono', monospace;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Grain overlay */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
|
||||
opacity: 0.035;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* ─── HERO ─── */
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 2rem 6rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Ambient breathing orb */
|
||||
.orb {
|
||||
position: absolute;
|
||||
width: 700px;
|
||||
height: 700px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle,
|
||||
rgba(232, 154, 10, 0.22) 0%,
|
||||
rgba(232, 154, 10, 0.07) 35%,
|
||||
transparent 65%);
|
||||
pointer-events: none;
|
||||
animation: breathe 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.12);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Secondary haze ring */
|
||||
.orb-ring {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle,
|
||||
transparent 45%,
|
||||
rgba(232, 154, 10, 0.03) 60%,
|
||||
transparent 70%);
|
||||
pointer-events: none;
|
||||
animation: breathe 7s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
max-width: 820px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 0.62rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.35em;
|
||||
color: var(--amber);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 1.25rem;
|
||||
opacity: 0;
|
||||
animation: fadeUp 0.6s 0.1s ease forwards;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 900;
|
||||
font-size: clamp(6.5rem, 22vw, 15rem);
|
||||
line-height: 0.85;
|
||||
letter-spacing: -0.01em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
opacity: 0;
|
||||
animation: fadeUp 0.7s 0.2s ease forwards;
|
||||
}
|
||||
|
||||
.app-name .accent {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.tagline {
|
||||
margin-top: 2.25rem;
|
||||
font-size: clamp(0.78rem, 1.4vw, 0.92rem);
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
color: var(--text);
|
||||
line-height: 2;
|
||||
opacity: 0;
|
||||
animation: fadeUp 0.7s 0.35s ease forwards;
|
||||
}
|
||||
|
||||
/* ─── BUTTONS ─── */
|
||||
.download-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 3rem;
|
||||
opacity: 0;
|
||||
animation: fadeUp 0.7s 0.5s ease forwards;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
height: 40px;
|
||||
padding: 0 1.4rem;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Azeret Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.06em;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
transition: all 0.18s ease;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-amber {
|
||||
background: var(--amber);
|
||||
color: #000;
|
||||
border: 1px solid var(--amber);
|
||||
}
|
||||
|
||||
.btn-amber:hover {
|
||||
background: var(--amber-bright);
|
||||
border-color: var(--amber-bright);
|
||||
box-shadow: 0 0 28px rgba(232, 154, 10, 0.35);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: #1e1a10;
|
||||
color: var(--text);
|
||||
border: 1px solid #5a4e30;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: #2a2214;
|
||||
border-color: var(--amber);
|
||||
color: var(--amber-bright);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: #181818;
|
||||
color: var(--text-dim);
|
||||
border: 1px solid #3a3530;
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: #202020;
|
||||
color: var(--text);
|
||||
border-color: #5a5550;
|
||||
}
|
||||
|
||||
.btn.disabled {
|
||||
opacity: 0.38;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.5rem;
|
||||
padding: 0.12rem 0.35rem;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
color: var(--text-muted);
|
||||
border-radius: 2px;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
fill: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* F-Droid official badge — self-contained graphic, sized to match buttons */
|
||||
.fdroid-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
.fdroid-badge:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.fdroid-badge img {
|
||||
display: block;
|
||||
height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* ─── DIVIDER ─── */
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ─── COMPATIBILITY STRIP ─── */
|
||||
.compat-strip {
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 3rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.compat-item {
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.compat-item span {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* ─── SECTION WRAPPER ─── */
|
||||
.section {
|
||||
padding: 5rem 2rem;
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 0.58rem;
|
||||
letter-spacing: 0.4em;
|
||||
color: var(--amber);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* ─── FEATURES GRID ─── */
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--border);
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--bg);
|
||||
padding: 2.5rem 1.75rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.feature-glyph {
|
||||
font-size: 1.6rem;
|
||||
line-height: 1;
|
||||
color: var(--amber);
|
||||
margin-bottom: 1.4rem;
|
||||
display: block;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 300;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
/* ─── STEPS ─── */
|
||||
.steps-list {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: grid;
|
||||
grid-template-columns: 5rem 1fr;
|
||||
gap: 1.5rem;
|
||||
padding: 2.25rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.step:first-child {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 900;
|
||||
font-size: 4rem;
|
||||
line-height: 1;
|
||||
color: var(--border-mid);
|
||||
padding-top: 0.1rem;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 0.74rem;
|
||||
font-weight: 300;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
.step-code {
|
||||
display: inline-block;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.1rem 0.45rem;
|
||||
border-radius: 2px;
|
||||
font-size: 0.68rem;
|
||||
color: var(--amber);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* ─── PERMISSION CARD ─── */
|
||||
.perm-card {
|
||||
border: 1px solid var(--border);
|
||||
padding: 2rem;
|
||||
margin-top: 2rem;
|
||||
display: grid;
|
||||
grid-template-columns: 2.5rem 1fr;
|
||||
gap: 1.5rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.perm-dot {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border-mid);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.9rem;
|
||||
color: var(--amber);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.perm-name {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 500;
|
||||
color: var(--amber);
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.perm-desc {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 300;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
/* ─── SCREENSHOTS ─── */
|
||||
.screenshots-track {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 1rem;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-mid) transparent;
|
||||
}
|
||||
|
||||
.screenshots-track::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.screenshots-track::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.screenshots-track::-webkit-scrollbar-thumb {
|
||||
background: var(--border-mid);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
flex-shrink: 0;
|
||||
width: 220px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.18s;
|
||||
}
|
||||
|
||||
.screenshot:hover {
|
||||
border-color: var(--border-mid);
|
||||
}
|
||||
|
||||
.screenshot img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ─── FOOTER ─── */
|
||||
footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 2rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.05em;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
/* ─── ANIMATIONS ─── */
|
||||
@keyframes fadeUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── RESPONSIVE ─── */
|
||||
@media (max-width: 700px) {
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.download-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.btn,
|
||||
.fdroid-badge {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.step {
|
||||
grid-template-columns: 3.5rem 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.perm-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
footer {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.compat-strip {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 889 KiB After Width: | Height: | Size: 889 KiB |
@@ -0,0 +1,234 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stims — Keep Your Screen Awake</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Stims keeps your Android screen awake while your chosen apps are open. Simple, lightweight, no bloat."
|
||||
/>
|
||||
<meta property="og:title" content="Stims — Keep Your Screen Awake" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Pick the apps. Forget the rest. Your screen stays on when you need it to."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@400;700;900&family=Azeret+Mono:ital,wght@0,300;0,400;0,500;1,300&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="home.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- ═══ HERO ═══ -->
|
||||
<section class="hero">
|
||||
<div class="orb"></div>
|
||||
<div class="orb-ring"></div>
|
||||
|
||||
<div class="hero-content">
|
||||
<p class="eyebrow">Android Utility</p>
|
||||
|
||||
<h1 class="app-name">STIM<span class="accent">S</span></h1>
|
||||
|
||||
<p class="tagline">
|
||||
Your screen stays on when you need it to.<br />
|
||||
Pick the apps. Forget the rest.
|
||||
</p>
|
||||
|
||||
<div class="download-row">
|
||||
<!-- Google Play — uncomment href when available -->
|
||||
<a href="#" class="btn btn-amber disabled" aria-disabled="true">
|
||||
<!-- Play icon -->
|
||||
<svg class="icon" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M5.31 3.07C4.55 2.64 3.79 3.13 3.79 4.01v15.98c0 .88.76 1.37 1.52.94l14.1-7.99a1.07 1.07 0 0 0 0-1.88L5.31 3.07z"
|
||||
/>
|
||||
</svg>
|
||||
Google Play
|
||||
<span class="badge">SOON</span>
|
||||
</a>
|
||||
|
||||
<!-- F-Droid -->
|
||||
<a
|
||||
href="https://f-droid.org/en/packages/acidburn.stims/"
|
||||
class="fdroid-badge"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://f-droid.org/badge/get-it-on.svg"
|
||||
alt="Get it on F-Droid"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- GitHub -->
|
||||
<a
|
||||
href="https://github.com/acidburnmonkey/stims"
|
||||
class="btn btn-ghost"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg class="icon" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z"
|
||||
/>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ COMPAT STRIP ═══ -->
|
||||
<div class="compat-strip">
|
||||
<span class="compat-item">Minimum <span>Android 7.0 (API 24)</span></span>
|
||||
<span class="compat-item">Target <span>Android 15 (API 35)</span></span>
|
||||
<span class="compat-item">Size <span>Lightweight</span></span>
|
||||
<span class="compat-item">License <span>GPL v3</span></span>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SCREENSHOTS ═══ -->
|
||||
<div class="section">
|
||||
<p class="section-label">Screenshots</p>
|
||||
<div class="screenshots-track">
|
||||
<div class="screenshot">
|
||||
<img
|
||||
src="phoneScreenshots/s1.jpeg"
|
||||
alt="Stims screenshot 1"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="screenshot">
|
||||
<img
|
||||
src="phoneScreenshots/s2.jpeg"
|
||||
alt="Stims screenshot 2"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="screenshot">
|
||||
<img
|
||||
src="phoneScreenshots/s3.jpeg"
|
||||
alt="Stims screenshot 3"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ═══ FEATURES ═══ -->
|
||||
<div class="section">
|
||||
<p class="section-label">What it does</p>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<span class="feature-glyph">◉</span>
|
||||
<h3 class="feature-title">Per-app control</h3>
|
||||
<p class="feature-desc">
|
||||
Choose exactly which apps keep the screen awake. Every other app
|
||||
behaves as normal. Your battery, your rules.
|
||||
</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<span class="feature-glyph">◈</span>
|
||||
<h3 class="feature-title">Silent service</h3>
|
||||
<p class="feature-desc">
|
||||
A lightweight background service watches the foreground app and
|
||||
manages the wake lock automatically — no interaction needed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<span class="feature-glyph">◇</span>
|
||||
<h3 class="feature-title">Zero bloat</h3>
|
||||
<p class="feature-desc">
|
||||
No accounts. No analytics. No network access. No trackers. One
|
||||
purpose, done well.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ═══ HOW IT WORKS ═══ -->
|
||||
<div class="section">
|
||||
<p class="section-label">Setup</p>
|
||||
<div class="steps-list">
|
||||
<div class="step">
|
||||
<div class="step-num">01</div>
|
||||
<div>
|
||||
<p class="step-title">Grant Usage Access</p>
|
||||
<p class="step-desc">
|
||||
On first launch, Stims will open the system permission screen.
|
||||
Navigate to
|
||||
<span class="step-code"
|
||||
>Settings → Apps → Special app access → Usage access</span
|
||||
>, find Stims, and toggle it on.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">02</div>
|
||||
<div>
|
||||
<p class="step-title">Pick your apps</p>
|
||||
<p class="step-desc">
|
||||
Back in Stims, tap any app in the list to mark it. Your selection
|
||||
is saved immediately — no confirm button needed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">03</div>
|
||||
<div>
|
||||
<p class="step-title">Done</p>
|
||||
<p class="step-desc">
|
||||
Switch to a marked app and the screen stays on at full brightness.
|
||||
Switch away and normal sleep behavior returns. That's the whole
|
||||
thing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- ═══ PERMISSIONS ═══ -->
|
||||
<div class="section">
|
||||
<p class="section-label">Permissions</p>
|
||||
<div class="perm-card">
|
||||
<div class="perm-dot">◎</div>
|
||||
<div>
|
||||
<p class="perm-name">Usage Access</p>
|
||||
<p class="perm-desc">
|
||||
Used solely to detect which app is currently in the foreground so
|
||||
the wake lock can be acquired or released accordingly. This data is
|
||||
processed entirely on-device and is never transmitted anywhere.
|
||||
Stims has no internet permission and cannot contact any external
|
||||
server.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ FOOTER ═══ -->
|
||||
<footer>
|
||||
<p class="footer-left">
|
||||
Stims · Android 7.0+ · Open Source
|
||||
</p>
|
||||
<nav class="footer-links">
|
||||
<a
|
||||
href="https://github.com/acidburnmonkey/stims"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>GitHub</a
|
||||
>
|
||||
<a href="privacy.html">Privacy Policy</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 103 KiB |
@@ -0,0 +1,292 @@
|
||||
:root {
|
||||
--bg: #070707;
|
||||
--surface: #0e0e0e;
|
||||
--border: #1c1c1c;
|
||||
--border-mid: #282828;
|
||||
--amber: #e89a0a;
|
||||
--text: #ede8dc;
|
||||
--text-dim: #8a857c;
|
||||
--text-muted: #474340;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Azeret Mono', monospace;
|
||||
font-weight: 300;
|
||||
line-height: 1.75;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
|
||||
opacity: 0.035;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* ─── HEADER ─── */
|
||||
header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1.25rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 900;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-logo span {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.header-back {
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
transition: color 0.18s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.header-back:hover {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
/* ─── HERO ─── */
|
||||
.doc-hero {
|
||||
padding: 5rem 2rem 3rem;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.doc-eyebrow {
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.4em;
|
||||
color: var(--amber);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.doc-title {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 900;
|
||||
font-size: clamp(3rem, 8vw, 5.5rem);
|
||||
line-height: 0.9;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.doc-meta {
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* ─── SUMMARY CARD ─── */
|
||||
.summary-card {
|
||||
max-width: 720px;
|
||||
margin: 3rem auto 0;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.summary-box {
|
||||
border: 1px solid var(--amber);
|
||||
border-left: 3px solid var(--amber);
|
||||
padding: 1.5rem 1.75rem;
|
||||
background: rgba(232, 154, 10, 0.04);
|
||||
}
|
||||
|
||||
.summary-box p {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 400;
|
||||
color: var(--text);
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
.summary-box strong {
|
||||
color: var(--amber);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ─── DOC BODY ─── */
|
||||
.doc-body {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 2rem 6rem;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
margin-bottom: 3.5rem;
|
||||
}
|
||||
|
||||
.doc-section-num {
|
||||
font-size: 0.58rem;
|
||||
letter-spacing: 0.35em;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-section-title {
|
||||
font-family: 'Big Shoulders Display', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.6rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--text);
|
||||
margin-bottom: 1.25rem;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 300;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.9;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.doc-section p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.doc-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.doc-section ul li {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 300;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.9;
|
||||
padding-left: 1.5rem;
|
||||
position: relative;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.doc-section ul li::before {
|
||||
content: '—';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--border-mid);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--text);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.mono-tag {
|
||||
display: inline-block;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.08rem 0.4rem;
|
||||
border-radius: 2px;
|
||||
font-size: 0.7rem;
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
/* Contact block */
|
||||
.contact-block {
|
||||
border: 1px solid var(--border);
|
||||
padding: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.contact-label {
|
||||
font-size: 0.58rem;
|
||||
letter-spacing: 0.25em;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.contact-block a {
|
||||
color: var(--amber);
|
||||
text-decoration: none;
|
||||
font-size: 0.78rem;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
|
||||
.contact-block a:hover {
|
||||
color: #ffb020;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ─── FOOTER ─── */
|
||||
footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 1.75rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.footer-left {
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
font-size: 0.62rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.05em;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
footer {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Privacy Policy — Stims</title>
|
||||
<meta name="description"
|
||||
content="Privacy policy for Stims, an Android app that keeps your screen awake for selected apps." />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Big+Shoulders+Display:wght@700;900&family=Azeret+Mono:ital,wght@0,300;0,400;0,500;1,300&display=swap"
|
||||
rel="stylesheet" />
|
||||
|
||||
<link rel="stylesheet" href="privacy.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- ═══ HEADER ═══ -->
|
||||
<header>
|
||||
<a href="index.html" class="header-logo">STIM<span>S</span></a>
|
||||
<a href="index.html" class="header-back"> ← Back to home </a>
|
||||
</header>
|
||||
|
||||
<!-- ═══ DOC HERO ═══ -->
|
||||
<div class="doc-hero">
|
||||
<p class="doc-eyebrow">Legal</p>
|
||||
<h1 class="doc-title">Privacy<br />Policy</h1>
|
||||
<p class="doc-meta">
|
||||
Effective date: 2026-03-24 · Applies to: Stims for Android
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SUMMARY ═══ -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-box">
|
||||
<p>
|
||||
<strong>Short version:</strong> Stims collects no personal data, sends
|
||||
nothing to any server, and contains no third-party SDKs, analytics, or
|
||||
advertising. Everything the app does stays entirely on your device.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ POLICY BODY ═══ -->
|
||||
<div class="doc-body">
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 01</p>
|
||||
<h2 class="doc-section-title">Information We Collect</h2>
|
||||
<p>
|
||||
<span class="highlight">We collect no personal information.</span>
|
||||
Stims does not ask for, store, or transmit any data that identifies
|
||||
you or your device to us or to any third party.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 02</p>
|
||||
<h2 class="doc-section-title">Permissions Used</h2>
|
||||
<p>Stims requires one special permission to function:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="highlight">Usage Access</span> — allows the app to
|
||||
detect which app is currently in the foreground. This is used
|
||||
exclusively to determine whether to acquire or release a screen wake
|
||||
lock. The data is read in-process, never written to persistent
|
||||
storage beyond the current session, and never transmitted.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Your list of selected apps is stored in
|
||||
<span class="mono-tag">SharedPreferences</span> — local device storage
|
||||
only, never synced or uploaded.
|
||||
</p>
|
||||
<p>
|
||||
Stims has <span class="highlight">no internet permission</span> in its
|
||||
manifest. It is architecturally incapable of making network requests.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 03</p>
|
||||
<h2 class="doc-section-title">Data Sharing</h2>
|
||||
<p>
|
||||
We share no data with anyone because we collect no data. There are no
|
||||
third-party SDKs, no analytics libraries, no advertising networks, and
|
||||
no crash reporting services included in the app.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 04</p>
|
||||
<h2 class="doc-section-title">Data Storage & Retention</h2>
|
||||
<p>
|
||||
The only persistent data Stims stores is your list of selected apps,
|
||||
saved to local <span class="mono-tag">SharedPreferences</span> on your
|
||||
device. This data:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Never leaves your device</li>
|
||||
<li>Is deleted when you uninstall the app</li>
|
||||
<li>
|
||||
Can be cleared at any time via Settings → Apps → Stims → Clear data
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 05</p>
|
||||
<h2 class="doc-section-title">Children's Privacy</h2>
|
||||
<p>
|
||||
Stims does not knowingly collect information from anyone, including
|
||||
children under the age of 13. Because no data is collected at all, the
|
||||
app has no age-specific data practices.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 06</p>
|
||||
<h2 class="doc-section-title">Changes to This Policy</h2>
|
||||
<p>
|
||||
If the privacy practices of Stims change — for example if a future
|
||||
version adds a feature requiring network access — this policy will be
|
||||
updated and the effective date revised. The current policy always
|
||||
reflects the latest published version of the app.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<p class="doc-section-num">§ 07</p>
|
||||
<h2 class="doc-section-title">Contact</h2>
|
||||
<p>
|
||||
Questions about this policy or the app can be directed to the project
|
||||
maintainer via GitHub:
|
||||
</p>
|
||||
<div class="contact-block">
|
||||
<p class="contact-label">GitHub</p>
|
||||
<a href="https://github.com/acidburnmonkey/stims" target="_blank"
|
||||
rel="noopener noreferrer">github.com/acidburnmonkey/stims</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ FOOTER ═══ -->
|
||||
<footer>
|
||||
<p class="footer-left">Stims · Privacy Policy</p>
|
||||
<nav class="footer-links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="https://github.com/acidburnmonkey/stims" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
rootProject.name = "stims"
|
||||
include ':app'
|
||||