package com.example.ui.screens import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Animation import androidx.compose.material.icons.filled.CleaningServices import androidx.compose.material.icons.filled.CropFree import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.FilterList import androidx.compose.material.icons.filled.FitScreen import androidx.compose.material.icons.filled.Headphones import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Restore import androidx.compose.material.icons.filled.Speed import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButtonDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.util.UnstableApi import com.example.data.model.MediaTypeFilter import com.example.data.model.VideoScaleMode import com.example.playback.PlaybackManager import com.example.ui.theme.AccentViolet import com.example.ui.theme.AmoledBlack import com.example.ui.theme.DividerSubtle import com.example.ui.theme.ErrorRed import com.example.ui.theme.SurfaceElevated import com.example.ui.theme.SurfaceSecondary import com.example.ui.theme.TextPrimary import com.example.ui.theme.TextSecondary import com.example.ui.viewmodel.MainViewModel @OptIn(UnstableApi::class) @Composable fun SettingsScreen( viewModel: MainViewModel, modifier: Modifier = Modifier ) { val feedFilter by viewModel.feedFilter.collectAsState() val videoScaleMode by viewModel.videoScaleMode.collectAsState() val resumePlayback by viewModel.resumePlaybackPref.collectAsState() val backgroundAudio by viewModel.backgroundAudioPref.collectAsState() val playbackSpeed by viewModel.playbackSpeedPref.collectAsState() val reducedMotion by viewModel.reducedMotionPref.collectAsState() var showSpeedDialog by remember { mutableStateOf(false) } var showFeedFilterDialog by remember { mutableStateOf(false) } var showScaleDialog by remember { mutableStateOf(false) } var showClearCacheConfirm by remember { mutableStateOf(false) } var showPrivacyDialog by remember { mutableStateOf(false) } var showAboutDialog by remember { mutableStateOf(false) } Column( modifier = modifier .fillMaxSize() .background(AmoledBlack) .statusBarsPadding() .verticalScroll(rememberScrollState()) .padding(bottom = 96.dp) .testTag("settings_screen") ) { Text( text = "Settings", color = TextPrimary, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp) ) // Playback Preferences Section SettingsSectionHeader("PLAYBACK") SettingsClickableItem( title = "Default Feed Filter", subtitle = feedFilter.label, icon = Icons.Default.FilterList, onClick = { showFeedFilterDialog = true }, testTag = "setting_feed_filter" ) SettingsClickableItem( title = "Video Screen Fit Mode", subtitle = if (videoScaleMode == VideoScaleMode.FIT) "Fit: Preserve Aspect Ratio (No Crop)" else "Fill: Crop Edges to Fill Screen", icon = if (videoScaleMode == VideoScaleMode.FIT) Icons.Default.FitScreen else Icons.Default.CropFree, onClick = { showScaleDialog = true }, testTag = "setting_video_scale" ) SettingsClickableItem( title = "Default Playback Speed", subtitle = "${playbackSpeed}x", icon = Icons.Default.Speed, onClick = { showSpeedDialog = true }, testTag = "setting_playback_speed" ) SettingsSwitchItem( title = "Resume Playback", subtitle = "Save position and resume unfinished tracks and videos", icon = Icons.Default.Restore, checked = resumePlayback, onCheckedChange = { viewModel.setResumePlayback(it) }, testTag = "setting_resume_playback" ) SettingsSwitchItem( title = "Background Audio", subtitle = "Continue playing audio when app is in the background or screen off", icon = Icons.Default.Headphones, checked = backgroundAudio, onCheckedChange = { viewModel.setBackgroundAudio(it) }, testTag = "setting_background_audio" ) // UI & Accessibility Section SettingsSectionHeader("ACCESSIBILITY & UI") SettingsSwitchItem( title = "Reduced Motion", subtitle = "Minimize animations and transitions", icon = Icons.Default.Animation, checked = reducedMotion, onCheckedChange = { viewModel.setReducedMotion(it) }, testTag = "setting_reduced_motion" ) // Library & Storage Section SettingsSectionHeader("LIBRARY & STORAGE") SettingsClickableItem( title = "Rescan Media Library", subtitle = "Scan device storage for new songs and videos", icon = Icons.Default.Refresh, onClick = { viewModel.scanMedia() }, testTag = "setting_rescan_media" ) SettingsClickableItem( title = "Clear Playback History", subtitle = "Clear recent history without touching files", icon = Icons.Default.History, onClick = { viewModel.clearHistory() }, testTag = "setting_clear_history" ) SettingsClickableItem( title = "Clear App Cache", subtitle = "Free temporary thumbnails and image caches", icon = Icons.Default.CleaningServices, onClick = { showClearCacheConfirm = true }, testTag = "setting_clear_cache" ) // About Section SettingsSectionHeader("ABOUT") SettingsClickableItem( title = "Privacy Policy", subtitle = "100% offline. Zero tracking, telemetry, or remote uploads.", icon = Icons.Default.Lock, onClick = { showPrivacyDialog = true }, testTag = "setting_privacy" ) SettingsClickableItem( title = "SwipePlay Version", subtitle = "Version 1.0.0 (Native Android AMOLED Build)", icon = Icons.Default.Info, onClick = { showAboutDialog = true }, testTag = "setting_about" ) } // Feed Filter Dialog if (showFeedFilterDialog) { AlertDialog( onDismissRequest = { showFeedFilterDialog = false }, confirmButton = { TextButton(onClick = { showFeedFilterDialog = false }) { Text("Done", color = AccentViolet) } }, title = { Text("Default Feed Filter", color = TextPrimary) }, text = { Column { MediaTypeFilter.values().forEach { filter -> Row( modifier = Modifier .fillMaxWidth() .clickable { viewModel.setFeedFilter(filter) showFeedFilterDialog = false } .padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { RadioButton( selected = feedFilter == filter, onClick = { viewModel.setFeedFilter(filter) showFeedFilterDialog = false }, colors = RadioButtonDefaults.colors(selectedColor = AccentViolet) ) Spacer(modifier = Modifier.width(8.dp)) Text(filter.label, color = TextPrimary) } } } }, containerColor = SurfaceElevated ) } // Scale Mode Dialog if (showScaleDialog) { AlertDialog( onDismissRequest = { showScaleDialog = false }, confirmButton = { TextButton(onClick = { showScaleDialog = false }) { Text("Done", color = AccentViolet) } }, title = { Text("Video Scaling Preference", color = TextPrimary) }, text = { Column { VideoScaleMode.values().forEach { mode -> Row( modifier = Modifier .fillMaxWidth() .clickable { viewModel.setVideoScaleMode(mode) showScaleDialog = false } .padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { RadioButton( selected = videoScaleMode == mode, onClick = { viewModel.setVideoScaleMode(mode) showScaleDialog = false }, colors = RadioButtonDefaults.colors(selectedColor = AccentViolet) ) Spacer(modifier = Modifier.width(8.dp)) Column { Text(mode.label, color = TextPrimary, fontWeight = FontWeight.SemiBold) Text( if (mode == VideoScaleMode.FIT) "Pure black letterboxing, never crop or stretch" else "Crop outer edges to fill window without distortion", color = TextSecondary, fontSize = 11.sp ) } } } } }, containerColor = SurfaceElevated ) } // Speed Dialog if (showSpeedDialog) { val speeds = listOf(0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f) AlertDialog( onDismissRequest = { showSpeedDialog = false }, confirmButton = { TextButton(onClick = { showSpeedDialog = false }) { Text("Done", color = AccentViolet) } }, title = { Text("Playback Speed", color = TextPrimary) }, text = { Column { speeds.forEach { sp -> Row( modifier = Modifier .fillMaxWidth() .clickable { viewModel.setPlaybackSpeed(sp) showSpeedDialog = false } .padding(vertical = 6.dp), verticalAlignment = Alignment.CenterVertically ) { RadioButton( selected = playbackSpeed == sp, onClick = { viewModel.setPlaybackSpeed(sp) showSpeedDialog = false }, colors = RadioButtonDefaults.colors(selectedColor = AccentViolet) ) Spacer(modifier = Modifier.width(8.dp)) Text("${sp}x", color = TextPrimary, fontSize = 14.sp) } } } }, containerColor = SurfaceElevated ) } // Clear Cache Dialog if (showClearCacheConfirm) { AlertDialog( onDismissRequest = { showClearCacheConfirm = false }, confirmButton = { Button( onClick = { viewModel.clearAppCache() showClearCacheConfirm = false }, colors = ButtonDefaults.buttonColors(containerColor = AccentViolet) ) { Text("Clear Cache", color = Color.Black, fontWeight = FontWeight.Bold) } }, dismissButton = { TextButton(onClick = { showClearCacheConfirm = false }) { Text("Cancel", color = TextSecondary) } }, title = { Text("Clear Temporary Cache?", color = TextPrimary) }, text = { Text("This clears temporary memory and thumbnail caches. Your original audio and video files will NOT be affected.", color = TextSecondary) }, containerColor = SurfaceElevated ) } // Privacy Dialog if (showPrivacyDialog) { AlertDialog( onDismissRequest = { showPrivacyDialog = false }, confirmButton = { TextButton(onClick = { showPrivacyDialog = false }) { Text("OK", color = AccentViolet) } }, title = { Text("Offline Privacy Guarantee", color = TextPrimary) }, text = { Text( "SwipePlay is completely local and offline. It requests storage permissions strictly to index and play your local audio and video files. It does not connect to external servers, track analytics, show ads, or upload files.", color = TextSecondary, fontSize = 13.sp ) }, containerColor = SurfaceElevated ) } // About Dialog if (showAboutDialog) { AlertDialog( onDismissRequest = { showAboutDialog = false }, confirmButton = { TextButton(onClick = { showAboutDialog = false }) { Text("Close", color = AccentViolet) } }, title = { Text("SwipePlay", color = TextPrimary, fontWeight = FontWeight.Bold) }, text = { Column { Text("A native Android offline music and video player with vertical swipe feed and exact aspect ratio playback.", color = TextSecondary, fontSize = 13.sp) Spacer(modifier = Modifier.height(10.dp)) Text("Powered by AndroidX Media3 ExoPlayer, Jetpack Compose, Room, and Material 3.", color = TextSecondary, fontSize = 12.sp) } }, containerColor = SurfaceElevated ) } } @Composable private fun SettingsSectionHeader(title: String) { Text( text = title, color = AccentViolet, fontSize = 12.sp, fontWeight = FontWeight.Bold, letterSpacing = 1.2.sp, modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 20.dp, bottom = 8.dp) ) } @Composable private fun SettingsClickableItem( title: String, subtitle: String, icon: androidx.compose.ui.graphics.vector.ImageVector, onClick: () -> Unit, testTag: String ) { Row( modifier = Modifier .fillMaxWidth() .clickable { onClick() } .padding(horizontal = 16.dp, vertical = 12.dp) .testTag(testTag), verticalAlignment = Alignment.CenterVertically ) { Icon(imageVector = icon, contentDescription = null, tint = AccentViolet, modifier = Modifier.size(24.dp)) Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { Text(text = title, color = TextPrimary, fontSize = 14.sp, fontWeight = FontWeight.Medium) Text(text = subtitle, color = TextSecondary, fontSize = 12.sp) } } } @Composable private fun SettingsSwitchItem( title: String, subtitle: String, icon: androidx.compose.ui.graphics.vector.ImageVector, checked: Boolean, onCheckedChange: (Boolean) -> Unit, testTag: String ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 10.dp) .testTag(testTag), verticalAlignment = Alignment.CenterVertically ) { Icon(imageVector = icon, contentDescription = null, tint = AccentViolet, modifier = Modifier.size(24.dp)) Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { Text(text = title, color = TextPrimary, fontSize = 14.sp, fontWeight = FontWeight.Medium) Text(text = subtitle, color = TextSecondary, fontSize = 12.sp) } Switch( checked = checked, onCheckedChange = onCheckedChange, colors = SwitchDefaults.colors( checkedThumbColor = AmoledBlack, checkedTrackColor = AccentViolet, uncheckedThumbColor = TextSecondary, uncheckedTrackColor = SurfaceElevated ) ) } }