Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
4590477b46 | |||
c4ca852666 | |||
6852576c57 | |||
9b25ae463b | |||
40e1901572 | |||
d3735685f1 | |||
ff8bf5e9e5 | |||
067827f5c9 |
43 changed files with 1227 additions and 621 deletions
|
@ -1,25 +1,31 @@
|
|||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidApplication)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.sqlDelight)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidTarget {
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
compilations.all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "21"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jvm("desktop")
|
||||
|
||||
sqldelight{
|
||||
databases {
|
||||
create("PolyculeDatabase") {
|
||||
packageName = "com.menagerie.ophelia"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val desktopMain by getting
|
||||
|
@ -28,6 +34,7 @@ kotlin {
|
|||
implementation(compose.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.material3.android)
|
||||
implementation(libs.sqldelight.android)
|
||||
}
|
||||
commonMain.dependencies {
|
||||
implementation(compose.runtime)
|
||||
|
@ -43,11 +50,13 @@ kotlin {
|
|||
implementation("org.jetbrains.androidx.navigation:navigation-compose:2.7.0-alpha07")
|
||||
api(libs.datastore.preferences)
|
||||
api(libs.datastore)
|
||||
implementation(libs.sqldelight.coroutines)
|
||||
}
|
||||
desktopMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.androidx.material3.desktop)
|
||||
implementation(libs.sqldelight.jvm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,8 +83,8 @@ android {
|
|||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
|
|
17
composeApp/src/androidMain/kotlin/AndroidAppModule.kt
Normal file
17
composeApp/src/androidMain/kotlin/AndroidAppModule.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
import android.content.Context
|
||||
import com.menagerie.ophelia.AppModule
|
||||
import com.menagerie.ophelia.DatabaseDriverFactory
|
||||
import com.menagerie.ophelia.PolyculeDatabase
|
||||
import com.menagerie.ophelia.data.user.UserDataSource
|
||||
|
||||
class AndroidAppModule(
|
||||
private val context: Context,
|
||||
) : AppModule {
|
||||
private val db by lazy {
|
||||
PolyculeDatabase(
|
||||
driver = DatabaseDriverFactory(context).create()
|
||||
)
|
||||
}
|
||||
|
||||
override fun provideUserDataSource() = UserDataSource(db)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.menagerie.ophelia
|
||||
|
||||
import android.content.Context
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
|
||||
actual class DatabaseDriverFactory(private val context: Context) {
|
||||
actual fun create(): SqlDriver =
|
||||
AndroidSqliteDriver(PolyculeDatabase.Schema, context, "POLYCULE_DATABASE")
|
||||
}
|
|
@ -12,13 +12,12 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
|
||||
setContent {
|
||||
MainView(
|
||||
MainView(
|
||||
prefs = remember {
|
||||
createDataStore(application)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<resources>
|
||||
<string name="app_name">Ophelia</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
app:fontProviderPackage="com.google.android.gms"
|
||||
app:fontProviderQuery="Calligraffitti"
|
||||
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
|
||||
</font-family>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="back">Back</string>
|
||||
<string name="app_tag">A Polycule Pocket-dex</string>
|
||||
<string name="welcome">Get Started</string>
|
||||
<string name="welcome_back">Welcome Back</string>
|
||||
|
||||
##Routes
|
||||
<string name="route_polyculeHome">polyculeHome</string>
|
||||
<string name="route_home">home</string>
|
||||
|
||||
##Bio
|
||||
<string name="add_bio">Add Bio</string>
|
||||
<string name="edit_bio">Edit Bio</string>
|
||||
<string name="finish">Finish</string>
|
||||
<string name="bio_load">Bio Loaded</string>
|
||||
<string name="open_context_menu">Open context menu</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="bio_add_toast">New Bio Added!</string>
|
||||
<string name="bio_edit_toast">Bio Edited!</string>
|
||||
<string name="bio_finish_toast">Welcome Aboard!</string>
|
||||
|
||||
|
||||
##Tags
|
||||
<string name="mono">Monogamous</string>
|
||||
<string name="plural">Plural</string>
|
||||
<string name="furry">Furry</string>
|
||||
<string name="therian">Therian</string>
|
||||
<string name="bdsm">BDSM</string>
|
||||
<string name="alcohol">Alcohol</string>
|
||||
<string name="weed">Weed</string>
|
||||
<string name="asexual">Asexual</string>
|
||||
<string name="trans">Transgender</string>
|
||||
|
||||
##Ophelia
|
||||
<string name = "ophelia_says">Ophelia Says</string>
|
||||
<string name = "final_check_in">What do you think; looks like you?</string>
|
||||
<string-array name="welcome_blurb">
|
||||
<item>Hi!</item>
|
||||
<item>I'm Ophelia, a Polycule companion system designed to help you keep track of your metas, reference a charter, and find things in common with your polycule.</item>
|
||||
<item>I'm meant to work dynamically with how polyamory works for you, so what all those words mean will be something we work out together later.</item>
|
||||
<item>First, I need to get to know "you".</item>
|
||||
<item>Click Go to get started.</item>
|
||||
</string-array>
|
||||
<string-array name="whats_your_name">
|
||||
<item>I'm Ophelia, what's your name?</item>
|
||||
<item>Your Name is how you'll be addressed in the app, and is the primary way you'll be referred to in polycules.</item>
|
||||
<item>But don't worry; your name, like everything about you, can be changed at any time!</item>
|
||||
</string-array>
|
||||
<string name="introduction_with_name">"Nice to meet you,%1$s!</string>
|
||||
<string-array name="introduction_to_pronouns">
|
||||
<item>Personal Pronouns are words used to refer to a person without always using their name.</item>
|
||||
<item>Since Pronouns are a personal choice, I want to leave them as open as possible for you!</item>
|
||||
<item>We're dealing with four Personal Pronouns, and using mine as an example: She (subjective) / Her (objective) / Hers (possessive) / Herself (reflexive)</item>
|
||||
<item>Your Pronouns will be used when referring to you in specific contexts, and are displayed alongside your name in most contexts.</item>
|
||||
</string-array>
|
||||
<string-array name="introduction_to_tags">
|
||||
<item>Tags are a way to highlight important things about yourself.</item>
|
||||
<item>They also lead to more detailed parts of you bio, where you can flesh out specific parts of your bio.</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="introduction_to_polycules">
|
||||
<item>Great!</item>
|
||||
<item>Now, there's just one more thing! We need to create a polycule for you. A "polycule" for our purposes is defined as a group of people with a shared relationship process.</item>
|
||||
<item>A person may be in a polycule with any number of people, including just themselves, and in any number of polycules.</item>
|
||||
<item>All we need to get started is a name:</item>
|
||||
</string-array>
|
||||
|
||||
##Helpers
|
||||
<string name = "go">Go</string>
|
||||
</resources>
|
|
@ -2,7 +2,9 @@ package com.menagerie.ophelia
|
|||
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Scaffold
|
||||
|
@ -18,14 +20,19 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.menagerie.ophelia.HomeScreen.HomeScreen
|
||||
import com.menagerie.ophelia.HomeScreen.MainMenu
|
||||
import com.menagerie.ophelia.HomeScreen.Settings
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.model.recipesList
|
||||
import com.menagerie.ophelia.recipesdetails.RecipeDetails
|
||||
import com.menagerie.ophelia.recipeslist.RecipesListScreen
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import com.menagerie.ophelia.view.HomeScreen.HomeScreen
|
||||
import com.menagerie.ophelia.view.HomeScreen.MainMenu
|
||||
import com.menagerie.ophelia.view.HomeScreen.Settings
|
||||
import com.menagerie.ophelia.view.newUser.AddNameScreen
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import com.menagerie.ophelia.view.recipe.model.recipesList
|
||||
import com.menagerie.ophelia.view.recipe.recipesdetails.RecipeDetails
|
||||
import com.menagerie.ophelia.view.recipe.recipeslist.RecipesListScreen
|
||||
import com.menagerie.ophelia.view.tags.model.Tag
|
||||
import com.menagerie.ophelia.view.tags.tagsdetails.TagsDetails
|
||||
import com.menagerie.ophelia.view.tags.tagslist.TagsListScreen
|
||||
import com.menagerie.ophelia.view.newUser.NewUserStartScreen
|
||||
|
||||
enum class DetailListAppScreen {
|
||||
List,
|
||||
|
@ -47,8 +54,6 @@ fun App(
|
|||
sensorManager = sensorManager,
|
||||
prefs = prefs
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -59,38 +64,58 @@ fun OpheliaNavHost(
|
|||
prefs: DataStore<Preferences>
|
||||
){
|
||||
val items by remember { mutableStateOf(recipesList) }
|
||||
var show by remember { mutableStateOf(false) }
|
||||
var showBar by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold {
|
||||
Column {
|
||||
//TODO : replace this with a composable task bar
|
||||
if (show) {
|
||||
Row {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.popBackStack()
|
||||
//TODO : replace row with a lazyGrid for better mobile support
|
||||
if (showBar) {
|
||||
|
||||
val listState = rememberLazyGridState()
|
||||
LazyVerticalGrid(
|
||||
state = listState, columns = GridCells.Fixed(if (isLarge) 3 else 1)
|
||||
) {
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.popBackStack()
|
||||
}
|
||||
) {
|
||||
Text(text = "Back - TEMP")
|
||||
}
|
||||
) {
|
||||
Text(text = "Back - TEMP")
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("mainMenu")
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("mainMenu")
|
||||
}
|
||||
) {
|
||||
Text(text = "Home - TEMP")
|
||||
}
|
||||
) {
|
||||
Text(text = "Home - TEMP")
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("settings")
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("settings")
|
||||
}
|
||||
) {
|
||||
Text(text = "Settings - TEMP")
|
||||
}
|
||||
) {
|
||||
Text(text = "Settings - TEMP")
|
||||
}
|
||||
Button(onClick = {
|
||||
//TODO : This should take the user to their bio page
|
||||
}){
|
||||
Text(text = "You - TEMP")
|
||||
item {
|
||||
Button(onClick = {
|
||||
//TODO : This should take the user to their bio page
|
||||
}) {
|
||||
Text(text = "You - TEMP")
|
||||
}
|
||||
}
|
||||
item {
|
||||
Button(onClick = {
|
||||
navController.navigate("dex")
|
||||
}) {
|
||||
Text(text = "Encyclopedia - TEMP")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,15 +131,17 @@ fun OpheliaNavHost(
|
|||
isLarge = isLarge,
|
||||
sensorManager = sensorManager
|
||||
)
|
||||
|
||||
welcomeGraph(navController)
|
||||
|
||||
composable("home") {
|
||||
showBar = false
|
||||
HomeScreen {
|
||||
show = true
|
||||
navController.navigate("mainMenu")
|
||||
navController.navigate("newUserStart")
|
||||
}
|
||||
}
|
||||
composable("mainMenu") {
|
||||
showBar = true
|
||||
MainMenu(
|
||||
onPolyClick = {
|
||||
//TODO : This should take the user to their polycule's main page
|
||||
|
@ -137,13 +164,14 @@ fun OpheliaNavHost(
|
|||
}
|
||||
}
|
||||
|
||||
//Graph for new user startup
|
||||
fun NavGraphBuilder.welcomeGraph(navController: NavHostController) {
|
||||
// composable("newUserStart") {
|
||||
// NewUserStartScreen { navController.navigate("newUserName") }
|
||||
// }
|
||||
// composable("newUserName") {
|
||||
// AddNameScreen { navController.navigate("newUserPronouns") }
|
||||
// }
|
||||
composable("newUserStart") {
|
||||
NewUserStartScreen { navController.navigate("mainMenu") }
|
||||
}
|
||||
composable("newUserName") {
|
||||
AddNameScreen { navController.navigate("newUserPronouns") }
|
||||
}
|
||||
// composable("newUserPronouns") {
|
||||
// AddPronounsScreen { navController.navigate("newUserTags") }
|
||||
// }
|
||||
|
@ -161,6 +189,7 @@ fun NavGraphBuilder.welcomeGraph(navController: NavHostController) {
|
|||
// }
|
||||
}
|
||||
|
||||
//Graph for Polycule navigation
|
||||
fun NavGraphBuilder.polyculeGraph(navController: NavHostController) {
|
||||
// composable("polyculeHome") {
|
||||
// PolyculeHomeView(
|
||||
|
@ -197,6 +226,7 @@ fun NavGraphBuilder.polyculeGraph(navController: NavHostController) {
|
|||
// }
|
||||
}
|
||||
|
||||
//Graph for your Cookbook
|
||||
fun NavGraphBuilder.recipeGraph(
|
||||
navController: NavHostController,
|
||||
items: List<Recipe>,
|
||||
|
@ -220,7 +250,37 @@ fun NavGraphBuilder.recipeGraph(
|
|||
isLarge = isLarge,
|
||||
sensorManager = sensorManager,
|
||||
recipe = currentRecipe,
|
||||
goBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//Graph for the Tags
|
||||
fun NavGraphBuilder.tagGraph(
|
||||
navController: NavHostController,
|
||||
items: List<Tag>,
|
||||
isLarge: Boolean,
|
||||
sensorManager: SensorManager?,
|
||||
) {
|
||||
|
||||
var currentTag = items.first()
|
||||
|
||||
composable(route = DetailListAppScreen.List.name){
|
||||
TagsListScreen(
|
||||
isLarge = isLarge,
|
||||
items = items,
|
||||
onClick = { tag ->
|
||||
currentTag = tag
|
||||
navController.navigate(DetailListAppScreen.Details.name)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable(route = DetailListAppScreen.Details.name) {
|
||||
TagsDetails(
|
||||
isLarge = isLarge,
|
||||
sensorManager = sensorManager,
|
||||
tag = currentTag,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.menagerie.ophelia
|
||||
|
||||
import com.menagerie.ophelia.data.user.UserDataSource
|
||||
|
||||
interface AppModule {
|
||||
fun provideUserDataSource(): UserDataSource
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.menagerie.ophelia
|
||||
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
|
||||
expect class DatabaseDriverFactory {
|
||||
fun create(): SqlDriver
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.menagerie.ophelia.data.user
|
||||
|
||||
import app.cash.sqldelight.coroutines.asFlow
|
||||
import app.cash.sqldelight.coroutines.mapToList
|
||||
import com.menagerie.ophelia.PolyculeDatabase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
class UserDataSource(db: PolyculeDatabase) {
|
||||
private val queries = db.userQueries
|
||||
|
||||
fun insert(id: Long?, name: String) {
|
||||
queries.insert(id = id, name = name)
|
||||
}
|
||||
|
||||
fun getAll() = queries.getAll().asFlow().mapToList(Dispatchers.IO)
|
||||
|
||||
fun update(id: Long, name: String) {
|
||||
queries.updateName(id = id, name = name)
|
||||
}
|
||||
|
||||
fun delete(id: Long) {
|
||||
queries.delete(id = id)
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
|
||||
import androidx.compose.animation.core.animateIntOffsetAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.sensor.Listener
|
||||
import com.menagerie.ophelia.sensor.SensorData
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import kotlin.math.PI
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import com.menagerie.ophelia.ui.theme.orangeDark
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
import com.menagerie.ophelia.ui.theme.yellow
|
||||
|
||||
@Composable
|
||||
fun RecipeDetailsLarge(
|
||||
recipe: Recipe,
|
||||
goBack: () -> Unit,
|
||||
sensorManager: SensorManager?,
|
||||
) {
|
||||
val imageRotation = remember { mutableStateOf(0) }
|
||||
val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) }
|
||||
val roll by derivedStateOf { (sensorDataLive.value.roll * 20).coerceIn(-4f, 4f) }
|
||||
val pitch by derivedStateOf { (sensorDataLive.value.pitch * 20).coerceIn(-4f, 4f) }
|
||||
|
||||
val tweenDuration = 300
|
||||
|
||||
sensorManager?.registerListener(object : Listener {
|
||||
override fun onUpdate(sensorData: SensorData) {
|
||||
sensorDataLive.value = sensorData
|
||||
}
|
||||
})
|
||||
|
||||
val backgroundShadowOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()),
|
||||
animationSpec = tween(tweenDuration)
|
||||
)
|
||||
val backgroundImageOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration)
|
||||
)
|
||||
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
imageRotation.value += (available.y * 0.5).toInt()
|
||||
return Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
imageRotation.value += ((delta * PI / 180) * 10).toInt()
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
imageRotation.value += available.y.toInt()
|
||||
return super.onPreFling(available)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(if (recipe.bgColor == sugar) yellow else sugar)
|
||||
) {
|
||||
val size = mutableStateOf(IntSize(0, 0))
|
||||
Row {
|
||||
Box(modifier = Modifier.fillMaxSize().onGloballyPositioned {
|
||||
size.value = it.size
|
||||
}.weight(1f).pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
// on every relayout Compose will send synthetic Move event,
|
||||
// so we skip it to avoid event spam
|
||||
if (event.type == PointerEventType.Move) {
|
||||
val position = event.changes.first().position
|
||||
sensorDataLive.value = SensorData(
|
||||
roll = position.x - size.value.height / 4,
|
||||
pitch = (position.y - size.value.width / 4)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(topEnd = 35.dp, bottomEnd = 35.dp)),
|
||||
shape = RoundedCornerShape(
|
||||
topEnd = 35.dp,
|
||||
bottomEnd = 35.dp,
|
||||
),
|
||||
) {
|
||||
// background image + its shadow
|
||||
Box(modifier = Modifier.fillMaxSize().background(recipe.bgColor)) {
|
||||
if (recipe.bgImageLarge != null) {
|
||||
val painter = painterResource(recipe.bgImageLarge)
|
||||
Image(painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.offset {
|
||||
backgroundShadowOffset.value
|
||||
}.graphicsLayer {
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
}.blur(radius = 8.dp),
|
||||
colorFilter = ColorFilter.tint(
|
||||
orangeDark.copy(alpha = 0.3f)
|
||||
)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.background(
|
||||
Color.Transparent,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).offset {
|
||||
backgroundImageOffset.value
|
||||
}.graphicsLayer {
|
||||
shadowElevation = 8f
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// image shadows and image
|
||||
Box(
|
||||
modifier = Modifier.aspectRatio(1f).padding(32.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Box(modifier = Modifier.padding(32.dp)) {
|
||||
Image(
|
||||
painter = painterResource(recipe.image),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
.padding(16.dp).rotate(imageRotation.value.toFloat())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(if (recipe.bgColor == sugar) yellow else sugar)
|
||||
.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
if (event.type == PointerEventType.Scroll) {
|
||||
val position = event.changes.first().position
|
||||
// on every relayout Compose will send synthetic Move event,
|
||||
// so we skip it to avoid event spam
|
||||
imageRotation.value =
|
||||
(imageRotation.value + position.getDistance()
|
||||
.toInt() * 0.010).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.weight(1f)
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(64.dp),
|
||||
userScrollEnabled = true,
|
||||
verticalArrangement = Arrangement.Absolute.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection),
|
||||
state = listState
|
||||
) {
|
||||
StepsAndDetails(
|
||||
recipe = recipe
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButton(goBack)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackButton(goBack: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier.padding(start = 32.dp, top = 16.dp).clip(
|
||||
RoundedCornerShape(50)
|
||||
).clickable {
|
||||
goBack()
|
||||
}.background(
|
||||
color = Color.Black, shape = RoundedCornerShape(50)
|
||||
).padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(Modifier.padding(start = 8.dp))
|
||||
Text(
|
||||
text = "Back to Recipes",
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
|
||||
import androidx.compose.animation.core.animateIntOffsetAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
import com.menagerie.ophelia.ui.theme.yellow
|
||||
import com.menagerie.ophelia.ui.theme.orangeDark
|
||||
import com.menagerie.ophelia.sensor.Listener
|
||||
import com.menagerie.ophelia.sensor.SensorData
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import kotlin.math.PI
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import androidx.compose.ui.draw.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RecipeDetailsSmall(
|
||||
recipe: Recipe,
|
||||
goBack: () -> Unit,
|
||||
sensorManager: SensorManager?,
|
||||
) {
|
||||
val imageRotation = remember { mutableStateOf(0) }
|
||||
val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) }
|
||||
val roll by derivedStateOf { (sensorDataLive.value.roll).coerceIn(-3f, 3f) }
|
||||
val pitch by derivedStateOf { (sensorDataLive.value.pitch).coerceIn(-2f, 2f) }
|
||||
|
||||
val tweenDuration = 300
|
||||
|
||||
sensorManager?.registerListener(object : Listener {
|
||||
override fun onUpdate(sensorData: SensorData) {
|
||||
sensorDataLive.value = sensorData
|
||||
}
|
||||
})
|
||||
|
||||
val backgroundShadowOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()),
|
||||
animationSpec = tween(tweenDuration)
|
||||
)
|
||||
val backgroundImageOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration)
|
||||
)
|
||||
|
||||
val toolbarOffsetHeightPx = remember { mutableStateOf(340f) }
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
val newOffset = toolbarOffsetHeightPx.value + delta
|
||||
toolbarOffsetHeightPx.value = newOffset.coerceIn(0f, 340f)
|
||||
imageRotation.value += (available.y * 0.5).toInt()
|
||||
return Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
imageRotation.value += ((delta * PI / 180) * 10).toInt()
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
imageRotation.value += available.y.toInt()
|
||||
return super.onPreFling(available)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val candidateHeight = maxOf(toolbarOffsetHeightPx.value, 300f)
|
||||
val listState = rememberLazyListState()
|
||||
val (fraction, setFraction) = remember { mutableStateOf(1f) }
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(color = if (recipe.bgColor == sugar) yellow else sugar)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection),
|
||||
state = listState
|
||||
) {
|
||||
|
||||
stickyHeader {
|
||||
Box(
|
||||
modifier = Modifier.shadow(
|
||||
elevation = if (fraction < 0.05) {
|
||||
((1 - fraction) * 16).dp
|
||||
} else 0.dp,
|
||||
clip = false,
|
||||
ambientColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f),
|
||||
spotColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f)
|
||||
).alpha(if (fraction < 0.2) 1f - fraction else 0f).fillMaxWidth()
|
||||
.background(
|
||||
recipe.bgColor,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).clip(RoundedCornerShape(bottomEnd = 35.dp, bottomStart = 35.dp))
|
||||
.height(candidateHeight.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
//bg image and shadow
|
||||
recipe.bgImage?.let {
|
||||
Image(painter = painterResource(it),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.offset {
|
||||
backgroundShadowOffset.value
|
||||
}.graphicsLayer {
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
}.blur(radius = 8.dp),
|
||||
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||||
orangeDark.copy(alpha = 0.3f)
|
||||
)
|
||||
)
|
||||
Image(painter = painterResource(it),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.background(
|
||||
Color.Transparent,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).offset {
|
||||
backgroundImageOffset.value
|
||||
}.graphicsLayer {
|
||||
shadowElevation = 8f
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
},
|
||||
alpha = 1 - fraction
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
) {
|
||||
Box {
|
||||
//image rounded shadow
|
||||
// Box(modifier = Modifier.offset {
|
||||
// IntOffset(
|
||||
// x = (roll * 2).dp.roundToPx(),
|
||||
// y = -(pitch * 2).dp.roundToPx()
|
||||
// )
|
||||
// }) {
|
||||
//
|
||||
// Image(
|
||||
// painter = painterResource(recipe.image),
|
||||
// contentDescription = null,
|
||||
// modifier = Modifier.aspectRatio(1f)
|
||||
// .align(Alignment.Center).padding(16.dp).shadow(
|
||||
// elevation = 16.dp,
|
||||
// shape = CircleShape,
|
||||
// clip = false,
|
||||
// ambientColor = Color.Red,
|
||||
// spotColor = Color.Red,
|
||||
// ),
|
||||
// colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||||
// orangeDark.copy(alpha = 0.0f)
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
|
||||
Image(
|
||||
painter = painterResource(recipe.image),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
.windowInsetsPadding(WindowInsets.systemBars)
|
||||
.padding(16.dp).rotate(imageRotation.value.toFloat())
|
||||
.background(
|
||||
Color.Transparent,
|
||||
CircleShape,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StepsAndDetails(
|
||||
recipe = recipe
|
||||
)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars).size(50.dp)
|
||||
.padding(10.dp).alpha(
|
||||
alpha = if (fraction <= 0) 1f else 0f,
|
||||
).background(
|
||||
color = Color.Black, shape = RoundedCornerShape(50)
|
||||
).shadow(elevation = 16.dp).padding(5.dp).clickable {
|
||||
goBack()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = recipe.bgColor,
|
||||
modifier = Modifier.size(30.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.HomeScreen
|
||||
package com.menagerie.ophelia.view.HomeScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.ui.theme.OpheliaTheme
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
|
@ -56,6 +55,7 @@ fun HeaderText(
|
|||
) {
|
||||
Text(text = text)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.HomeScreen
|
||||
package com.menagerie.ophelia.view.HomeScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Button
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.HomeScreen
|
||||
package com.menagerie.ophelia.view.HomeScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Button
|
|
@ -0,0 +1,73 @@
|
|||
package com.menagerie.ophelia.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ophelia.composeapp.generated.resources.Res
|
||||
import ophelia.composeapp.generated.resources.header_font
|
||||
import ophelia.composeapp.generated.resources.ophelia
|
||||
import ophelia.composeapp.generated.resources.welcome_blurb
|
||||
import org.jetbrains.compose.resources.StringArrayResource
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringArrayResource
|
||||
|
||||
|
||||
//@Composable
|
||||
//fun OpheliaSpanStyle(): SpanStyle {
|
||||
// val fontFamily = (Res.font.header_font)
|
||||
//
|
||||
// return SpanStyle(
|
||||
// letterSpacing = MaterialTheme.typography.bodyLarge.letterSpacing,
|
||||
// fontFamily = fontFamily,
|
||||
// fontSize = MaterialTheme.typography.bodyLarge.fontSize)
|
||||
//}
|
||||
|
||||
|
||||
@Composable
|
||||
fun OpheliaFace(
|
||||
modifier: Modifier
|
||||
) {
|
||||
Image(painter = painterResource(Res.drawable.ophelia),
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OpheliaWelcome(
|
||||
modifier: Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = OpheliaSaysArray(Res.array.welcome_blurb)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OpheliaSaysArray(
|
||||
res: StringArrayResource,
|
||||
separator: String = "\n\n"
|
||||
): AnnotatedString {
|
||||
return buildAnnotatedString {
|
||||
//pushStyle(OpheliaSpanStyle())
|
||||
append(stringArrayResource(res).joinToString(separator = separator))
|
||||
toAnnotatedString()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipeslist
|
||||
package com.menagerie.ophelia.view.common.list
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
@ -14,7 +14,7 @@ import org.jetbrains.compose.resources.DrawableResource
|
|||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Composable
|
||||
fun RecipeImage(imageBitmap: DrawableResource, modifier: Modifier){
|
||||
fun ListImage(imageBitmap: DrawableResource, modifier: Modifier){
|
||||
Box(modifier = modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipeslist
|
||||
package com.menagerie.ophelia.view.common.list
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
|
@ -18,14 +18,14 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RecipeListItemImageWrapper(
|
||||
fun ListItemImageWrapper(
|
||||
modifier: Modifier,
|
||||
child: @Composable () -> Unit,
|
||||
) {
|
||||
val animationDuration = 700
|
||||
val scale = remember { Animatable(0.3f) }
|
||||
val rotation = remember {Animatable(20f)}
|
||||
val offset = remember {Animatable(0f)}
|
||||
val rotation = remember { Animatable(20f) }
|
||||
val offset = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scale.animateTo(
|
||||
|
@ -47,13 +47,13 @@ fun RecipeListItemImageWrapper(
|
|||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
offset.animateTo(
|
||||
60f,
|
||||
animationSpec = tween(
|
||||
durationMillis = animationDuration / 2,
|
||||
easing = FastOutSlowInEasing
|
||||
offset.animateTo(
|
||||
60f,
|
||||
animationSpec = tween(
|
||||
durationMillis = animationDuration / 2,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
)
|
||||
)
|
||||
offset.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = spring(
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipeslist
|
||||
package com.menagerie.ophelia.view.common.list
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
|
@ -11,13 +11,13 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
|
||||
const val perspectiveValue = 0.004
|
||||
const val rotateX = 9f
|
||||
|
||||
//Wrapper for animating cards
|
||||
@Composable
|
||||
fun RecipeListItemWrapper(
|
||||
fun ListItemWrapper(
|
||||
child: @Composable () -> Unit,
|
||||
scrollDirection: Boolean
|
||||
scrollDirection: Boolean,
|
||||
) {
|
||||
val scaleAnimatable = remember { Animatable(initialValue = 0.75f) }
|
||||
val rotateXAnimatable =
|
||||
|
@ -66,4 +66,5 @@ fun RecipeListItemWrapper(
|
|||
{
|
||||
child()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.menagerie.ophelia.view.newUser
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun AddNameScreen(
|
||||
onGo: () -> Unit,
|
||||
) {
|
||||
//val inputViewModel: BioListViewModel.InputBioViewModel()
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.menagerie.ophelia.view.newUser
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.view.OpheliaFace
|
||||
import com.menagerie.ophelia.view.OpheliaWelcome
|
||||
import ophelia.composeapp.generated.resources.Res
|
||||
import ophelia.composeapp.generated.resources.go
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Composable
|
||||
fun NewUserStartScreen(
|
||||
onGo: () -> Unit = {},
|
||||
) {
|
||||
Scaffold {
|
||||
Column {
|
||||
OpheliaFace(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 12.dp)
|
||||
)
|
||||
OpheliaWelcome(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Button(
|
||||
onClick = onGo,
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.align(Alignment.End)
|
||||
) {
|
||||
Text(stringResource(Res.string.go))
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier.padding(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.model
|
||||
package com.menagerie.ophelia.view.recipe.model
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.model
|
||||
package com.menagerie.ophelia.view.recipe.model
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.menagerie.ophelia.ui.theme.green
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
@ -20,7 +20,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import ophelia.composeapp.generated.resources.Res
|
||||
import ophelia.composeapp.generated.resources.ophelia
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
@ -22,7 +22,7 @@ import androidx.compose.ui.text.style.LineHeightStyle
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
|
||||
@Composable
|
||||
fun InstructionItem(recipe: Recipe, index: Int) {
|
|
@ -1,28 +1,21 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun RecipeDetails(
|
||||
recipe: Recipe,
|
||||
goBack: () -> Unit,
|
||||
sensorManager: SensorManager?,
|
||||
isLarge: Boolean,
|
||||
) {
|
||||
if (isLarge) RecipeDetailsLarge(
|
||||
recipe = recipe,
|
||||
goBack = goBack,
|
||||
sensorManager = sensorManager
|
||||
)
|
||||
else RecipeDetailsSmall(
|
||||
recipe = recipe,
|
||||
goBack = goBack,
|
||||
sensorManager = sensorManager
|
||||
)
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.animation.core.animateIntOffsetAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import com.menagerie.ophelia.sensor.Listener
|
||||
import com.menagerie.ophelia.sensor.SensorData
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import com.menagerie.ophelia.ui.theme.orangeDark
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
import com.menagerie.ophelia.ui.theme.yellow
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import kotlin.math.PI
|
||||
|
||||
@Composable
|
||||
fun RecipeDetailsLarge(
|
||||
recipe: Recipe,
|
||||
sensorManager: SensorManager?,
|
||||
) {
|
||||
val imageRotation = remember { mutableStateOf(0) }
|
||||
val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) }
|
||||
val roll by derivedStateOf { (sensorDataLive.value.roll * 20).coerceIn(-4f, 4f) }
|
||||
val pitch by derivedStateOf { (sensorDataLive.value.pitch * 20).coerceIn(-4f, 4f) }
|
||||
|
||||
val tweenDuration = 300
|
||||
|
||||
sensorManager?.registerListener(object : Listener {
|
||||
override fun onUpdate(sensorData: SensorData) {
|
||||
sensorDataLive.value = sensorData
|
||||
}
|
||||
})
|
||||
|
||||
val backgroundShadowOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()),
|
||||
animationSpec = tween(tweenDuration)
|
||||
)
|
||||
val backgroundImageOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration)
|
||||
)
|
||||
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
imageRotation.value += (available.y * 0.5).toInt()
|
||||
return Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
imageRotation.value += ((delta * PI / 180) * 10).toInt()
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
imageRotation.value += available.y.toInt()
|
||||
return super.onPreFling(available)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(if (recipe.bgColor == sugar) yellow else sugar)
|
||||
) {
|
||||
val size = mutableStateOf(IntSize(0, 0))
|
||||
Row {
|
||||
Box(modifier = Modifier.fillMaxSize().onGloballyPositioned {
|
||||
size.value = it.size
|
||||
}.weight(1f).pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
// on every relayout Compose will send synthetic Move event,
|
||||
// so we skip it to avoid event spam
|
||||
if (event.type == PointerEventType.Move) {
|
||||
val position = event.changes.first().position
|
||||
sensorDataLive.value = SensorData(
|
||||
roll = position.x - size.value.height / 4,
|
||||
pitch = (position.y - size.value.width / 4)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(topEnd = 35.dp, bottomEnd = 35.dp)),
|
||||
shape = RoundedCornerShape(
|
||||
topEnd = 35.dp,
|
||||
bottomEnd = 35.dp,
|
||||
),
|
||||
) {
|
||||
// background image + its shadow
|
||||
Box(modifier = Modifier.fillMaxSize().background(recipe.bgColor)) {
|
||||
if (recipe.bgImageLarge != null) {
|
||||
val painter = painterResource(recipe.bgImageLarge)
|
||||
Image(painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.offset {
|
||||
backgroundShadowOffset.value
|
||||
}.graphicsLayer {
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
}.blur(radius = 8.dp),
|
||||
colorFilter = ColorFilter.tint(
|
||||
orangeDark.copy(alpha = 0.3f)
|
||||
)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.background(
|
||||
Color.Transparent,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).offset {
|
||||
backgroundImageOffset.value
|
||||
}.graphicsLayer {
|
||||
shadowElevation = 8f
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// image shadows and image
|
||||
Box(
|
||||
modifier = Modifier.aspectRatio(1f).padding(32.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Box(modifier = Modifier.padding(32.dp)) {
|
||||
Image(
|
||||
painter = painterResource(recipe.image),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
.padding(16.dp).rotate(imageRotation.value.toFloat())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(if (recipe.bgColor == sugar) yellow else sugar)
|
||||
.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
if (event.type == PointerEventType.Scroll) {
|
||||
val position = event.changes.first().position
|
||||
// on every relayout Compose will send synthetic Move event,
|
||||
// so we skip it to avoid event spam
|
||||
imageRotation.value =
|
||||
(imageRotation.value + position.getDistance()
|
||||
.toInt() * 0.010).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.weight(1f)
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(64.dp),
|
||||
userScrollEnabled = true,
|
||||
verticalArrangement = Arrangement.Absolute.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection),
|
||||
state = listState
|
||||
) {
|
||||
StepsAndDetails(
|
||||
recipe = recipe
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.animation.core.animateIntOffsetAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
import com.menagerie.ophelia.ui.theme.yellow
|
||||
import com.menagerie.ophelia.ui.theme.orangeDark
|
||||
import com.menagerie.ophelia.sensor.Listener
|
||||
import com.menagerie.ophelia.sensor.SensorData
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import kotlin.math.PI
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import androidx.compose.ui.draw.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun RecipeDetailsSmall(
|
||||
recipe: Recipe,
|
||||
sensorManager: SensorManager?,
|
||||
) {
|
||||
val imageRotation = remember { mutableStateOf(0) }
|
||||
val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) }
|
||||
val roll by derivedStateOf { (sensorDataLive.value.roll).coerceIn(-3f, 3f) }
|
||||
val pitch by derivedStateOf { (sensorDataLive.value.pitch).coerceIn(-2f, 2f) }
|
||||
|
||||
val tweenDuration = 300
|
||||
|
||||
sensorManager?.registerListener(object : Listener {
|
||||
override fun onUpdate(sensorData: SensorData) {
|
||||
sensorDataLive.value = sensorData
|
||||
}
|
||||
})
|
||||
|
||||
val backgroundShadowOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset((roll * 6f).toInt(), (pitch * 6f).toInt()),
|
||||
animationSpec = tween(tweenDuration)
|
||||
)
|
||||
val backgroundImageOffset = animateIntOffsetAsState(
|
||||
targetValue = IntOffset(-roll.toInt(), pitch.toInt()), animationSpec = tween(tweenDuration)
|
||||
)
|
||||
|
||||
val toolbarOffsetHeightPx = remember { mutableStateOf(340f) }
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
val newOffset = toolbarOffsetHeightPx.value + delta
|
||||
toolbarOffsetHeightPx.value = newOffset.coerceIn(0f, 340f)
|
||||
imageRotation.value += (available.y * 0.5).toInt()
|
||||
return Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset, available: Offset, source: NestedScrollSource
|
||||
): Offset {
|
||||
val delta = available.y
|
||||
imageRotation.value += ((delta * PI / 180) * 10).toInt()
|
||||
return super.onPostScroll(consumed, available, source)
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
imageRotation.value += available.y.toInt()
|
||||
return super.onPreFling(available)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val candidateHeight = maxOf(toolbarOffsetHeightPx.value, 300f)
|
||||
val listState = rememberLazyListState()
|
||||
val (fraction, setFraction) = remember { mutableStateOf(1f) }
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.background(color = if (recipe.bgColor == sugar) yellow else sugar)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection),
|
||||
state = listState
|
||||
) {
|
||||
|
||||
stickyHeader {
|
||||
Box(
|
||||
modifier = Modifier.shadow(
|
||||
elevation = if (fraction < 0.05) {
|
||||
((1 - fraction) * 16).dp
|
||||
} else 0.dp,
|
||||
clip = false,
|
||||
ambientColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f),
|
||||
spotColor = Color(0xffCE5A01).copy(if (fraction < 0.1) 1f - fraction else 0f)
|
||||
).alpha(if (fraction < 0.2) 1f - fraction else 0f).fillMaxWidth()
|
||||
.background(
|
||||
recipe.bgColor,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).clip(RoundedCornerShape(bottomEnd = 35.dp, bottomStart = 35.dp))
|
||||
.height(candidateHeight.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
//bg image and shadow
|
||||
recipe.bgImage?.let {
|
||||
Image(painter = painterResource(it),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.offset {
|
||||
backgroundShadowOffset.value
|
||||
}.graphicsLayer {
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
}.blur(radius = 8.dp),
|
||||
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||||
orangeDark.copy(alpha = 0.3f)
|
||||
)
|
||||
)
|
||||
Image(painter = painterResource(it),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.background(
|
||||
Color.Transparent,
|
||||
RoundedCornerShape(
|
||||
bottomEnd = 35.dp, bottomStart = 35.dp
|
||||
),
|
||||
).offset {
|
||||
backgroundImageOffset.value
|
||||
}.graphicsLayer {
|
||||
shadowElevation = 8f
|
||||
scaleX = 1.050f
|
||||
scaleY = 1.050f
|
||||
},
|
||||
alpha = 1 - fraction
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
) {
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(recipe.image),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
|
||||
.windowInsetsPadding(WindowInsets.systemBars)
|
||||
.padding(16.dp).rotate(imageRotation.value.toFloat())
|
||||
.background(
|
||||
Color.Transparent,
|
||||
CircleShape,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StepsAndDetails(
|
||||
recipe = recipe
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipesdetails
|
||||
package com.menagerie.ophelia.view.recipe.recipesdetails
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
|
@ -8,7 +8,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
|
||||
|
||||
internal fun LazyListScope.StepsAndDetails(
|
||||
|
@ -39,7 +39,7 @@ internal fun LazyListScope.StepsAndDetails(
|
|||
|
||||
}
|
||||
|
||||
itemsIndexed(recipe.ingredients) { index, value ->
|
||||
itemsIndexed(recipe.ingredients) { _, value ->
|
||||
IngredientItem(recipe, value)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipeslist
|
||||
package com.menagerie.ophelia.view.recipe.recipeslist
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -22,7 +22,9 @@ import androidx.compose.ui.draw.shadow
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.common.list.ListImage
|
||||
import com.menagerie.ophelia.view.common.list.ListItemImageWrapper
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
|
||||
|
||||
@Composable
|
||||
|
@ -73,9 +75,9 @@ fun RecipeListItem(
|
|||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
RecipeListItemImageWrapper(modifier = Modifier.align(Alignment.BottomEnd)
|
||||
ListItemImageWrapper(modifier = Modifier.align(Alignment.BottomEnd)
|
||||
.fillMaxWidth(0.45f).aspectRatio(1f), child = {
|
||||
RecipeImage(
|
||||
ListImage(
|
||||
imageBitmap = recipe.image, modifier = Modifier
|
||||
)
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
package com.menagerie.ophelia.recipeslist
|
||||
package com.menagerie.ophelia.view.recipe.recipeslist
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
|
@ -17,7 +17,8 @@ import androidx.compose.foundation.lazy.grid.LazyGridState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.menagerie.ophelia.model.Recipe
|
||||
import com.menagerie.ophelia.view.common.list.ListItemWrapper
|
||||
import com.menagerie.ophelia.view.recipe.model.Recipe
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
|
||||
@Composable
|
||||
|
@ -39,7 +40,7 @@ fun RecipesListScreen(
|
|||
}
|
||||
items(items.size) { item ->
|
||||
val recipe = items[item]
|
||||
RecipeListItemWrapper(
|
||||
ListItemWrapper(
|
||||
scrollDirection = listState.isScrollingUp(),
|
||||
child = {
|
||||
RecipeListItem(
|
|
@ -0,0 +1,15 @@
|
|||
package com.menagerie.ophelia.view.tags.model
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||
|
||||
data class Tag @OptIn(ExperimentalResourceApi::class) constructor(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val image: DrawableResource,
|
||||
val bgImage: DrawableResource? = null,
|
||||
val bgImageLarge: DrawableResource? = null,
|
||||
val bgColor: Color
|
||||
)
|
|
@ -0,0 +1,71 @@
|
|||
package com.menagerie.ophelia.view.tags.model
|
||||
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
import ophelia.composeapp.generated.resources.Res
|
||||
import ophelia.composeapp.generated.resources.ophelia
|
||||
|
||||
val tagList = listOf(
|
||||
Tag(
|
||||
id = 1,
|
||||
title = "Alcohol",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 2,
|
||||
title = "Asexual",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 3,
|
||||
title = "BDSM",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 4,
|
||||
title = "Furry",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 5,
|
||||
title = "Monogamous",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 6,
|
||||
title = "Plural",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 7,
|
||||
title = "Therian",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 8,
|
||||
title = "Transgender",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
Tag(
|
||||
id = 9,
|
||||
title = "Weed",
|
||||
description = "",
|
||||
image = Res.drawable.ophelia,
|
||||
bgColor = sugar
|
||||
),
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
package com.menagerie.ophelia.view.tags.tagsdetails
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.menagerie.ophelia.sensor.SensorManager
|
||||
import com.menagerie.ophelia.view.tags.model.Tag
|
||||
|
||||
@Composable
|
||||
fun TagsDetails(
|
||||
sensorManager: SensorManager?,
|
||||
tag: Tag,
|
||||
isLarge: Boolean
|
||||
) {
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package com.menagerie.ophelia.view.tags.tagslist
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.menagerie.ophelia.view.common.list.ListImage
|
||||
import com.menagerie.ophelia.view.common.list.ListItemImageWrapper
|
||||
import com.menagerie.ophelia.view.tags.model.Tag
|
||||
|
||||
//The Actual Content of the Card
|
||||
@Composable
|
||||
fun TagListItem(
|
||||
tag: Tag,
|
||||
onClick: (tag: Tag) -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier) {
|
||||
Box(modifier = Modifier.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth().aspectRatio(1.5f)
|
||||
.shadow(
|
||||
elevation = 16.dp,
|
||||
shape = RoundedCornerShape(35.dp),
|
||||
clip = true,
|
||||
ambientColor = Color(0xffCE5A01),
|
||||
spotColor = Color(0xffCE5A01)
|
||||
)
|
||||
.background(color = tag.bgColor, shape = RoundedCornerShape(35.dp)).fillMaxHeight().clickable {
|
||||
onClick(tag)
|
||||
}) {
|
||||
Card(
|
||||
backgroundColor = tag.bgColor,
|
||||
shape = RoundedCornerShape(35.dp),
|
||||
modifier = Modifier.clip(RoundedCornerShape(35.dp))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(1.5f)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxHeight().padding(16.dp).fillMaxWidth(0.55f),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Column(modifier = Modifier.align(Alignment.Bottom)) {
|
||||
Text(
|
||||
text = tag.title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
)
|
||||
|
||||
Text(
|
||||
tag.description,
|
||||
style = MaterialTheme.typography.displayMedium,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
ListItemImageWrapper(modifier = Modifier.align(Alignment.BottomEnd)
|
||||
.fillMaxWidth(0.45f).aspectRatio(1f), child = {
|
||||
ListImage(
|
||||
imageBitmap = tag.image, modifier = Modifier
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.menagerie.ophelia.view.tags.tagslist
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.menagerie.ophelia.view.common.list.ListItemWrapper
|
||||
import com.menagerie.ophelia.view.tags.model.Tag
|
||||
import com.menagerie.ophelia.ui.theme.sugar
|
||||
|
||||
@Composable
|
||||
fun TagsListScreen(
|
||||
items: List<Tag>,
|
||||
onClick: (tag: Tag) -> Unit,
|
||||
isLarge: Boolean,
|
||||
) {
|
||||
//wrap the whole screen in a box (or scaffold)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(sugar)
|
||||
){
|
||||
val listState = rememberLazyGridState() //keep track of how the cells are arranged
|
||||
LazyVerticalGrid(
|
||||
state = listState,
|
||||
columns = GridCells.Fixed(if (isLarge) 3 else 1) //TODO : replace check with enum values?
|
||||
|
||||
) {
|
||||
if(isLarge.not())
|
||||
item {
|
||||
Spacer(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars))
|
||||
} //add a top spacer on mobile for the system bars
|
||||
items(items.size) { item ->
|
||||
val tag = items[item]
|
||||
ListItemWrapper(
|
||||
scrollDirection = listState.isScrollingUp(),
|
||||
child = {
|
||||
TagListItem(
|
||||
tag = tag,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
)
|
||||
} //grid items list of tags
|
||||
} // grid
|
||||
} // Box
|
||||
} // screen
|
||||
|
||||
|
||||
//TODO : Move this to its own helper file
|
||||
@Composable
|
||||
private fun LazyGridState.isScrollingUp(): Boolean {
|
||||
var previousIndex by remember(this) {mutableStateOf(firstVisibleItemIndex)}
|
||||
var previousScrollOffset by remember(this) {mutableStateOf(firstVisibleItemScrollOffset)}
|
||||
return remember(this) {
|
||||
derivedStateOf {
|
||||
if(previousIndex != firstVisibleItemIndex) {
|
||||
previousIndex > firstVisibleItemIndex
|
||||
} else {
|
||||
previousScrollOffset >= firstVisibleItemScrollOffset
|
||||
}.also {
|
||||
previousIndex = firstVisibleItemIndex
|
||||
previousScrollOffset = firstVisibleItemScrollOffset
|
||||
}
|
||||
}
|
||||
}.value
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE User (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
insert:
|
||||
INSERT OR REPLACE INTO User(id, name)
|
||||
VALUES(?,?);
|
||||
|
||||
getAll:
|
||||
SELECT * FROM User;
|
||||
|
||||
updateName:
|
||||
UPDATE User
|
||||
SET name = :name
|
||||
WHERE id IS :id;
|
||||
|
||||
delete:
|
||||
DELETE FROM User
|
||||
WHERE id IS :id;
|
|
@ -0,0 +1,12 @@
|
|||
package com.menagerie.ophelia
|
||||
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
|
||||
actual class DatabaseDriverFactory {
|
||||
actual fun create(): SqlDriver {
|
||||
val driver: SqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
PolyculeDatabase.Schema.create(driver)
|
||||
return driver
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.menagerie.ophelia
|
||||
|
||||
import com.menagerie.ophelia.data.user.UserDataSource
|
||||
|
||||
class DesktopAppModule : AppModule {
|
||||
private val db by lazy {
|
||||
PolyculeDatabase(
|
||||
driver = DatabaseDriverFactory().create()
|
||||
)
|
||||
}
|
||||
|
||||
override fun provideUserDataSource() = UserDataSource(db)
|
||||
}
|
|
@ -3,8 +3,10 @@ package com.menagerie.ophelia
|
|||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.menagerie.ophelia.ui.theme.OpheliaTheme
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -28,6 +30,7 @@ fun main() {
|
|||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Ophelia",
|
||||
state = rememberWindowState(height = 900.dp, width = 1200.dp)
|
||||
) {
|
||||
|
||||
val currentThemeString by prefs
|
||||
|
|
17
composeApp/src/main/res/values/arrays.xml
Normal file
17
composeApp/src/main/res/values/arrays.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<array name="com_google_android_gms_fonts_certs">
|
||||
<item>@array/com_google_android_gms_fonts_certs_dev</item>
|
||||
<item>@array/com_google_android_gms_fonts_certs_prod</item>
|
||||
</array>
|
||||
<string-array name="com_google_android_gms_fonts_certs_dev">
|
||||
<item>
|
||||
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||
</item>
|
||||
</string-array>
|
||||
<string-array name="com_google_android_gms_fonts_certs_prod">
|
||||
<item>
|
||||
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
|
||||
</item>
|
||||
</string-array>
|
||||
</resources>
|
|
@ -19,6 +19,7 @@ material3Android = "1.3.1"
|
|||
material3Desktop = "1.3.1"
|
||||
uiTextGoogleFonts = "1.7.5"
|
||||
core = "1.15.0"
|
||||
sqldelight = "2.0.2"
|
||||
navigation-compose = "2.7.0-alpha07"
|
||||
datastore = "1.1.1"
|
||||
|
||||
|
@ -39,6 +40,9 @@ kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-co
|
|||
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
|
||||
androidx-material3-desktop = { group = "androidx.compose.material3", name = "material3-desktop", version.ref = "material3Desktop" }
|
||||
androidx-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
|
||||
sqldelight-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
sqldelight-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
||||
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
||||
androidx-core = { group = "androidx.core", name = "core", version.ref = "core" }
|
||||
datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
|
||||
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
|
||||
|
@ -48,4 +52,5 @@ androidApplication = { id = "com.android.application", version.ref = "agp" }
|
|||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
Loading…
Add table
Reference in a new issue