Compare commits

..

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

7 changed files with 54 additions and 117 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,38 +1,25 @@
package uk.sky.bob.application package uk.sky.bob.application
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.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.ExperimentalMaterialApi
import androidx.compose.material.FilterChip 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.material.TextField
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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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 androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp 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) @OptIn(ExperimentalMaterialApi::class)
@ -40,60 +27,38 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
@Preview @Preview
fun App() { fun App() {
MaterialTheme { MaterialTheme {
val scaffoldState = rememberScaffoldState() Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
val scope = rememberCoroutineScope() Row {
val state = remember { mutableStateOf(Sentiment.HAPPY) }
for (emotion in Sentiment.entries) {
FilterChip(
onClick = { state.value = emotion },
selected = state.value == emotion,
modifier = Modifier.padding(8.dp),
leadingIcon = { Text(emotion.leadingIcon) },
) {
Text(emotion.friendlyName)
}
}
}
Scaffold(scaffoldState = scaffoldState) { Row {
Box( var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
modifier = Modifier.fillMaxHeight(), mutableStateOf(
contentAlignment = Alignment.Center, TextFieldValue("")
) { )
Column( }
Modifier.fillMaxWidth(), TextField(
horizontalAlignment = Alignment.CenterHorizontally, value = text,
) { onValueChange = { text = it },
val sentiment = remember { mutableStateOf(Sentiment.HAPPY) } label = { Text("Your comment") },
var comment by rememberSaveable(stateSaver = TextFieldValue.Saver) { maxLines = 3,
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)) { Row {
TextField( Button(onClick = { /* Handle submit */ }) {
value = comment, Text("Submit")
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

@ -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"
}