Compare commits
7 commits
3cb2a649fc
...
706848b240
Author | SHA1 | Date | |
---|---|---|---|
706848b240 | |||
a41ee75b03 | |||
de8fe102bf | |||
ec6fe692a3 | |||
bd9163b2e1 | |||
ed227bf2b6 | |||
063ec7632f |
6 changed files with 96 additions and 50 deletions
4
.github/workflows/build.yaml
vendored
4
.github/workflows/build.yaml
vendored
|
@ -12,10 +12,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
|
@ -11,10 +11,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
|
45
README.md
45
README.md
|
@ -1,14 +1,39 @@
|
|||
This is a Kotlin Multiplatform project targeting Android, iOS.
|
||||
# Bob -- The Handy Feedback App
|
||||
|
||||
* `/composeApp` is for code that will be shared across your Compose Multiplatform applications.
|
||||
It contains several subfolders:
|
||||
- `commonMain` is for code that’s 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 Apple’s CoreCrypto for the iOS part of your Kotlin app,
|
||||
`iosMain` would be the right folder for such calls.
|
||||
This project is a simple Kotlin Multiplatform Application that allows users to enter free-text and
|
||||
submit the content to a server over HTTP. It also includes a form with sentiment selection and
|
||||
displays a Snackbar whenever the submit button is pressed.
|
||||
|
||||
* `/iosApp` contains iOS applications. Even if you’re 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.
|
||||
## Features
|
||||
|
||||
- Free-text input form
|
||||
- Sentiment selection using FilterChips
|
||||
- Submit content to a server using Retrofit
|
||||
- Display Snackbar on form submission
|
||||
|
||||
Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…
|
||||
## Technologies Used
|
||||
|
||||
- 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.
|
||||
|
|
|
@ -8,24 +8,31 @@ 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.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.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
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.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.Modifier
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
@ -33,45 +40,59 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
|
|||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
Scaffold(scaffoldState = scaffoldState) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val sentiment = remember { mutableStateOf(Sentiment.HAPPY) }
|
||||
var comment by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(
|
||||
TextFieldValue("")
|
||||
)
|
||||
}
|
||||
TextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
label = { Text("Your comment") },
|
||||
modifier = Modifier.height(100.dp).fillMaxWidth().padding(8.dp),
|
||||
)
|
||||
}
|
||||
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)) {
|
||||
Button(onClick = { /* Handle submit */ }, modifier = Modifier.padding(8.dp)) {
|
||||
Icon(Icons.AutoMirrored.Filled.Send, contentDescription = "Send")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[versions]
|
||||
agp = "8.5.2"
|
||||
agp = "8.8.0"
|
||||
android-compileSdk = "34"
|
||||
android-minSdk = "24"
|
||||
android-targetSdk = "34"
|
||||
|
@ -11,7 +11,7 @@ androidx-espresso-core = "3.6.1"
|
|||
androidx-lifecycle = "2.8.4"
|
||||
androidx-material = "1.12.0"
|
||||
androidx-test-junit = "1.2.1"
|
||||
compose-multiplatform = "1.7.0"
|
||||
compose-multiplatform = "1.7.3"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.1.0"
|
||||
|
||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue