Skip to content

Commit

Permalink
Feature: Support text storage (#11)
Browse files Browse the repository at this point in the history
* Use key composable to separate conv detail screens. Automatically remember last typed text in persistent storage.

* Update comments.
  • Loading branch information
TimOrtel authored Nov 15, 2023
1 parent 07bff0c commit eeacf87
Show file tree
Hide file tree
Showing 23 changed files with 305 additions and 95 deletions.
2 changes: 1 addition & 1 deletion feature/course-view/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies {
implementation(project(":feature:metis"))

implementation(libs.kotlinx.datetime)
implementation(libs.accompanist.placeholder.material)
implementation(libs.placeholder.material)

testImplementation(project(":feature:login"))
testImplementation(project(":feature:login-test"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.google.accompanist.placeholder.material.placeholder
import io.github.fornewid.placeholder.material3.placeholder
import de.tum.informatics.www1.artemis.native_app.core.data.DataState
import de.tum.informatics.www1.artemis.native_app.core.model.Course
import de.tum.informatics.www1.artemis.native_app.core.ui.common.BasicDataStateUi
Expand Down
2 changes: 1 addition & 1 deletion feature/lecture-view/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies {
implementation(project(":feature:metis"))

implementation(libs.kotlinx.datetime)
implementation(libs.accompanist.placeholder.material)
implementation(libs.placeholder.material)
implementation(libs.accompanist.swiperefresh)

testImplementation(project(":feature:login"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import com.google.accompanist.placeholder.material.placeholder
import io.github.fornewid.placeholder.material3.placeholder
import de.tum.informatics.www1.artemis.native_app.core.model.lecture.Attachment
import de.tum.informatics.www1.artemis.native_app.core.ui.LocalLinkOpener
import de.tum.informatics.www1.artemis.native_app.core.ui.alert.TextAlertDialog
Expand Down
8 changes: 8 additions & 0 deletions feature/metis/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ dependencies {
api(project(":feature:metis:conversation"))
api(project(":feature:metis:manage-conversations"))

implementation(libs.androidx.compose.material)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)
implementation(libs.kotlinx.datetime)
implementation(libs.placeholder.material)
implementation(libs.accompanist.flowlayout)
implementation(libs.androidx.appcompat)

implementation(libs.androidx.dataStore.core)
implementation(libs.androidx.dataStore.preferences)

Expand Down
3 changes: 3 additions & 0 deletions feature/metis/conversation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ dependencies {

implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)
implementation(libs.placeholder.material)

implementation(libs.androidx.emoji2)
implementation(libs.androidx.emoji2.views)
implementation(libs.androidx.emoji2.views.helper)
implementation(libs.androidx.emoji2.emojiPicker)

implementation(libs.androidx.dataStore.preferences)

testImplementation(project(":feature:metis-test"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ser
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.network.impl.MetisModificationServiceImpl
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.network.impl.MetisServiceImpl
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.MetisStorageService
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.ReplyTextStorageService
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.impl.MetisStorageServiceImpl
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.impl.ReplyTextStorageServiceImpl
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.MetisListViewModel
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.thread.MetisThreadViewModel
import org.koin.android.ext.koin.androidContext
Expand All @@ -22,6 +24,7 @@ val conversationModule = module {
single<EmojiService> { EmojiServiceImpl(androidContext()) }

single<MetisStorageService> { MetisStorageServiceImpl(get()) }
single<ReplyTextStorageService> { ReplyTextStorageServiceImpl(androidContext()) }

single<MetisContextManager> { MetisContextManager(get(), get()) }

Expand All @@ -37,6 +40,7 @@ val conversationModule = module {
get(),
get(),
get(),
get(),
get()
)
}
Expand All @@ -55,6 +59,7 @@ val conversationModule = module {
get(),
get(),
get(),
get(),
get()
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage

internal interface ReplyTextStorageService {

suspend fun getStoredReplyText(
serverHost: String,
courseId: Long,
conversationId: Long,
postId: Long?
): String

suspend fun updateStoredReplyText(
serverHost: String,
courseId: Long,
conversationId: Long,
postId: Long?,
text: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.impl

import android.content.Context
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.ReplyTextStorageService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

internal class ReplyTextStorageServiceImpl(private val context: Context) : ReplyTextStorageService {

private companion object {
private const val DATA_STORE_NAME = "ReplyTextStorage"
}

private val Context.dataStore by preferencesDataStore(DATA_STORE_NAME)

override suspend fun getStoredReplyText(
serverHost: String,
courseId: Long,
conversationId: Long,
postId: Long?
): String = context
.dataStore
.data
.map { it[getKey(serverHost, courseId, conversationId, postId)].orEmpty() }
.first()

override suspend fun updateStoredReplyText(
serverHost: String,
courseId: Long,
conversationId: Long,
postId: Long?,
text: String
) {
context.dataStore.edit { data ->
data[getKey(serverHost, courseId, conversationId, postId)] = text
}
}

private fun getKey(
serverHost: String,
courseId: Long,
conversationId: Long,
postId: Long?
): Preferences.Key<String> =
stringPreferencesKey("$serverHost|$courseId|$conversationId|$postId")
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import de.tum.informatics.www1.artemis.native_app.core.data.isSuccess
import de.tum.informatics.www1.artemis.native_app.core.ui.common.BasicHintTextField
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.MetisContext
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.MetisChatList
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.MetisListViewModel
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.shared.isReplyEnabled
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.ui.humanReadableName
import io.github.fornewid.placeholder.material3.placeholder
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

Expand Down Expand Up @@ -93,7 +95,11 @@ fun ConversationScreen(
hintStyle = MaterialTheme.typography.bodyMedium
)
} else {
Text(text = title, maxLines = 1)
Text(
modifier = Modifier.placeholder(!conversationDataState.isSuccess),
text = title,
maxLines = 1
)
}
},
navigationIcon = {
Expand Down Expand Up @@ -127,14 +133,16 @@ fun ConversationScreen(
) { padding ->
val isReplyEnabled = isReplyEnabled(conversationDataState = conversationDataState)

MetisChatList(
modifier = Modifier
.fillMaxSize()
.padding(padding),
viewModel = viewModel,
listContentPadding = PaddingValues(),
onClickViewPost = onClickViewPost,
isReplyEnabled = isReplyEnabled
)
if (conversationDataState.isSuccess) {
MetisChatList(
modifier = Modifier
.fillMaxSize()
.padding(padding),
viewModel = viewModel,
listContentPadding = PaddingValues(),
onClickViewPost = onClickViewPost,
isReplyEnabled = isReplyEnabled
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ internal fun MetisChatList(
val hasModerationRights by viewModel.hasModerationRights.collectAsState()

MetisReplyHandler(
initialReplyTextProvider = viewModel,
onCreatePost = viewModel::createPost,
onEditPost = viewModel::editPost,
onDeletePost = viewModel::deletePost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ser
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.network.MetisService
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.network.MetisService.StandalonePostsContext
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.MetisStorageService
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.storage.ReplyTextStorageService
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.shared.MetisContentViewModel
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.CourseWideContext
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.DisplayPriority
Expand All @@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
Expand All @@ -65,6 +67,7 @@ internal class MetisListViewModel(
accountDataService: AccountDataService,
networkStatusProvider: NetworkStatusProvider,
conversationService: ConversationService,
replyTextStorageService: ReplyTextStorageService,
private val coroutineContext: CoroutineContext = EmptyCoroutineContext
) : MetisContentViewModel(
initialMetisContext,
Expand All @@ -76,9 +79,9 @@ internal class MetisListViewModel(
accountDataService,
networkStatusProvider,
conversationService,
replyTextStorageService,
coroutineContext
) {

private val _filter = MutableStateFlow<List<MetisFilter>>(emptyList())
val filter: StateFlow<List<MetisFilter>> = _filter

Expand Down Expand Up @@ -181,8 +184,10 @@ internal class MetisListViewModel(
}
}

fun createPost(postText: String): Deferred<MetisModificationFailure?> {
fun createPost(): Deferred<MetisModificationFailure?> {
return viewModelScope.async(coroutineContext) {
val postText = newMessageText.first()

val conversation =
loadConversation() ?: return@async MetisModificationFailure.CREATE_POST

Expand All @@ -200,25 +205,11 @@ internal class MetisListViewModel(
}
}

fun addMetisFilter(metisFilter: MetisFilter) {
_filter.value = _filter.value + metisFilter
}

fun removeMetisFilter(metisFilter: MetisFilter) {
_filter.value = _filter.value.filterNot { it == metisFilter }
}

fun updateCourseWideContext(new: CourseWideContext?) {
_courseWideContext.value = new
}

fun updateQuery(new: String) {
_query.value = new.ifEmpty { null }
}

fun updateSortingStrategy(new: MetisSortingStrategy) {
_sortingStrategy.value = new
}
override suspend fun getPostId(): Long? = null // by definition we are list, so no post

private fun insertDateSeparators(pagingList: PagingData<ChatListItem.PostChatListItem>) =
pagingList.insertSeparators { before: ChatListItem.PostChatListItem?, after: ChatListItem.PostChatListItem? ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.reply

import kotlinx.coroutines.flow.Flow

interface InitialReplyTextProvider {

val newMessageText: Flow<String>

fun updateInitialReplyText(text: String)
}
Loading

0 comments on commit eeacf87

Please sign in to comment.