Compare commits

..

6 commits

Author SHA1 Message Date
f2d5687a5f
Add pull request workflow
Some checks failed
Build & Test / Validate (push) Successful in 23s
Build & Test / Run Unit Tests (push) Failing after 6m59s
2024-11-16 16:47:42 -07:00
4bf56a5918
fixup! Enable predictive back gesture 2024-11-16 16:45:34 -07:00
d533682af6
Enable predictive back gesture 2024-11-16 16:43:34 -07:00
b5c23fc5ca
Bump various dependencies 2024-11-16 16:40:25 -07:00
7447a5ca6d
Bump max SDK 2024-11-16 15:27:22 -07:00
a4ec4c8aa4
Bump AGP version 2024-11-16 15:23:43 -07:00
26 changed files with 210 additions and 151 deletions

View file

@ -0,0 +1,74 @@
name: Build & Test
on:
pull_request:
push:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
name: Validate
steps:
- uses: actions/checkout@v4
- name: set up JDK
uses: https://git.wbrawner.com/actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Validate Gradle Wrapper
uses: https://git.wbrawner.com/gradle/actions/wrapper-validation@v4
unit_tests:
name: Run Unit Tests
runs-on: ubuntu-latest
needs:
- validate
steps:
- uses: actions/checkout@v4
- name: set up JDK
uses: https://git.wbrawner.com/actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Android SDK
uses: https://git.wbrawner.com/android-actions/setup-android@v3
- name: Setup Gradle
uses: https://git.wbrawner.com/gradle/actions/setup-gradle@v4
- name: Run checks
run: ./gradlew check
# TODO: Uncomment the following once I get unit tests written
# - name: Publish JUnit Results
# uses: actions/upload-artifact@v3
# if: always()
# with:
# name: Unit Test Results
# path: "*/build/reports/*"
# if-no-files-found: error
# TODO: Uncomment the following once I get UI tests written
# ui_tests:
# runs-on: ubuntu-latest
# name: Run UI Tests
# needs:
# - validate
# steps:
# - uses: actions/checkout@v2
# - name: set up JDK
# uses: https://git.wbrawner.com/actions/setup-java@v3
# with:
# distribution: 'zulu'
# java-version: '17'
# - name: Build with Gradle
# uses: https://git.wbrawner.com/gradle/gradle-build-action@v2
# with:
# arguments: assembleDebug assembleDebugAndroidTest
# - name: Grant execute permission for flank_auth.sh
# run: chmod +x flank_auth.sh
# - name: Add auth for flank
# env:
# GCLOUD_KEY: ${{ secrets.GCLOUD_KEY }}
# run: |
# ./flank_auth.sh
# - name: Run UI tests
# uses: https://git.wbrawner.com/gradle/gradle-build-action@v2
# with:
# arguments: runFlank

8
.gitignore vendored
View file

@ -1,15 +1,11 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea/caches .idea
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
keystore.properties keystore.properties
.kotlin

View file

@ -237,6 +237,7 @@
<option name="preferredColumnWidths"> <option name="preferredColumnWidths">
<map> <map>
<entry key="Duration" value="90" /> <entry key="Duration" value="90" />
<entry key="Medium_Phone_API_35" value="120" />
<entry key="Pixel_3a_API_33_arm64-v8a" value="120" /> <entry key="Pixel_3a_API_33_arm64-v8a" value="120" />
<entry key="Tests" value="360" /> <entry key="Tests" value="360" />
</map> </map>

View file

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

View file

@ -5,18 +5,6 @@
<entry key="app"> <entry key="app">
<State /> <State />
</entry> </entry>
<entry key="app.androidTest">
<State />
</entry>
<entry key="app.unitTest">
<State />
</entry>
<entry key="pihelper-android.Pi-helper.app">
<State />
</entry>
<entry key="testInvalidHost()">
<State />
</entry>
</value> </value>
</component> </component>
</project> </project>

View file

@ -4,6 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.20" /> <option name="version" value="2.0.21" />
</component> </component>
</project> </project>

View file

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CMakeSettings"> <component name="CMakeSettings">
<configurations> <configurations>
@ -64,7 +65,7 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -3,10 +3,11 @@ import java.io.FileNotFoundException
import java.util.* import java.util.*
plugins { plugins {
id("com.android.application") alias(libs.plugins.android.application)
id("kotlin-android") alias(libs.plugins.kotlin.android)
id("kotlin-kapt") alias(libs.plugins.kotlin.ksp)
id("dagger.hilt.android.plugin") alias(libs.plugins.hilt.android)
alias(libs.plugins.compose)
} }
val keystoreProperties = Properties() val keystoreProperties = Properties()
@ -56,11 +57,11 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_21
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "21"
} }
buildFeatures { buildFeatures {
compose = true compose = true
@ -68,6 +69,7 @@ android {
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
} }
namespace = "com.wbrawner.pihelper"
} }
dependencies { dependencies {
@ -76,7 +78,7 @@ dependencies {
implementation(libs.bundles.compose) implementation(libs.bundles.compose)
implementation(libs.hilt.android.core) implementation(libs.hilt.android.core)
implementation(libs.hilt.navigation.compose) implementation(libs.hilt.navigation.compose)
kapt(libs.hilt.android.kapt) ksp(libs.hilt.android.ksp)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.splash) implementation(libs.androidx.splash)
@ -91,7 +93,7 @@ dependencies {
androidTestImplementation(libs.test.ext) androidTestImplementation(libs.test.ext)
androidTestImplementation(libs.espresso) androidTestImplementation(libs.espresso)
androidTestImplementation(libs.hilt.android.testing) androidTestImplementation(libs.hilt.android.testing)
kaptAndroidTest(libs.hilt.android.kapt) kspAndroidTest(libs.hilt.android.ksp)
androidTestImplementation(libs.compose.test.junit) androidTestImplementation(libs.compose.test.junit)
debugImplementation(libs.compose.test.manifest) debugImplementation(libs.compose.test.manifest)
} }

View file

@ -4,7 +4,6 @@ import android.content.Context
import androidx.compose.ui.test.* import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.wbrawner.pihelper.CONNECT_BUTTON_TAG
fun onAddScreen(testRule: ComposeTestRule, actions: AddScreenRobot.() -> Unit) = fun onAddScreen(testRule: ComposeTestRule, actions: AddScreenRobot.() -> Unit) =
AddScreenRobot(testRule).apply { actions() } AddScreenRobot(testRule).apply { actions() }
@ -22,7 +21,7 @@ class AddScreenRobot(private val testRule: ComposeTestRule) {
fun inputHost(host: String) = fun inputHost(host: String) =
testRule.onNodeWithContentDescription("Pi-hole host input").performTextInput(host) testRule.onNodeWithContentDescription("Pi-hole host input").performTextInput(host)
fun clickConnect() = testRule.onNode(hasTestTag(CONNECT_BUTTON_TAG)).performClick() fun clickConnect() = testRule.onNode(hasText("Connect")).performClick()
fun verifyErrorMessageIsDisplayed(message: String) { fun verifyErrorMessageIsDisplayed(message: String) {
testRule.waitUntil(2_000) { testRule.waitUntil(2_000) {

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="com.wbrawner.pihelper">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application <application
android:name=".PiHelperApplication" android:name=".PiHelperApplication"
android:allowBackup="true" android:allowBackup="true"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_descriptor" android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"

View file

@ -6,6 +6,7 @@ import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -24,6 +25,7 @@ val emulatorBuildModels = listOf(
@Composable @Composable
fun AddScreen(store: Store) { fun AddScreen(store: Store) {
val state by store.state.collectAsState()
val effect by store.effects.collectAsState(initial = Effect.Empty) val effect by store.effects.collectAsState(initial = Effect.Empty)
val context = LocalContext.current val context = LocalContext.current
AddScreen( AddScreen(
@ -39,8 +41,7 @@ fun AddScreen(store: Store) {
connectivityManager.allNetworks connectivityManager.allNetworks
.filter { .filter {
connectivityManager.getNetworkCapabilities(it) connectivityManager.getNetworkCapabilities(it)
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
?: false
} }
.mapNotNull { network -> .mapNotNull { network ->
connectivityManager.getLinkProperties(network) connectivityManager.getLinkProperties(network)
@ -61,7 +62,7 @@ fun AddScreen(store: Store) {
connectToPihole = { connectToPihole = {
store.dispatch(Action.Connect(it)) store.dispatch(Action.Connect(it))
}, },
store.state.value.loading, state.loading,
error = effect as? Effect.Error error = effect as? Effect.Error
) )
} }

View file

@ -4,16 +4,15 @@ import android.animation.ObjectAnimator
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowInsetsController
import android.view.animation.AnticipateInterpolator import android.view.animation.AnticipateInterpolator
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@ -39,22 +38,10 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen() installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent { setContent {
val isDarkTheme = isSystemInDarkTheme() BackHandler {
LaunchedEffect(key1 = isDarkTheme) { store.dispatch(Action.Back)
if (isDarkTheme) return@LaunchedEffect
window.navigationBarColor =
ContextCompat.getColor(this@MainActivity, R.color.colorSurface)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
window.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
} }
val launchIntent = remember { intent } val launchIntent = remember { intent }
LaunchedEffect(launchIntent) { LaunchedEffect(launchIntent) {
@ -121,10 +108,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
override fun onBackPressed() {
store.dispatch(Action.Back)
}
} }
@Composable @Composable

View file

@ -4,19 +4,24 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.wbrawner.pihelper.shared.Store import com.wbrawner.pihelper.shared.Store
import com.wbrawner.pihelper.shared.ui.component.LoadingSpinner
import com.wbrawner.pihelper.shared.ui.theme.PihelperTheme import com.wbrawner.pihelper.shared.ui.theme.PihelperTheme
@Composable @Composable
fun ScanScreen(store: Store) { fun ScanScreen(store: Store) {
ScanningStatus(store.state.value.scanning?.let { "Scanning $it..." }) val state by store.state.collectAsState()
ScanningStatus(state.scanning?.let { "Scanning $it..." })
} }
@Composable @Composable

View file

@ -1,25 +1,12 @@
import java.net.URI plugins {
alias(libs.plugins.android.application) apply false
buildscript { alias(libs.plugins.android.library) apply false
repositories { alias(libs.plugins.kotlin.android) apply false
gradlePluginPortal() alias(libs.plugins.kotlin.jvm) apply false
google() alias(libs.plugins.kotlin.multiplatform) apply false
mavenCentral() alias(libs.plugins.kotlin.serialization) apply false
} alias(libs.plugins.compose) apply false
dependencies { alias(libs.plugins.jetbrainsCompose) apply false
classpath(libs.bundles.plugins) alias(libs.plugins.hilt.android) apply false
} alias(libs.plugins.kotlin.ksp) apply false
}
allprojects {
repositories {
google()
mavenCentral()
maven {
url = URI("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
maven {
url = URI("https://maven.pkg.jetbrains.space/public/p/compose/dev/")
}
}
} }

View file

@ -19,3 +19,6 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -1,24 +1,27 @@
[versions] [versions]
androidx-core = "1.10.1" androidGradlePlugin = "8.7.2"
androidx-appcompat = "1.6.1" androidx-core = "1.15.0"
androidx-appcompat = "1.7.0"
androidx-splash = "1.0.1" androidx-splash = "1.0.1"
androidx-test-runner = "1.5.2" androidx-test-runner = "1.6.2"
androidx-test-orchestrator = "1.4.2" androidx-test-orchestrator = "1.5.1"
compose = "1.6.0" compose = "1.7.5"
compose-multiplatform = "1.7.1"
compose-compiler = "1.4.2" compose-compiler = "1.4.2"
compose-material = "1.5.0" compose-material = "1.7.5"
compose-material3 = "1.1.1" compose-material3 = "1.3.1"
espresso = "3.5.1" espresso = "3.6.1"
hilt-android = "2.44" hilt-android = "2.51.1"
kotlin = "1.9.22" kotlin = "2.0.21"
kotlinx-serialization = "1.4.1" kotlin-ksp = "2.0.21-1.0.28"
kotlinx-coroutines = "1.6.4" kotlinx-serialization = "1.6.3"
kotlinx-datetime = "0.4.0" kotlinx-coroutines = "1.9.0"
ktor = "2.1.2" kotlinx-datetime = "0.6.0"
material = "1.9.0" ktor = "2.3.12"
maxSdk = "33" logbackClassic = "1.5.12"
material = "1.12.0"
maxSdk = "35"
minSdk = "23" minSdk = "23"
navigation = "2.7.0"
okhttp = "4.10.0" okhttp = "4.10.0"
plausible = "0.1.0-SNAPSHOT" plausible = "0.1.0-SNAPSHOT"
settings = "0.8.1" settings = "0.8.1"
@ -26,7 +29,6 @@ versionCode = "5"
versionName = "1.1.1" versionName = "1.1.1"
[libraries] [libraries]
android-gradle = { module = "com.android.tools.build:gradle", version = "7.4.2" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-splash = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splash" } androidx-splash = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splash" }
@ -36,25 +38,17 @@ compose-activity = { module = "androidx.activity:activity-compose", version = "1
compose-material = { module = "androidx.compose.material:material", version.ref = "compose-material" } compose-material = { module = "androidx.compose.material:material", version.ref = "compose-material" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
compose-material3-window = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "compose-material3" } compose-material3-window = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "compose-material3" }
compose-plugin-jetbrains = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose" }
compose-test-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } compose-test-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
compose-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" } compose-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
dagger-hilt = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt-android" }
espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
hilt-android-core = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" } hilt-android-core = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" }
hilt-android-kapt = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt-android" } hilt-android-ksp = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt-android" }
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt-android" } hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt-android" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.0.0" } hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.2.0" }
junit = { module = "junit:junit", version = "4.13.2" } junit = { module = "junit:junit", version = "4.13.2" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
ktor-client-ios = { module = "io.ktor:ktor-client-ios", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-client-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
@ -63,14 +57,12 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackClassic" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "settings" } multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "settings" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version = "navigation" } navigation-compose = { module = "androidx.navigation:navigation-compose", version = "navigation" }
navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
plausible = { module = "com.wbrawner.plausible:plausible-android", version.ref = "plausible" } plausible = { module = "com.wbrawner.plausible:plausible-android", version.ref = "plausible" }
preference = { module = "androidx.preference:preference-ktx", version = "1.2.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.0" }
test-ext = { module = "androidx.test.ext:junit", version = "1.1.5" } test-ext = { module = "androidx.test.ext:junit", version = "1.1.5" }
@ -78,4 +70,15 @@ test-ext = { module = "androidx.test.ext:junit", version = "1.1.5" }
[bundles] [bundles]
compose = ["compose-ui", "compose-material", "compose-material3", "compose-material3-window", "compose-tooling", "compose-activity", "navigation-compose"] compose = ["compose-ui", "compose-material", "compose-material3", "compose-material3-window", "compose-tooling", "compose-activity", "navigation-compose"]
coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"] coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"]
plugins = ["android-gradle", "compose-plugin-jetbrains", "kotlin-gradle", "dagger-hilt", "kotlin-serialization"]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin-ksp" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt-android" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip

View file

@ -1,4 +1,26 @@
import java.net.URI
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = URI("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}
maven {
url = URI("https://maven.pkg.jetbrains.space/public/p/compose/dev/")
}
}
}
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "Pi-helper" rootProject.name = "Pi-helper"
include(":app") include(":app", ":desktop", ":shared")
include(":desktop")
include(":shared")

View file

@ -1,12 +1,13 @@
plugins { plugins {
kotlin("multiplatform") alias(libs.plugins.kotlin.multiplatform)
id("com.android.library") alias(libs.plugins.android.library)
kotlin("plugin.serialization") alias(libs.plugins.jetbrainsCompose)
id("org.jetbrains.compose") alias(libs.plugins.compose)
alias(libs.plugins.kotlin.serialization)
} }
kotlin { kotlin {
android() androidTarget()
listOf(iosArm64(), iosSimulatorArm64()).forEach { listOf(iosArm64(), iosSimulatorArm64()).forEach {
it.binaries.framework { it.binaries.framework {
baseName = "Pihelper" baseName = "Pihelper"
@ -30,13 +31,14 @@ kotlin {
api(compose.foundation) api(compose.foundation)
api(compose.material3) api(compose.material3)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources) api(compose.components.resources)
} }
} }
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation(libs.plausible) implementation(libs.plausible)
api(compose.runtime)
} }
} }
@ -64,15 +66,15 @@ android {
compileSdk = libs.versions.maxSdk.get().toInt() compileSdk = libs.versions.maxSdk.get().toInt()
defaultConfig { defaultConfig {
minSdk = libs.versions.minSdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.maxSdk.get().toInt()
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_21
} }
buildTypes { buildTypes {
release { release {
consumerProguardFiles("proguard-rules.pro") consumerProguardFiles("proguard-rules.pro")
} }
} }
namespace = "com.wbrawner.pihelper.shared"
} }

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="com.wbrawner.pihelper.shared">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
</manifest> </manifest>

View file

@ -8,7 +8,7 @@ import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material3.TopAppBarDefaults.smallTopAppBarColors import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -37,7 +37,7 @@ fun InfoScreen(onBackClicked: () -> Unit, onForgetPiholeClicked: () -> Unit) {
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
colors = smallTopAppBarColors( colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface containerColor = MaterialTheme.colorScheme.surface
), ),
title = { Text("About Pi-helper") }, title = { Text("About Pi-helper") },

View file

@ -11,7 +11,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material3.TopAppBarDefaults.smallTopAppBarColors import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -67,7 +67,7 @@ fun MainScreen(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Pi-helper") }, title = { Text("Pi-helper") },
colors = smallTopAppBarColors( colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.onBackground titleContentColor = MaterialTheme.colorScheme.onBackground
), ),
@ -109,7 +109,7 @@ fun MainScreen(
Text( Text(
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = "${it.message}" text = it.message
) )
} }
} }

View file

@ -10,9 +10,12 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.InternalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import pi_helper.shared.generated.resources.Res
import pi_helper.shared.generated.resources.ic_app_logo
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class, InternalResourceApi::class)
@Composable @Composable
fun LoadingSpinner(animate: Boolean = false) { fun LoadingSpinner(animate: Boolean = false) {
val animation = rememberInfiniteTransition() val animation = rememberInfiniteTransition()
@ -27,7 +30,7 @@ fun LoadingSpinner(animate: Boolean = false) {
) )
Image( Image(
modifier = Modifier.rotate(if (animate) rotation else 0f), modifier = Modifier.rotate(if (animate) rotation else 0f),
painter = painterResource(DrawableResource("img/ic_app_logo.xml")), painter = painterResource(Res.drawable.ic_app_logo),
contentDescription = "Loading", contentDescription = "Loading",
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground) colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground)
) )

View file

@ -1,11 +0,0 @@
package com.wbrawner.pihelper.shared.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)