diff --git a/app/src/main/java/com/cornellappdev/resell/android/model/api/FeedbackApiService.kt b/app/src/main/java/com/cornellappdev/resell/android/model/api/FeedbackApiService.kt new file mode 100644 index 00000000..e7140b90 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/resell/android/model/api/FeedbackApiService.kt @@ -0,0 +1,22 @@ +package com.cornellappdev.resell.android.model.api + +import com.google.gson.annotations.SerializedName +import retrofit2.http.Body +import retrofit2.http.POST + +interface FeedbackApiService { + @POST("feedback") + suspend fun sendFeedback(@Body reportBody: FeedbackBody): FeedbackBody + + @POST("feedback/search") + suspend fun searchFeedback(@Body reportBody : SearchFeedback): SearchFeedback +} + +data class FeedbackBody( + val userId: String, + val content: String +) + +data class SearchFeedback( + val query: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/resell/android/model/api/RetrofitInstance.kt b/app/src/main/java/com/cornellappdev/resell/android/model/api/RetrofitInstance.kt index 00d727e3..f69f76a1 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/model/api/RetrofitInstance.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/model/api/RetrofitInstance.kt @@ -149,6 +149,15 @@ class RetrofitInstance @Inject constructor( .create(SettingsApiService::class.java) } + val feedbackApi: FeedbackApiService by lazy { + Retrofit.Builder() + .baseUrl(BuildConfig.BASE_API_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(FeedbackApiService::class.java) + } + val notificationsApi: FcmApiService by lazy { Retrofit.Builder() .baseUrl(BuildConfig.FCM_URL) diff --git a/app/src/main/java/com/cornellappdev/resell/android/model/settings/FeedbackRepository.kt b/app/src/main/java/com/cornellappdev/resell/android/model/settings/FeedbackRepository.kt new file mode 100644 index 00000000..c314225b --- /dev/null +++ b/app/src/main/java/com/cornellappdev/resell/android/model/settings/FeedbackRepository.kt @@ -0,0 +1,20 @@ +package com.cornellappdev.resell.android.model.settings + +import com.cornellappdev.resell.android.model.api.FeedbackBody +import com.cornellappdev.resell.android.model.api.RetrofitInstance +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class FeedbackRepository @Inject constructor( + private val retrofitInstance: RetrofitInstance +) { + suspend fun sendFeedback(uid: String, content: String) { + retrofitInstance.feedbackApi.sendFeedback( + FeedbackBody( + userId = uid, + content = content + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportDetailsScreen.kt b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportDetailsScreen.kt index a9ef49d7..ab9f6ccf 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportDetailsScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -14,9 +15,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.cornellappdev.resell.android.R import com.cornellappdev.resell.android.ui.components.global.ResellHeader import com.cornellappdev.resell.android.ui.components.global.ResellTextButton import com.cornellappdev.resell.android.ui.components.global.ResellTextEntry @@ -32,8 +35,8 @@ fun ReportDetailsScreen( ) { val uiState = reportDetailsViewModel.collectUiStateValue() - Box( - modifier = Modifier.fillMaxSize() + Column( + modifier = Modifier.fillMaxSize(), ) { Content( title = uiState.title, @@ -43,16 +46,17 @@ fun ReportDetailsScreen( onDetailsChanged = reportDetailsViewModel::onTypedContentChanged, ) - ResellTextButton( - text = "Submit", - state = uiState.buttonState, - onClick = { - reportDetailsViewModel.onSubmitPressed() - }, - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 46.dp) - ) + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize()) { + ResellTextButton( + text = "Submit", + state = uiState.buttonState, + onClick = { + reportDetailsViewModel.onSubmitPressed() + }, + modifier = Modifier + .padding(bottom = 46.dp) + ) + } } } @@ -67,43 +71,37 @@ private fun Content( ) { Column( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .background(Color.White) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { ResellHeader( title = title, + leftPainter = R.drawable.ic_chevron_left ) + + Column(modifier = Modifier.defaultHorizontalPadding().padding(vertical = 36.dp)) { + Text( + text = "Explain what happened: ", + style = title1, + ) - Spacer(Modifier.height(24.dp)) + Spacer(Modifier.height(16.dp)) - Text( - text = subtitle, - style = title1, - ) + ResellTextEntry( + text = details, + onTextChange = onDetailsChanged, + inlineLabel = false, + multiLineHeight = 205.dp, + singleLine = false, + // TODO: probably wrong max lines + maxLines = 20, + placeholder = body, + textFontStyle = body2, + ) - Spacer(Modifier.height(16.dp)) + } - Text( - text = body, - style = body2, - color = AppDev, - ) - - Spacer(Modifier.height(8.dp)) - - // TODO Incorrect text entry design - ResellTextEntry( - text = details, - onTextChange = onDetailsChanged, - inlineLabel = false, - multiLineHeight = 255.dp, - singleLine = false, - // TODO: probably wrong max lines - maxLines = 20, - modifier = Modifier.defaultHorizontalPadding(), - placeholder = "enter feedback details..." - ) } } diff --git a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportNavigation.kt b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportNavigation.kt index bb4c3886..78c12506 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportNavigation.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/reporting/ReportNavigation.kt @@ -9,6 +9,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.cornellappdev.resell.android.viewmodel.navigation.ReportNavigationViewModel +import com.cornellappdev.resell.android.viewmodel.report.ReportType import kotlinx.serialization.Serializable @Composable @@ -27,7 +28,7 @@ fun ReportNavigation( NavHost( navController = navController, startDestination = uiState.initialPage ?: ReportScreen.Reason( - reportPost = true, + reportType = ReportType.POST, postId = "", userId = "" ), @@ -51,14 +52,14 @@ fun ReportNavigation( sealed class ReportScreen { @Serializable data class Reason( - val reportPost: Boolean, + val reportType: ReportType, val postId: String, val userId: String ) : ReportScreen() @Serializable data class Details( - val reportPost: Boolean, + val reportType: ReportType, val postId: String, val userId: String, val reason: String, @@ -66,7 +67,7 @@ sealed class ReportScreen { @Serializable data class Confirmation( - val reportPost: Boolean, + val reportType: ReportType, val userId: String, ) : ReportScreen() } diff --git a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/root/RootNavigation.kt b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/root/RootNavigation.kt index 03b933d5..a1f870f9 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/root/RootNavigation.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/root/RootNavigation.kt @@ -33,6 +33,7 @@ import com.cornellappdev.resell.android.ui.screens.onboarding.OnboardingNavigati import com.cornellappdev.resell.android.ui.screens.pdp.PostDetailPage import com.cornellappdev.resell.android.ui.screens.reporting.ReportNavigation import com.cornellappdev.resell.android.ui.screens.settings.SettingsNavigation +import com.cornellappdev.resell.android.viewmodel.report.ReportType import com.cornellappdev.resell.android.viewmodel.root.RootNavigationViewModel import com.cornellappdev.resell.android.viewmodel.root.RootSheet import kotlinx.coroutines.launch @@ -232,7 +233,7 @@ sealed class ResellRootRoute { @Serializable data class REPORT( - val reportPost: Boolean, + val reportType: ReportType, val postId: String, val userId: String ) : ResellRootRoute() diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/externalprofile/ExternalProfileViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/externalprofile/ExternalProfileViewModel.kt index f124be33..85432da8 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/externalprofile/ExternalProfileViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/externalprofile/ExternalProfileViewModel.kt @@ -14,6 +14,7 @@ import com.cornellappdev.resell.android.ui.screens.externalprofile.ExternalProfi import com.cornellappdev.resell.android.ui.screens.root.ResellRootRoute import com.cornellappdev.resell.android.viewmodel.ResellViewModel import com.cornellappdev.resell.android.viewmodel.navigation.RootNavigationRepository +import com.cornellappdev.resell.android.viewmodel.report.ReportType import com.cornellappdev.resell.android.viewmodel.root.OptionType import com.cornellappdev.resell.android.viewmodel.root.RootConfirmationRepository import com.cornellappdev.resell.android.viewmodel.root.RootDialogRepository @@ -98,7 +99,7 @@ class ExternalProfileViewModel @Inject constructor( OptionType.REPORT -> { rootNavigationRepository.navigate( ResellRootRoute.REPORT( - reportPost = false, + reportType = ReportType.USER, postId = "", userId = stateValue().uid, ) diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/navigation/ReportNavigationViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/navigation/ReportNavigationViewModel.kt index 1c7cf4e9..d3db58d9 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/navigation/ReportNavigationViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/navigation/ReportNavigationViewModel.kt @@ -38,7 +38,7 @@ class ReportNavigationViewModel @Inject constructor( applyMutation { copy( initialPage = ReportScreen.Reason( - reportPost = navArgs.reportPost, + reportType = navArgs.reportType, postId = navArgs.postId, userId = navArgs.userId ) diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/pdp/PostDetailViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/pdp/PostDetailViewModel.kt index aca7c548..fe941f0e 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/pdp/PostDetailViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/pdp/PostDetailViewModel.kt @@ -22,6 +22,7 @@ import com.cornellappdev.resell.android.util.UIEvent import com.cornellappdev.resell.android.util.richieUrl import com.cornellappdev.resell.android.viewmodel.ResellViewModel import com.cornellappdev.resell.android.viewmodel.navigation.RootNavigationRepository +import com.cornellappdev.resell.android.viewmodel.report.ReportType import com.cornellappdev.resell.android.viewmodel.root.OptionType import com.cornellappdev.resell.android.viewmodel.root.RootConfirmationRepository import com.cornellappdev.resell.android.viewmodel.root.RootDialogContent @@ -185,7 +186,7 @@ class PostDetailViewModel @Inject constructor( OptionType.REPORT -> { rootNavigationRepository.navigate( ResellRootRoute.REPORT( - reportPost = true, + reportType = ReportType.POST, postId = stateValue().postId, userId = stateValue().uid, ) diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportConfirmationViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportConfirmationViewModel.kt index 32711e28..292192fc 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportConfirmationViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportConfirmationViewModel.kt @@ -3,6 +3,7 @@ package com.cornellappdev.resell.android.viewmodel.report import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.cornellappdev.resell.android.model.api.Report import com.cornellappdev.resell.android.model.classes.ResellApiResponse import com.cornellappdev.resell.android.model.profile.ProfileRepository import com.cornellappdev.resell.android.model.settings.BlockedUsersRepository @@ -27,33 +28,33 @@ class ReportConfirmationViewModel @Inject constructor( ) : ResellViewModel( initialUiState = ConfirmationUiState( - reportPost = true, + reportType = ReportType.POST, userId = "" ) ) { data class ConfirmationUiState( - private val reportPost: Boolean, + private val reportType: ReportType, val userId: String, val accountName: ResellApiResponse = ResellApiResponse.Pending ) { val headerTitle: String - get() = if (reportPost) { - "Report Post" - } else { - "Report Account" + get() = when(reportType) { + ReportType.POST -> "Report Post" + ReportType.USER -> "Report Account" + ReportType.POST_TRANSACTION -> "Report Transaction" } val title: String - get() = if (reportPost) { - "Thank you for reporting this post" - } else { - "Thank you for reporting this account" + get() = when(reportType) { + ReportType.POST -> "Thank you for reporting this post" + ReportType.USER -> "Thank you for reporting this account" + ReportType.POST_TRANSACTION -> "Thank you for reporting this transaction" } val body: String - get() = "Your report is valued in keeping Resell a safe community. We will be carefully reviewing the ${if (reportPost) "post" else "account"} and taking any necessary action." + get() = "Your report is valued in keeping Resell a safe community. We will be carefully reviewing the information and taking any necessary action." //TODO CHECK W DESIGN val blockText: String get() = if (accountName is ResellApiResponse.Success) "Block ${accountName.data}?" else "Block Account?" @@ -101,7 +102,7 @@ class ReportConfirmationViewModel @Inject constructor( applyMutation { copy( - reportPost = navArgs.reportPost, + reportType = navArgs.reportType, userId = navArgs.userId ) } diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportDetailsViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportDetailsViewModel.kt index 62f0eac9..0c78dfbd 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportDetailsViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportDetailsViewModel.kt @@ -4,6 +4,8 @@ import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.cornellappdev.resell.android.model.api.Report +import com.cornellappdev.resell.android.model.settings.FeedbackRepository import com.cornellappdev.resell.android.model.settings.SettingsRepository import com.cornellappdev.resell.android.ui.components.global.ResellTextButtonState import com.cornellappdev.resell.android.ui.screens.reporting.ReportScreen @@ -19,11 +21,12 @@ class ReportDetailsViewModel @Inject constructor( private val reportNavigationRepository: ReportNavigationRepository, private val savedStateHandle: SavedStateHandle, private val settingsRepository: SettingsRepository, - private val rootConfirmationRepository: RootConfirmationRepository + private val rootConfirmationRepository: RootConfirmationRepository, + private val feedbackRepository: FeedbackRepository ) : ResellViewModel( initialUiState = ReportDetailsUiState( - reportPost = true, + reportType = ReportType.POST, reason = "", typedContent = "" ) @@ -31,25 +34,26 @@ class ReportDetailsViewModel @Inject constructor( data class ReportDetailsUiState( - val reportPost: Boolean, + val reportType: ReportType, val loadingSubmit: Boolean = false, val reason: String, val typedContent: String, val postId: String = "", val userId: String = "", + val username: String = "", ) { val title: String - get() = if (reportPost) { - "Report Post" - } else { - "Report Account" + get() = when(reportType) { + ReportType.POST -> "Report Post" + ReportType.USER -> "Report Account" + ReportType.POST_TRANSACTION -> "Submit Feedback" } val body: String - get() = if (reportPost) { - "Please provide more details about the post" - } else { - "Please provide more details about the account" + get() = when(reportType) { + ReportType.POST -> "Please provide more details about the post" + ReportType.USER -> "Please provide more details about the account" + ReportType.POST_TRANSACTION -> "Describe any issues that occurred during your transaction with $username" } val buttonState @@ -67,7 +71,7 @@ class ReportDetailsViewModel @Inject constructor( applyMutation { copy( - reportPost = navArgs.reportPost, + reportType = navArgs.reportType, reason = navArgs.reason, postId = navArgs.postId, userId = navArgs.userId @@ -93,24 +97,28 @@ class ReportDetailsViewModel @Inject constructor( try { // TODO Report message too? - if (stateValue().reportPost) { - settingsRepository.reportPost( + when(stateValue().reportType) { + ReportType.POST -> settingsRepository.reportPost( id = stateValue().postId, uid = stateValue().userId, reason = stateValue().reason ) - } else { - settingsRepository.reportProfile( + ReportType.USER -> settingsRepository.reportProfile( uid = stateValue().userId, reason = stateValue().reason, description = stateValue().typedContent ) + ReportType.POST_TRANSACTION -> //TODO REPLACE THIS LATER WHEN BACKEND REPLIES THIS WILL BREAK + feedbackRepository.sendFeedback( + uid = stateValue().userId, + content = stateValue().typedContent + ) } val navArgs = savedStateHandle.toRoute() reportNavigationRepository.navigate( ReportScreen.Confirmation( - reportPost = navArgs.reportPost, + reportType = navArgs.reportType, userId = navArgs.userId ) ) diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportReasonViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportReasonViewModel.kt index 921b1333..314187e0 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportReasonViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/report/ReportReasonViewModel.kt @@ -2,6 +2,7 @@ package com.cornellappdev.resell.android.viewmodel.report import androidx.lifecycle.SavedStateHandle import androidx.navigation.toRoute +import com.cornellappdev.resell.android.model.api.Report import com.cornellappdev.resell.android.ui.screens.reporting.ReportScreen import com.cornellappdev.resell.android.viewmodel.ResellViewModel import com.cornellappdev.resell.android.viewmodel.navigation.ReportNavigationRepository @@ -15,7 +16,7 @@ class ReportReasonViewModel @Inject constructor( ) : ResellViewModel( initialUiState = ReportReasonUiState( - reportPost = true + reportType = ReportType.POST ) ) { @@ -23,28 +24,45 @@ class ReportReasonViewModel @Inject constructor( * @param reportPost True if it's a report of a post, false if it's a report of a user. */ data class ReportReasonUiState( - private val reportPost: Boolean, + private val reportType: ReportType, ) { + val user_reasons : List = listOf(ReportReason.FRAUD, ReportReason.ILLEGAL, ReportReason.HATE_SPEECH, ReportReason.BULLYING, ReportReason.SEXUAL_MISCONDUCT, ReportReason.INTELLECTUAL_PROPERTY, ReportReason.OTHER) + val post_reasons : List = listOf(ReportReason.ILLEGAL, ReportReason.HATE_SPEECH, ReportReason.BULLYING, ReportReason.SEXUAL_MISCONDUCT, ReportReason.SPAM, ReportReason.OTHER) + val ptf_reasons : List = listOf(ReportReason.STOPPED_RESPONDING, ReportReason.ITEM_DAMAGED, ReportReason.ITEM_DIFFERENT, ReportReason.APP_ERROR, ReportReason.OTHER) + + val reasons: List - get() = ReportReason.entries.filter { - if (reportPost) { - it.appliesToPost - } else { - it.appliesToUser + get() = + if(reportType == ReportType.POST_TRANSACTION) { + ptf_reasons.map { it.message } + } + else if (reportType == ReportType.POST) { + post_reasons.map { it.message } + } + else { + user_reasons.map { it.message } } - }.map { it.message } + val title: String - get() = if (reportPost) { + get() = if(reportType == ReportType.POST_TRANSACTION) { + "Submit Feedback" + } + else if (reportType == ReportType.POST) { "Report Post" - } else { + } + else { "Report Account" } val subtitle: String - get() = if (reportPost) { + get() = if(reportType == ReportType.POST_TRANSACTION) { + "Explain what happened:" + } + else if (reportType == ReportType.POST) { "Why do you want to report this post?" - } else { + } + else { "Why do you want to report this account?" } } @@ -54,7 +72,7 @@ class ReportReasonViewModel @Inject constructor( savedStateHandle.toRoute().let { navArgs -> applyMutation { copy( - reportPost = navArgs.reportPost + reportType = ReportType.POST ) } } @@ -65,7 +83,7 @@ class ReportReasonViewModel @Inject constructor( val navArgs = savedStateHandle.toRoute() reportNavigationRepository.navigate( ReportScreen.Details( - reportPost = navArgs.reportPost, + reportType = navArgs.reportType, postId = navArgs.postId, userId = navArgs.userId, reason = reason @@ -74,13 +92,25 @@ class ReportReasonViewModel @Inject constructor( } } +// TODO: make hardcoded list of report reasons for each type (user/post/ptf) +// TODO: use enum for the text stuff later in ReportDetailsViewModel +// TODO: delete all the bools enum class ReportReason( - val appliesToPost: Boolean = true, - val appliesToUser: Boolean = true, val message: String ) { + STOPPED_RESPONDING( + message = "User stopped responding" + ), + ITEM_DAMAGED( + message = "Item arrived damaged" + ), + ITEM_DIFFERENT( + message = "Item is different than what I expected" + ), + APP_ERROR( + message = "There was an error on the app" + ), FRAUD( - appliesToPost = false, message = "Fraudulent behavior" ), ILLEGAL( @@ -96,14 +126,19 @@ enum class ReportReason( message = "Sexual misconduct or nudity" ), SPAM( - appliesToUser = false, message = "Spam" ), INTELLECTUAL_PROPERTY( - appliesToPost = false, message = "Unauthorized use of intellectual property" ), OTHER( message = "Other" ) } + +//TODO : add enum params +enum class ReportType { + POST, + USER, + POST_TRANSACTION +}