├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_nfc.xml
│ │ │ │ ├── ic_cloud_off.xml
│ │ │ │ ├── ic_contactless.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ └── values-night
│ │ │ │ └── themes.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── hexxotest
│ │ │ │ └── spoolcompanion
│ │ │ │ ├── network
│ │ │ │ ├── data
│ │ │ │ │ ├── Extra.kt
│ │ │ │ │ ├── Vendor.kt
│ │ │ │ │ ├── SpoolItem.kt
│ │ │ │ │ └── Filament.kt
│ │ │ │ └── SpoolApi.kt
│ │ │ │ ├── ui
│ │ │ │ ├── SettingsDataStore.kt
│ │ │ │ ├── NfcTagViewModel.kt
│ │ │ │ ├── theme
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Color.kt
│ │ │ │ ├── SpoolViewModel.kt
│ │ │ │ ├── SpoolCompanionApp.kt
│ │ │ │ └── HomeScreen.kt
│ │ │ │ ├── models
│ │ │ │ └── SpoolListEntry.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── hexxotest
│ │ │ └── spoolcompanion
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── hexxotest
│ │ └── spoolcompanion
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── docs
└── images
│ ├── nfc_write.png
│ ├── showcase.png
│ ├── spool_list.png
│ ├── set_url_screen.png
│ └── url_settings_dialog.png
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── settings.gradle.kts
├── .gitignore
├── README.md
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/docs/images/nfc_write.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/docs/images/nfc_write.png
--------------------------------------------------------------------------------
/docs/images/showcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/docs/images/showcase.png
--------------------------------------------------------------------------------
/docs/images/spool_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/docs/images/spool_list.png
--------------------------------------------------------------------------------
/docs/images/set_url_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/docs/images/set_url_screen.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/docs/images/url_settings_dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/docs/images/url_settings_dialog.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/V-aruu/SpoolCompanion/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/network/data/Extra.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.network.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | class Extra
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/SettingsDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui
2 |
3 | import androidx.datastore.preferences.core.stringPreferencesKey
4 |
5 | private val SPOOLMAN_URL = stringPreferencesKey("spoolman_url")
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 12 17:03:05 CEST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/network/data/Vendor.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.network.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Vendor(
7 | val external_id: String = "",
8 | val extra: Extra,
9 | val id: Int = -1,
10 | val name: String = "",
11 | val registered: String = ""
12 | )
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/hexxotest/spoolcompanion/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/models/SpoolListEntry.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.models
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | data class SpoolListEntry(
6 | val id: Int = -1,
7 | val filamentId: Int = -1,
8 | val comment: String = "",
9 | val color: Color = Color.Transparent,
10 | val vendorName: String = "",
11 | val name: String = "",
12 | val material: String = "",
13 | val diameter: Double = 0.0,
14 | val weight: String = ""
15 | )
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/NfcTagViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.setValue
5 | import androidx.compose.runtime.mutableIntStateOf
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.lifecycle.ViewModel
8 |
9 | class NfcTagViewModel : ViewModel() {
10 |
11 | var spoolId by mutableIntStateOf(-1)
12 |
13 | var filamentId by mutableIntStateOf(-1)
14 |
15 | var isDialogShown by mutableStateOf(false)
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_nfc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SpoolCompanion
3 | SpoolCompanion
4 | Loading…
5 | Failed to load spools, check internet connection (or Spoolman\'s URL)
6 | This device does not support NFC
7 | Dismiss
8 | Select spool:
9 | Please set Spoolman\'s URL in the settings dialog
10 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "SpoolCompanion"
23 | include(":app")
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/network/data/SpoolItem.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.network.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class SpoolItem(
7 | val archived: Boolean = false,
8 | val extra: Extra,
9 | val filament: Filament,
10 | val id: Int = -1,
11 | val initial_weight: Double = 0.0,
12 | val registered: String = "",
13 | val remaining_length: Double = 0.0,
14 | val remaining_weight: Double = 0.0,
15 | val spool_weight: Double = 0.0,
16 | val used_length: Double = 0.0,
17 | val used_weight: Double = 0.0,
18 | val comment: String = ""
19 | )
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/network/data/Filament.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.network.data
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Filament(
7 | val color_hex: String = "",
8 | val density: Double = 0.0,
9 | val diameter: Double = 0.0,
10 | val external_id: String = "",
11 | val extra: Extra,
12 | val id: Int = -1,
13 | val material: String = "",
14 | val name: String = "",
15 | val registered: String = "",
16 | val settings_bed_temp: Int = 0,
17 | val settings_extruder_temp: Int = 0,
18 | val spool_weight: Double = 0.0,
19 | val vendor: Vendor,
20 | val weight: Double= 0.0
21 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cloud_off.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/hexxotest/spoolcompanion/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.hexxotest.spoolcompanion", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_contactless.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Google Services (e.g. APIs or Firebase)
30 | google-services.json
31 |
32 | # Android Profiling
33 | *.hprof
34 |
35 | # Default .gitignore
36 | .gradle
37 | /local.properties
38 | /.idea/caches
39 | /.idea/libraries
40 | /.idea/modules.xml
41 | /.idea/workspace.xml
42 | /.idea/navEditor.xml
43 | /.idea/assetWizardSettings.xml
44 | .DS_Store
45 | /build
46 | /captures
47 | .externalNativeBuild
48 | .cxx
49 |
50 | # Project specifics
51 | screen_record.mp4
52 | showcase.xcf
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/network/SpoolApi.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.network
2 |
3 | import com.hexxotest.spoolcompanion.network.data.SpoolItem
4 | import kotlinx.serialization.json.Json
5 | import okhttp3.MediaType.Companion.toMediaType
6 | import retrofit2.Retrofit
7 | import retrofit2.converter.kotlinx.serialization.asConverterFactory
8 | import retrofit2.http.GET
9 |
10 | class SpoolApi(
11 | baseUrl: String = ""
12 | ) {
13 |
14 | interface SpoolApiService {
15 | @GET("spool")
16 | suspend fun getSpoolList(): List
17 | }
18 |
19 | private val json = Json { ignoreUnknownKeys = true }
20 |
21 | private val retrofit = Retrofit.Builder()
22 | .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
23 | .baseUrl("$baseUrl/api/v1/")
24 | .build()
25 |
26 | val retrofitService: SpoolApiService by lazy {
27 | retrofit.create(SpoolApiService::class.java)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # :iphone: Android App - Spoolman Companion
3 |
4 | A quick Android app that allows you to write NDEF records on NFC/RFID tags, from your mobile phone.
5 |
6 | Combined with [nfc2klipper](https://github.com/bofh69/nfc2klipper) it allows you to quickly load filament from a simple NFC tag.
7 |
8 | # Overview
9 |
10 |
11 |
12 | I made this app because I was tired of having to go to the printer NFC reader/writer to enroll new tags in my collection.
13 |
14 | This app lets you simply choose a spool - from your [Spoolman](https://github.com/Donkie/Spoolman) database - and write the associated NFC tag.
15 |
16 | And all this, from your mobile phone !
17 |
18 | ## Tech
19 |
20 | I am a completely noob in Android app development, this was my first app using Kotlin and Jetpack Compose. It was a nice learning project, I'll try to keep it updated and bring in new features.
21 |
22 | > [!WARNING]
23 | > I haven't tested this app on several devices, and it is still an early development.
24 | >
25 | > User shall use this app with caution, and at their own risks.
26 |
27 | ## TODO
28 | - [ ] Handle a Spoolman property (like `tag_written`) to retrieve/record if this spool has a tag attached (and filter the list ?)
29 | - [ ] Implement a better UI theme + Dark and Light variants
30 | - [ ] Implement an app icon
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | id("org.jetbrains.kotlin.plugin.serialization") version "1.9.10"
5 | }
6 |
7 | android {
8 | namespace = "com.hexxotest.spoolcompanion"
9 | compileSdk = 34
10 |
11 | defaultConfig {
12 | applicationId = "com.hexxotest.spoolcompanion"
13 | minSdk = 24
14 | targetSdk = 34
15 | versionCode = 1
16 | versionName = "1.0"
17 |
18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19 | vectorDrawables {
20 | useSupportLibrary = true
21 | }
22 |
23 | setProperty("archivesBaseName", "SpoolCompanion")
24 | }
25 |
26 | buildTypes {
27 | release {
28 | isMinifyEnabled = false
29 | proguardFiles(
30 | getDefaultProguardFile("proguard-android-optimize.txt"),
31 | "proguard-rules.pro"
32 | )
33 | }
34 | }
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_1_8
37 | targetCompatibility = JavaVersion.VERSION_1_8
38 | }
39 | kotlinOptions {
40 | jvmTarget = "1.8"
41 | }
42 | buildFeatures {
43 | compose = true
44 | }
45 | composeOptions {
46 | kotlinCompilerExtensionVersion = "1.5.1"
47 | }
48 | packaging {
49 | resources {
50 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
51 | }
52 | }
53 | }
54 |
55 | dependencies {
56 |
57 | // Core
58 | implementation(libs.androidx.core.ktx)
59 | implementation(libs.androidx.appcompat)
60 | implementation(libs.androidx.lifecycle.runtime.ktx)
61 | implementation(libs.androidx.activity.compose)
62 | implementation(libs.androidx.lifecycle.viewmodel.compose)
63 | implementation(platform(libs.androidx.compose.bom))
64 | implementation(libs.material)
65 | implementation(libs.androidx.ui)
66 | implementation(libs.androidx.ui.graphics)
67 | implementation(libs.androidx.ui.tooling.preview)
68 | implementation(libs.androidx.material3)
69 | testImplementation(libs.junit)
70 | androidTestImplementation(libs.androidx.junit)
71 | androidTestImplementation(libs.androidx.espresso.core)
72 |
73 | // Retrofit
74 | implementation(libs.okhttp)
75 | implementation(libs.retrofit)
76 | implementation(libs.kotlinx.serialization.json)
77 | implementation(libs.converter.kotlinx.serialization)
78 | androidTestImplementation(platform(libs.androidx.compose.bom))
79 | androidTestImplementation(libs.androidx.ui.test.junit4)
80 | debugImplementation(libs.androidx.ui.tooling)
81 | debugImplementation(libs.androidx.ui.test.manifest)
82 |
83 | // Datastore
84 | implementation(libs.androidx.datastore.preferences)
85 |
86 | }
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.5.2"
3 | datastorePreferences = "1.1.1"
4 | kotlin = "1.9.0"
5 | coreKtx = "1.13.1"
6 | junit = "4.13.2"
7 | junitVersion = "1.1.5"
8 | espressoCore = "3.5.1"
9 | appcompat = "1.6.1"
10 | kotlinxSerializationJson = "1.6.3"
11 | material = "1.10.0"
12 | okhttp = "4.11.0"
13 | retrofit = "2.11.0"
14 | lifecycleRuntimeKtx = "2.8.4"
15 | activityCompose = "1.9.1"
16 | composeBom = "2024.04.01"
17 |
18 | [libraries]
19 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
20 | androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
21 | androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
22 | converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
23 | junit = { group = "junit", name = "junit", version.ref = "junit" }
24 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
25 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
26 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
27 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
28 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
29 | okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
30 | retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
31 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
32 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
33 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
34 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
35 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
36 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
37 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
38 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
39 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
40 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
41 |
42 | [plugins]
43 | android-application = { id = "com.android.application", version.ref = "agp" }
44 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/SpoolViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import com.hexxotest.spoolcompanion.models.SpoolListEntry
11 | import com.hexxotest.spoolcompanion.network.SpoolApi
12 | import kotlinx.coroutines.launch
13 | import java.io.IOException
14 |
15 | class SpoolViewModel(spoolmanUrl: String) : ViewModel() {
16 |
17 | private val url = spoolmanUrl
18 |
19 | sealed interface UiState {
20 | data class Success(val spools: List) : UiState
21 | data object Error : UiState
22 | data object Loading : UiState
23 | }
24 |
25 | var currentUiState: UiState by mutableStateOf(UiState.Loading)
26 | private set
27 |
28 | init {
29 | getSpools()
30 | }
31 |
32 | private fun getSpools() {
33 | viewModelScope.launch {
34 | currentUiState = try {
35 | val spoolApi = SpoolApi(baseUrl = url)
36 | val spools = spoolApi.retrofitService.getSpoolList()
37 | val entries = spools.map { spool ->
38 | SpoolListEntry(
39 | id = spool.id,
40 | filamentId = spool.filament.id,
41 | vendorName = spool.filament.vendor.name,
42 | name = spool.filament.name,
43 | color = Color(android.graphics.Color.parseColor("#${spool.filament.color_hex}")),
44 | material = spool.filament.material,
45 | weight = convertWeightDoubleToString(spool.filament.weight),
46 | diameter = spool.filament.diameter,
47 | comment = spool.comment
48 | )
49 | }
50 | UiState.Success(entries)
51 | } catch (e: Exception) {
52 | UiState.Error
53 | }
54 | }
55 | }
56 |
57 | private fun convertWeightDoubleToString(weight: Double): String {
58 | // Weight var
59 | var weightInfo: Double = weight
60 | // Define unit 'g' default, 'kg' if > 1000 g
61 | var unit: String = "g"
62 | // Check if greater than a kilo
63 | if (weightInfo >= 1000) {
64 | weightInfo /= 1000.0
65 | unit = "kg"
66 | }
67 | // Return formatted string
68 | val weightStr: String = weightInfo.toString()
69 | return if (weightStr.contains(".0")) {
70 | "${weightStr.substring(0, weightStr.length - 2)} $unit"
71 | } else {
72 | "$weightStr $unit"
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion
2 |
3 | import android.app.PendingIntent
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.nfc.NdefMessage
7 | import android.nfc.NdefRecord
8 | import android.nfc.NfcAdapter
9 | import android.nfc.Tag
10 | import android.nfc.tech.Ndef
11 | import android.nfc.tech.NfcA
12 | import android.os.Bundle
13 | import android.os.Parcelable
14 | import android.util.Log
15 | import androidx.activity.ComponentActivity
16 | import androidx.activity.compose.setContent
17 | import androidx.activity.enableEdgeToEdge
18 | import androidx.activity.viewModels
19 | import androidx.compose.foundation.layout.fillMaxSize
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.ui.Modifier
23 | import androidx.core.content.IntentCompat
24 | import androidx.datastore.core.DataStore
25 | import androidx.datastore.preferences.core.Preferences
26 | import androidx.datastore.preferences.preferencesDataStore
27 | import com.hexxotest.spoolcompanion.ui.NfcTagViewModel
28 | import com.hexxotest.spoolcompanion.ui.NoNfcApp
29 | import com.hexxotest.spoolcompanion.ui.SpoolCompanionApp
30 | import com.hexxotest.spoolcompanion.ui.theme.SpoolCompanionTheme
31 |
32 | val Context.dataStore: DataStore by preferencesDataStore(name = "settings")
33 |
34 | class MainActivity : ComponentActivity() {
35 |
36 | private val nfcTagViewModel: NfcTagViewModel by viewModels()
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 |
40 | // Init methods
41 | super.onCreate(savedInstanceState)
42 | enableEdgeToEdge()
43 |
44 | // NFC
45 | val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
46 |
47 | // Content handling
48 | setContent {
49 | SpoolCompanionTheme {
50 | Surface(
51 | color = MaterialTheme.colorScheme.background,
52 | modifier = Modifier
53 | .fillMaxSize()
54 | ) {
55 | if (nfcAdapter == null) {
56 | NoNfcApp()
57 | } else {
58 | SpoolCompanionApp(nfcTagViewModel)
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
65 | public override fun onPause() {
66 | super.onPause()
67 | NfcAdapter.getDefaultAdapter(this)?.disableForegroundDispatch(this)
68 | }
69 |
70 | public override fun onResume() {
71 | super.onResume()
72 | val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
73 | if (nfcAdapter != null) {
74 | val intent = Intent(this, javaClass).apply {
75 | addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
76 | }
77 | val pendingIntent: PendingIntent = PendingIntent.getActivity(
78 | this, 0, intent,
79 | PendingIntent.FLAG_MUTABLE
80 | )
81 | nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
82 | }
83 | }
84 |
85 | public override fun onNewIntent(intent: Intent) {
86 | super.onNewIntent(intent)
87 | Log.w("INTENT", intent.action.toString())
88 | if (intent.action == NfcAdapter.ACTION_TAG_DISCOVERED ||
89 | intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED
90 | ) {
91 | val tag: Tag = IntentCompat.getParcelableExtra(
92 | intent,
93 | NfcAdapter.EXTRA_TAG,
94 | Parcelable::class.java
95 | ) as Tag
96 | if (nfcTagViewModel.isDialogShown) {
97 | Log.w(
98 | "NFC",
99 | "SPOOL:${nfcTagViewModel.spoolId} | FILAMENT:${nfcTagViewModel.filamentId}"
100 | )
101 | val msg = NdefMessage(
102 | NdefRecord.createTextRecord(
103 | null,
104 | "SPOOL:${nfcTagViewModel.spoolId}\nFILAMENT:${nfcTagViewModel.filamentId}"
105 | )
106 | )
107 | Ndef.get(tag).use {
108 | it.connect()
109 | it.writeNdefMessage(msg)
110 | }
111 | nfcTagViewModel.isDialogShown = false
112 | }
113 | }
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.darkColorScheme
6 | import androidx.compose.material3.lightColorScheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.Immutable
9 | import androidx.compose.ui.graphics.Color
10 |
11 | private val lightScheme = lightColorScheme(
12 | primary = primaryLight,
13 | onPrimary = onPrimaryLight,
14 | primaryContainer = primaryContainerLight,
15 | onPrimaryContainer = onPrimaryContainerLight,
16 | secondary = secondaryLight,
17 | onSecondary = onSecondaryLight,
18 | secondaryContainer = secondaryContainerLight,
19 | onSecondaryContainer = onSecondaryContainerLight,
20 | tertiary = tertiaryLight,
21 | onTertiary = onTertiaryLight,
22 | tertiaryContainer = tertiaryContainerLight,
23 | onTertiaryContainer = onTertiaryContainerLight,
24 | error = errorLight,
25 | onError = onErrorLight,
26 | errorContainer = errorContainerLight,
27 | onErrorContainer = onErrorContainerLight,
28 | background = backgroundLight,
29 | onBackground = onBackgroundLight,
30 | surface = surfaceLight,
31 | onSurface = onSurfaceLight,
32 | surfaceVariant = surfaceVariantLight,
33 | onSurfaceVariant = onSurfaceVariantLight,
34 | outline = outlineLight,
35 | outlineVariant = outlineVariantLight,
36 | scrim = scrimLight,
37 | inverseSurface = inverseSurfaceLight,
38 | inverseOnSurface = inverseOnSurfaceLight,
39 | inversePrimary = inversePrimaryLight,
40 | surfaceDim = surfaceDimLight,
41 | surfaceBright = surfaceBrightLight,
42 | surfaceContainerLowest = surfaceContainerLowestLight,
43 | surfaceContainerLow = surfaceContainerLowLight,
44 | surfaceContainer = surfaceContainerLight,
45 | surfaceContainerHigh = surfaceContainerHighLight,
46 | surfaceContainerHighest = surfaceContainerHighestLight,
47 | )
48 |
49 | private val darkScheme = darkColorScheme(
50 | primary = primaryDark,
51 | onPrimary = onPrimaryDark,
52 | primaryContainer = primaryContainerDark,
53 | onPrimaryContainer = onPrimaryContainerDark,
54 | secondary = secondaryDark,
55 | onSecondary = onSecondaryDark,
56 | secondaryContainer = secondaryContainerDark,
57 | onSecondaryContainer = onSecondaryContainerDark,
58 | tertiary = tertiaryDark,
59 | onTertiary = onTertiaryDark,
60 | tertiaryContainer = tertiaryContainerDark,
61 | onTertiaryContainer = onTertiaryContainerDark,
62 | error = errorDark,
63 | onError = onErrorDark,
64 | errorContainer = errorContainerDark,
65 | onErrorContainer = onErrorContainerDark,
66 | background = backgroundDark,
67 | onBackground = onBackgroundDark,
68 | surface = surfaceDark,
69 | onSurface = onSurfaceDark,
70 | surfaceVariant = surfaceVariantDark,
71 | onSurfaceVariant = onSurfaceVariantDark,
72 | outline = outlineDark,
73 | outlineVariant = outlineVariantDark,
74 | scrim = scrimDark,
75 | inverseSurface = inverseSurfaceDark,
76 | inverseOnSurface = inverseOnSurfaceDark,
77 | inversePrimary = inversePrimaryDark,
78 | surfaceDim = surfaceDimDark,
79 | surfaceBright = surfaceBrightDark,
80 | surfaceContainerLowest = surfaceContainerLowestDark,
81 | surfaceContainerLow = surfaceContainerLowDark,
82 | surfaceContainer = surfaceContainerDark,
83 | surfaceContainerHigh = surfaceContainerHighDark,
84 | surfaceContainerHighest = surfaceContainerHighestDark,
85 | )
86 |
87 | @Immutable
88 | data class ColorFamily(
89 | val color: Color,
90 | val onColor: Color,
91 | val colorContainer: Color,
92 | val onColorContainer: Color
93 | )
94 |
95 | val unspecified_scheme = ColorFamily(
96 | Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
97 | )
98 |
99 | @Composable
100 | fun SpoolCompanionTheme(
101 | darkTheme: Boolean = isSystemInDarkTheme(),
102 | // Dynamic color is available on Android 12+
103 | dynamicColor: Boolean = true,
104 | content: @Composable () -> Unit
105 | ) {
106 |
107 | val colorScheme = lightScheme
108 |
109 | // val colorScheme = when {
110 | // dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
111 | // val context = LocalContext.current
112 | // if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
113 | // }
114 | //
115 | // darkTheme -> darkScheme
116 | // else -> lightScheme
117 | // }
118 |
119 | MaterialTheme(
120 | colorScheme = colorScheme,
121 | typography = Typography,
122 | content = content
123 | )
124 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/SpoolCompanionApp.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui
2 |
3 | import android.content.Context
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.Arrangement
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.filled.Settings
15 | import androidx.compose.material3.AlertDialog
16 | import androidx.compose.material3.Button
17 | import androidx.compose.material3.ExperimentalMaterial3Api
18 | import androidx.compose.material3.Icon
19 | import androidx.compose.material3.IconButton
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Scaffold
22 | import androidx.compose.material3.Surface
23 | import androidx.compose.material3.Text
24 | import androidx.compose.material3.TextField
25 | import androidx.compose.material3.TopAppBar
26 | import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.runtime.MutableState
29 | import androidx.compose.runtime.getValue
30 | import androidx.compose.runtime.mutableStateOf
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.runtime.setValue
33 | import androidx.compose.ui.Alignment
34 | import androidx.compose.ui.Modifier
35 | import androidx.compose.ui.graphics.ColorFilter
36 | import androidx.compose.ui.platform.LocalContext
37 | import androidx.compose.ui.res.painterResource
38 | import androidx.compose.ui.res.stringResource
39 | import androidx.compose.ui.text.style.TextAlign
40 | import androidx.compose.ui.tooling.preview.Devices
41 | import androidx.compose.ui.tooling.preview.Preview
42 | import androidx.compose.ui.unit.dp
43 | import androidx.core.content.edit
44 | import androidx.lifecycle.viewmodel.compose.viewModel
45 | import com.hexxotest.spoolcompanion.R
46 |
47 | @OptIn(ExperimentalMaterial3Api::class)
48 | @Composable
49 | fun SpoolCompanionApp(nfcTagViewModel: NfcTagViewModel) {
50 |
51 | val context = LocalContext.current
52 |
53 | var showSettingsDialog by remember { mutableStateOf(false) }
54 |
55 | val sharedPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
56 | val spoolmanUrlFromPrefs = sharedPrefs.getString("spoolman_url", "")
57 | val spoolmanUrl = remember { mutableStateOf(spoolmanUrlFromPrefs) }
58 |
59 | var spoolViewModel: SpoolViewModel? = null
60 |
61 | Scaffold(
62 | topBar = {
63 | TopAppBar(
64 | colors = topAppBarColors(
65 | containerColor = MaterialTheme.colorScheme.primaryContainer,
66 | titleContentColor = MaterialTheme.colorScheme.primary,
67 | ),
68 | title = {
69 | Text(stringResource(id = R.string.top_app_bar))
70 | },
71 | actions = {
72 | IconButton(onClick = { showSettingsDialog = true }) {
73 | Icon(
74 | imageVector = Icons.Filled.Settings,
75 | contentDescription = "Settings"
76 | )
77 | }
78 | }
79 | )
80 | }
81 | ) { innerPadding ->
82 | Surface(
83 | modifier = Modifier
84 | .padding(innerPadding)
85 | .background(MaterialTheme.colorScheme.surface)
86 | ) {
87 | if (spoolmanUrlFromPrefs.toString().isNotEmpty()) {
88 | spoolViewModel = spoolViewModel ?: SpoolViewModel(spoolmanUrlFromPrefs.toString())
89 | HomeScreen(
90 | uiState = spoolViewModel!!.currentUiState,
91 | nfcTagViewModel = nfcTagViewModel
92 | )
93 | } else {
94 | NoUrlApp()
95 | }
96 | }
97 | }
98 |
99 | if (showSettingsDialog) {
100 | SettingsDialog(
101 | spoolmanUrl = spoolmanUrl,
102 | onDismiss = { showSettingsDialog = false },
103 | onConfirm = {
104 | sharedPrefs.edit {
105 | putString("spoolman_url", spoolmanUrl.value)
106 | apply()
107 | }
108 | showSettingsDialog = false
109 | }
110 | )
111 | }
112 |
113 | }
114 |
115 | @Composable
116 | fun SettingsDialog(
117 | spoolmanUrl: MutableState,
118 | onDismiss: () -> Unit,
119 | onConfirm: () -> Unit
120 | ) {
121 |
122 | AlertDialog(
123 | title = { Text(text = "Settings") },
124 | onDismissRequest = { onDismiss() },
125 | confirmButton = {
126 | Button(
127 | onClick = { onConfirm() })
128 | {
129 | Text(text = "OK")
130 | }
131 | },
132 | dismissButton = {
133 | Button(
134 | onClick = { onDismiss() })
135 | {
136 | Text(text = "Cancel")
137 | }
138 | },
139 | text = {
140 | Column {
141 | Text(
142 | text = "Spoolman URL:",
143 | style = MaterialTheme.typography.titleSmall
144 | )
145 | Spacer(modifier = Modifier.height(8.dp))
146 | TextField(
147 | value = spoolmanUrl.value ?: "",
148 | onValueChange = { spoolmanUrl.value = it },
149 | maxLines = 1,
150 | singleLine = true,
151 | placeholder = { Text(text = "http://0.0.0.0:7912/") })
152 | }
153 | }
154 | )
155 | }
156 |
157 | @Composable
158 | fun NoNfcApp() {
159 | Column(
160 | verticalArrangement = Arrangement.Center,
161 | horizontalAlignment = Alignment.CenterHorizontally,
162 | modifier = Modifier
163 | .fillMaxSize()
164 | ) {
165 | Image(
166 | modifier = Modifier
167 | .size(128.dp),
168 | painter = painterResource(id = R.drawable.ic_nfc),
169 | contentDescription = "NFC Error",
170 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.inversePrimary)
171 | )
172 | Text(
173 | text = stringResource(R.string.nfc_error),
174 | color = MaterialTheme.colorScheme.primary,
175 | modifier = Modifier.padding(16.dp)
176 | )
177 | }
178 | }
179 |
180 | @Composable
181 | fun NoUrlApp() {
182 | Column(
183 | verticalArrangement = Arrangement.Center,
184 | horizontalAlignment = Alignment.CenterHorizontally,
185 | modifier = Modifier
186 | .fillMaxSize()
187 | ) {
188 | Icon(
189 | modifier = Modifier
190 | .size(128.dp),
191 | imageVector = Icons.Filled.Settings,
192 | contentDescription = "No url for spoolman",
193 | tint = MaterialTheme.colorScheme.inversePrimary
194 | )
195 | Text(
196 | text = stringResource(R.string.no_url_error),
197 | color = MaterialTheme.colorScheme.primary,
198 | modifier = Modifier.padding(16.dp),
199 | textAlign = TextAlign.Center
200 | )
201 | }
202 | }
203 |
204 | @Preview(showBackground = true, device = Devices.PIXEL_7A)
205 | @Composable
206 | fun DefaultPreview() {
207 | NoUrlApp()
208 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.PaddingValues
11 | import androidx.compose.foundation.layout.Row
12 | import androidx.compose.foundation.layout.Spacer
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.foundation.layout.fillMaxWidth
15 | import androidx.compose.foundation.layout.height
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.lazy.LazyColumn
19 | import androidx.compose.foundation.lazy.items
20 | import androidx.compose.foundation.shape.RoundedCornerShape
21 | import androidx.compose.material3.Card
22 | import androidx.compose.material3.CircularProgressIndicator
23 | import androidx.compose.material3.ElevatedCard
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.material3.Text
26 | import androidx.compose.material3.TextButton
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.draw.shadow
31 | import androidx.compose.ui.graphics.Color
32 | import androidx.compose.ui.graphics.ColorFilter
33 | import androidx.compose.ui.res.painterResource
34 | import androidx.compose.ui.res.stringResource
35 | import androidx.compose.ui.text.font.FontStyle
36 | import androidx.compose.ui.text.style.TextAlign
37 | import androidx.compose.ui.tooling.preview.Devices
38 | import androidx.compose.ui.tooling.preview.Preview
39 | import androidx.compose.ui.unit.dp
40 | import androidx.compose.ui.window.Dialog
41 | import com.hexxotest.spoolcompanion.R
42 | import com.hexxotest.spoolcompanion.models.SpoolListEntry
43 |
44 | @Composable
45 | // Main Screen selector : switch between Spool list, loading animation or error message
46 | fun HomeScreen(
47 | uiState: SpoolViewModel.UiState,
48 | modifier: Modifier = Modifier,
49 | nfcTagViewModel: NfcTagViewModel
50 | ) {
51 | when (uiState) {
52 | is SpoolViewModel.UiState.Success -> {
53 | if (nfcTagViewModel.isDialogShown) {
54 | WriteNfcDialog(nfcTagViewModel)
55 | }
56 | SpoolList(
57 | spools = uiState.spools,
58 | modifier = modifier.fillMaxSize(),
59 | nfcTagViewModel = nfcTagViewModel
60 | )
61 | }
62 | is SpoolViewModel.UiState.Error -> ErrorScreen()
63 | is SpoolViewModel.UiState.Loading -> LoadingScreen()
64 | }
65 | }
66 |
67 | @Composable
68 | fun LoadingScreen() {
69 | Column(
70 | verticalArrangement = Arrangement.Center,
71 | horizontalAlignment = Alignment.CenterHorizontally,
72 | modifier = Modifier
73 | .fillMaxSize()
74 | ) {
75 | CircularProgressIndicator(
76 | modifier = Modifier
77 | .size(128.dp),
78 | trackColor = MaterialTheme.colorScheme.inversePrimary
79 | )
80 | Text(
81 | text = stringResource(R.string.loading),
82 | modifier = Modifier
83 | .padding(top = 16.dp),
84 | textAlign = TextAlign.Center
85 | )
86 | }
87 | }
88 |
89 | @Composable
90 | fun ErrorScreen() {
91 | Column(
92 | verticalArrangement = Arrangement.Center,
93 | horizontalAlignment = Alignment.CenterHorizontally,
94 | modifier = Modifier
95 | .fillMaxSize()
96 | ) {
97 | Image(
98 | modifier = Modifier
99 | .size(128.dp),
100 | painter = painterResource(id = R.drawable.ic_cloud_off),
101 | contentDescription = "Network Error",
102 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.inversePrimary)
103 | )
104 | Text(
105 | text = stringResource(R.string.loading_failed),
106 | modifier = Modifier.padding(16.dp),
107 | textAlign = TextAlign.Center
108 | )
109 | }
110 | }
111 |
112 | @Composable
113 | fun SpoolEntry(
114 | spool: SpoolListEntry,
115 | modifier: Modifier = Modifier,
116 | nfcTagViewModel: NfcTagViewModel
117 | ) {
118 | ElevatedCard(
119 | onClick = {
120 | nfcTagViewModel.isDialogShown = true
121 | nfcTagViewModel.spoolId = spool.id
122 | nfcTagViewModel.filamentId = spool.filamentId
123 | },
124 | modifier = modifier
125 | .fillMaxWidth()
126 | .shadow(4.dp, shape = MaterialTheme.shapes.small),
127 | shape = MaterialTheme.shapes.small,
128 | ) {
129 | Row(
130 | verticalAlignment = Alignment.CenterVertically,
131 | modifier = modifier
132 | .padding(12.dp)
133 | ) {
134 | Box(
135 | modifier = modifier
136 | .size(24.dp)
137 | .background(spool.color)
138 | .border(1.dp, Color.Black)
139 | )
140 | Column(
141 | modifier = Modifier
142 | .padding(start = 8.dp)
143 | ) {
144 | Text(
145 | text = "${spool.vendorName} - ${spool.name} " +
146 | "(${spool.material}, ${spool.diameter} mm, ${spool.weight})",
147 | )
148 | if (spool.comment.isNotEmpty()) {
149 | Text(
150 | text = spool.comment,
151 | fontStyle = FontStyle.Italic,
152 | modifier = Modifier.padding(start = 8.dp)
153 | )
154 | }
155 | }
156 | }
157 | }
158 |
159 | }
160 |
161 | @Composable
162 | fun SpoolList(
163 | spools: List,
164 | modifier: Modifier = Modifier,
165 | contentPadding: PaddingValues = PaddingValues(0.dp),
166 | nfcTagViewModel: NfcTagViewModel
167 | ) {
168 | LazyColumn(
169 | modifier = modifier
170 | .fillMaxSize()
171 | .padding(all = 4.dp),
172 | contentPadding = contentPadding,
173 | verticalArrangement = Arrangement.spacedBy(4.dp)
174 | ) {
175 | items(spools, itemContent = { item ->
176 | SpoolEntry(spool = item, nfcTagViewModel = nfcTagViewModel)
177 | })
178 | }
179 | }
180 |
181 | @Composable
182 | fun WriteNfcDialog(
183 | nfcTagViewModel: NfcTagViewModel
184 | ) {
185 | if (nfcTagViewModel.isDialogShown) {
186 | Dialog(
187 | onDismissRequest = {
188 | nfcTagViewModel.isDialogShown = false
189 | }) {
190 | Card(
191 | modifier = Modifier
192 | .fillMaxWidth(),
193 | shape = RoundedCornerShape(16.dp),
194 | ) {
195 | Column(
196 | horizontalAlignment = Alignment.CenterHorizontally,
197 | verticalArrangement = Arrangement.Center,
198 | modifier = Modifier
199 | .padding(16.dp)
200 | .fillMaxWidth()
201 | ) {
202 | Image(
203 | modifier = Modifier
204 | .size(128.dp),
205 | painter = painterResource(id = R.drawable.ic_contactless),
206 | contentDescription = "NFC",
207 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.inversePrimary)
208 | )
209 | Text(
210 | text = "Approach an NFC tag...",
211 | textAlign = TextAlign.Center,
212 | color = MaterialTheme.colorScheme.primary
213 | )
214 | Spacer(modifier = Modifier.height(16.dp))
215 | Box(
216 | modifier = Modifier
217 | .fillMaxWidth()
218 | ) {
219 | TextButton(
220 | modifier = Modifier.align(Alignment.CenterEnd),
221 | onClick = {
222 | nfcTagViewModel.isDialogShown = false
223 | }
224 | ) {
225 | Text(stringResource(id = R.string.dismiss))
226 | }
227 | }
228 | }
229 | }
230 | }
231 | }
232 | }
233 |
234 | @Preview(
235 | showBackground = true, device = Devices.PIXEL_7A,
236 | uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL
237 | )
238 | @Composable
239 | fun ListPreview() {
240 | val spoolList = listOf(
241 | SpoolListEntry(
242 | name = "Jetpack Blue",
243 | vendorName = "MyVendor",
244 | comment = "My comment about this specific filament",
245 | material = "ABS",
246 | diameter = 1.75,
247 | weight = "1 kg",
248 | color = Color.Blue
249 | ),
250 | SpoolListEntry(
251 | name = "Compose Red",
252 | vendorName = "MainVendor",
253 | comment = "",
254 | material = "ABS",
255 | diameter = 1.75,
256 | weight = "1 kg",
257 | color = Color.Red
258 | )
259 | )
260 | SpoolList(spools = spoolList, nfcTagViewModel = NfcTagViewModel())
261 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hexxotest/spoolcompanion/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.hexxotest.spoolcompanion.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
12 |
13 | val primaryLight = Color(0xFF8B4F24)
14 | val onPrimaryLight = Color(0xFFFFFFFF)
15 | val primaryContainerLight = Color(0xFFFFDBC7)
16 | val onPrimaryContainerLight = Color(0xFF311300)
17 | val secondaryLight = Color(0xFF755846)
18 | val onSecondaryLight = Color(0xFFFFFFFF)
19 | val secondaryContainerLight = Color(0xFFFFDBC7)
20 | val onSecondaryContainerLight = Color(0xFF2B1709)
21 | val tertiaryLight = Color(0xFF606134)
22 | val onTertiaryLight = Color(0xFFFFFFFF)
23 | val tertiaryContainerLight = Color(0xFFE6E6AD)
24 | val onTertiaryContainerLight = Color(0xFF1C1D00)
25 | val errorLight = Color(0xFFBA1A1A)
26 | val onErrorLight = Color(0xFFFFFFFF)
27 | val errorContainerLight = Color(0xFFFFDAD6)
28 | val onErrorContainerLight = Color(0xFF410002)
29 | val backgroundLight = Color(0xFFFFF8F5)
30 | val onBackgroundLight = Color(0xFF221A15)
31 | val surfaceLight = Color(0xFFFFF8F5)
32 | val onSurfaceLight = Color(0xFF221A15)
33 | val surfaceVariantLight = Color(0xFFF4DED3)
34 | val onSurfaceVariantLight = Color(0xFF52443C)
35 | val outlineLight = Color(0xFF84746A)
36 | val outlineVariantLight = Color(0xFFD7C3B8)
37 | val scrimLight = Color(0xFF000000)
38 | val inverseSurfaceLight = Color(0xFF382E29)
39 | val inverseOnSurfaceLight = Color(0xFFFFEDE5)
40 | val inversePrimaryLight = Color(0xFFFFB688)
41 | val surfaceDimLight = Color(0xFFE7D7CE)
42 | val surfaceBrightLight = Color(0xFFFFF8F5)
43 | val surfaceContainerLowestLight = Color(0xFFFFFFFF)
44 | val surfaceContainerLowLight = Color(0xFFFFF1EA)
45 | val surfaceContainerLight = Color(0xFFFCEBE2)
46 | val surfaceContainerHighLight = Color(0xFFF6E5DC)
47 | val surfaceContainerHighestLight = Color(0xFFF0DFD7)
48 |
49 | val primaryLightMediumContrast = Color(0xFF69350A)
50 | val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
51 | val primaryContainerLightMediumContrast = Color(0xFFA56538)
52 | val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
53 | val secondaryLightMediumContrast = Color(0xFF573D2C)
54 | val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
55 | val secondaryContainerLightMediumContrast = Color(0xFF8D6E5B)
56 | val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
57 | val tertiaryLightMediumContrast = Color(0xFF44451B)
58 | val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
59 | val tertiaryContainerLightMediumContrast = Color(0xFF777748)
60 | val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
61 | val errorLightMediumContrast = Color(0xFF8C0009)
62 | val onErrorLightMediumContrast = Color(0xFFFFFFFF)
63 | val errorContainerLightMediumContrast = Color(0xFFDA342E)
64 | val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
65 | val backgroundLightMediumContrast = Color(0xFFFFF8F5)
66 | val onBackgroundLightMediumContrast = Color(0xFF221A15)
67 | val surfaceLightMediumContrast = Color(0xFFFFF8F5)
68 | val onSurfaceLightMediumContrast = Color(0xFF221A15)
69 | val surfaceVariantLightMediumContrast = Color(0xFFF4DED3)
70 | val onSurfaceVariantLightMediumContrast = Color(0xFF4E4038)
71 | val outlineLightMediumContrast = Color(0xFF6B5C53)
72 | val outlineVariantLightMediumContrast = Color(0xFF88776E)
73 | val scrimLightMediumContrast = Color(0xFF000000)
74 | val inverseSurfaceLightMediumContrast = Color(0xFF382E29)
75 | val inverseOnSurfaceLightMediumContrast = Color(0xFFFFEDE5)
76 | val inversePrimaryLightMediumContrast = Color(0xFFFFB688)
77 | val surfaceDimLightMediumContrast = Color(0xFFE7D7CE)
78 | val surfaceBrightLightMediumContrast = Color(0xFFFFF8F5)
79 | val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
80 | val surfaceContainerLowLightMediumContrast = Color(0xFFFFF1EA)
81 | val surfaceContainerLightMediumContrast = Color(0xFFFCEBE2)
82 | val surfaceContainerHighLightMediumContrast = Color(0xFFF6E5DC)
83 | val surfaceContainerHighestLightMediumContrast = Color(0xFFF0DFD7)
84 |
85 | val primaryLightHighContrast = Color(0xFF3B1900)
86 | val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
87 | val primaryContainerLightHighContrast = Color(0xFF69350A)
88 | val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
89 | val secondaryLightHighContrast = Color(0xFF321D0F)
90 | val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
91 | val secondaryContainerLightHighContrast = Color(0xFF573D2C)
92 | val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
93 | val tertiaryLightHighContrast = Color(0xFF232400)
94 | val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
95 | val tertiaryContainerLightHighContrast = Color(0xFF44451B)
96 | val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
97 | val errorLightHighContrast = Color(0xFF4E0002)
98 | val onErrorLightHighContrast = Color(0xFFFFFFFF)
99 | val errorContainerLightHighContrast = Color(0xFF8C0009)
100 | val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
101 | val backgroundLightHighContrast = Color(0xFFFFF8F5)
102 | val onBackgroundLightHighContrast = Color(0xFF221A15)
103 | val surfaceLightHighContrast = Color(0xFFFFF8F5)
104 | val onSurfaceLightHighContrast = Color(0xFF000000)
105 | val surfaceVariantLightHighContrast = Color(0xFFF4DED3)
106 | val onSurfaceVariantLightHighContrast = Color(0xFF2D211A)
107 | val outlineLightHighContrast = Color(0xFF4E4038)
108 | val outlineVariantLightHighContrast = Color(0xFF4E4038)
109 | val scrimLightHighContrast = Color(0xFF000000)
110 | val inverseSurfaceLightHighContrast = Color(0xFF382E29)
111 | val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
112 | val inversePrimaryLightHighContrast = Color(0xFFFFE7DB)
113 | val surfaceDimLightHighContrast = Color(0xFFE7D7CE)
114 | val surfaceBrightLightHighContrast = Color(0xFFFFF8F5)
115 | val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
116 | val surfaceContainerLowLightHighContrast = Color(0xFFFFF1EA)
117 | val surfaceContainerLightHighContrast = Color(0xFFFCEBE2)
118 | val surfaceContainerHighLightHighContrast = Color(0xFFF6E5DC)
119 | val surfaceContainerHighestLightHighContrast = Color(0xFFF0DFD7)
120 |
121 | val primaryDark = Color(0xFFFFB688)
122 | val onPrimaryDark = Color(0xFF512400)
123 | val primaryContainerDark = Color(0xFF6E390E)
124 | val onPrimaryContainerDark = Color(0xFFFFDBC7)
125 | val secondaryDark = Color(0xFFE5BFA8)
126 | val onSecondaryDark = Color(0xFF432B1C)
127 | val secondaryContainerDark = Color(0xFF5B4130)
128 | val onSecondaryContainerDark = Color(0xFFFFDBC7)
129 | val tertiaryDark = Color(0xFFCACA93)
130 | val onTertiaryDark = Color(0xFF32320A)
131 | val tertiaryContainerDark = Color(0xFF48491E)
132 | val onTertiaryContainerDark = Color(0xFFE6E6AD)
133 | val errorDark = Color(0xFFFFB4AB)
134 | val onErrorDark = Color(0xFF690005)
135 | val errorContainerDark = Color(0xFF93000A)
136 | val onErrorContainerDark = Color(0xFFFFDAD6)
137 | val backgroundDark = Color(0xFF19120D)
138 | val onBackgroundDark = Color(0xFFF0DFD7)
139 | val surfaceDark = Color(0xFF19120D)
140 | val onSurfaceDark = Color(0xFFF0DFD7)
141 | val surfaceVariantDark = Color(0xFF52443C)
142 | val onSurfaceVariantDark = Color(0xFFD7C3B8)
143 | val outlineDark = Color(0xFF9F8D83)
144 | val outlineVariantDark = Color(0xFF52443C)
145 | val scrimDark = Color(0xFF000000)
146 | val inverseSurfaceDark = Color(0xFFF0DFD7)
147 | val inverseOnSurfaceDark = Color(0xFF382E29)
148 | val inversePrimaryDark = Color(0xFF8B4F24)
149 | val surfaceDimDark = Color(0xFF19120D)
150 | val surfaceBrightDark = Color(0xFF413731)
151 | val surfaceContainerLowestDark = Color(0xFF140D08)
152 | val surfaceContainerLowDark = Color(0xFF221A15)
153 | val surfaceContainerDark = Color(0xFF261E19)
154 | val surfaceContainerHighDark = Color(0xFF312823)
155 | val surfaceContainerHighestDark = Color(0xFF3D332D)
156 |
157 | val primaryDarkMediumContrast = Color(0xFFFFBC92)
158 | val onPrimaryDarkMediumContrast = Color(0xFF290F00)
159 | val primaryContainerDarkMediumContrast = Color(0xFFC68051)
160 | val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
161 | val secondaryDarkMediumContrast = Color(0xFFE9C3AD)
162 | val onSecondaryDarkMediumContrast = Color(0xFF251105)
163 | val secondaryContainerDarkMediumContrast = Color(0xFFAB8A75)
164 | val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
165 | val tertiaryDarkMediumContrast = Color(0xFFCECE97)
166 | val onTertiaryDarkMediumContrast = Color(0xFF171700)
167 | val tertiaryContainerDarkMediumContrast = Color(0xFF939361)
168 | val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
169 | val errorDarkMediumContrast = Color(0xFFFFBAB1)
170 | val onErrorDarkMediumContrast = Color(0xFF370001)
171 | val errorContainerDarkMediumContrast = Color(0xFFFF5449)
172 | val onErrorContainerDarkMediumContrast = Color(0xFF000000)
173 | val backgroundDarkMediumContrast = Color(0xFF19120D)
174 | val onBackgroundDarkMediumContrast = Color(0xFFF0DFD7)
175 | val surfaceDarkMediumContrast = Color(0xFF19120D)
176 | val onSurfaceDarkMediumContrast = Color(0xFFFFFAF8)
177 | val surfaceVariantDarkMediumContrast = Color(0xFF52443C)
178 | val onSurfaceVariantDarkMediumContrast = Color(0xFFDBC7BC)
179 | val outlineDarkMediumContrast = Color(0xFFB29F95)
180 | val outlineVariantDarkMediumContrast = Color(0xFF918076)
181 | val scrimDarkMediumContrast = Color(0xFF000000)
182 | val inverseSurfaceDarkMediumContrast = Color(0xFFF0DFD7)
183 | val inverseOnSurfaceDarkMediumContrast = Color(0xFF312823)
184 | val inversePrimaryDarkMediumContrast = Color(0xFF703A10)
185 | val surfaceDimDarkMediumContrast = Color(0xFF19120D)
186 | val surfaceBrightDarkMediumContrast = Color(0xFF413731)
187 | val surfaceContainerLowestDarkMediumContrast = Color(0xFF140D08)
188 | val surfaceContainerLowDarkMediumContrast = Color(0xFF221A15)
189 | val surfaceContainerDarkMediumContrast = Color(0xFF261E19)
190 | val surfaceContainerHighDarkMediumContrast = Color(0xFF312823)
191 | val surfaceContainerHighestDarkMediumContrast = Color(0xFF3D332D)
192 |
193 | val primaryDarkHighContrast = Color(0xFFFFFAF8)
194 | val onPrimaryDarkHighContrast = Color(0xFF000000)
195 | val primaryContainerDarkHighContrast = Color(0xFFFFBC92)
196 | val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
197 | val secondaryDarkHighContrast = Color(0xFFFFFAF8)
198 | val onSecondaryDarkHighContrast = Color(0xFF000000)
199 | val secondaryContainerDarkHighContrast = Color(0xFFE9C3AD)
200 | val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
201 | val tertiaryDarkHighContrast = Color(0xFFFFFEC3)
202 | val onTertiaryDarkHighContrast = Color(0xFF000000)
203 | val tertiaryContainerDarkHighContrast = Color(0xFFCECE97)
204 | val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
205 | val errorDarkHighContrast = Color(0xFFFFF9F9)
206 | val onErrorDarkHighContrast = Color(0xFF000000)
207 | val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
208 | val onErrorContainerDarkHighContrast = Color(0xFF000000)
209 | val backgroundDarkHighContrast = Color(0xFF19120D)
210 | val onBackgroundDarkHighContrast = Color(0xFFF0DFD7)
211 | val surfaceDarkHighContrast = Color(0xFF19120D)
212 | val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
213 | val surfaceVariantDarkHighContrast = Color(0xFF52443C)
214 | val onSurfaceVariantDarkHighContrast = Color(0xFFFFFAF8)
215 | val outlineDarkHighContrast = Color(0xFFDBC7BC)
216 | val outlineVariantDarkHighContrast = Color(0xFFDBC7BC)
217 | val scrimDarkHighContrast = Color(0xFF000000)
218 | val inverseSurfaceDarkHighContrast = Color(0xFFF0DFD7)
219 | val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
220 | val inversePrimaryDarkHighContrast = Color(0xFF471F00)
221 | val surfaceDimDarkHighContrast = Color(0xFF19120D)
222 | val surfaceBrightDarkHighContrast = Color(0xFF413731)
223 | val surfaceContainerLowestDarkHighContrast = Color(0xFF140D08)
224 | val surfaceContainerLowDarkHighContrast = Color(0xFF221A15)
225 | val surfaceContainerDarkHighContrast = Color(0xFF261E19)
226 | val surfaceContainerHighDarkHighContrast = Color(0xFF312823)
227 | val surfaceContainerHighestDarkHighContrast = Color(0xFF3D332D)
228 |
--------------------------------------------------------------------------------