Skip to content

Commit

Permalink
Added message actions, theme settings
Browse files Browse the repository at this point in the history
  • Loading branch information
F0x1d committed May 11, 2023
1 parent 1c88b12 commit 06ee9e1
Show file tree
Hide file tree
Showing 20 changed files with 271 additions and 37 deletions.
10 changes: 5 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ android {
applicationId "com.f0x1d.sense"
minSdk 23
targetSdk 33
versionCode 7
versionName "1.0.6"
versionCode 8
versionName "1.0.7"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -44,7 +44,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.0'
kotlinCompilerExtensionVersion '1.4.7'
}
packagingOptions {
resources {
Expand Down Expand Up @@ -80,7 +80,7 @@ dependencies {
implementation 'androidx.activity:activity-compose:1.7.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material3:material3:1.1.0-rc01'
implementation 'androidx.compose.material3:material3:1.2.0-alpha01'
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
Expand All @@ -89,7 +89,7 @@ dependencies {
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-compose:$nav_version"

implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "androidx.datastore:datastore-preferences:1.1.0-alpha04"

testImplementation 'junit:junit:4.13.2'

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/f0x1d/sense/database/dao/MessagesDao.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.f0x1d.sense.database.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
Expand Down Expand Up @@ -29,4 +30,7 @@ interface MessagesDao {

@Query("DELETE FROM ChatMessage WHERE content is NULL")
suspend fun deleteEmptyMessages()

@Delete
suspend fun delete(vararg messages: ChatMessage)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.f0x1d.sense.extensions

import android.app.DownloadManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
Expand All @@ -15,4 +17,8 @@ fun Context.toast(@StringRes stringRes: Int, length: Int = Toast.LENGTH_SHORT) =

fun Context.openLink(url: String) = startActivity(
Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))
)
)

fun Context.copyText(text: String) = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).also {
it.setPrimaryClip(ClipData.newPlainText("Sense", text))
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.f0x1d.sense.store.datastore

import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.f0x1d.sense.store.datastore.base.BaseDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
Expand All @@ -9,18 +11,39 @@ import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SettingsDataStore @Inject constructor(@ApplicationContext context: Context): BaseDataStore(context, "settings_data") {
class SettingsDataStore @Inject constructor(@ApplicationContext context: Context): BaseDataStore(context, DATA_STORE_NAME) {

companion object {
val API_KEY_KEY = stringPreferencesKey("api_key")
val MODEL_KEY = stringPreferencesKey("model")
const val DATA_STORE_NAME = "settings_data"

const val THEME = "theme"
const val AMOLED = "amoled"

const val API_KEY = "api_key"
const val MODEL = "model"

val THEME_KEY = intPreferencesKey(THEME)
val AMOLED_KEY = booleanPreferencesKey(AMOLED)

val API_KEY_KEY = stringPreferencesKey(API_KEY)
val MODEL_KEY = stringPreferencesKey(MODEL)
}

val theme = getAsFlow(THEME_KEY).map {
it ?: 0
}
val amoled = getAsFlow(AMOLED_KEY).map {
it ?: false
}

val apiKey = getAsFlow(API_KEY_KEY)
val model = getAsFlow(MODEL_KEY).map {
it ?: "gpt-3.5-turbo"
}

suspend fun saveTheme(theme: Int) = save(THEME_KEY, theme)
suspend fun saveAmoled(amoled: Boolean) = save(AMOLED_KEY, amoled)

suspend fun saveApiKey(apiKey: String?) = save(API_KEY_KEY, apiKey)
suspend fun saveModel(model: String?) = save(MODEL_KEY, model)
}
10 changes: 9 additions & 1 deletion app/src/main/java/com/f0x1d/sense/ui/activity/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.f0x1d.sense.model.Screen
import com.f0x1d.sense.store.datastore.SettingsDataStore
import com.f0x1d.sense.ui.screen.ChatScreen
import com.f0x1d.sense.ui.screen.ChatsScreen
import com.f0x1d.sense.ui.screen.PicturesScreen
Expand All @@ -41,7 +42,13 @@ class MainActivity: ComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)

setContent {
OpenAITheme {
val theme by viewModel.theme.collectAsStateWithLifecycle(initialValue = 0)
val amoled by viewModel.amoled.collectAsStateWithLifecycle(initialValue = false)

OpenAITheme(
theme = theme,
amoled = amoled
) {
Surface(modifier = Modifier.imePadding()) {
val openSetup by viewModel.shouldOpenSetup.collectAsStateWithLifecycle(initialValue = false)

Expand Down Expand Up @@ -91,4 +98,5 @@ class MainActivity: ComponentActivity() {
@InstallIn(ActivityComponent::class)
interface ViewModelFactoryProvider {
fun chatViewModelFactory(): ChatViewModelAssistedFactory
fun settingsDataStore(): SettingsDataStore
}
33 changes: 32 additions & 1 deletion app/src/main/java/com/f0x1d/sense/ui/screen/ChatScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.f0x1d.sense.ui.screen

import android.app.Activity
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.scaleIn
Expand Down Expand Up @@ -34,6 +35,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
Expand All @@ -43,8 +45,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.f0x1d.sense.R
import com.f0x1d.sense.database.entity.ChatMessage
import com.f0x1d.sense.extensions.copyText
import com.f0x1d.sense.ui.activity.ViewModelFactoryProvider
import com.f0x1d.sense.ui.widget.Message
import com.f0x1d.sense.ui.widget.MessageAction
import com.f0x1d.sense.ui.widget.NavigationBackIcon
import com.f0x1d.sense.ui.widget.TypingMessage
import com.f0x1d.sense.viewmodel.ChatViewModel
Expand Down Expand Up @@ -111,7 +116,12 @@ fun ChatScreen(navController: NavController, chatId: Long) {
} else {
Message(
message = message,
needTitle = needTitle
needTitle = needTitle,
actions = if (message.generating) emptyList() else generateMessageActions(
context = LocalContext.current,
message = message,
viewModel = viewModel
)
)
}
}
Expand Down Expand Up @@ -190,6 +200,27 @@ fun ChatScreen(navController: NavController, chatId: Long) {
}
}

@Composable
private fun generateMessageActions(
context: Context,
message: ChatMessage,
viewModel: ChatViewModel
) = remember {
listOf(
MessageAction(
title = android.R.string.copy,
icon = R.drawable.ic_copy,
onClick = { context.copyText(message.content ?: "") }
),
MessageAction(
title = R.string.delete,
icon = R.drawable.ic_delete_message,
tint = Color.Red,
onClick = { viewModel.delete(message) }
)
)
}

@Composable
fun chatViewModel(chatId: Long): ChatViewModel {
val factory = EntryPointAccessors.fromActivity(
Expand Down
62 changes: 50 additions & 12 deletions app/src/main/java/com/f0x1d/sense/ui/screen/SettingsScreen.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
package com.f0x1d.sense.ui.screen

import androidx.annotation.StringRes
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.f0x1d.sense.R
import com.f0x1d.sense.ui.widget.NavigationBackIcon
import com.f0x1d.sense.viewmodel.SettingsViewModel

@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun SettingsScreen(navController: NavController) {
val viewModel = hiltViewModel<SettingsViewModel>()

val theme by viewModel.theme.collectAsStateWithLifecycle(initialValue = 0)
val amoled by viewModel.amoled.collectAsStateWithLifecycle(initialValue = false)

val apiKey by viewModel.apiKey
val model by viewModel.model

Expand All @@ -39,13 +59,37 @@ fun SettingsScreen(navController: NavController) {
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = 10.dp)
) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
listOf(R.string.follow_system, R.string.light, R.string.dark).forEachIndexed { index, i ->
FilterChip(
selected = theme == index,
onClick = { viewModel.selectTheme(index) },
label = { Text(text = stringResource(id = i)) }
)
}
}

FilterChip(
selected = amoled,
onClick = { viewModel.selectAmoled(!amoled) },
label = { Text("AMOLED") }
)

Spacer(modifier = Modifier.size(5.dp))

SettingsTextField(
value = apiKey,
onValueChange = { viewModel.updateFor(viewModel.apiKey, it) },
labelResource = R.string.api_key
)

Spacer(modifier = Modifier.size(10.dp))

SettingsTextField(
value = model,
onValueChange = { viewModel.updateFor(viewModel.model, it) },
Expand All @@ -59,13 +103,7 @@ fun SettingsScreen(navController: NavController) {
@Composable
private fun SettingsTextField(value: String, onValueChange: (String) -> Unit, @StringRes labelResource: Int) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(
top = 10.dp,
start = 10.dp,
end = 10.dp
),
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = onValueChange,
label = { Text(text = stringResource(id = labelResource)) },
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/f0x1d/sense/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,31 @@ private val LightColorScheme = lightColorScheme(

@Composable
fun OpenAITheme(
darkTheme: Boolean = isSystemInDarkTheme(),
theme: Int = 0,
amoled: Boolean = false,
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val darkTheme = when (theme) {
0 -> isSystemInDarkTheme()
1 -> false
else -> true
}

val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}.run {
if (amoled && darkTheme) copy(
background = AmoledBackground,
surface = AmoledBackground
) else this
}

val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
Expand Down
Loading

0 comments on commit 06ee9e1

Please sign in to comment.