feat: move AI writing help to Application Preferences
Replace the sparkle toggle button in the compose toolbar with a persistent setting in UI/Application Preferences. The new "Propose AI text improvements" setting (Always/Never) controls whether the tone chip row appears in the compose screen. When enabled and the device supports on-device AI, the tone chips show automatically below the text field. Removes the per-session toggle button from BottomRowActions. https://claude.ai/code/session_01RbCYGrbbapRMike8WQy41F
This commit is contained in:
@@ -38,6 +38,7 @@ data class UiSettings(
|
||||
val dontAskForNotificationPermissions: Boolean = false,
|
||||
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
|
||||
val gallerySet: ProfileGalleryType = ProfileGalleryType.CLASSIC,
|
||||
val automaticallyProposeAiImprovements: BooleanType = BooleanType.NEVER,
|
||||
)
|
||||
|
||||
enum class ThemeType(
|
||||
|
||||
@@ -38,6 +38,7 @@ class UiSettingsFlow(
|
||||
val dontAskForNotificationPermissions: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||
val featureSet: MutableStateFlow<FeatureSetType> = MutableStateFlow(FeatureSetType.SIMPLIFIED),
|
||||
val gallerySet: MutableStateFlow<ProfileGalleryType> = MutableStateFlow(ProfileGalleryType.CLASSIC),
|
||||
val automaticallyProposeAiImprovements: MutableStateFlow<BooleanType> = MutableStateFlow(BooleanType.NEVER),
|
||||
) {
|
||||
val listOfFlows: List<Flow<Any?>> =
|
||||
listOf<Flow<Any?>>(
|
||||
@@ -52,6 +53,7 @@ class UiSettingsFlow(
|
||||
dontAskForNotificationPermissions,
|
||||
featureSet,
|
||||
gallerySet,
|
||||
automaticallyProposeAiImprovements,
|
||||
)
|
||||
|
||||
// emits at every change in any of the propertyes.
|
||||
@@ -69,6 +71,7 @@ class UiSettingsFlow(
|
||||
flows[8] as Boolean,
|
||||
flows[9] as FeatureSetType,
|
||||
flows[10] as ProfileGalleryType,
|
||||
flows[11] as BooleanType,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,6 +88,7 @@ class UiSettingsFlow(
|
||||
dontAskForNotificationPermissions.value,
|
||||
featureSet.value,
|
||||
gallerySet.value,
|
||||
automaticallyProposeAiImprovements.value,
|
||||
)
|
||||
|
||||
fun update(torSettings: UiSettings): Boolean {
|
||||
@@ -134,6 +138,10 @@ class UiSettingsFlow(
|
||||
gallerySet.tryEmit(torSettings.gallerySet)
|
||||
any = true
|
||||
}
|
||||
if (automaticallyProposeAiImprovements.value != torSettings.automaticallyProposeAiImprovements) {
|
||||
automaticallyProposeAiImprovements.tryEmit(torSettings.automaticallyProposeAiImprovements)
|
||||
any = true
|
||||
}
|
||||
|
||||
return any
|
||||
}
|
||||
@@ -164,6 +172,7 @@ class UiSettingsFlow(
|
||||
MutableStateFlow(uiSettings.dontAskForNotificationPermissions),
|
||||
MutableStateFlow(uiSettings.featureSet),
|
||||
MutableStateFlow(uiSettings.gallerySet),
|
||||
MutableStateFlow(uiSettings.automaticallyProposeAiImprovements),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -102,6 +102,7 @@ class UiSharedPreferences(
|
||||
val UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS = booleanPreferencesKey("ui.dont_ask_for_notification_permissions")
|
||||
val UI_FEATURE_SET = stringPreferencesKey("ui.feature_set")
|
||||
val UI_GALLERY_SET = stringPreferencesKey("ui.gallery_set")
|
||||
val UI_PROPOSE_AI_IMPROVEMENTS = stringPreferencesKey("ui.propose_ai_improvements")
|
||||
|
||||
suspend fun uiPreferences(context: Context): UiSettings? =
|
||||
try {
|
||||
@@ -120,6 +121,7 @@ class UiSharedPreferences(
|
||||
dontAskForNotificationPermissions = preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] ?: false,
|
||||
featureSet = preferences[UI_FEATURE_SET]?.let { FeatureSetType.valueOf(it) } ?: FeatureSetType.SIMPLIFIED,
|
||||
gallerySet = preferences[UI_GALLERY_SET]?.let { ProfileGalleryType.valueOf(it) } ?: ProfileGalleryType.CLASSIC,
|
||||
automaticallyProposeAiImprovements = preferences[UI_PROPOSE_AI_IMPROVEMENTS]?.let { BooleanType.valueOf(it) } ?: BooleanType.NEVER,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
@@ -155,6 +157,7 @@ class UiSharedPreferences(
|
||||
preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] = sharedSettings.dontAskForNotificationPermissions
|
||||
preferences[UI_FEATURE_SET] = sharedSettings.featureSet.name
|
||||
preferences[UI_GALLERY_SET] = sharedSettings.gallerySet.name
|
||||
preferences[UI_PROPOSE_AI_IMPROVEMENTS] = sharedSettings.automaticallyProposeAiImprovements.name
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
|
||||
+1
-9
@@ -65,7 +65,6 @@ import androidx.core.net.toUri
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.ai.WritingAssistantStatus
|
||||
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
|
||||
@@ -83,7 +82,6 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.Nav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.aihelp.AiWritingHelpButton
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.aihelp.AiWritingHelpPanel
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
||||
@@ -564,7 +562,7 @@ private fun NewPostScreenBody(
|
||||
}
|
||||
|
||||
AiWritingHelpPanel(
|
||||
isVisible = postViewModel.wantsAiHelp,
|
||||
isVisible = postViewModel.showAiPanel,
|
||||
isProcessing = postViewModel.isAiProcessing,
|
||||
result = postViewModel.aiResult,
|
||||
onToneSelected = postViewModel::requestAiTransform,
|
||||
@@ -667,12 +665,6 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
|
||||
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.aiStatus is WritingAssistantStatus.Available) {
|
||||
AiWritingHelpButton(postViewModel.wantsAiHelp) {
|
||||
postViewModel.wantsAiHelp = !postViewModel.wantsAiHelp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
||||
import com.vitorpamplona.amethyst.commons.compose.setTextAndPlaceCursorAtBeginning
|
||||
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState.EmojiMedia
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
@@ -301,12 +302,19 @@ open class ShortNotePostViewModel :
|
||||
// TODO: Remove useMockAi before shipping. Set to true to test UI without Gemini Nano.
|
||||
private val useMockAi = true
|
||||
|
||||
var wantsAiHelp by mutableStateOf(false)
|
||||
var aiResult by mutableStateOf<WritingResult?>(null)
|
||||
var aiStatus by mutableStateOf<WritingAssistantStatus>(WritingAssistantStatus.Unavailable)
|
||||
var isAiProcessing by mutableStateOf(false)
|
||||
private var writingAssistant: WritingAssistant? = null
|
||||
|
||||
val showAiPanel: Boolean
|
||||
get() {
|
||||
val prefEnabled =
|
||||
accountViewModel.settings.uiSettingsFlow.automaticallyProposeAiImprovements.value ==
|
||||
BooleanType.ALWAYS
|
||||
return prefEnabled && aiStatus is WritingAssistantStatus.Available
|
||||
}
|
||||
|
||||
fun initWritingAssistant(context: android.content.Context) {
|
||||
if (writingAssistant == null) {
|
||||
writingAssistant =
|
||||
|
||||
+21
@@ -121,6 +121,7 @@ fun SettingsScreen(sharedPrefs: UiSettingsFlow) {
|
||||
ImmersiveScrollingChoice(sharedPrefs)
|
||||
FeatureSetChoice(sharedPrefs)
|
||||
GalleryChoice(sharedPrefs)
|
||||
AiWritingHelpChoice(sharedPrefs)
|
||||
PushNotificationSettingsRow(sharedPrefs)
|
||||
}
|
||||
}
|
||||
@@ -366,6 +367,26 @@ fun GalleryChoice(sharedPrefs: UiSettingsFlow) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AiWritingHelpChoice(sharedPrefs: UiSettingsFlow) {
|
||||
val aiIndex by sharedPrefs.automaticallyProposeAiImprovements.collectAsState()
|
||||
|
||||
val booleanItems =
|
||||
persistentListOf(
|
||||
TitleExplainer(stringRes(ConnectivityType.ALWAYS.resourceId)),
|
||||
TitleExplainer(stringRes(ConnectivityType.NEVER.resourceId)),
|
||||
)
|
||||
|
||||
SettingsRow(
|
||||
R.string.ai_writing_setting_title,
|
||||
R.string.ai_writing_setting_description,
|
||||
booleanItems,
|
||||
aiIndex.screenCode,
|
||||
) {
|
||||
sharedPrefs.automaticallyProposeAiImprovements.tryEmit(parseBooleanType(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsRow(
|
||||
name: Int,
|
||||
|
||||
@@ -2201,6 +2201,8 @@
|
||||
<string name="relay_leave_request">Relay leave request</string>
|
||||
|
||||
<string name="ai_writing_help">AI Writing Help</string>
|
||||
<string name="ai_writing_setting_title">Propose AI text improvements</string>
|
||||
<string name="ai_writing_setting_description">Show AI writing help options when composing posts (on-device, requires supported hardware)</string>
|
||||
<string name="ai_writing_use_this">Use This</string>
|
||||
<string name="ai_writing_dismiss">Dismiss</string>
|
||||
<string name="ai_tone_correct">Correct</string>
|
||||
|
||||
Reference in New Issue
Block a user