Skip to content

Commit

Permalink
Merge pull request #94 from DimensionDev/feature/bluesky_status_action
Browse files Browse the repository at this point in the history
[WIP][Android][Bluesky] Add status action
  • Loading branch information
Tlaster authored Nov 16, 2023
2 parents 05ae612 + 6558150 commit 4e58532
Show file tree
Hide file tree
Showing 20 changed files with 884 additions and 30 deletions.
3 changes: 0 additions & 3 deletions app/src/main/java/dev/dimension/flare/di/AndroidModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import dev.dimension.flare.ui.component.status.mastodon.DefaultMastodonStatusEve
import dev.dimension.flare.ui.component.status.mastodon.MastodonStatusEvent
import dev.dimension.flare.ui.component.status.misskey.DefaultMisskeyStatusEvent
import dev.dimension.flare.ui.component.status.misskey.MisskeyStatusEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.dsl.binds
import org.koin.core.module.dsl.singleOf
import org.koin.core.module.dsl.withOptions
Expand All @@ -26,7 +24,6 @@ val androidModule =
singleOf(::DefaultMastodonStatusEvent) withOptions {
binds(listOf(MastodonStatusEvent::class))
}
single { CoroutineScope(Dispatchers.IO) }
singleOf(::ComposeUseCase)
singleOf(::StatusEvent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Reply
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.FormatQuote
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Reply
import androidx.compose.material.icons.filled.Report
import androidx.compose.material.icons.filled.SyncAlt
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
Expand All @@ -38,19 +40,24 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.dimension.flare.R
import dev.dimension.flare.common.deeplink
import dev.dimension.flare.data.repository.AccountRepository
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.ui.component.HtmlText2
import dev.dimension.flare.ui.component.status.CommonStatusHeaderComponent
import dev.dimension.flare.ui.component.status.StatusActionButton
import dev.dimension.flare.ui.component.status.StatusMediaComponent
import dev.dimension.flare.ui.component.status.StatusRetweetHeaderComponent
import dev.dimension.flare.ui.component.status.UiStatusQuoted
import dev.dimension.flare.ui.model.UiAccount
import dev.dimension.flare.ui.model.UiMedia
import dev.dimension.flare.ui.model.UiStatus
import dev.dimension.flare.ui.model.contentDirection
import dev.dimension.flare.ui.screen.destinations.BlueskyReportStatusRouteDestination
import dev.dimension.flare.ui.screen.destinations.DeleteStatusConfirmRouteDestination
import dev.dimension.flare.ui.screen.destinations.ProfileRouteDestination
import dev.dimension.flare.ui.theme.MediumAlpha
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@Composable
internal fun BlueskyStatusComponent(
Expand Down Expand Up @@ -151,6 +158,9 @@ private fun StatusFooterComponent(
var showRenoteMenu by remember {
mutableStateOf(false)
}
var showMoreMenu by remember {
mutableStateOf(false)
}
Row(
modifier =
modifier
Expand Down Expand Up @@ -253,7 +263,51 @@ private fun StatusFooterComponent(
icon = Icons.Default.MoreHoriz,
text = null,
onClicked = {
event.onMoreClick(data)
showMoreMenu = true
},
content = {
DropdownMenu(
expanded = showMoreMenu,
onDismissRequest = { showMoreMenu = false },
) {
if (!data.isFromMe) {
DropdownMenuItem(
text = {
Text(
text = stringResource(id = R.string.blusky_item_action_report),
)
},
leadingIcon = {
Icon(
imageVector = Icons.Default.Report,
contentDescription = null,
)
},
onClick = {
showMoreMenu = false
event.onReportClick(data)
},
)
} else {
DropdownMenuItem(
text = {
Text(
text = stringResource(id = R.string.blusky_item_action_delete),
)
},
leadingIcon = {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
)
},
onClick = {
showMoreMenu = false
event.onDeleteClick(data)
},
)
}
}
},
)
}
Expand All @@ -275,11 +329,14 @@ internal interface BlueskyStatusEvent {

fun onLikeClick(data: UiStatus.Bluesky)

fun onMoreClick(data: UiStatus.Bluesky)
fun onReportClick(data: UiStatus.Bluesky)

fun onDeleteClick(data: UiStatus.Bluesky)
}

internal class DefaultBlueskyStatusEvent(
private val context: Context,
private val accountRepository: AccountRepository,
private val scope: CoroutineScope,
) : BlueskyStatusEvent {
override fun onStatusClick(data: UiStatus.Bluesky) {
Expand Down Expand Up @@ -331,12 +388,45 @@ internal class DefaultBlueskyStatusEvent(
}

override fun onReblogClick(data: UiStatus.Bluesky) {
scope.launch {
val account =
accountRepository.get(data.accountKey) as? UiAccount.Bluesky ?: return@launch
account.dataSource.reblog(data)
}
}

override fun onLikeClick(data: UiStatus.Bluesky) {
scope.launch {
val account =
accountRepository.get(data.accountKey) as? UiAccount.Bluesky ?: return@launch
account.dataSource.like(data)
}
}

override fun onReportClick(data: UiStatus.Bluesky) {
val intent =
Intent(
Intent.ACTION_VIEW,
Uri.parse(
BlueskyReportStatusRouteDestination(data.statusKey)
.deeplink(),
),
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}

override fun onMoreClick(data: UiStatus.Bluesky) {
override fun onDeleteClick(data: UiStatus.Bluesky) {
val intent =
Intent(
Intent.ACTION_VIEW,
Uri.parse(
DeleteStatusConfirmRouteDestination(data.statusKey)
.deeplink(),
),
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}

override fun onQuoteClick(data: UiStatus.Bluesky) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package dev.dimension.flare.ui.screen.status.action

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.ramcosta.composedestinations.annotation.DeepLink
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.FULL_ROUTE_PLACEHOLDER
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.dimension.flare.R
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.molecule.producePresenter
import dev.dimension.flare.ui.model.onSuccess
import dev.dimension.flare.ui.presenter.status.action.BlueskyReportStatusPresenter
import dev.dimension.flare.ui.presenter.status.action.BlueskyReportStatusState

@Composable
@Destination(
deepLinks = [
DeepLink(
uriPattern = "flare://$FULL_ROUTE_PLACEHOLDER",
),
],
)
fun BlueskyReportStatusRoute(
navigator: DestinationsNavigator,
statusKey: MicroBlogKey,
) {
BlueskyReportStatusDialog(
statusKey = statusKey,
onBack = {
navigator.navigateUp()
},
)
}

@Composable
internal fun BlueskyReportStatusDialog(
statusKey: MicroBlogKey,
onBack: () -> Unit,
) {
val state by producePresenter(key = statusKey.toString()) {
blueskyReportStatusPresenter(statusKey)
}

AlertDialog(
onDismissRequest = onBack,
confirmButton = {
TextButton(
enabled = state.reason != null,
onClick = {
state.status.onSuccess { status ->
state.reason?.let {
state.report(it, status)
onBack.invoke()
}
}
},
) {
Text(text = stringResource(id = R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = onBack) {
Text(text = stringResource(id = R.string.cancel))
}
},
title = {
Text(text = stringResource(id = R.string.report_title))
},
text = {
Column {
Text(text = stringResource(id = R.string.report_description))
state.allReasons.forEach {
val interactionSource =
remember(it) {
MutableInteractionSource()
}
ListItem(
modifier =
Modifier.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {
state.selectReason(it)
},
),
headlineContent = {
Text(text = stringResource(id = it.stringRes))
},
supportingContent = {
Text(text = stringResource(id = it.descriptionRes))
},
leadingContent = {
RadioButton(
selected = state.reason == it,
interactionSource = interactionSource,
onClick = {
state.selectReason(it)
},
)
},
)
}
}
},
)
}

@Composable
private fun blueskyReportStatusPresenter(statusKey: MicroBlogKey) =
run {
val state =
remember(statusKey) {
BlueskyReportStatusPresenter(statusKey)
}.invoke()

object : BlueskyReportStatusState by state {
}
}

private val BlueskyReportStatusState.ReportReason.stringRes: Int
get() =
when (this) {
BlueskyReportStatusState.ReportReason.Spam -> R.string.report_reason_spam_title
BlueskyReportStatusState.ReportReason.Violation -> R.string.report_reason_violation_title
BlueskyReportStatusState.ReportReason.Misleading -> R.string.report_reason_misleading_title
BlueskyReportStatusState.ReportReason.Sexual -> R.string.report_reason_sexual_title
BlueskyReportStatusState.ReportReason.Rude -> R.string.report_reason_rude_title
BlueskyReportStatusState.ReportReason.Other -> R.string.report_reason_other_title
}

private val BlueskyReportStatusState.ReportReason.descriptionRes: Int
get() =
when (this) {
BlueskyReportStatusState.ReportReason.Spam -> R.string.report_reason_spam_description
BlueskyReportStatusState.ReportReason.Violation -> R.string.report_reason_violation_description
BlueskyReportStatusState.ReportReason.Misleading -> R.string.report_reason_misleading_description
BlueskyReportStatusState.ReportReason.Sexual -> R.string.report_reason_sexual_description
BlueskyReportStatusState.ReportReason.Rude -> R.string.report_reason_rude_description
BlueskyReportStatusState.ReportReason.Other -> R.string.report_reason_other_description
}
Loading

0 comments on commit 4e58532

Please sign in to comment.