Update to Material3

This also adds a setting to change the URL for the scanner
This commit is contained in:
William Brawner 2023-12-03 19:52:51 -07:00
parent c48b0e9440
commit 5941aaab38
Signed by: wbrawner
GPG key ID: 8FF12381C6C90D35
14 changed files with 393 additions and 121 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\billy\.android\avd\Pixel_4_API_30.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-06-29T16:11:52.959392800Z" />
</component>
</project>

View file

@ -5,15 +5,15 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View file

@ -4,12 +4,13 @@ plugins {
} }
android { android {
compileSdk = 30 namespace = "com.wbrawner.skerge"
compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "com.wbrawner.skerge" applicationId = "com.wbrawner.skerge"
minSdk = 26 minSdk = 29
targetSdk = 30 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@ -43,26 +44,30 @@ android {
compose = true compose = true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String kotlinCompilerExtensionVersion = "1.5.4"
} }
} }
dependencies { dependencies {
val composeBom = platform("androidx.compose:compose-bom:2023.10.01")
implementation("androidx.core:core-ktx:1.5.0") implementation(composeBom)
implementation("androidx.appcompat:appcompat:1.3.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("com.google.android.material:material:1.3.0") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}") implementation("com.google.android.material:material:1.10.0")
implementation("androidx.compose.material:material:${rootProject.extra["compose_version"]}") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}") implementation("androidx.compose.material:material")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.3.0-beta02") implementation("androidx.compose.ui:ui-tooling")
val ktorVersion = "1.6.0" implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.activity:activity-compose:1.8.0")
val ktorVersion = "2.3.6"
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-android:$ktorVersion") implementation("io.ktor:ktor-client-android:$ktorVersion")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion") androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.2") androidTestImplementation(composeBom)
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
} }

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="com.wbrawner.skerge">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -16,6 +15,7 @@
android:name="com.wbrawner.skerge.MainActivity" android:name="com.wbrawner.skerge.MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:windowSoftInputMode="adjustPan|adjustResize"
android:theme="@style/Theme.Skerge.NoActionBar"> android:theme="@style/Theme.Skerge.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

@ -5,99 +5,198 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.* import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Share
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope import androidx.core.view.WindowCompat
import com.wbrawner.skerge.ui.theme.SkergeTheme import com.wbrawner.skerge.ui.theme.SkergeTheme
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels {
private val scansDirectory: File by lazy { File(cacheDir, "scans") } MainViewModel.Factory(applicationContext)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
val darkMode = isSystemInDarkTheme()
LaunchedEffect(darkMode) {
WindowCompat.getInsetsController(window, window.decorView)
.isAppearanceLightNavigationBars = !darkMode
}
val pages by viewModel.pages.collectAsState(initial = emptyList())
val scannerUrl by viewModel.scannerUrl.collectAsState(initial = "")
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val (loading, setLoading) = remember { mutableStateOf(false) }
var pdfShareJob: Job? by remember { mutableStateOf(null) }
SkergeTheme { SkergeTheme {
ScanScreen( ScanScreen(
addButtonClicked = { viewModel.requestScan(scansDirectory) }, loading = loading,
shareButtonClicked = { sharePdf(viewModel.pages.replayCache.first()) }, setLoading = { loading ->
removePage = viewModel::removePage, if (!loading) {
pagesFlow = viewModel.pages pdfShareJob?.cancel()
)
} }
} setLoading(loading)
} },
scannerUrl = scannerUrl,
private fun sharePdf(pages: List<Page>) { setScannerUrl = viewModel::setScannerUrl,
// TODO: Show loading dialog for this addButtonClicked = viewModel::requestScan,
lifecycleScope.launch { shareButtonClicked = {
pdfShareJob = coroutineScope.launch {
delay(10_000)
val file = if (pages.size == 1) { val file = if (pages.size == 1) {
pages.first().file ?: return@launch pages.first().file ?: return@launch
} else { } else {
pages.merge() pages.merge()
} }
startActivity(file.buildShareIntent(this@MainActivity)) startActivity(file.buildShareIntent(context))
}
},
removePage = viewModel::removePage,
pages = pages
)
}
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ScanScreen( fun ScanScreen(
loading: Boolean,
setLoading: (Boolean) -> Unit,
scannerUrl: String,
setScannerUrl: (String) -> Unit,
addButtonClicked: () -> Unit, addButtonClicked: () -> Unit,
shareButtonClicked: () -> Unit, shareButtonClicked: () -> Unit,
removePage: (Page) -> Unit, removePage: (Page) -> Unit,
pagesFlow: SharedFlow<List<Page>> pages: List<Page>
) { ) {
val pages = pagesFlow.collectAsState(initial = emptyList()) var showScannerUrlInput by remember { mutableStateOf(false) }
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
topBar = { topBar = {
TopAppBar( TopAppBar(
modifier = Modifier.statusBarsPadding(),
title = { Text("Skerge") }, title = { Text("Skerge") },
actions = { actions = {
IconButton(onClick = shareButtonClicked) { IconButton(onClick = shareButtonClicked) {
Icon(imageVector = Icons.Default.Share, contentDescription = "Share") Icon(imageVector = Icons.Default.Share, contentDescription = "Share")
} }
IconButton(onClick = { showScannerUrlInput = true }) {
Icon(imageVector = Icons.Default.Settings, contentDescription = "Settings")
}
} }
) )
}, },
floatingActionButton = { floatingActionButton = {
if (pages.value.none { it.file == null }) { if (pages.none { it.file == null }) {
FloatingActionButton(onClick = addButtonClicked) { FloatingActionButton(onClick = addButtonClicked) {
Icon(imageVector = Icons.Default.Add, contentDescription = "Add") Icon(imageVector = Icons.Default.Add, contentDescription = "Add")
} }
} }
} }
) { ) { paddingValues ->
if (pages.value.isEmpty()) { if (pages.isEmpty()) {
EmptyDocumentView() EmptyDocumentView(modifier = Modifier.padding(paddingValues))
} else { } else {
PageList(pages.value, removePage) PageList(
modifier = Modifier.padding(paddingValues),
pages = pages,
removePage = removePage
)
}
if (loading) {
AlertDialog(
onDismissRequest = { setLoading(false) },
confirmButton = {
TextButton(onClick = { setLoading(false) }) {
Text("Cancel")
}
},
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Merging PDF...")
CircularProgressIndicator()
}
}
)
} else if (showScannerUrlInput) {
val (scannerInput, setScannerInput) = remember { mutableStateOf(scannerUrl) }
AlertDialog(
onDismissRequest = { showScannerUrlInput = false },
dismissButton = {
TextButton(onClick = { showScannerUrlInput = false }) {
Text("Cancel")
}
},
confirmButton = {
TextButton(
onClick = {
setScannerUrl(scannerInput)
showScannerUrlInput = false
}
) {
Text("Save")
}
},
text = {
OutlinedTextField(
value = scannerInput,
onValueChange = setScannerInput,
label = {
Text("Scanner URL")
},
keyboardActions = KeyboardActions {
setScannerUrl(scannerInput)
showScannerUrlInput = false
},
keyboardOptions = KeyboardOptions.Default.copy(
autoCorrect = false,
capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Uri
)
)
}
)
} }
} }
} }
@Composable @Composable
fun EmptyDocumentView() { fun EmptyDocumentView(modifier: Modifier) {
Column( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@ -113,8 +212,8 @@ fun EmptyDocumentView() {
} }
@Composable @Composable
fun PageList(pages: List<Page>, removePage: (Page) -> Unit) { fun PageList(pages: List<Page>, removePage: (Page) -> Unit, modifier: Modifier) {
LazyColumn(modifier = Modifier.fillMaxSize()) { LazyColumn(modifier = modifier.fillMaxSize()) {
itemsIndexed(items = pages) { index, page -> itemsIndexed(items = pages) { index, page ->
val topPadding = if (index == 0) 16.dp else 8.dp val topPadding = if (index == 0) 16.dp else 8.dp
val bottomPadding = if (index == pages.size - 1) 16.dp else 8.dp val bottomPadding = if (index == pages.size - 1) 16.dp else 8.dp
@ -130,6 +229,7 @@ fun PageList(pages: List<Page>, removePage: (Page) -> Unit) {
} }
} }
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun PagePreview(page: Page, removePage: (Page) -> Unit) { fun PagePreview(page: Page, removePage: (Page) -> Unit) {
val (pageBitmap, setPageBitmap) = remember { mutableStateOf<Bitmap?>(null) } val (pageBitmap, setPageBitmap) = remember { mutableStateOf<Bitmap?>(null) }
@ -137,11 +237,26 @@ fun PagePreview(page: Page, removePage: (Page) -> Unit) {
setPageBitmap(page.loadBitmap().first) setPageBitmap(page.loadBitmap().first)
} }
pageBitmap?.let { pageBitmap?.let {
var showMenu by remember { mutableStateOf(false) }
Box(
modifier = Modifier.fillMaxSize()
) {
Image( Image(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.combinedClickable(
onClick = {},
onLongClick = {
showMenu = true
}
),
bitmap = it.asImageBitmap(), bitmap = it.asImageBitmap(),
contentDescription = null contentDescription = null
) )
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem(text = { Text("Remove") }, onClick = { removePage(page) })
}
}
} }
?: page.error?.let { ?: page.error?.let {
Column( Column(
@ -173,7 +288,7 @@ fun LoadingPage() {
@Composable @Composable
fun DefaultPreview() { fun DefaultPreview() {
SkergeTheme { SkergeTheme {
ScanScreen({}, {}, {}, MutableSharedFlow()) ScanScreen(false, {}, "", {}, {}, {}, {}, emptyList())
} }
} }

View file

@ -1,8 +1,12 @@
package com.wbrawner.skerge package com.wbrawner.skerge
import android.content.Context
import androidx.preference.PreferenceManager
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@ -10,11 +14,21 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
class MainViewModel(private val scannerService: ScannerService = HpScannerService()) : ViewModel() { class MainViewModel(
private val scannerService: ScannerService,
private val fileDirectory: File
) : ViewModel() {
private val _pages = MutableStateFlow<List<Page>>(emptyList()) private val _pages = MutableStateFlow<List<Page>>(emptyList())
val pages = _pages.asSharedFlow() val pages = _pages.asSharedFlow()
private val _scannerUrl = MutableStateFlow(scannerService.url)
val scannerUrl = _scannerUrl.asSharedFlow()
fun requestScan(fileDirectory: File) { fun setScannerUrl(url: String) {
scannerService.url = url
_scannerUrl.value = url
}
fun requestScan() {
val page = Page() val page = Page()
_pages.value = _pages.value.toMutableList().apply { _pages.value = _pages.value.toMutableList().apply {
add(page) add(page)
@ -104,6 +118,20 @@ class MainViewModel(private val scannerService: ScannerService = HpScannerServic
} }
_pages.value = updatedPages _pages.value = updatedPages
} }
class Factory(private val context: Context) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
val scannerService = HpScannerService(sharedPreferences = sharedPreferences)
val fileDirectory = File(context.applicationContext.cacheDir, "scans")
return MainViewModel(scannerService, fileDirectory) as T
}
}
} }
data class Page( data class Page(

View file

@ -1,17 +1,25 @@
package com.wbrawner.skerge package com.wbrawner.skerge
import android.content.SharedPreferences
import androidx.core.content.edit
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.features.* import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.onDownload
import io.ktor.client.plugins.timeout
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.client.utils.* import io.ktor.client.utils.*
import io.ktor.http.* import io.ktor.http.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit
interface ScannerService { interface ScannerService {
var url: String
suspend fun requestScan(scanSettings: ScanSettings = ScanSettings()): String suspend fun requestScan(scanSettings: ScanSettings = ScanSettings()): String
suspend fun getScanStatus(scanId: String): ScannerStatus suspend fun getScanStatus(scanId: String): ScannerStatus
suspend fun downloadFile( suspend fun downloadFile(
@ -21,20 +29,30 @@ interface ScannerService {
) )
} }
// TODO: It would be cool to be able to autodiscover the printer(s) or manually enter the details private const val KEY_SCANNER_URL = "scannerUrl"
const val SCANNER_URL = "http://brawner.print"
class HpScannerService( class HpScannerService(
private val client: HttpClient = HttpClient(CIO) { private val client: HttpClient = HttpClient(CIO) {
install(HttpTimeout) {
requestTimeoutMillis = TimeUnit.MINUTES.toMillis(2)
}
buildHeaders { buildHeaders {
append("Content-Type", "text/xml") append("Content-Type", "text/xml")
} }
} },
private val sharedPreferences: SharedPreferences
) : ScannerService { ) : ScannerService {
override var url: String
get() = sharedPreferences.getString(KEY_SCANNER_URL, null).orEmpty()
set(value) = sharedPreferences.edit { putString(KEY_SCANNER_URL, value) }
override suspend fun requestScan(scanSettings: ScanSettings): String { override suspend fun requestScan(scanSettings: ScanSettings): String {
val url = URLBuilder(SCANNER_URL).path("eSCL", "ScanJobs").build() val url = URLBuilder(url).run {
path("eSCL", "ScanJobs")
build()
}
val response: HttpResponse = client.post(url) { val response: HttpResponse = client.post(url) {
body = scanSettings.toXml() setBody(scanSettings.toXml())
} }
val location = response.headers["Location"] val location = response.headers["Location"]
?: throw IOException("Scanner didn't return location") ?: throw IOException("Scanner didn't return location")
@ -42,7 +60,13 @@ class HpScannerService(
} }
override suspend fun getScanStatus(scanId: String): ScannerStatus = ScannerStatus.fromXml( override suspend fun getScanStatus(scanId: String): ScannerStatus = ScannerStatus.fromXml(
client.get(URLBuilder(SCANNER_URL).path("eSCL", "ScannerStatus").build()) client.get(
URLBuilder(url)
.run {
path("eSCL", "ScannerStatus")
build()
}
).body()
) )
override suspend fun downloadFile( override suspend fun downloadFile(
@ -51,13 +75,16 @@ class HpScannerService(
onProgress: (downloaded: Long, size: Long) -> Unit onProgress: (downloaded: Long, size: Long) -> Unit
) { ) {
val httpResponse: HttpResponse = client.get( val httpResponse: HttpResponse = client.get(
URLBuilder(SCANNER_URL).path("eSCL", "ScanJobs", uuid, "NextDocument").build() URLBuilder(url).run {
path("eSCL", "ScanJobs", uuid, "NextDocument")
build()
}
) { ) {
onDownload { bytesSentTotal, contentLength -> onDownload { bytesSentTotal, contentLength ->
onProgress(bytesSentTotal, contentLength) onProgress(bytesSentTotal, contentLength)
} }
} }
val responseBody: ByteArray = httpResponse.receive() val responseBody: ByteArray = httpResponse.body()
destination.writeBytes(responseBody) destination.writeBytes(responseBody)
} }
} }

View file

@ -1,5 +1,67 @@
package com.wbrawner.skerge.ui.theme package com.wbrawner.skerge.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val SkergeBlue = Color(0xFF0096d6) val md_theme_light_primary = Color(0xFF006491)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFC9E6FF)
val md_theme_light_onPrimaryContainer = Color(0xFF001E2F)
val md_theme_light_secondary = Color(0xFF4F606E)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFD3E5F5)
val md_theme_light_onSecondaryContainer = Color(0xFF0C1D29)
val md_theme_light_tertiary = Color(0xFF64597C)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFEADDFF)
val md_theme_light_onTertiaryContainer = Color(0xFF201635)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFCFCFF)
val md_theme_light_onBackground = Color(0xFF191C1E)
val md_theme_light_surface = Color(0xFFFCFCFF)
val md_theme_light_onSurface = Color(0xFF191C1E)
val md_theme_light_surfaceVariant = Color(0xFFDDE3EA)
val md_theme_light_onSurfaceVariant = Color(0xFF41474D)
val md_theme_light_outline = Color(0xFF71787E)
val md_theme_light_inverseOnSurface = Color(0xFFF0F0F3)
val md_theme_light_inverseSurface = Color(0xFF2E3133)
val md_theme_light_inversePrimary = Color(0xFF8ACEFF)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006491)
val md_theme_light_outlineVariant = Color(0xFFC1C7CE)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF8ACEFF)
val md_theme_dark_onPrimary = Color(0xFF00344D)
val md_theme_dark_primaryContainer = Color(0xFF004C6E)
val md_theme_dark_onPrimaryContainer = Color(0xFFC9E6FF)
val md_theme_dark_secondary = Color(0xFFB7C9D9)
val md_theme_dark_onSecondary = Color(0xFF22323F)
val md_theme_dark_secondaryContainer = Color(0xFF384956)
val md_theme_dark_onSecondaryContainer = Color(0xFFD3E5F5)
val md_theme_dark_tertiary = Color(0xFFCEC0E8)
val md_theme_dark_onTertiary = Color(0xFF352B4B)
val md_theme_dark_tertiaryContainer = Color(0xFF4C4163)
val md_theme_dark_onTertiaryContainer = Color(0xFFEADDFF)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1E)
val md_theme_dark_onBackground = Color(0xFFE2E2E5)
val md_theme_dark_surface = Color(0xFF191C1E)
val md_theme_dark_onSurface = Color(0xFFE2E2E5)
val md_theme_dark_surfaceVariant = Color(0xFF41474D)
val md_theme_dark_onSurfaceVariant = Color(0xFFC1C7CE)
val md_theme_dark_outline = Color(0xFF8B9198)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1E)
val md_theme_dark_inverseSurface = Color(0xFFE2E2E5)
val md_theme_dark_inversePrimary = Color(0xFF006491)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF8ACEFF)
val md_theme_dark_outlineVariant = Color(0xFF41474D)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF0096D6)

View file

@ -1,35 +1,90 @@
package com.wbrawner.skerge.ui.theme package com.wbrawner.skerge.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material.darkColors import androidx.compose.material3.lightColorScheme
import androidx.compose.material.lightColors import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
primary = SkergeBlue, private val LightColors = lightColorScheme(
primaryVariant = SkergeBlue, primary = md_theme_light_primary,
secondary = SkergeBlue onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
) )
private val LightColorPalette = lightColors(
primary = SkergeBlue, private val DarkColors = darkColorScheme(
primaryVariant = SkergeBlue, primary = md_theme_dark_primary,
secondary = SkergeBlue onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
) )
@Composable @Composable
fun SkergeTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { fun SkergeTheme(
val colors = if (darkTheme) { useDarkTheme: Boolean = isSystemInDarkTheme(),
DarkColorPalette content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else { } else {
LightColorPalette DarkColors
} }
MaterialTheme( MaterialTheme(
colors = colors, colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content content = content
) )
} }

View file

@ -5,13 +5,11 @@
<item name="colorPrimary">@color/skerge_blue</item> <item name="colorPrimary">@color/skerge_blue</item>
<item name="colorPrimaryVariant">@color/skerge_blue</item> <item name="colorPrimaryVariant">@color/skerge_blue</item>
<item name="colorOnPrimary">@color/white</item> <item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/skerge_blue</item> <item name="colorSecondary">@color/skerge_blue</item>
<item name="colorSecondaryVariant">@color/skerge_blue</item> <item name="colorSecondaryVariant">@color/skerge_blue</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. --> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">@android:color/transparent</item>
<!-- Customize your theme here. -->
</style> </style>
<style name="Theme.Skerge.NoActionBar"> <style name="Theme.Skerge.NoActionBar">

View file

@ -1,12 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
val compose_version by extra("1.0.0-beta09")
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:7.0.0-beta05") classpath("com.android.tools.build:gradle:8.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
} }
} }

View file

@ -1,6 +1,6 @@
#Tue Jun 29 09:32:45 MDT 2021 #Tue Jun 29 09:32:45 MDT 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

0
gradlew vendored Normal file → Executable file
View file