Compare commits

..

No commits in common. "master" and "v0.1.1" have entirely different histories.

9 changed files with 51 additions and 142 deletions

View file

@ -12,10 +12,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'

View file

@ -11,10 +11,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'

View file

@ -1,39 +1,14 @@
# Bob -- The Handy Feedback App This is a Kotlin Multiplatform project targeting Android, iOS.
This project is a simple Kotlin Multiplatform Application that allows users to enter free-text and * `/composeApp` is for code that will be shared across your Compose Multiplatform applications.
submit the content to a server over HTTP. It also includes a form with sentiment selection and It contains several subfolders:
displays a Snackbar whenever the submit button is pressed. - `commonMain` is for code thats common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name.
For example, if you want to use Apples CoreCrypto for the iOS part of your Kotlin app,
`iosMain` would be the right folder for such calls.
## Features * `/iosApp` contains iOS applications. Even if youre sharing your UI with Compose Multiplatform,
you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
- Free-text input form
- Sentiment selection using FilterChips
- Submit content to a server using Retrofit
- Display Snackbar on form submission
## Technologies Used Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…
- Kotlin
- Jetpack Compose
- Retrofit
- Gradle
## Getting Started
### Installation
1. Download the latest release from
the [releases page](https://git.brmartin.co.uk/bob/mobile-application/releases)
2. Install the application following the on-screen instructions.
### Usage
1. Run the application on an Android emulator or a physical device.
2. Select a sentiment using the FilterChips.
3. Enter your text in the provided text field.
4. Press the submit button to send the content to the server.
### Project Structure
- `composeApp/src/commonMain/kotlin/uk/sky/bob/application/App.kt`: Main Compose UI and form
submission logic.

View file

@ -1,102 +1,37 @@
package uk.sky.bob.application package uk.sky.bob.application
import androidx.compose.foundation.layout.Box import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.ChipDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FilterChip
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextField import androidx.compose.runtime.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.input.TextFieldValue import org.jetbrains.compose.resources.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jetbrains.compose.ui.tooling.preview.Preview import org.jetbrains.compose.ui.tooling.preview.Preview
@OptIn(ExperimentalMaterialApi::class) import bob.composeapp.generated.resources.Res
import bob.composeapp.generated.resources.compose_multiplatform
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
MaterialTheme { MaterialTheme {
val scaffoldState = rememberScaffoldState() var showContent by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { showContent = !showContent }) {
Scaffold(scaffoldState = scaffoldState) { Text("Click me!")
Box( }
modifier = Modifier.fillMaxHeight(), AnimatedVisibility(showContent) {
contentAlignment = Alignment.Center, val greeting = remember { Greeting().greet() }
) { Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Column( Image(painterResource(Res.drawable.compose_multiplatform), null)
Modifier.fillMaxWidth(), Text("Compose: $greeting")
horizontalAlignment = Alignment.CenterHorizontally,
) {
val sentiment = remember { mutableStateOf(Sentiment.HAPPY) }
var comment by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(
TextFieldValue("")
)
}
Row(modifier = Modifier.padding(8.dp)) {
for (emotion in Sentiment.entries) {
FilterChip(
onClick = { sentiment.value = emotion },
selected = sentiment.value == emotion,
modifier = Modifier.padding(8.dp),
leadingIcon = { Text(emotion.leadingIcon, fontSize = 20.sp) },
colors = ChipDefaults.outlinedFilterChipColors(),
border = ChipDefaults.outlinedBorder,
) {
Text(emotion.friendlyName, fontSize = 20.sp)
}
}
}
Row(modifier = Modifier.padding(8.dp)) {
TextField(
value = comment,
onValueChange = { comment = it },
label = { Text("Your comment") },
modifier = Modifier.height(100.dp).fillMaxWidth().padding(8.dp),
)
}
Row(modifier = Modifier.padding(8.dp)) {
Button(onClick = {
sentiment.value = Sentiment.HAPPY
comment = TextFieldValue("")
scope.launch {
delay(1000)
scaffoldState.snackbarHostState.showSnackbar("Feedback sent")
}
}, modifier = Modifier.padding(8.dp)) {
Icon(Icons.AutoMirrored.Filled.Send, contentDescription = "Send")
}
}
} }
} }
} }
} }
} }

View file

@ -0,0 +1,9 @@
package uk.sky.bob.application
class Greeting {
private val platform = getPlatform()
fun greet(): String {
return "Hello, ${platform.name}!"
}
}

View file

@ -1,7 +0,0 @@
package uk.sky.bob.application
enum class Sentiment(val friendlyName: String, val leadingIcon: String) {
HAPPY("Happy", "\uD83D\uDE03"),
NEUTRAL("Neutral", "\uD83D\uDE10"),
SAD("Sad", "\uD83D\uDE1E");
}

View file

@ -1,19 +1,19 @@
[versions] [versions]
agp = "8.10.1" agp = "8.5.2"
android-compileSdk = "35" android-compileSdk = "34"
android-minSdk = "24" android-minSdk = "24"
android-targetSdk = "34" android-targetSdk = "34"
androidx-activityCompose = "1.10.1" androidx-activityCompose = "1.9.3"
androidx-appcompat = "1.7.1" androidx-appcompat = "1.7.0"
androidx-constraintlayout = "2.2.1" androidx-constraintlayout = "2.2.0"
androidx-core-ktx = "1.16.0" androidx-core-ktx = "1.15.0"
androidx-espresso-core = "3.6.1" androidx-espresso-core = "3.6.1"
androidx-lifecycle = "2.9.1" androidx-lifecycle = "2.8.4"
androidx-material = "1.12.0" androidx-material = "1.12.0"
androidx-test-junit = "1.2.1" androidx-test-junit = "1.2.1"
compose-multiplatform = "1.7.3" compose-multiplatform = "1.7.0"
junit = "4.13.2" junit = "4.13.2"
kotlin = "2.1.21" kotlin = "2.1.0"
[libraries] [libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View file

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}