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 dontAskForNotificationPermissions: Boolean = false,
|
||||||
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
|
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
|
||||||
val gallerySet: ProfileGalleryType = ProfileGalleryType.CLASSIC,
|
val gallerySet: ProfileGalleryType = ProfileGalleryType.CLASSIC,
|
||||||
|
val automaticallyProposeAiImprovements: BooleanType = BooleanType.NEVER,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ThemeType(
|
enum class ThemeType(
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class UiSettingsFlow(
|
|||||||
val dontAskForNotificationPermissions: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
val dontAskForNotificationPermissions: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||||
val featureSet: MutableStateFlow<FeatureSetType> = MutableStateFlow(FeatureSetType.SIMPLIFIED),
|
val featureSet: MutableStateFlow<FeatureSetType> = MutableStateFlow(FeatureSetType.SIMPLIFIED),
|
||||||
val gallerySet: MutableStateFlow<ProfileGalleryType> = MutableStateFlow(ProfileGalleryType.CLASSIC),
|
val gallerySet: MutableStateFlow<ProfileGalleryType> = MutableStateFlow(ProfileGalleryType.CLASSIC),
|
||||||
|
val automaticallyProposeAiImprovements: MutableStateFlow<BooleanType> = MutableStateFlow(BooleanType.NEVER),
|
||||||
) {
|
) {
|
||||||
val listOfFlows: List<Flow<Any?>> =
|
val listOfFlows: List<Flow<Any?>> =
|
||||||
listOf<Flow<Any?>>(
|
listOf<Flow<Any?>>(
|
||||||
@@ -52,6 +53,7 @@ class UiSettingsFlow(
|
|||||||
dontAskForNotificationPermissions,
|
dontAskForNotificationPermissions,
|
||||||
featureSet,
|
featureSet,
|
||||||
gallerySet,
|
gallerySet,
|
||||||
|
automaticallyProposeAiImprovements,
|
||||||
)
|
)
|
||||||
|
|
||||||
// emits at every change in any of the propertyes.
|
// emits at every change in any of the propertyes.
|
||||||
@@ -69,6 +71,7 @@ class UiSettingsFlow(
|
|||||||
flows[8] as Boolean,
|
flows[8] as Boolean,
|
||||||
flows[9] as FeatureSetType,
|
flows[9] as FeatureSetType,
|
||||||
flows[10] as ProfileGalleryType,
|
flows[10] as ProfileGalleryType,
|
||||||
|
flows[11] as BooleanType,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ class UiSettingsFlow(
|
|||||||
dontAskForNotificationPermissions.value,
|
dontAskForNotificationPermissions.value,
|
||||||
featureSet.value,
|
featureSet.value,
|
||||||
gallerySet.value,
|
gallerySet.value,
|
||||||
|
automaticallyProposeAiImprovements.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun update(torSettings: UiSettings): Boolean {
|
fun update(torSettings: UiSettings): Boolean {
|
||||||
@@ -134,6 +138,10 @@ class UiSettingsFlow(
|
|||||||
gallerySet.tryEmit(torSettings.gallerySet)
|
gallerySet.tryEmit(torSettings.gallerySet)
|
||||||
any = true
|
any = true
|
||||||
}
|
}
|
||||||
|
if (automaticallyProposeAiImprovements.value != torSettings.automaticallyProposeAiImprovements) {
|
||||||
|
automaticallyProposeAiImprovements.tryEmit(torSettings.automaticallyProposeAiImprovements)
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
|
||||||
return any
|
return any
|
||||||
}
|
}
|
||||||
@@ -164,6 +172,7 @@ class UiSettingsFlow(
|
|||||||
MutableStateFlow(uiSettings.dontAskForNotificationPermissions),
|
MutableStateFlow(uiSettings.dontAskForNotificationPermissions),
|
||||||
MutableStateFlow(uiSettings.featureSet),
|
MutableStateFlow(uiSettings.featureSet),
|
||||||
MutableStateFlow(uiSettings.gallerySet),
|
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_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS = booleanPreferencesKey("ui.dont_ask_for_notification_permissions")
|
||||||
val UI_FEATURE_SET = stringPreferencesKey("ui.feature_set")
|
val UI_FEATURE_SET = stringPreferencesKey("ui.feature_set")
|
||||||
val UI_GALLERY_SET = stringPreferencesKey("ui.gallery_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? =
|
suspend fun uiPreferences(context: Context): UiSettings? =
|
||||||
try {
|
try {
|
||||||
@@ -120,6 +121,7 @@ class UiSharedPreferences(
|
|||||||
dontAskForNotificationPermissions = preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] ?: false,
|
dontAskForNotificationPermissions = preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] ?: false,
|
||||||
featureSet = preferences[UI_FEATURE_SET]?.let { FeatureSetType.valueOf(it) } ?: FeatureSetType.SIMPLIFIED,
|
featureSet = preferences[UI_FEATURE_SET]?.let { FeatureSetType.valueOf(it) } ?: FeatureSetType.SIMPLIFIED,
|
||||||
gallerySet = preferences[UI_GALLERY_SET]?.let { ProfileGalleryType.valueOf(it) } ?: ProfileGalleryType.CLASSIC,
|
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) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
@@ -155,6 +157,7 @@ class UiSharedPreferences(
|
|||||||
preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] = sharedSettings.dontAskForNotificationPermissions
|
preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] = sharedSettings.dontAskForNotificationPermissions
|
||||||
preferences[UI_FEATURE_SET] = sharedSettings.featureSet.name
|
preferences[UI_FEATURE_SET] = sharedSettings.featureSet.name
|
||||||
preferences[UI_GALLERY_SET] = sharedSettings.gallerySet.name
|
preferences[UI_GALLERY_SET] = sharedSettings.gallerySet.name
|
||||||
|
preferences[UI_PROPOSE_AI_IMPROVEMENTS] = sharedSettings.automaticallyProposeAiImprovements.name
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
|
|||||||
+1
-9
@@ -65,7 +65,6 @@ import androidx.core.net.toUri
|
|||||||
import androidx.core.util.Consumer
|
import androidx.core.util.Consumer
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
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.StrippingFailureDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
|
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.navigation.topbars.PostingTopBar
|
||||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
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.aihelp.AiWritingHelpPanel
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
||||||
@@ -564,7 +562,7 @@ private fun NewPostScreenBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AiWritingHelpPanel(
|
AiWritingHelpPanel(
|
||||||
isVisible = postViewModel.wantsAiHelp,
|
isVisible = postViewModel.showAiPanel,
|
||||||
isProcessing = postViewModel.isAiProcessing,
|
isProcessing = postViewModel.isAiProcessing,
|
||||||
result = postViewModel.aiResult,
|
result = postViewModel.aiResult,
|
||||||
onToneSelected = postViewModel::requestAiTransform,
|
onToneSelected = postViewModel::requestAiTransform,
|
||||||
@@ -667,12 +665,6 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
|
|||||||
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
|
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.compose.setTextAndPlaceCursorAtBeginning
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState.EmojiMedia
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState.EmojiMedia
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.BooleanType
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
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.
|
// TODO: Remove useMockAi before shipping. Set to true to test UI without Gemini Nano.
|
||||||
private val useMockAi = true
|
private val useMockAi = true
|
||||||
|
|
||||||
var wantsAiHelp by mutableStateOf(false)
|
|
||||||
var aiResult by mutableStateOf<WritingResult?>(null)
|
var aiResult by mutableStateOf<WritingResult?>(null)
|
||||||
var aiStatus by mutableStateOf<WritingAssistantStatus>(WritingAssistantStatus.Unavailable)
|
var aiStatus by mutableStateOf<WritingAssistantStatus>(WritingAssistantStatus.Unavailable)
|
||||||
var isAiProcessing by mutableStateOf(false)
|
var isAiProcessing by mutableStateOf(false)
|
||||||
private var writingAssistant: WritingAssistant? = null
|
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) {
|
fun initWritingAssistant(context: android.content.Context) {
|
||||||
if (writingAssistant == null) {
|
if (writingAssistant == null) {
|
||||||
writingAssistant =
|
writingAssistant =
|
||||||
|
|||||||
+21
@@ -121,6 +121,7 @@ fun SettingsScreen(sharedPrefs: UiSettingsFlow) {
|
|||||||
ImmersiveScrollingChoice(sharedPrefs)
|
ImmersiveScrollingChoice(sharedPrefs)
|
||||||
FeatureSetChoice(sharedPrefs)
|
FeatureSetChoice(sharedPrefs)
|
||||||
GalleryChoice(sharedPrefs)
|
GalleryChoice(sharedPrefs)
|
||||||
|
AiWritingHelpChoice(sharedPrefs)
|
||||||
PushNotificationSettingsRow(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
|
@Composable
|
||||||
fun SettingsRow(
|
fun SettingsRow(
|
||||||
name: Int,
|
name: Int,
|
||||||
|
|||||||
@@ -2201,6 +2201,8 @@
|
|||||||
<string name="relay_leave_request">Relay leave request</string>
|
<string name="relay_leave_request">Relay leave request</string>
|
||||||
|
|
||||||
<string name="ai_writing_help">AI Writing Help</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_use_this">Use This</string>
|
||||||
<string name="ai_writing_dismiss">Dismiss</string>
|
<string name="ai_writing_dismiss">Dismiss</string>
|
||||||
<string name="ai_tone_correct">Correct</string>
|
<string name="ai_tone_correct">Correct</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user