mirror of
https://github.com/jahruz67/Ambient-Launcher.git
synced 2026-08-08 18:14:05 +00:00
Ambient Launcher #1
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.ambient.launcher"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.ambient.launcher"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("androidx.compose:compose-bom:2024.12.01"))
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("androidx.activity:activity-compose:1.10.0")
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("io.coil-kt:coil-compose:2.7.0")
|
||||
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
# No custom rules are required.
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AmbientLauncher">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="userLandscape">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.ambient.launcher
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class LauncherUiState(
|
||||
val settings: LauncherSettings = LauncherSettings(),
|
||||
val apps: List<LauncherApp> = emptyList(),
|
||||
val localImages: List<Any> = emptyList(),
|
||||
val weather: WeatherNow? = null,
|
||||
val weatherLoading: Boolean = false,
|
||||
val weatherError: String? = null
|
||||
)
|
||||
|
||||
class LauncherViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val store = SettingsStore(application)
|
||||
private val weatherRepository = WeatherRepository()
|
||||
private val _uiState = MutableStateFlow(LauncherUiState())
|
||||
val uiState: StateFlow<LauncherUiState> = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
loadApps()
|
||||
viewModelScope.launch {
|
||||
store.settings.collectLatest { settings ->
|
||||
val old = _uiState.value.settings
|
||||
_uiState.value = _uiState.value.copy(settings = settings)
|
||||
if (settings.localFolderUri != old.localFolderUri) loadLocalImages(settings.localFolderUri)
|
||||
if (
|
||||
settings.useFahrenheit != old.useFahrenheit ||
|
||||
_uiState.value.weather == null
|
||||
) {
|
||||
refreshWeather()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSettings(transform: (LauncherSettings) -> LauncherSettings) {
|
||||
val updated = transform(_uiState.value.settings)
|
||||
_uiState.value = _uiState.value.copy(settings = updated)
|
||||
viewModelScope.launch { store.save(updated) }
|
||||
}
|
||||
|
||||
fun togglePinned(app: LauncherApp) {
|
||||
updateSettings { settings ->
|
||||
val id = app.component.flattenToString()
|
||||
val next = settings.pinnedComponents.toMutableSet().apply {
|
||||
if (!add(id)) remove(id)
|
||||
}
|
||||
settings.copy(pinnedComponents = next)
|
||||
}
|
||||
}
|
||||
|
||||
fun setLocalFolder(uri: String) {
|
||||
updateSettings { it.copy(localFolderUri = uri) }
|
||||
loadLocalImages(uri)
|
||||
}
|
||||
|
||||
fun refreshWeather() {
|
||||
val settings = _uiState.value.settings
|
||||
val lat = settings.latitude.toDoubleOrNull()
|
||||
val lon = settings.longitude.toDoubleOrNull()
|
||||
if (lat == null || lon == null || lat !in -90.0..90.0 || lon !in -180.0..180.0) {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
weatherLoading = false,
|
||||
weatherError = "Enter valid latitude and longitude."
|
||||
)
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
_uiState.value = _uiState.value.copy(weatherLoading = true, weatherError = null)
|
||||
runCatching { weatherRepository.load(lat, lon, settings.useFahrenheit) }
|
||||
.onSuccess {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
weather = it,
|
||||
weatherLoading = false,
|
||||
weatherError = null
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
weatherLoading = false,
|
||||
weatherError = "Weather unavailable. Check the connection."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launch(app: LauncherApp): Boolean {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setComponent(app.component)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
|
||||
return runCatching {
|
||||
getApplication<Application>().startActivity(intent)
|
||||
}.isSuccess
|
||||
}
|
||||
|
||||
private fun loadApps() {
|
||||
viewModelScope.launch {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val context = getApplication<Application>()
|
||||
val pm = context.packageManager
|
||||
val intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
val flags = if (android.os.Build.VERSION.SDK_INT >= 33) {
|
||||
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong())
|
||||
} else null
|
||||
val resolved = if (flags != null) {
|
||||
pm.queryIntentActivities(intent, flags)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
pm.queryIntentActivities(intent, PackageManager.MATCH_ALL)
|
||||
}
|
||||
resolved.mapNotNull { info ->
|
||||
val activity = info.activityInfo ?: return@mapNotNull null
|
||||
if (activity.packageName == context.packageName) return@mapNotNull null
|
||||
LauncherApp(
|
||||
label = info.loadLabel(pm).toString(),
|
||||
component = ComponentName(activity.packageName, activity.name),
|
||||
icon = info.loadIcon(pm)
|
||||
)
|
||||
}.distinctBy { it.component }.sortedBy { it.label.lowercase() }
|
||||
}
|
||||
_uiState.value = _uiState.value.copy(apps = result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLocalImages(uri: String) {
|
||||
viewModelScope.launch {
|
||||
val images = withContext(Dispatchers.IO) {
|
||||
if (uri.isBlank()) return@withContext emptyList()
|
||||
val context = getApplication<Application>()
|
||||
runCatching {
|
||||
DocumentFile.fromTreeUri(context, android.net.Uri.parse(uri))
|
||||
?.listFiles()
|
||||
?.asSequence()
|
||||
?.filter { it.isFile && it.type?.startsWith("image/") == true }
|
||||
?.mapNotNull { it.uri }
|
||||
?.take(250)
|
||||
?.toList()
|
||||
?: emptyList()
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
_uiState.value = _uiState.value.copy(localImages = images)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
package com.ambient.launcher
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
data class LauncherApp(
|
||||
val label: String,
|
||||
val component: ComponentName,
|
||||
val icon: Drawable
|
||||
)
|
||||
|
||||
data class WeatherNow(
|
||||
val temperature: Double,
|
||||
val apparentTemperature: Double,
|
||||
val weatherCode: Int,
|
||||
val high: Double,
|
||||
val low: Double,
|
||||
val humidity: Int,
|
||||
val windSpeed: Double,
|
||||
val hourly: List<HourlyWeather>,
|
||||
val daily: List<DailyWeather>
|
||||
)
|
||||
|
||||
data class HourlyWeather(
|
||||
val time: String,
|
||||
val temperature: Double,
|
||||
val precipitationChance: Int
|
||||
)
|
||||
|
||||
data class DailyWeather(
|
||||
val date: String,
|
||||
val high: Double,
|
||||
val low: Double,
|
||||
val weatherCode: Int
|
||||
)
|
||||
|
||||
enum class KeepAwakeMode { WHILE_VISIBLE, WHILE_CHARGING, SYSTEM_DEFAULT }
|
||||
enum class ClockCorner { BOTTOM_LEFT, BOTTOM_RIGHT, TOP_LEFT, TOP_RIGHT }
|
||||
|
||||
data class LauncherSettings(
|
||||
val intervalSeconds: Int = 60,
|
||||
val darkenPercent: Int = 18,
|
||||
val keepAwakeMode: KeepAwakeMode = KeepAwakeMode.WHILE_CHARGING,
|
||||
val clockCorner: ClockCorner = ClockCorner.BOTTOM_RIGHT,
|
||||
val showWeather: Boolean = true,
|
||||
val locationName: String = "San Francisco",
|
||||
val latitude: String = "37.7749",
|
||||
val longitude: String = "-122.4194",
|
||||
val useFahrenheit: Boolean = true,
|
||||
val weatherEveryPhotos: Int = 5,
|
||||
val localFolderUri: String = "",
|
||||
val pinnedComponents: Set<String> = emptySet(),
|
||||
val nightRoutineEnabled: Boolean = false,
|
||||
val nightStartHour: Int = 23,
|
||||
val nightEndHour: Int = 7,
|
||||
val nightDimPercent: Int = 88
|
||||
)
|
||||
|
||||
sealed interface Slide {
|
||||
data class Photo(val source: Any, val credit: String) : Slide
|
||||
data object Weather : Slide
|
||||
}
|
||||
|
||||
fun weatherDescription(code: Int): String = when (code) {
|
||||
0 -> "Clear"
|
||||
1, 2 -> "Partly cloudy"
|
||||
3 -> "Overcast"
|
||||
45, 48 -> "Fog"
|
||||
51, 53, 55, 56, 57 -> "Drizzle"
|
||||
61, 63, 65, 66, 67 -> "Rain"
|
||||
71, 73, 75, 77 -> "Snow"
|
||||
80, 81, 82 -> "Rain showers"
|
||||
85, 86 -> "Snow showers"
|
||||
95, 96, 99 -> "Thunderstorm"
|
||||
else -> "Unknown"
|
||||
}
|
||||
|
||||
fun weatherGlyph(code: Int): String = when (code) {
|
||||
0 -> "☀"
|
||||
1, 2 -> "🌤"
|
||||
3 -> "☁"
|
||||
45, 48 -> "🌫"
|
||||
51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82 -> "🌧"
|
||||
71, 73, 75, 77, 85, 86 -> "❄"
|
||||
95, 96, 99 -> "⛈"
|
||||
else -> "•"
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.ambient.launcher
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val Context.dataStore by preferencesDataStore("ambient_launcher")
|
||||
|
||||
class SettingsStore(private val context: Context) {
|
||||
private object Keys {
|
||||
val interval = intPreferencesKey("interval")
|
||||
val darken = intPreferencesKey("darken")
|
||||
val keepAwake = stringPreferencesKey("keep_awake")
|
||||
val corner = stringPreferencesKey("clock_corner")
|
||||
val showWeather = booleanPreferencesKey("show_weather")
|
||||
val locationName = stringPreferencesKey("location_name")
|
||||
val latitude = stringPreferencesKey("latitude")
|
||||
val longitude = stringPreferencesKey("longitude")
|
||||
val fahrenheit = booleanPreferencesKey("fahrenheit")
|
||||
val weatherFrequency = intPreferencesKey("weather_frequency")
|
||||
val localFolder = stringPreferencesKey("local_folder")
|
||||
val pinned = stringPreferencesKey("pinned")
|
||||
val nightEnabled = booleanPreferencesKey("night_enabled")
|
||||
val nightStart = intPreferencesKey("night_start")
|
||||
val nightEnd = intPreferencesKey("night_end")
|
||||
val nightDim = intPreferencesKey("night_dim")
|
||||
}
|
||||
|
||||
val settings: Flow<LauncherSettings> = context.dataStore.data.map { p ->
|
||||
LauncherSettings(
|
||||
intervalSeconds = p[Keys.interval] ?: 60,
|
||||
darkenPercent = p[Keys.darken] ?: 18,
|
||||
keepAwakeMode = enumOrDefault(p[Keys.keepAwake], KeepAwakeMode.WHILE_CHARGING),
|
||||
clockCorner = enumOrDefault(p[Keys.corner], ClockCorner.BOTTOM_RIGHT),
|
||||
showWeather = p[Keys.showWeather] ?: true,
|
||||
locationName = p[Keys.locationName] ?: "San Francisco",
|
||||
latitude = p[Keys.latitude] ?: "37.7749",
|
||||
longitude = p[Keys.longitude] ?: "-122.4194",
|
||||
useFahrenheit = p[Keys.fahrenheit] ?: true,
|
||||
weatherEveryPhotos = p[Keys.weatherFrequency] ?: 5,
|
||||
localFolderUri = p[Keys.localFolder] ?: "",
|
||||
pinnedComponents = p[Keys.pinned]
|
||||
?.split('|')
|
||||
?.filter(String::isNotBlank)
|
||||
?.toSet()
|
||||
?: emptySet(),
|
||||
nightRoutineEnabled = p[Keys.nightEnabled] ?: false,
|
||||
nightStartHour = p[Keys.nightStart] ?: 23,
|
||||
nightEndHour = p[Keys.nightEnd] ?: 7,
|
||||
nightDimPercent = p[Keys.nightDim] ?: 88
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun save(value: LauncherSettings) {
|
||||
context.dataStore.edit { p ->
|
||||
p[Keys.interval] = value.intervalSeconds
|
||||
p[Keys.darken] = value.darkenPercent
|
||||
p[Keys.keepAwake] = value.keepAwakeMode.name
|
||||
p[Keys.corner] = value.clockCorner.name
|
||||
p[Keys.showWeather] = value.showWeather
|
||||
p[Keys.locationName] = value.locationName
|
||||
p[Keys.latitude] = value.latitude
|
||||
p[Keys.longitude] = value.longitude
|
||||
p[Keys.fahrenheit] = value.useFahrenheit
|
||||
p[Keys.weatherFrequency] = value.weatherEveryPhotos
|
||||
p[Keys.localFolder] = value.localFolderUri
|
||||
p[Keys.pinned] = value.pinnedComponents.joinToString("|")
|
||||
p[Keys.nightEnabled] = value.nightRoutineEnabled
|
||||
p[Keys.nightStart] = value.nightStartHour
|
||||
p[Keys.nightEnd] = value.nightEndHour
|
||||
p[Keys.nightDim] = value.nightDimPercent
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : Enum<T>> enumOrDefault(raw: String?, default: T): T =
|
||||
enumValues<T>().firstOrNull { it.name == raw } ?: default
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.ambient.launcher
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
|
||||
class WeatherRepository {
|
||||
suspend fun load(latitude: Double, longitude: Double, fahrenheit: Boolean): WeatherNow =
|
||||
withContext(Dispatchers.IO) {
|
||||
val unit = if (fahrenheit) "fahrenheit" else "celsius"
|
||||
val windUnit = if (fahrenheit) "mph" else "kmh"
|
||||
val endpoint = buildString {
|
||||
append("https://api.open-meteo.com/v1/forecast")
|
||||
append("?latitude=${encode(latitude)}&longitude=${encode(longitude)}")
|
||||
append("¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m")
|
||||
append("&hourly=temperature_2m,precipitation_probability")
|
||||
append("&daily=weather_code,temperature_2m_max,temperature_2m_min")
|
||||
append("&temperature_unit=$unit&wind_speed_unit=$windUnit&timezone=auto&forecast_days=7")
|
||||
}
|
||||
val connection = URL(endpoint).openConnection() as HttpURLConnection
|
||||
connection.connectTimeout = 8_000
|
||||
connection.readTimeout = 8_000
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
try {
|
||||
if (connection.responseCode !in 200..299) {
|
||||
error("Weather service returned ${connection.responseCode}")
|
||||
}
|
||||
val root = JSONObject(connection.inputStream.bufferedReader().use { it.readText() })
|
||||
parse(root)
|
||||
} finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parse(root: JSONObject): WeatherNow {
|
||||
val current = root.getJSONObject("current")
|
||||
val daily = root.getJSONObject("daily")
|
||||
val hourly = root.getJSONObject("hourly")
|
||||
val dailyTime = daily.getJSONArray("time")
|
||||
val dailyCodes = daily.getJSONArray("weather_code")
|
||||
val highs = daily.getJSONArray("temperature_2m_max")
|
||||
val lows = daily.getJSONArray("temperature_2m_min")
|
||||
val hourlyTimes = hourly.getJSONArray("time")
|
||||
val hourlyTemps = hourly.getJSONArray("temperature_2m")
|
||||
val rain = hourly.getJSONArray("precipitation_probability")
|
||||
val currentTime = current.getString("time")
|
||||
var start = 0
|
||||
for (i in 0 until hourlyTimes.length()) {
|
||||
if (hourlyTimes.getString(i) >= currentTime) {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return WeatherNow(
|
||||
temperature = current.getDouble("temperature_2m"),
|
||||
apparentTemperature = current.getDouble("apparent_temperature"),
|
||||
weatherCode = current.getInt("weather_code"),
|
||||
high = highs.getDouble(0),
|
||||
low = lows.getDouble(0),
|
||||
humidity = current.getInt("relative_humidity_2m"),
|
||||
windSpeed = current.getDouble("wind_speed_10m"),
|
||||
hourly = (start until minOf(start + 8, hourlyTimes.length())).map { i ->
|
||||
HourlyWeather(hourlyTimes.getString(i), hourlyTemps.getDouble(i), rain.optInt(i))
|
||||
},
|
||||
daily = (0 until minOf(7, dailyTime.length())).map { i ->
|
||||
DailyWeather(
|
||||
dailyTime.getString(i),
|
||||
highs.getDouble(i),
|
||||
lows.getDouble(i),
|
||||
dailyCodes.getInt(i)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun encode(value: Double): String =
|
||||
URLEncoder.encode(value.toString(), Charsets.UTF_8.name())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="315"
|
||||
android:endColor="#16243A"
|
||||
android:startColor="#594755"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:fillColor="#132238" android:pathData="M0,0h108v108h-108z" />
|
||||
<path android:fillColor="#84BFEA" android:pathData="M0,78L27,46L43,63L67,30L108,78V108H0z" />
|
||||
<path android:fillColor="#F6D58D" android:pathData="M77,19a11,11 0,1 0,0.1 0z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Ambient Launcher</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,11 @@
|
||||
<resources>
|
||||
<style name="Theme.AmbientLauncher" parent="android:style/Theme.Material.NoActionBar">
|
||||
<item name="android:fontFamily">sans</item>
|
||||
<item name="android:windowActionModeOverlay">true</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:colorAccent">#D4E7FF</item>
|
||||
<item name="android:navigationBarColor">#000000</item>
|
||||
<item name="android:statusBarColor">#000000</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user