diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c80ea808..75514cb9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -163,6 +163,8 @@ android { lint { abortOnError = false disable += "MissingTranslation" + disable += "NullSafeMutableLiveData" +// checkReleaseBuilds false } } diff --git a/app/src/main/java/chat/peptide/activities/InviteActivity.kt b/app/src/main/java/chat/peptide/activities/InviteActivity.kt index 08a220bc..3de43204 100644 --- a/app/src/main/java/chat/peptide/activities/InviteActivity.kt +++ b/app/src/main/java/chat/peptide/activities/InviteActivity.kt @@ -17,13 +17,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -57,7 +55,9 @@ import chat.peptide.api.schemas.RsResult import chat.peptide.api.settings.LoadedSettings import chat.peptide.api.settings.SyncedSettings import chat.peptide.composables.generic.IconPlaceholder +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.RemoteImage +import chat.peptide.composables.generic.SquareButton import chat.peptide.ui.theme.PeptideTheme import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage @@ -245,7 +245,7 @@ fun InviteScreen( Spacer(modifier = Modifier.height(16.dp)) Row { - Button( + SquareButton( onClick = { viewModel.joinInvite(inviteCode) }, @@ -257,7 +257,7 @@ fun InviteScreen( Spacer(modifier = Modifier.width(8.dp)) - TextButton( + PepTextButton( onClick = onFinish, modifier = Modifier .testTag("decline_invite") @@ -307,7 +307,7 @@ fun InvalidInviteError(error: PeptideError? = null, onDismissRequest: () -> Unit } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismissRequest() } @@ -347,7 +347,7 @@ fun NoInviteSpecifiedError(onDismissRequest: () -> Unit) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismissRequest() } diff --git a/app/src/main/java/chat/peptide/activities/MainActivity.kt b/app/src/main/java/chat/peptide/activities/MainActivity.kt index c1a6f6c8..b5850009 100644 --- a/app/src/main/java/chat/peptide/activities/MainActivity.kt +++ b/app/src/main/java/chat/peptide/activities/MainActivity.kt @@ -1,895 +1,906 @@ -package chat.peptide.activities +package chat.peptide.activities + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Bundle +import android.util.Log +import android.view.KeyEvent +import android.view.KeyboardShortcutGroup +import android.view.KeyboardShortcutInfo +import android.view.Menu +import android.view.View +import android.view.ViewTreeObserver +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.EaseInOutExpo +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.core.view.WindowCompat +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import chat.peptide.BuildConfig +import chat.peptide.R +import chat.peptide.PeptideApplication +import chat.peptide.api.ApplicationPlatform +import chat.peptide.api.HitRateLimitException +import chat.peptide.api.PeptideAPI +import chat.peptide.api.PeptideAPI.setPlatform +import chat.peptide.api.PeptideHttp +import chat.peptide.api.UrlsStorageKeys +import chat.peptide.api.api +import chat.peptide.api.routes.microservices.geo.queryGeo +import chat.peptide.api.routes.microservices.health.healthCheck +import chat.peptide.api.routes.onboard.needsOnboarding +import chat.peptide.api.schemas.HealthNotice +import chat.peptide.api.settings.Experiments +import chat.peptide.api.settings.GeoStateProvider +import chat.peptide.api.settings.LoadedSettings +import chat.peptide.api.settings.SyncedSettings +import chat.peptide.composables.generic.HealthAlert +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.voice.VoicePermissionSwitch +import chat.peptide.composables.voice.VoiceSheet +import chat.peptide.material.EasingTokens +import chat.peptide.ndk.NativeLibraries +import chat.peptide.persistence.KVStorage +import chat.peptide.screens.DefaultDestinationScreen +import chat.peptide.screens.about.AboutScreen +import chat.peptide.screens.about.AttributionScreen +import chat.peptide.screens.chat.ChatRouterScreen +import chat.peptide.screens.chat.views.channel.ChannelScreen +import chat.peptide.screens.chooseplatform.ChoosePlatformScreen +import chat.peptide.screens.create.CreateGroupScreen +import chat.peptide.screens.labs.LabsRootScreen +import chat.peptide.screens.login.LoginGreetingScreen +import chat.peptide.screens.login.LoginScreen +import chat.peptide.screens.login.MfaScreen +import chat.peptide.screens.login2.InitScreen +import chat.peptide.screens.main.MainScreen +import chat.peptide.screens.register.OnboardingScreen +import chat.peptide.screens.register.RegisterDetailsScreen +import chat.peptide.screens.register.RegisterGreetingScreen +import chat.peptide.screens.register.RegisterVerifyScreen +import chat.peptide.screens.services.DiscoverScreen +import chat.peptide.screens.settings.AppearanceSettingsScreen +import chat.peptide.screens.settings.ChangelogsSettingsScreen +import chat.peptide.screens.settings.ChatSettingsScreen +import chat.peptide.screens.settings.DebugSettingsScreen +import chat.peptide.screens.settings.ExperimentsSettingsScreen +import chat.peptide.screens.settings.LanguagePickerSettingsScreen +import chat.peptide.screens.settings.ProfileSettingsScreen +import chat.peptide.screens.settings.SessionSettingsScreen +import chat.peptide.screens.settings.SettingsScreen +import chat.peptide.screens.settings.channel.ChannelSettingsHome +import chat.peptide.screens.settings.channel.ChannelSettingsOverview +import chat.peptide.screens.settings.channel.ChannelSettingsPermissions +import chat.peptide.ui.theme.PeptideTheme +import chat.peptide.utils.DeepLinkHandler +import com.google.android.material.color.DynamicColors +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import io.ktor.client.request.get +import io.sentry.android.core.SentryAndroid +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject import io.sentry.Sentry -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.os.Bundle -import android.util.Log -import android.view.KeyEvent -import android.view.KeyboardShortcutGroup -import android.view.KeyboardShortcutInfo -import android.view.Menu -import android.view.View -import android.view.ViewTreeObserver -import android.widget.Toast -import androidx.activity.compose.BackHandler -import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.EaseInOutExpo -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.layout.widthIn -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.core.view.WindowCompat -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import chat.peptide.BuildConfig -import chat.peptide.R -import chat.peptide.PeptideApplication -import chat.peptide.api.ApplicationPlatform -import chat.peptide.api.HitRateLimitException -import chat.peptide.api.PeptideAPI -import chat.peptide.api.PeptideAPI.setPlatform -import chat.peptide.api.PeptideHttp -import chat.peptide.api.UrlsStorageKeys -import chat.peptide.api.api -import chat.peptide.api.routes.microservices.geo.queryGeo -import chat.peptide.api.routes.microservices.health.healthCheck -import chat.peptide.api.routes.onboard.needsOnboarding -import chat.peptide.api.schemas.HealthNotice -import chat.peptide.api.settings.Experiments -import chat.peptide.api.settings.GeoStateProvider -import chat.peptide.api.settings.LoadedSettings -import chat.peptide.api.settings.SyncedSettings -import chat.peptide.composables.generic.HealthAlert -import chat.peptide.composables.voice.VoicePermissionSwitch -import chat.peptide.composables.voice.VoiceSheet -import chat.peptide.material.EasingTokens -import chat.peptide.ndk.NativeLibraries -import chat.peptide.persistence.KVStorage -import chat.peptide.screens.DefaultDestinationScreen -import chat.peptide.screens.about.AboutScreen -import chat.peptide.screens.about.AttributionScreen -import chat.peptide.screens.chat.ChatRouterScreen -import chat.peptide.screens.chat.views.channel.ChannelScreen -import chat.peptide.screens.chooseplatform.ChoosePlatformScreen -import chat.peptide.screens.create.CreateGroupScreen -import chat.peptide.screens.labs.LabsRootScreen -import chat.peptide.screens.login.LoginGreetingScreen -import chat.peptide.screens.login.LoginScreen -import chat.peptide.screens.login.MfaScreen -import chat.peptide.screens.login2.InitScreen -import chat.peptide.screens.main.MainScreen -import chat.peptide.screens.register.OnboardingScreen -import chat.peptide.screens.register.RegisterDetailsScreen -import chat.peptide.screens.register.RegisterGreetingScreen -import chat.peptide.screens.register.RegisterVerifyScreen -import chat.peptide.screens.services.DiscoverScreen -import chat.peptide.screens.settings.AppearanceSettingsScreen -import chat.peptide.screens.settings.ChangelogsSettingsScreen -import chat.peptide.screens.settings.ChatSettingsScreen -import chat.peptide.screens.settings.DebugSettingsScreen -import chat.peptide.screens.settings.ExperimentsSettingsScreen -import chat.peptide.screens.settings.LanguagePickerSettingsScreen -import chat.peptide.screens.settings.ProfileSettingsScreen -import chat.peptide.screens.settings.SessionSettingsScreen -import chat.peptide.screens.settings.SettingsScreen -import chat.peptide.screens.settings.channel.ChannelSettingsHome -import chat.peptide.screens.settings.channel.ChannelSettingsOverview -import chat.peptide.screens.settings.channel.ChannelSettingsPermissions -import chat.peptide.ui.theme.PeptideTheme -import chat.peptide.utils.DeepLinkHandler -import com.google.android.material.color.DynamicColors -import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import io.ktor.client.request.get -import io.sentry.android.core.SentryAndroid -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -@SuppressLint("StaticFieldLeak") -class MainActivityViewModel @Inject constructor( - private val kvStorage: KVStorage, - @ApplicationContext private val context: Context -) : ViewModel() { - val nextDestination = MutableStateFlow(null) - var isConnected = MutableStateFlow(false) - val isReady = MutableStateFlow(false) - val couldNotLogIn = MutableStateFlow(false) - - private fun hasInternetConnection(): Boolean { - val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - val network = connectivityManager.activeNetwork ?: return false - val capabilities = - connectivityManager.getNetworkCapabilities(network) ?: return false - - return when { - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true - capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true - else -> false - } - } - - private suspend fun canReachPeptide(): Boolean { - try { - val res = PeptideHttp.get("/".api()) - return res.status.value == 200 - } catch (_: Exception) { - return false - } - } - - private suspend fun startWithDestination(destination: String) { - nextDestination.emit(destination) - isReady.emit(true) - } - - private suspend fun startWithoutDestination() { - isReady.emit(true) - } - - private fun doPreStartupTasks() { - Log.d("MainActivity", "Performing pre-startup tasks") - viewModelScope.launch { - Log.d("MainActivity", "Hydrating Experiments from KV") - Experiments.hydrateWithKv() - Log.d("MainActivity", "Performing health check") - doHealthCheck() - Log.d("MainActivity", "Performing update geo state") - updateGeoState() - } - } - - /** - * Sets the default platform on application creation. - * It retrieves the platform from key-value storage and sets it in the PeptideAPI. - * If the platform is not found or invalid, it logs the user out. - */ - fun setDefaultPlatformOnCreate() { - viewModelScope.launch { - ApplicationPlatform.fromName( - name = kvStorage.get(UrlsStorageKeys.PLATFORM) ?: "---" - )?.let { platform -> - setPlatform(platform) - } ?: run { - logOut() - } - } - } - - private suspend fun updateGeoState() { - try { - Log.d("MainActivity", "Querying geo state") - GeoStateProvider.updateGeoState(queryGeo()) - } catch (e: Exception) { - Log.e("MainActivity", "Failed to query geo state", e) - } - } - - fun checkLoggedInState() { - viewModelScope.launch { - Log.d("MainActivity", "Checking logged in state") - - isConnected.emit(hasInternetConnection()) - - Log.d("MainActivity", "Checking if we can reach Peptide") - - if (!isConnected.value) return@launch startWithoutDestination() - - Log.d("MainActivity", "We can reach Peptide, checking if we're logged in") - - val token = kvStorage.get("sessionToken") - ?: return@launch startWithDestination("choose-platform") - val id = kvStorage.get("sessionId") ?: "" - - Log.d( - "MainActivity", - "We have a session token, checking if it's valid and if we can still reach Peptide" - ) - - val canReachPeptide = canReachPeptide() - val valid = try { - PeptideAPI.checkSessionToken(token) - } catch (_: Throwable) { - false - } - - if (canReachPeptide && !valid) { - Log.d("MainActivity", "Session token is invalid, could not log in") - couldNotLogIn.emit(true) - } else { - try { - Log.d("MainActivity", "Session token is valid, checking onboarding state") - val onboard = needsOnboarding(token) - if (onboard) { - Log.d("MainActivity", "Onboarding state is incomplete, starting onboarding") - startWithDestination("register/onboarding") - return@launch - } - } catch (e: HitRateLimitException) { - Log.e("MainActivity", "Rate limited while checking onboarding state", e) - Toast.makeText( - context, - context.getString(R.string.rate_limit_toast), - Toast.LENGTH_SHORT - ).show() - return@launch startWithoutDestination() - } catch (e: Exception) { - Log.e("MainActivity", "Failed to check onboarding state, could not log in", e) - couldNotLogIn.emit(true) - } - - try { - Log.d("MainActivity", "Onboarding state is complete, logging in") - PeptideAPI.loginAs(token) - PeptideAPI.setSessionId(id) - if (Experiments.usePolar.isEnabled) { - startWithDestination("main") - } else { - startWithDestination("chat") - } - } catch (e: Exception) { - Log.e("MainActivity", "Failed to login, could not log in", e) - couldNotLogIn.emit(true) - } - } - } - } - - fun logOut() { - viewModelScope.launch { - kvStorage.remove("sessionToken") - kvStorage.remove("sessionId") - startWithDestination("choose-platform") - } - } - - fun updateNextDestination(destination: String) { - viewModelScope.launch { - nextDestination.emit(null) - nextDestination.emit(destination) - } - } - - val activeAlert = MutableStateFlow(null) - val isAlertActive = MutableStateFlow(false) - - private fun doHealthCheck() { - viewModelScope.launch { - try { - val health = healthCheck() - if (health.alert != null) { - activeAlert.emit(health) - isAlertActive.emit(true) - } - } catch (e: Exception) { - Log.e("MainActivity", "Failed to perform health check", e) - } - } - } - - fun onDismissHealthAlert() { - viewModelScope.launch { - activeAlert.emit(null) - isAlertActive.emit(false) - } - } - - fun onDismissLoginError() { - viewModelScope.launch { - couldNotLogIn.emit(false) - } - } - - init { - Log.d("MainActivity", "Starting up") - doPreStartupTasks() - checkLoggedInState() - } -} - -@AndroidEntryPoint -class MainActivity : AppCompatActivity() { - private val viewModel by viewModels() - - // Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours - // See the other one in DefaultDestinationScreen.kt - override fun onResume() { - super.onResume() - DynamicColors.applyToActivityIfAvailable(this) - DynamicColors.applyToActivitiesIfAvailable(PeptideApplication.instance) - @Suppress("DEPRECATION") // We are fixing a bug in the splash screen - window.statusBarColor = Color.Transparent.toArgb() - } - - // Same as above for configuration changes (rotation, dark mode, etc.) - override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { - super.onConfigurationChanged(newConfig) - DynamicColors.applyToActivityIfAvailable(this) - DynamicColors.applyToActivitiesIfAvailable(PeptideApplication.instance) - @Suppress("DEPRECATION") // We are fixing a bug in the splash screen - window.statusBarColor = Color.Transparent.toArgb() - } - - @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) - override fun onCreate(savedInstanceState: Bundle?) { + +@HiltViewModel +@SuppressLint("StaticFieldLeak") +class MainActivityViewModel @Inject constructor( + private val kvStorage: KVStorage, + @ApplicationContext private val context: Context +) : ViewModel() { + val nextDestination = MutableStateFlow(null) + var isConnected = MutableStateFlow(false) + val isReady = MutableStateFlow(false) + val couldNotLogIn = MutableStateFlow(false) + + private fun hasInternetConnection(): Boolean { + val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + val network = connectivityManager.activeNetwork ?: return false + val capabilities = + connectivityManager.getNetworkCapabilities(network) ?: return false + + return when { + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true + else -> false + } + } + + private suspend fun canReachPeptide(): Boolean { + try { + val res = PeptideHttp.get("/".api()) + return res.status.value == 200 + } catch (_: Exception) { + return false + } + } + + private suspend fun startWithDestination(destination: String) { + nextDestination.emit(destination) + isReady.emit(true) + } + + private suspend fun startWithoutDestination() { + isReady.emit(true) + } + + private fun doPreStartupTasks() { + Log.d("MainActivity", "Performing pre-startup tasks") + viewModelScope.launch { + Log.d("MainActivity", "Hydrating Experiments from KV") + Experiments.hydrateWithKv() + Log.d("MainActivity", "Performing health check") + doHealthCheck() + Log.d("MainActivity", "Performing update geo state") + updateGeoState() + } + } + + /** + * Sets the default platform on application creation. + * It retrieves the platform from key-value storage and sets it in the PeptideAPI. + * If the platform is not found or invalid, it logs the user out. + */ + fun setDefaultPlatformOnCreate() { + viewModelScope.launch { + ApplicationPlatform.fromName( + name = kvStorage.get(UrlsStorageKeys.PLATFORM) ?: "---" + )?.let { platform -> + setPlatform(platform) + } ?: run { + logOut() + } + } + } + + private suspend fun updateGeoState() { + try { + Log.d("MainActivity", "Querying geo state") + GeoStateProvider.updateGeoState(queryGeo()) + } catch (e: Exception) { + Log.e("MainActivity", "Failed to query geo state", e) + } + } + + fun checkLoggedInState() { + viewModelScope.launch { + Log.d("MainActivity", "Checking logged in state") + + isConnected.emit(hasInternetConnection()) + + Log.d("MainActivity", "Checking if we can reach Peptide") + + if (!isConnected.value) return@launch startWithoutDestination() + + Log.d("MainActivity", "We can reach Peptide, checking if we're logged in") + + val token = kvStorage.get("sessionToken") + ?: return@launch startWithDestination("choose-platform") + val id = kvStorage.get("sessionId") ?: "" + + Log.d( + "MainActivity", + "We have a session token, checking if it's valid and if we can still reach Peptide" + ) + + val canReachPeptide = canReachPeptide() + val valid = try { + PeptideAPI.checkSessionToken(token) + } catch (_: Throwable) { + false + } + + if (canReachPeptide && !valid) { + Log.d("MainActivity", "Session token is invalid, could not log in") + couldNotLogIn.emit(true) + } else { + try { + Log.d("MainActivity", "Session token is valid, checking onboarding state") + val onboard = needsOnboarding(token) + if (onboard) { + Log.d("MainActivity", "Onboarding state is incomplete, starting onboarding") + startWithDestination("register/onboarding") + return@launch + } + } catch (e: HitRateLimitException) { + Log.e("MainActivity", "Rate limited while checking onboarding state", e) + Toast.makeText( + context, + context.getString(R.string.rate_limit_toast), + Toast.LENGTH_SHORT + ).show() + return@launch startWithoutDestination() + } catch (e: Exception) { + Log.e("MainActivity", "Failed to check onboarding state, could not log in", e) + couldNotLogIn.emit(true) + } + + try { + Log.d("MainActivity", "Onboarding state is complete, logging in") + PeptideAPI.loginAs(token) + PeptideAPI.setSessionId(id) + if (Experiments.usePolar.isEnabled) { + startWithDestination("main") + } else { + startWithDestination("chat") + } + } catch (e: Exception) { + Log.e("MainActivity", "Failed to login, could not log in", e) + couldNotLogIn.emit(true) + } + } + } + } + + fun logOut() { + viewModelScope.launch { + kvStorage.remove("sessionToken") + kvStorage.remove("sessionId") + startWithDestination("choose-platform") + } + } + + fun updateNextDestination(destination: String) { + viewModelScope.launch { + nextDestination.emit(null) + nextDestination.emit(destination) + } + } + + val activeAlert = MutableStateFlow(null) + val isAlertActive = MutableStateFlow(false) + + private fun doHealthCheck() { + viewModelScope.launch { + try { + val health = healthCheck() + if (health.alert != null) { + activeAlert.emit(health) + isAlertActive.emit(true) + } + } catch (e: Exception) { + Log.e("MainActivity", "Failed to perform health check", e) + } + } + } + + fun onDismissHealthAlert() { + viewModelScope.launch { + activeAlert.emit(null) + isAlertActive.emit(false) + } + } + + fun onDismissLoginError() { + viewModelScope.launch { + couldNotLogIn.emit(false) + } + } + + init { + Log.d("MainActivity", "Starting up") + doPreStartupTasks() + checkLoggedInState() + } +} + +@AndroidEntryPoint +class MainActivity : AppCompatActivity() { + private val viewModel by viewModels() + + // Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours + // See the other one in DefaultDestinationScreen.kt + override fun onResume() { + super.onResume() + DynamicColors.applyToActivityIfAvailable(this) + DynamicColors.applyToActivitiesIfAvailable(PeptideApplication.instance) + @Suppress("DEPRECATION") // We are fixing a bug in the splash screen + window.statusBarColor = Color.Transparent.toArgb() + } + + // Same as above for configuration changes (rotation, dark mode, etc.) + override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { + super.onConfigurationChanged(newConfig) + DynamicColors.applyToActivityIfAvailable(this) + DynamicColors.applyToActivitiesIfAvailable(PeptideApplication.instance) + @Suppress("DEPRECATION") // We are fixing a bug in the splash screen + window.statusBarColor = Color.Transparent.toArgb() + } + + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - // Set the default platform for the Platform API. - viewModel.setDefaultPlatformOnCreate() - - SentryAndroid.init(this) { options -> - options.dsn = BuildConfig.SENTRY_DSN - options.release = BuildConfig.VERSION_NAME - } - - @Suppress("DEPRECATION") // We are fixing a bug in the splash screen - window.statusBarColor = Color.Transparent.toArgb() - WindowCompat.setDecorFitsSystemWindows(window, false) - - PeptideAPI.hydrateFromPersistentCache() - - // Handle deep link data if available - handleDeepLinkData(intent) - - setContent { - val windowSizeClass = calculateWindowSizeClass(this) - AppEntrypoint( - windowSizeClass, - viewModel.nextDestination.collectAsState().value, - viewModel.isConnected.collectAsState().value, - viewModel.activeAlert.collectAsState().value, - viewModel.isAlertActive.collectAsState().value, - viewModel.couldNotLogIn.collectAsState().value, - viewModel::logOut, - viewModel::onDismissHealthAlert, - viewModel::onDismissLoginError, - viewModel::checkLoggedInState, - viewModel::updateNextDestination - ) - } - - val content: View = findViewById(android.R.id.content) - content.viewTreeObserver.addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - // Check whether the initial data is ready. - return if (viewModel.isReady.value) { - // The content is ready. Start drawing. - content.viewTreeObserver.removeOnPreDrawListener(this) - true - } else { - // The content isn't ready. Suspend. - false - } - } - } - ) + // waiting for view to draw to better represent a captured error with a screenshot + findViewById(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener { + try { + throw Exception("This app uses Sentry! :)") + } catch (e: Exception) { + Sentry.captureException(e) + } } - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - handleDeepLinkData(intent) - } - - private fun handleDeepLinkData(intent: Intent) { - // Check for deep link data from DeepLinkActivity - val channelId = intent.getStringExtra(DeepLinkActivity.PATH_CHANNEL) - val serverId = intent.getStringExtra(DeepLinkActivity.PATH_SERVER) - val userId = intent.getStringExtra(DeepLinkActivity.PATH_USER) - val messageId = intent.getStringExtra(DeepLinkActivity.PATH_MESSAGE) - val nextDestination = intent.getStringExtra("next_destination") - - if (nextDestination != null) { - Log.d(TAG, "Setting next destination from deep link: $nextDestination") - viewModel.updateNextDestination(nextDestination) - return - } - - // Store deep link data in a static companion object for ChatRouterScreen to access - DeepLinkHandler.channelId = channelId - DeepLinkHandler.serverId = serverId - DeepLinkHandler.userId = userId - DeepLinkHandler.messageId = messageId - DeepLinkHandler.hasDeepLink = channelId != null || serverId != null || userId != null || messageId != null - - // Wait for the app to be ready before navigating to the deep link destination - viewModel.viewModelScope.launch { - viewModel.isReady.collect { isReady -> - if (isReady) { - when { - channelId != null -> { - Log.d(TAG, "Navigating to channel from deep link: $channelId") - // Check if Polar mode is enabled - if (Experiments.usePolar.isEnabled) { - viewModel.updateNextDestination("main/conversation/$channelId") - } else { - // For non-Polar mode, we'll use the DeepLinkHandler to communicate with ChatRouterScreen - viewModel.updateNextDestination("chat") - } - } - serverId != null -> { - Log.d(TAG, "Navigating to server from deep link: $serverId") - // Navigate to chat screen, the ChatRouterScreen will handle the server navigation - viewModel.updateNextDestination("chat") - } - userId != null -> { - Log.d(TAG, "Navigating to user from deep link: $userId") - // Navigate to chat screen, the ChatRouterScreen will handle the user navigation - viewModel.updateNextDestination("chat") - } - messageId != null -> { - Log.d(TAG, "Navigating to message from deep link: $messageId") - // Navigate to chat screen, the ChatRouterScreen will handle the message navigation - viewModel.updateNextDestination("chat") - } - } - // Only collect once - return@collect - } - } - } - } - - override fun onProvideKeyboardShortcuts( - data: MutableList?, - menu: Menu?, - deviceId: Int - ) { - val messaging = KeyboardShortcutGroup( - getString(R.string.keyboard_shortcut_messaging), - listOf( - KeyboardShortcutInfo( - getString(R.string.keyboard_shortcut_messaging_new_line), - KeyEvent.KEYCODE_ENTER, - 0 - ), - KeyboardShortcutInfo( - getString(R.string.keyboard_shortcut_messaging_send_message), - KeyEvent.KEYCODE_ENTER, - KeyEvent.META_CTRL_ON - ) - ) - ) - - data?.add(messaging) - } - - companion object { - private const val TAG = "MainActivity" - init { - NativeLibraries.init() - } - } -} - -val PeptideTweenInt: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) -val PeptideTweenFloat: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) -val PeptideTweenDp: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) - -val NavTweenInt: FiniteAnimationSpec = tween(350, easing = EaseInOutExpo) -val NavTweenFloat: FiniteAnimationSpec = tween(350, easing = EaseInOutExpo) - -// This composable handles the main compose entrypoint of the app, provides the main navigation -// graph, and handles the animation and layout for the voice chat UI. -@Composable -fun AppEntrypoint( - windowSizeClass: WindowSizeClass, - nextDestination: String?, - isConnected: Boolean, - healthNotice: HealthNotice?, - isHealthAlertActive: Boolean, - couldNotLogIn: Boolean, - onLogout: () -> Unit = {}, - onDismissHealthAlert: () -> Unit = {}, - onDismissLoginError: () -> Unit = {}, - onRetryConnection: () -> Unit, - onUpdateNextDestination: (String) -> Unit = {} -) { - var showVoiceUI by rememberSaveable { mutableStateOf(false) } - var voiceChannelId by rememberSaveable { mutableStateOf(null) } - - val chatUIScale by animateFloatAsState( - if (showVoiceUI) 0.8f else 1.0f, - animationSpec = tween( - durationMillis = 300, - easing = EasingTokens.EmphasizedDecelerate - ) - ) - val chatUIOpacity by animateFloatAsState( - if (showVoiceUI) 0.8f else 1.0f, - animationSpec = tween( - durationMillis = 300, - easing = EasingTokens.EmphasizedDecelerate - ) - ) - - BackHandler(showVoiceUI) { - showVoiceUI = false - } - - val keyboardController = LocalSoftwareKeyboardController.current - LaunchedEffect(showVoiceUI) { - if (showVoiceUI) keyboardController?.hide() - } - - val navController = rememberNavController() - - PeptideTheme( - requestedTheme = LoadedSettings.theme, - colourOverrides = SyncedSettings.android.colourOverrides, - ) { - Box( - Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surfaceContainerLowest) - ) { - Surface( - modifier = Modifier - .fillMaxSize() - .scale(chatUIScale) - .alpha(chatUIOpacity), - color = MaterialTheme.colorScheme.background - ) { - if (isHealthAlertActive) { - healthNotice?.let { - HealthAlert(notice = healthNotice, onDismiss = onDismissHealthAlert) - } - } - - if (couldNotLogIn) { - AlertDialog( - onDismissRequest = { - // no-op - }, - title = { - Text(stringResource(R.string.could_not_log_in_heading)) - }, - text = { - Text(stringResource(R.string.could_not_log_in_body)) - }, - confirmButton = { - TextButton( - onClick = { - onDismissLoginError() - onRetryConnection() - } - ) { - Text(stringResource(R.string.could_not_log_in_cta_try_again)) - } - }, - dismissButton = { - TextButton( - onClick = { - onDismissLoginError() - onLogout() - } - ) { - Text(stringResource(R.string.could_not_log_in_cta_logout)) - } - } - ) - } - - NavHost( - navController = navController, - startDestination = "default", - enterTransition = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = NavTweenInt, - initialOffset = { it / 3 } - ) + fadeIn(animationSpec = NavTweenFloat) - }, - exitTransition = { - slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = NavTweenInt, - targetOffset = { it / 3 } - ) + fadeOut(animationSpec = NavTweenFloat) - }, - popEnterTransition = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = NavTweenInt, - initialOffset = { it / 3 } - ) + fadeIn(animationSpec = NavTweenFloat) - }, - popExitTransition = { - slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = NavTweenInt, - targetOffset = { it / 2 } - ) + fadeOut(animationSpec = NavTweenFloat) - } - ) { - composable("default") { - DefaultDestinationScreen( - navController, - nextDestination, - isConnected, - onRetryConnection - ) - } - - composable("choose-platform") { ChoosePlatformScreen(navController) } - composable("login/greeting") { LoginGreetingScreen(navController) } - composable("login/login") { LoginScreen(navController) } - composable("login/mfa/{mfaTicket}/{allowedAuthTypes}") { backStackEntry -> - val mfaTicket = backStackEntry.arguments?.getString("mfaTicket") ?: "" - val allowedAuthTypes = - backStackEntry.arguments?.getString("allowedAuthTypes") ?: "" - - MfaScreen(navController, allowedAuthTypes, mfaTicket) - } - - composable("register/greeting") { RegisterGreetingScreen(navController) } - composable("register/details") { RegisterDetailsScreen(navController) } - composable("register/verify/{email}") { backStackEntry -> - val email = backStackEntry.arguments?.getString("email") ?: "" - - RegisterVerifyScreen(navController, email) - } - composable("register/onboarding") { - OnboardingScreen( - navController, - onOnboardingComplete = { - onUpdateNextDestination("chat") - navController.popBackStack( - navController.graph.startDestinationRoute!!, - inclusive = true - ) - navController.navigate("default") - } - ) - } - - composable("login2/init") { InitScreen(navController, windowSizeClass) } - - // This is only used outside of Polar mode - // Otherwise you may be looking for "main" right below - composable( - "chat", - enterTransition = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Up, - animationSpec = tween( - 400, - easing = EasingTokens.EmphasizedDecelerate - ), - initialOffset = { it / 3 } - ) + fadeIn(animationSpec = PeptideTweenFloat) - } - ) { - ChatRouterScreen( - navController, - windowSizeClass, - onNullifiedUser = { - onRetryConnection() - navController.popBackStack( - navController.graph.startDestinationRoute!!, - inclusive = true - ) - navController.navigate("default") - }, - onEnterVoiceUI = { channelId -> - showVoiceUI = true - voiceChannelId = channelId - }, - ) - } - - // This is only the main screen in Polar mode - // Otherwise you may be looking for "chat" right above - composable( - "main", - enterTransition = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Up, - animationSpec = tween( - 400, - easing = EasingTokens.EmphasizedDecelerate - ), - initialOffset = { it / 3 } - ) + fadeIn(animationSpec = PeptideTweenFloat) + scaleIn( - animationSpec = tween( - 400, - easing = EasingTokens.EmphasizedDecelerate - ), - initialScale = 0.8f, - transformOrigin = TransformOrigin.Center - ) - } - ) { - MainScreen(navController) - } - composable( - "main/conversation/{channelId}", - enterTransition = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - animationSpec = tween( - 600, - easing = EasingTokens.EmphasizedDecelerate - ), - initialOffset = { it } - ) + fadeIn(animationSpec = PeptideTweenFloat) - }, - exitTransition = { - slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - animationSpec = tween( - 600, - easing = EasingTokens.EmphasizedDecelerate - ), - targetOffset = { it } - ) + fadeOut(animationSpec = PeptideTweenFloat) - } - ) { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") ?: "" - ChannelScreen( - channelId = channelId, - useDrawer = false, - useBackButton = true, - backToChannelsScreen = { - navController.navigate("main") - }, - backButtonAction = { - navController.popBackStack() - }, - useChatUI = true - ) - } - - composable("create/group") { CreateGroupScreen(navController) } - - composable("discover") { DiscoverScreen(navController) } - - composable("settings") { SettingsScreen(navController) } - composable("settings/profile") { ProfileSettingsScreen(navController) } - composable("settings/sessions") { SessionSettingsScreen(navController) } - composable("settings/appearance") { AppearanceSettingsScreen(navController) } - composable("settings/chat") { ChatSettingsScreen(navController) } - composable("settings/debug") { DebugSettingsScreen(navController) } - composable("settings/experiments") { ExperimentsSettingsScreen(navController) } - composable("settings/changelogs") { ChangelogsSettingsScreen(navController) } - composable("settings/language") { LanguagePickerSettingsScreen(navController) } - - composable("settings/channel/{channelId}") { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") ?: "" - ChannelSettingsHome(navController, channelId) - } - composable("settings/channel/{channelId}/overview") { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") ?: "" - ChannelSettingsOverview(navController, channelId) - } - composable("settings/channel/{channelId}/permissions") { backStackEntry -> - val channelId = backStackEntry.arguments?.getString("channelId") ?: "" - ChannelSettingsPermissions(navController, channelId) - } - - composable("about") { AboutScreen(navController) } - composable("about/oss") { AttributionScreen(navController) } - - composable("labs") { LabsRootScreen(navController) } - } - } - - if (showVoiceUI) { // if tapped outside the voice UI, close it - Box( - Modifier - .fillMaxSize() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - showVoiceUI = false - } - ) - } - - AnimatedVisibility( - visible = showVoiceUI, - modifier = Modifier.align(Alignment.BottomCenter), - enter = slideInVertically( - initialOffsetY = { it -> it }, - animationSpec = tween( - durationMillis = 300, - easing = EasingTokens.EmphasizedDecelerate - ) - ), - exit = slideOutVertically( - targetOffsetY = { it -> it }, - animationSpec = tween( - durationMillis = 300, - easing = EasingTokens.EmphasizedDecelerate - ) - ) - ) { - // We need a box as applying the padding elsewhere leads to either - // janky animation or layout - Box(Modifier.safeDrawingPadding()) { - Card( - Modifier - .fillMaxWidth() - .widthIn(max = 600.dp) - .padding(8.dp) - ) { - VoicePermissionSwitch( - onCancel = { - showVoiceUI = false - } - ) { - voiceChannelId?.let { - VoiceSheet( - it, - onDisconnect = { - showVoiceUI = false - voiceChannelId = null - } - ) - } - } - } - } - } - } - } -} + + + // Set the default platform for the Platform API. + viewModel.setDefaultPlatformOnCreate() + + SentryAndroid.init(this) { options -> + options.dsn = BuildConfig.SENTRY_DSN + options.release = BuildConfig.VERSION_NAME + } + + @Suppress("DEPRECATION") // We are fixing a bug in the splash screen + window.statusBarColor = Color.Transparent.toArgb() + WindowCompat.setDecorFitsSystemWindows(window, false) + + PeptideAPI.hydrateFromPersistentCache() + + // Handle deep link data if available + handleDeepLinkData(intent) + + setContent { + val windowSizeClass = calculateWindowSizeClass(this) + AppEntrypoint( + windowSizeClass, + viewModel.nextDestination.collectAsState().value, + viewModel.isConnected.collectAsState().value, + viewModel.activeAlert.collectAsState().value, + viewModel.isAlertActive.collectAsState().value, + viewModel.couldNotLogIn.collectAsState().value, + viewModel::logOut, + viewModel::onDismissHealthAlert, + viewModel::onDismissLoginError, + viewModel::checkLoggedInState, + viewModel::updateNextDestination + ) + } + + val content: View = findViewById(android.R.id.content) + content.viewTreeObserver.addOnPreDrawListener( + object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + // Check whether the initial data is ready. + return if (viewModel.isReady.value) { + // The content is ready. Start drawing. + content.viewTreeObserver.removeOnPreDrawListener(this) + true + } else { + // The content isn't ready. Suspend. + false + } + } + } + ) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleDeepLinkData(intent) + } + + private fun handleDeepLinkData(intent: Intent) { + // Check for deep link data from DeepLinkActivity + val channelId = intent.getStringExtra(DeepLinkActivity.PATH_CHANNEL) + val serverId = intent.getStringExtra(DeepLinkActivity.PATH_SERVER) + val userId = intent.getStringExtra(DeepLinkActivity.PATH_USER) + val messageId = intent.getStringExtra(DeepLinkActivity.PATH_MESSAGE) + val nextDestination = intent.getStringExtra("next_destination") + + if (nextDestination != null) { + Log.d(TAG, "Setting next destination from deep link: $nextDestination") + viewModel.updateNextDestination(nextDestination) + return + } + + // Store deep link data in a static companion object for ChatRouterScreen to access + DeepLinkHandler.channelId = channelId + DeepLinkHandler.serverId = serverId + DeepLinkHandler.userId = userId + DeepLinkHandler.messageId = messageId + DeepLinkHandler.hasDeepLink = channelId != null || serverId != null || userId != null || messageId != null + + // Wait for the app to be ready before navigating to the deep link destination + viewModel.viewModelScope.launch { + viewModel.isReady.collect { isReady -> + if (isReady) { + when { + channelId != null -> { + Log.d(TAG, "Navigating to channel from deep link: $channelId") + // Check if Polar mode is enabled + if (Experiments.usePolar.isEnabled) { + viewModel.updateNextDestination("main/conversation/$channelId") + } else { + // For non-Polar mode, we'll use the DeepLinkHandler to communicate with ChatRouterScreen + viewModel.updateNextDestination("chat") + } + } + serverId != null -> { + Log.d(TAG, "Navigating to server from deep link: $serverId") + // Navigate to chat screen, the ChatRouterScreen will handle the server navigation + viewModel.updateNextDestination("chat") + } + userId != null -> { + Log.d(TAG, "Navigating to user from deep link: $userId") + // Navigate to chat screen, the ChatRouterScreen will handle the user navigation + viewModel.updateNextDestination("chat") + } + messageId != null -> { + Log.d(TAG, "Navigating to message from deep link: $messageId") + // Navigate to chat screen, the ChatRouterScreen will handle the message navigation + viewModel.updateNextDestination("chat") + } + } + // Only collect once + return@collect + } + } + } + } + + override fun onProvideKeyboardShortcuts( + data: MutableList?, + menu: Menu?, + deviceId: Int + ) { + val messaging = KeyboardShortcutGroup( + getString(R.string.keyboard_shortcut_messaging), + listOf( + KeyboardShortcutInfo( + getString(R.string.keyboard_shortcut_messaging_new_line), + KeyEvent.KEYCODE_ENTER, + 0 + ), + KeyboardShortcutInfo( + getString(R.string.keyboard_shortcut_messaging_send_message), + KeyEvent.KEYCODE_ENTER, + KeyEvent.META_CTRL_ON + ) + ) + ) + + data?.add(messaging) + } + + companion object { + private const val TAG = "MainActivity" + init { + NativeLibraries.init() + } + } +} + +val PeptideTweenInt: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) +val PeptideTweenFloat: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) +val PeptideTweenDp: FiniteAnimationSpec = tween(400, easing = EaseInOutExpo) + +val NavTweenInt: FiniteAnimationSpec = tween(350, easing = EaseInOutExpo) +val NavTweenFloat: FiniteAnimationSpec = tween(350, easing = EaseInOutExpo) + +// This composable handles the main compose entrypoint of the app, provides the main navigation +// graph, and handles the animation and layout for the voice chat UI. +@Composable +fun AppEntrypoint( + windowSizeClass: WindowSizeClass, + nextDestination: String?, + isConnected: Boolean, + healthNotice: HealthNotice?, + isHealthAlertActive: Boolean, + couldNotLogIn: Boolean, + onLogout: () -> Unit = {}, + onDismissHealthAlert: () -> Unit = {}, + onDismissLoginError: () -> Unit = {}, + onRetryConnection: () -> Unit, + onUpdateNextDestination: (String) -> Unit = {} +) { + var showVoiceUI by rememberSaveable { mutableStateOf(false) } + var voiceChannelId by rememberSaveable { mutableStateOf(null) } + + val chatUIScale by animateFloatAsState( + if (showVoiceUI) 0.8f else 1.0f, + animationSpec = tween( + durationMillis = 300, + easing = EasingTokens.EmphasizedDecelerate + ) + ) + val chatUIOpacity by animateFloatAsState( + if (showVoiceUI) 0.8f else 1.0f, + animationSpec = tween( + durationMillis = 300, + easing = EasingTokens.EmphasizedDecelerate + ) + ) + + BackHandler(showVoiceUI) { + showVoiceUI = false + } + + val keyboardController = LocalSoftwareKeyboardController.current + LaunchedEffect(showVoiceUI) { + if (showVoiceUI) keyboardController?.hide() + } + + val navController = rememberNavController() + + PeptideTheme( + requestedTheme = LoadedSettings.theme, + colourOverrides = SyncedSettings.android.colourOverrides, + ) { + Box( + Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.surfaceContainerLowest) + ) { + Surface( + modifier = Modifier + .fillMaxSize() + .scale(chatUIScale) + .alpha(chatUIOpacity), + color = MaterialTheme.colorScheme.background + ) { + if (isHealthAlertActive) { + healthNotice?.let { + HealthAlert(notice = healthNotice, onDismiss = onDismissHealthAlert) + } + } + + if (couldNotLogIn) { + AlertDialog( + onDismissRequest = { + // no-op + }, + title = { + Text(stringResource(R.string.could_not_log_in_heading)) + }, + text = { + Text(stringResource(R.string.could_not_log_in_body)) + }, + confirmButton = { + PepTextButton( + onClick = { + onDismissLoginError() + onRetryConnection() + } + ) { + Text(stringResource(R.string.could_not_log_in_cta_try_again)) + } + }, + dismissButton = { + PepTextButton( + onClick = { + onDismissLoginError() + onLogout() + } + ) { + Text(stringResource(R.string.could_not_log_in_cta_logout)) + } + } + ) + } + + NavHost( + navController = navController, + startDestination = "default", + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = NavTweenInt, + initialOffset = { it / 3 } + ) + fadeIn(animationSpec = NavTweenFloat) + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = NavTweenInt, + targetOffset = { it / 3 } + ) + fadeOut(animationSpec = NavTweenFloat) + }, + popEnterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = NavTweenInt, + initialOffset = { it / 3 } + ) + fadeIn(animationSpec = NavTweenFloat) + }, + popExitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = NavTweenInt, + targetOffset = { it / 2 } + ) + fadeOut(animationSpec = NavTweenFloat) + } + ) { + composable("default") { + DefaultDestinationScreen( + navController, + nextDestination, + isConnected, + onRetryConnection + ) + } + + composable("choose-platform") { ChoosePlatformScreen(navController) } + composable("login/greeting") { LoginGreetingScreen(navController) } + composable("login/login") { LoginScreen(navController) } + composable("login/mfa/{mfaTicket}/{allowedAuthTypes}") { backStackEntry -> + val mfaTicket = backStackEntry.arguments?.getString("mfaTicket") ?: "" + val allowedAuthTypes = + backStackEntry.arguments?.getString("allowedAuthTypes") ?: "" + + MfaScreen(navController, allowedAuthTypes, mfaTicket) + } + + composable("register/greeting") { RegisterGreetingScreen(navController) } + composable("register/details") { RegisterDetailsScreen(navController) } + composable("register/verify/{email}") { backStackEntry -> + val email = backStackEntry.arguments?.getString("email") ?: "" + + RegisterVerifyScreen(navController, email) + } + composable("register/onboarding") { + OnboardingScreen( + navController, + onOnboardingComplete = { + onUpdateNextDestination("chat") + navController.popBackStack( + navController.graph.startDestinationRoute!!, + inclusive = true + ) + navController.navigate("default") + } + ) + } + + composable("login2/init") { InitScreen(navController, windowSizeClass) } + + // This is only used outside of Polar mode + // Otherwise you may be looking for "main" right below + composable( + "chat", + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + animationSpec = tween( + 400, + easing = EasingTokens.EmphasizedDecelerate + ), + initialOffset = { it / 3 } + ) + fadeIn(animationSpec = PeptideTweenFloat) + } + ) { + ChatRouterScreen( + navController, + windowSizeClass, + onNullifiedUser = { + onRetryConnection() + navController.popBackStack( + navController.graph.startDestinationRoute!!, + inclusive = true + ) + navController.navigate("default") + }, + onEnterVoiceUI = { channelId -> + showVoiceUI = true + voiceChannelId = channelId + }, + ) + } + + // This is only the main screen in Polar mode + // Otherwise you may be looking for "chat" right above + composable( + "main", + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + animationSpec = tween( + 400, + easing = EasingTokens.EmphasizedDecelerate + ), + initialOffset = { it / 3 } + ) + fadeIn(animationSpec = PeptideTweenFloat) + scaleIn( + animationSpec = tween( + 400, + easing = EasingTokens.EmphasizedDecelerate + ), + initialScale = 0.8f, + transformOrigin = TransformOrigin.Center + ) + } + ) { + MainScreen(navController) + } + composable( + "main/conversation/{channelId}", + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + animationSpec = tween( + 600, + easing = EasingTokens.EmphasizedDecelerate + ), + initialOffset = { it } + ) + fadeIn(animationSpec = PeptideTweenFloat) + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + animationSpec = tween( + 600, + easing = EasingTokens.EmphasizedDecelerate + ), + targetOffset = { it } + ) + fadeOut(animationSpec = PeptideTweenFloat) + } + ) { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") ?: "" + ChannelScreen( + channelId = channelId, + useDrawer = false, + useBackButton = true, + backToChannelsScreen = { + navController.navigate("main") + }, + backButtonAction = { + navController.popBackStack() + }, + useChatUI = true + ) + } + + composable("create/group") { CreateGroupScreen(navController) } + + composable("discover") { DiscoverScreen(navController) } + + composable("settings") { SettingsScreen(navController) } + composable("settings/profile") { ProfileSettingsScreen(navController) } + composable("settings/sessions") { SessionSettingsScreen(navController) } + composable("settings/appearance") { AppearanceSettingsScreen(navController) } + composable("settings/chat") { ChatSettingsScreen(navController) } + composable("settings/debug") { DebugSettingsScreen(navController) } + composable("settings/experiments") { ExperimentsSettingsScreen(navController) } + composable("settings/changelogs") { ChangelogsSettingsScreen(navController) } + composable("settings/language") { LanguagePickerSettingsScreen(navController) } + + composable("settings/channel/{channelId}") { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") ?: "" + ChannelSettingsHome(navController, channelId) + } + composable("settings/channel/{channelId}/overview") { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") ?: "" + ChannelSettingsOverview(navController, channelId) + } + composable("settings/channel/{channelId}/permissions") { backStackEntry -> + val channelId = backStackEntry.arguments?.getString("channelId") ?: "" + ChannelSettingsPermissions(navController, channelId) + } + + composable("about") { AboutScreen(navController) } + composable("about/oss") { AttributionScreen(navController) } + + composable("labs") { LabsRootScreen(navController) } + } + } + + if (showVoiceUI) { // if tapped outside the voice UI, close it + Box( + Modifier + .fillMaxSize() + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + showVoiceUI = false + } + ) + } + + AnimatedVisibility( + visible = showVoiceUI, + modifier = Modifier.align(Alignment.BottomCenter), + enter = slideInVertically( + initialOffsetY = { it -> it }, + animationSpec = tween( + durationMillis = 300, + easing = EasingTokens.EmphasizedDecelerate + ) + ), + exit = slideOutVertically( + targetOffsetY = { it -> it }, + animationSpec = tween( + durationMillis = 300, + easing = EasingTokens.EmphasizedDecelerate + ) + ) + ) { + // We need a box as applying the padding elsewhere leads to either + // janky animation or layout + Box(Modifier.safeDrawingPadding()) { + Card( + Modifier + .fillMaxWidth() + .widthIn(max = 600.dp) + .padding(8.dp) + ) { + VoicePermissionSwitch( + onCancel = { + showVoiceUI = false + } + ) { + voiceChannelId?.let { + VoiceSheet( + it, + onDisconnect = { + showVoiceUI = false + voiceChannelId = null + } + ) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/chat/peptide/composables/chat/Reaction.kt b/app/src/main/java/chat/peptide/composables/chat/Reaction.kt index de4f8a96..f9a45b1e 100644 --- a/app/src/main/java/chat/peptide/composables/chat/Reaction.kt +++ b/app/src/main/java/chat/peptide/composables/chat/Reaction.kt @@ -44,7 +44,7 @@ fun Reaction( val background by animateColorAsState( targetValue = if (hasOwn) { - MaterialTheme.colorScheme.primary + MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.surfaceContainer }, @@ -52,7 +52,7 @@ fun Reaction( ) val foreground by animateColorAsState( targetValue = if (hasOwn) { - MaterialTheme.colorScheme.onPrimary + MaterialTheme.colorScheme.onSecondaryContainer } else { MaterialTheme.colorScheme.onSurface }, diff --git a/app/src/main/java/chat/peptide/composables/chat/TimeRift.kt b/app/src/main/java/chat/peptide/composables/chat/TimeRift.kt index 43371743..b1d1844b 100644 --- a/app/src/main/java/chat/peptide/composables/chat/TimeRift.kt +++ b/app/src/main/java/chat/peptide/composables/chat/TimeRift.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton @Composable fun TimeRift(modifier: Modifier = Modifier, onMessageLoad: () -> Unit) { @@ -48,7 +49,7 @@ fun TimeRift(modifier: Modifier = Modifier, onMessageLoad: () -> Unit) { Spacer(modifier = Modifier.height(5.dp)) - TextButton(onClick = onMessageLoad) { + PepTextButton(onClick = onMessageLoad) { Text( text = stringResource(id = R.string.time_rift_cta), color = LocalContentColor.current.copy( diff --git a/app/src/main/java/chat/peptide/composables/generic/FormTextField.kt b/app/src/main/java/chat/peptide/composables/generic/FormTextField.kt index f5641c75..d869c0f7 100644 --- a/app/src/main/java/chat/peptide/composables/generic/FormTextField.kt +++ b/app/src/main/java/chat/peptide/composables/generic/FormTextField.kt @@ -3,6 +3,7 @@ package chat.peptide.composables.generic import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.ImeAction diff --git a/app/src/main/java/chat/peptide/composables/generic/HealthAlert.kt b/app/src/main/java/chat/peptide/composables/generic/HealthAlert.kt index e864cc47..1c5a41a7 100644 --- a/app/src/main/java/chat/peptide/composables/generic/HealthAlert.kt +++ b/app/src/main/java/chat/peptide/composables/generic/HealthAlert.kt @@ -32,7 +32,7 @@ fun HealthAlert(notice: HealthNotice, onDismiss: () -> Unit) { confirmButton = { notice.alert?.actions?.map { action -> when (action.type) { - "external" -> TextButton( + "external" -> PepTextButton( onClick = { val customTab = CustomTabsIntent.Builder() .setShowTitle(true) @@ -42,7 +42,7 @@ fun HealthAlert(notice: HealthNotice, onDismiss: () -> Unit) { .build() ) .build() - customTab.launchUrl(context, action.href?.toUri() ?: return@TextButton) + customTab.launchUrl(context, action.href?.toUri() ?: return@PepTextButton) } ) { Text( @@ -52,7 +52,7 @@ fun HealthAlert(notice: HealthNotice, onDismiss: () -> Unit) { } } } - TextButton(onClick = onDismiss) { + PepTextButton(onClick = onDismiss) { Text(stringResource(R.string.service_health_alert_actions_dismiss)) } } diff --git a/app/src/main/java/chat/peptide/composables/generic/InlineMediaPicker.kt b/app/src/main/java/chat/peptide/composables/generic/InlineMediaPicker.kt index f26205f9..9639b72f 100644 --- a/app/src/main/java/chat/peptide/composables/generic/InlineMediaPicker.kt +++ b/app/src/main/java/chat/peptide/composables/generic/InlineMediaPicker.kt @@ -86,7 +86,7 @@ fun InlineMediaPicker( if (canRemove) { Spacer(modifier = Modifier.height(8.dp)) - TextButton( + PepTextButton( onClick = { onRemove() }, diff --git a/app/src/main/java/chat/peptide/composables/generic/NonIdealState.kt b/app/src/main/java/chat/peptide/composables/generic/NonIdealState.kt index b647341d..6d48cab9 100644 --- a/app/src/main/java/chat/peptide/composables/generic/NonIdealState.kt +++ b/app/src/main/java/chat/peptide/composables/generic/NonIdealState.kt @@ -95,7 +95,7 @@ fun NonIdealStatePreview() { title = { Text("Channel") }, description = { Text("You are not in any channels.") }, actions = { - Button(onClick = {}) { + SquareButton(onClick = {}) { Text("Create a channel") } } diff --git a/app/src/main/java/chat/peptide/composables/generic/SquareButton.kt b/app/src/main/java/chat/peptide/composables/generic/PepButtons.kt similarity index 52% rename from app/src/main/java/chat/peptide/composables/generic/SquareButton.kt rename to app/src/main/java/chat/peptide/composables/generic/PepButtons.kt index b357832c..5a3f33a9 100644 --- a/app/src/main/java/chat/peptide/composables/generic/SquareButton.kt +++ b/app/src/main/java/chat/peptide/composables/generic/PepButtons.kt @@ -2,8 +2,11 @@ package chat.peptide.composables.generic import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -13,11 +16,16 @@ fun SquareButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + colors: ButtonColors = ButtonDefaults.buttonColors().copy( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), content: @Composable () -> Unit ) { Button( onClick = onClick, modifier = modifier, + colors = colors, enabled = enabled, shape = MaterialTheme.shapes.small.copy( topStart = CornerSize(8.dp), @@ -30,6 +38,31 @@ fun SquareButton( } } +@Composable +fun PepTextButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + content: @Composable () -> Unit +) { + TextButton( + onClick = onClick, + modifier = modifier, + colors = ButtonDefaults.textButtonColors().copy( + contentColor = MaterialTheme.colorScheme.onBackground, + ), + shape = MaterialTheme.shapes.small.copy( + topStart = CornerSize(8.dp), + topEnd = CornerSize(8.dp), + bottomStart = CornerSize(8.dp), + bottomEnd = CornerSize(8.dp) + ), + enabled = enabled, + ) { + content() + } +} + @Composable fun SquareElevatedButton( onClick: () -> Unit, @@ -39,6 +72,10 @@ fun SquareElevatedButton( ElevatedButton( onClick = onClick, modifier = modifier, + colors = ButtonDefaults.buttonColors().copy( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, + ), shape = MaterialTheme.shapes.small.copy( topStart = CornerSize(8.dp), topEnd = CornerSize(8.dp), diff --git a/app/src/main/java/chat/peptide/composables/generic/SheetButton.kt b/app/src/main/java/chat/peptide/composables/generic/SheetButton.kt index 7d1a7b9b..cf8ea564 100644 --- a/app/src/main/java/chat/peptide/composables/generic/SheetButton.kt +++ b/app/src/main/java/chat/peptide/composables/generic/SheetButton.kt @@ -1,14 +1,20 @@ package chat.peptide.composables.generic +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material3.ListItem +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.internals.extensions.TransparentListItemColours @@ -88,4 +94,75 @@ fun SheetButton( } ) } -} \ No newline at end of file +} + +@Preview +@Composable +private fun SheetButtonPreview() { + MaterialTheme { + SheetButton( + headlineContent = { Text("Headline") }, + leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.surface) + ) + } +} + +@Preview +@Composable +private fun SheetButtonWithSupportingContentPreview() { + MaterialTheme { + SheetButton( + headlineContent = { Text("Headline") }, + leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }, + supportingContent = { Text("Supporting content") }, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.surface) + ) + } +} + +@Preview +@Composable +private fun SheetButtonWithTrailingContentPreview() { + MaterialTheme { + SheetButton( + headlineContent = { Text("Headline") }, + leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }, + trailingContent = { Text("Trailing") }, + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.surface) + ) + } +} + +@Preview +@Composable +private fun DangerousSheetButtonPreview() { + MaterialTheme { + SheetButton( + headlineContent = { Text("Headline") }, + leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }, + onClick = {}, + dangerous = true, + modifier = Modifier.background(MaterialTheme.colorScheme.surface) + ) + } +} + +@Preview +@Composable +private fun SpecialSheetButtonPreview() { + MaterialTheme { + SheetButton( + headlineContent = { Text("Headline") }, + leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }, + onClick = {}, + special = true, + modifier = Modifier.background(MaterialTheme.colorScheme.surface) + ) + } +} + + diff --git a/app/src/main/java/chat/peptide/composables/markdown/MarkdownText.kt b/app/src/main/java/chat/peptide/composables/markdown/MarkdownText.kt index a08472cb..a35df96a 100644 --- a/app/src/main/java/chat/peptide/composables/markdown/MarkdownText.kt +++ b/app/src/main/java/chat/peptide/composables/markdown/MarkdownText.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale @@ -200,7 +201,7 @@ fun annotateText(node: AstNode): AnnotatedString { pushStyle( LocalTextStyle.current.toSpanStyle() .copy( - color = MaterialTheme.colorScheme.secondary + color = Color(0xff4775bf) ) ) append(url.value) diff --git a/app/src/main/java/chat/peptide/composables/screens/chat/AttachmentManager.kt b/app/src/main/java/chat/peptide/composables/screens/chat/AttachmentManager.kt index f7eb7015..b11f84ea 100644 --- a/app/src/main/java/chat/peptide/composables/screens/chat/AttachmentManager.kt +++ b/app/src/main/java/chat/peptide/composables/screens/chat/AttachmentManager.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator @@ -30,7 +29,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Switch import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -52,7 +50,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R import chat.peptide.api.routes.microservices.autumn.FileArgs +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.RemoteImage +import chat.peptide.composables.generic.SquareButton import kotlinx.coroutines.launch import java.io.File @@ -125,7 +125,7 @@ fun FilePreviewSheet( } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Button(onClick = { + SquareButton(onClick = { onDismiss() }, modifier = Modifier.weight(1f)) { Icon(Icons.Default.Close, contentDescription = null) @@ -133,7 +133,7 @@ fun FilePreviewSheet( Text(stringResource(R.string.attachment_preview_close)) } if (canRemove) { - TextButton(onClick = { + PepTextButton(onClick = { onRemove() }, modifier = Modifier.weight(1f)) { Icon( diff --git a/app/src/main/java/chat/peptide/composables/screens/chat/discover/DiscoverServersList.kt b/app/src/main/java/chat/peptide/composables/screens/chat/discover/DiscoverServersList.kt index 702bff94..1fc58149 100644 --- a/app/src/main/java/chat/peptide/composables/screens/chat/discover/DiscoverServersList.kt +++ b/app/src/main/java/chat/peptide/composables/screens/chat/discover/DiscoverServersList.kt @@ -105,7 +105,6 @@ fun DiscoverServersList( CircularProgressIndicator( modifier = Modifier .padding(16.dp), - color = MaterialTheme.colorScheme.secondary ) } } @@ -238,7 +237,7 @@ fun ServerItem( Text( text = server.description, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onBackground.copy( alpha = descriptionAlpha, ), @@ -248,46 +247,43 @@ fun ServerItem( } Spacer(modifier = Modifier.height(12.dp)) - if (isProcessing) { - CircularProgressIndicator( + IconButton( + onClick = onClick, + ) { + Box( modifier = Modifier - .size(24.dp), - strokeWidth = 2.dp - ) - } else { - IconButton( - onClick = onClick, + .fillMaxSize() ) { - Box( - modifier = Modifier - .fillMaxSize() - ) { - if (isJoined) { - Box( - modifier = Modifier - .size(20.dp) - .clip(CircleShape) - .background(Color(0xFF43bf83)) - .align(Alignment.Center), - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.size(16.dp), - painter = painterResource(R.drawable.icn_check_24dp), - contentDescription = "Already joined", - colorFilter = ColorFilter.tint(Color.White) - ) - - } - } else { - // Show arrow icon for servers not joined yet + when { + isProcessing -> CircularProgressIndicator( + modifier = Modifier.size(20.dp) + .align(Alignment.Center), + strokeWidth = 2.dp + ) + isJoined -> Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(Color(0xFF43bf83)) + .align(Alignment.Center), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.size(16.dp), + painter = painterResource(R.drawable.icn_check_24dp), + contentDescription = "Already joined", + colorFilter = ColorFilter.tint(Color.White) + ) + } + else -> { Image( painter = painterResource(R.drawable.icn_arrow_forward_24dp), contentDescription = "Join server", - modifier = Modifier - .align(Alignment.Center), + modifier = Modifier.align(Alignment.Center), colorFilter = ColorFilter.tint( - color = MaterialTheme.colorScheme.onBackground.copy(alpha = contentAlpha) + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = contentAlpha + ) ) ) } @@ -354,4 +350,22 @@ private fun JoinedServerItemPreview() { isJoined = true, isProcessing = false ) +} +@Preview +@Composable +private fun JoinedServerItemLoadingPreview() { + val server = ServerData( + id = "1", + name = "Peptide Lounge", + description = "The official Peptide server. Hang out, chat, and get help with Peptide.", + inviteCode = "peptide", + disabled = false, + showColor = null, + ) + ServerItem( + server = server, + onClick = {}, + isJoined = true, + isProcessing = true + ) } \ No newline at end of file diff --git a/app/src/main/java/chat/peptide/composables/screens/chat/discover/ServerInviteDialog.kt b/app/src/main/java/chat/peptide/composables/screens/chat/discover/ServerInviteDialog.kt index ec43e932..f326209d 100644 --- a/app/src/main/java/chat/peptide/composables/screens/chat/discover/ServerInviteDialog.kt +++ b/app/src/main/java/chat/peptide/composables/screens/chat/discover/ServerInviteDialog.kt @@ -35,7 +35,9 @@ import chat.peptide.api.PeptideAPI import chat.peptide.api.PeptideError import chat.peptide.api.schemas.Invite import chat.peptide.composables.generic.IconPlaceholder +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.RemoteImage +import chat.peptide.composables.generic.SquareButton @Composable fun ServerInviteDialog( @@ -74,7 +76,7 @@ fun ServerInviteDialog( modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(32.dp)) - Button( + SquareButton( onClick = onDismiss, modifier = Modifier.fillMaxWidth(), ) { @@ -100,7 +102,7 @@ fun ServerInviteDialog( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom // Align buttons to the bottom ) { - Button( + SquareButton( onClick = onJoinClick, modifier = Modifier.fillMaxWidth(), enabled = !isLoading @@ -121,7 +123,7 @@ fun ServerInviteDialog( Text(text = stringResource(id = R.string.invite_join)) } } - TextButton( + PepTextButton( onClick = onDismiss, modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt index b616ecdb..77c20e4c 100644 --- a/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt +++ b/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt @@ -208,13 +208,19 @@ fun ChannelSideDrawer( onDestinationChanged(ChatRouterDestination.Home) } .size(48.dp) - .background(MaterialTheme.colorScheme.primaryContainer), + .background( + color = if (currentDestination is ChatRouterDestination.Home) { + MaterialTheme.colorScheme.secondaryContainer + } else MaterialTheme.colorScheme.primaryContainer + ), contentAlignment = Alignment.Center ) { Icon( painter = painterResource(R.drawable.ic_direct_messages), contentDescription = stringResource(R.string.discover_alt), - tint = MaterialTheme.colorScheme.onPrimaryContainer + tint = if (currentDestination is ChatRouterDestination.Home) { + MaterialTheme.colorScheme.onSecondaryContainer + } else MaterialTheme.colorScheme.onBackground ) } } @@ -292,20 +298,32 @@ fun ChannelSideDrawer( Box( Modifier .padding(8.dp) - .clip(RoundedCornerShape(16.dp)) + .clip(animateDpAsState( + targetValue = if (currentDestination is ChatRouterDestination.Discover) 16.dp else 24.dp, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + label = "Discover button corner radius" + ).value.let { RoundedCornerShape(it) }) .clickable { onDestinationChanged( ChatRouterDestination.Discover ) } .size(48.dp) - .background(MaterialTheme.colorScheme.secondaryContainer), + .background(animateColorAsState( + targetValue = if (currentDestination is ChatRouterDestination.Discover) { + MaterialTheme.colorScheme.secondaryContainer + } else MaterialTheme.colorScheme.primaryContainer, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + label = "Discover button background" + ).value), contentAlignment = Alignment.Center ) { Icon( painter = painterResource(R.drawable.icn_explore_24dp), contentDescription = stringResource(R.string.discover_alt), - tint = MaterialTheme.colorScheme.onSecondaryContainer + tint = if (currentDestination is ChatRouterDestination.Discover) { + MaterialTheme.colorScheme.onSecondaryContainer + } else MaterialTheme.colorScheme.onPrimaryContainer ) } } @@ -319,13 +337,13 @@ fun ChannelSideDrawer( onShowAddServerSheet() } .size(48.dp) - .background(MaterialTheme.colorScheme.secondaryContainer), + .background(MaterialTheme.colorScheme.primaryContainer), contentAlignment = Alignment.Center ) { Icon( painter = painterResource(R.drawable.icn_add_24dp), contentDescription = stringResource(R.string.server_plus_alt), - tint = MaterialTheme.colorScheme.onSecondaryContainer + tint = MaterialTheme.colorScheme.onPrimaryContainer ) } } @@ -566,14 +584,13 @@ fun ChannelSideDrawer( onClick = { onDestinationChanged(ChatRouterDestination.Friends) }, - containerColor = MaterialTheme.colorScheme.secondary, - contentColor = MaterialTheme.colorScheme.onSecondary, + containerColor = MaterialTheme.colorScheme.primary, shape = CircleShape ) { Icon( painter = painterResource(R.drawable.ic_new_message), contentDescription = stringResource(R.string.message_friends), - tint = MaterialTheme.colorScheme.onSecondary + tint = MaterialTheme.colorScheme.onPrimary ) } } @@ -929,9 +946,7 @@ fun ChannelItem( horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), modifier = Modifier .padding(start = 8.dp, end = 8.dp) - .clip( - CircleShape - ) + .clip(CircleShape) .combinedClickable( onLongClickLabel = stringResource(R.string.channel_context_sheet_open), onLongClick = { diff --git a/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt b/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt index aeaf45d1..23a82b63 100644 --- a/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt +++ b/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -42,6 +41,7 @@ import chat.peptide.api.routes.user.unfriendUser import chat.peptide.api.schemas.User import chat.peptide.callbacks.Action import chat.peptide.callbacks.ActionChannel +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.Platform import kotlinx.coroutines.launch import logcat.LogPriority @@ -61,7 +61,7 @@ fun UserButtons( var menuOpen by remember { mutableStateOf(false) } if (user.id == null) return Row { - Button( + SquareButton( onClick = { scope.launch { try { @@ -88,7 +88,7 @@ fun UserButtons( when (user.relationship) { "None" -> { if (user.bot == null) { - Button( + SquareButton( onClick = { scope.launch { try { @@ -134,7 +134,7 @@ fun UserButtons( } "User" -> { - Button( + SquareButton( onClick = { scope.launch { ActionChannel.send(Action.TopNavigate("settings/profile")) @@ -176,7 +176,7 @@ fun UserButtons( } "Outgoing" -> { - Button( + SquareButton( onClick = { scope.launch { try { @@ -194,7 +194,7 @@ fun UserButtons( } "Incoming" -> { - Button( + SquareButton( onClick = { scope.launch { try { @@ -209,7 +209,7 @@ fun UserButtons( ) { Text(stringResource(R.string.user_info_sheet_accept_request)) } - Button( + SquareButton( onClick = { scope.launch { try { @@ -231,7 +231,7 @@ fun UserButtons( } "Blocked" -> { - Button( + SquareButton( onClick = { scope.launch { try { diff --git a/app/src/main/java/chat/peptide/composables/screens/settings/appearance/CornerRadiusPicker.kt b/app/src/main/java/chat/peptide/composables/screens/settings/appearance/CornerRadiusPicker.kt index 5c89b5a9..5445ec64 100644 --- a/app/src/main/java/chat/peptide/composables/screens/settings/appearance/CornerRadiusPicker.kt +++ b/app/src/main/java/chat/peptide/composables/screens/settings/appearance/CornerRadiusPicker.kt @@ -12,12 +12,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -32,6 +30,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton enum class CornerRadiusPreset(val percentage: Int) { SHARP(0), @@ -84,7 +84,7 @@ fun CornerRadiusPicker(percentage: Int, onUpdate: (Int) -> Unit, modifier: Modif } }, confirmButton = { - Button( + SquareButton( onClick = { showOtherModal = false onUpdate(sliderPosition.toInt()) @@ -94,7 +94,7 @@ fun CornerRadiusPicker(percentage: Int, onUpdate: (Int) -> Unit, modifier: Modif } }, dismissButton = { - TextButton( + PepTextButton( onClick = { showOtherModal = false } diff --git a/app/src/main/java/chat/peptide/composables/screens/splash/DisconnectedScreen.kt b/app/src/main/java/chat/peptide/composables/screens/splash/DisconnectedScreen.kt index e311d859..c846eb62 100644 --- a/app/src/main/java/chat/peptide/composables/screens/splash/DisconnectedScreen.kt +++ b/app/src/main/java/chat/peptide/composables/screens/splash/DisconnectedScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R +import chat.peptide.composables.generic.SquareButton @Composable fun DisconnectedScreen(onRetry: () -> Unit) { @@ -45,7 +46,7 @@ fun DisconnectedScreen(onRetry: () -> Unit) { .fillMaxWidth() ) - Button(onClick = onRetry) { + SquareButton(onClick = onRetry) { Text(stringResource(R.string.tap_to_retry)) } } diff --git a/app/src/main/java/chat/peptide/composables/settings/SettingsHintBanner.kt b/app/src/main/java/chat/peptide/composables/settings/SettingsHintBanner.kt index ce697ded..d9c00b7e 100644 --- a/app/src/main/java/chat/peptide/composables/settings/SettingsHintBanner.kt +++ b/app/src/main/java/chat/peptide/composables/settings/SettingsHintBanner.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import chat.peptide.composables.generic.SquareButton @Composable fun SettingsHintBanner( @@ -72,7 +73,7 @@ private fun SimpleSettingsHintBannerPreview() { ) }, button = { - Button(onClick = {}) { + SquareButton(onClick = {}) { Text("Let's test it out!") } }, diff --git a/app/src/main/java/chat/peptide/composables/voice/VoicePermissionSwitch.kt b/app/src/main/java/chat/peptide/composables/voice/VoicePermissionSwitch.kt index a03def46..18618f63 100644 --- a/app/src/main/java/chat/peptide/composables/voice/VoicePermissionSwitch.kt +++ b/app/src/main/java/chat/peptide/composables/voice/VoicePermissionSwitch.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.net.toUri import chat.peptide.R +import chat.peptide.composables.generic.SquareButton import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.rememberMultiplePermissionsState @@ -116,7 +117,7 @@ fun VoicePermissionSwitch(onCancel: () -> Unit, onPermissionGranted: @Composable color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.fillMaxWidth() ) - Button( + SquareButton( onClick = { if (fullyRevoked) { // Launch settings to allow the user to manually enable permissions diff --git a/app/src/main/java/chat/peptide/composables/voice/VoiceSheet.kt b/app/src/main/java/chat/peptide/composables/voice/VoiceSheet.kt index 4a787eae..d7abb126 100644 --- a/app/src/main/java/chat/peptide/composables/voice/VoiceSheet.kt +++ b/app/src/main/java/chat/peptide/composables/voice/VoiceSheet.kt @@ -102,12 +102,12 @@ fun VoiceSheet( Text("status = ${room.state.name}") } item(key = "controls") { - Button(onClick = { + SquareButton(onClick = { room.setMicrophoneMute(true) }) { Text("mute 🎤🫷😤") } - Button(onClick = { + SquareButton(onClick = { room.setMicrophoneMute(false) }) { Text("unmute 🗣️📢🔥") @@ -117,7 +117,7 @@ fun VoiceSheet( Row( horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Button( + SquareButton( colors = ButtonDefaults.buttonColors().copy( containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer, diff --git a/app/src/main/java/chat/peptide/dialogs/NotificationRationaleDialog.kt b/app/src/main/java/chat/peptide/dialogs/NotificationRationaleDialog.kt index 02580232..14e57127 100644 --- a/app/src/main/java/chat/peptide/dialogs/NotificationRationaleDialog.kt +++ b/app/src/main/java/chat/peptide/dialogs/NotificationRationaleDialog.kt @@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,6 +19,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.peptide.BuildConfig import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton @Composable fun NotificationRationaleDialog( @@ -62,15 +62,17 @@ fun NotificationRationaleDialog( } }, dismissButton = { - TextButton(onClick = { - onSelected(false) - onDismiss() - }) { + PepTextButton( + onClick = { + onSelected(false) + onDismiss() + } + ) { Text(stringResource(id = R.string.spark_notifications_rationale_dismiss)) } }, confirmButton = { - Button(onClick = { + SquareButton(onClick = { onSelected(true) onDismiss() }) { diff --git a/app/src/main/java/chat/peptide/screens/about/AboutScreen.kt b/app/src/main/java/chat/peptide/screens/about/AboutScreen.kt index 4abc7151..aab5f681 100644 --- a/app/src/main/java/chat/peptide/screens/about/AboutScreen.kt +++ b/app/src/main/java/chat/peptide/screens/about/AboutScreen.kt @@ -55,6 +55,7 @@ import chat.peptide.api.PeptideAPI import chat.peptide.api.PeptideJson import chat.peptide.api.routes.misc.Root import chat.peptide.api.routes.misc.getRootRoute +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.PrimaryTabs import chat.peptide.internals.Platform import kotlinx.coroutines.launch @@ -250,7 +251,7 @@ fun AboutScreen(navController: NavController, viewModel: AboutViewModel = viewMo 1 -> { DebugInfo(viewModel) - TextButton(onClick = ::copyDebugInformation) { + PepTextButton(onClick = ::copyDebugInformation) { Text(text = stringResource(id = R.string.copy)) } } diff --git a/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt index fff0a0fd..4d978cb1 100644 --- a/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt @@ -75,6 +75,7 @@ import chat.peptide.api.routes.push.subscribePush import chat.peptide.callbacks.Action import chat.peptide.callbacks.ActionChannel import chat.peptide.composables.chat.DisconnectedNotice +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.screens.chat.drawer.ChannelSideDrawer import chat.peptide.dialogs.NotificationRationaleDialog import chat.peptide.internals.Changelogs @@ -554,7 +555,7 @@ fun ChatRouterScreen( Text(stringResource(id = R.string.notice_platform_mod_dm_description)) }, confirmButton = { - TextButton(onClick = { + PepTextButton(onClick = { showPlatformModDMHint = false DirectMessages.getPlatformModerationDM()?.id?.let { viewModel.setSaveDestination(ChatRouterDestination.Channel(it)) @@ -714,7 +715,7 @@ fun ChatRouterScreen( ) }, confirmButton = { - TextButton(onClick = { + PepTextButton(onClick = { showChannelUnavailableAlert = false }) { Text(text = stringResource(id = R.string.ok)) diff --git a/app/src/main/java/chat/peptide/screens/chat/dialogs/InviteDialog.kt b/app/src/main/java/chat/peptide/screens/chat/dialogs/InviteDialog.kt index fe4dccc0..4bfb5b63 100644 --- a/app/src/main/java/chat/peptide/screens/chat/dialogs/InviteDialog.kt +++ b/app/src/main/java/chat/peptide/screens/chat/dialogs/InviteDialog.kt @@ -44,6 +44,8 @@ import androidx.compose.ui.unit.sp import chat.peptide.R import chat.peptide.api.PeptideAPI import chat.peptide.api.routes.channel.createInvite +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.Platform import chat.peptide.ui.theme.FragmentMono import kotlinx.coroutines.Job @@ -196,13 +198,13 @@ fun InviteDialog(channelId: String, onDismissRequest: () -> Unit) { modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End ) { - TextButton(onClick = { + PepTextButton(onClick = { onDismissRequest() }) { Text(stringResource(R.string.invite_dialog_close)) } Spacer(Modifier.width(8.dp)) - Button(onClick = { + SquareButton(onClick = { clipboardManager.setText(AnnotatedString.Builder().apply { append(PeptideAPI.getCurrentInvitesUrl()) append("/") diff --git a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportMessageDialog.kt b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportMessageDialog.kt index 8b5a691d..91ae448e 100644 --- a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportMessageDialog.kt +++ b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportMessageDialog.kt @@ -44,6 +44,7 @@ import chat.peptide.api.routes.user.blockUser import chat.peptide.api.schemas.ContentReportReason import chat.peptide.composables.chat.Message import chat.peptide.composables.generic.FormTextField +import chat.peptide.composables.generic.PepTextButton import kotlinx.coroutines.launch enum class MessageReportFlowState { @@ -200,7 +201,7 @@ fun ReportMessageDialog(onDismiss: () -> Unit, messageId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -210,7 +211,7 @@ fun ReportMessageDialog(onDismiss: () -> Unit, messageId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { state.value = MessageReportFlowState.Sending }, @@ -305,7 +306,7 @@ fun ReportMessageDialog(onDismiss: () -> Unit, messageId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -315,7 +316,7 @@ fun ReportMessageDialog(onDismiss: () -> Unit, messageId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { scope.launch { blockUser(message.author ?: return@launch) @@ -358,7 +359,7 @@ fun ReportMessageDialog(onDismiss: () -> Unit, messageId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, diff --git a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportServerDialog.kt b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportServerDialog.kt index 3e95cb7d..f044174a 100644 --- a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportServerDialog.kt +++ b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportServerDialog.kt @@ -37,6 +37,7 @@ import chat.peptide.api.PeptideAPI import chat.peptide.api.routes.safety.putServerReport import chat.peptide.api.schemas.ContentReportReason import chat.peptide.composables.generic.FormTextField +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.screens.settings.ServerOverview import kotlinx.coroutines.launch @@ -169,7 +170,7 @@ fun ReportServerDialog(onDismiss: () -> Unit, serverId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -179,7 +180,7 @@ fun ReportServerDialog(onDismiss: () -> Unit, serverId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { state.value = ServerReportFlowState.Sending }, @@ -263,7 +264,7 @@ fun ReportServerDialog(onDismiss: () -> Unit, serverId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -303,7 +304,7 @@ fun ReportServerDialog(onDismiss: () -> Unit, serverId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, diff --git a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportUserDialog.kt b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportUserDialog.kt index 60ffdd84..201850d0 100644 --- a/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportUserDialog.kt +++ b/app/src/main/java/chat/peptide/screens/chat/dialogs/safety/ReportUserDialog.kt @@ -36,6 +36,7 @@ import chat.peptide.api.routes.safety.putUserReport import chat.peptide.api.routes.user.blockUser import chat.peptide.api.schemas.UserReportReason import chat.peptide.composables.generic.FormTextField +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.screens.settings.UserOverview import kotlinx.coroutines.launch @@ -162,7 +163,7 @@ fun ReportUserDialog(onDismiss: () -> Unit, userId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -172,7 +173,7 @@ fun ReportUserDialog(onDismiss: () -> Unit, userId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { state.value = UserReportFlowState.Sending }, @@ -267,7 +268,7 @@ fun ReportUserDialog(onDismiss: () -> Unit, userId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, @@ -277,7 +278,7 @@ fun ReportUserDialog(onDismiss: () -> Unit, userId: String) { } }, confirmButton = { - TextButton( + PepTextButton( onClick = { scope.launch { blockUser(userId) @@ -320,7 +321,7 @@ fun ReportUserDialog(onDismiss: () -> Unit, userId: String) { } }, dismissButton = { - TextButton( + PepTextButton( onClick = { onDismiss() }, diff --git a/app/src/main/java/chat/peptide/screens/chat/views/FriendsScreen.kt b/app/src/main/java/chat/peptide/screens/chat/views/FriendsScreen.kt index 95c7480b..d9bcbd1c 100644 --- a/app/src/main/java/chat/peptide/screens/chat/views/FriendsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/chat/views/FriendsScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -37,7 +36,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.ToggleFloatingActionButton import androidx.compose.material3.ToggleFloatingActionButtonDefaults.animateIcon @@ -92,6 +90,8 @@ import chat.peptide.components.vectorassets.HL_USERNAME import chat.peptide.components.vectorassets.PeptideTagIntro import chat.peptide.composables.chat.MemberListItem import chat.peptide.composables.generic.CountableListHeader +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.generic.UserAvatar import chat.peptide.internals.extensions.zero import chat.peptide.markdown.jbm.asHexString @@ -240,7 +240,7 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerCli verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { - TextButton( + PepTextButton( onClick = { scope.launch { val clipboardText = @@ -279,7 +279,7 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerCli ) { Text(stringResource(R.string.friends_add_by_tag_sheet_paste_from_clipboard)) } - Button( + SquareButton( onClick = { scope.launch { if (username.isNotBlank() && tag.isNotBlank()) { @@ -353,7 +353,7 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerCli textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) - Button( + SquareButton( onClick = { qrResult = null contents = null @@ -413,7 +413,7 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerCli modifier = Modifier.fillMaxWidth() ) - Button( + SquareButton( onClick = { scope.launch { contents?.let { userContents -> @@ -451,7 +451,7 @@ fun FriendsScreen(topNav: NavController, useDrawer: Boolean = false, onDrawerCli textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) - Button( + SquareButton( onClick = { qrResult = null contents = null diff --git a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt index fc4d7806..815481dc 100644 --- a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt @@ -141,7 +141,9 @@ import chat.peptide.composables.chat.MessageField import chat.peptide.composables.chat.SystemMessage import chat.peptide.composables.emoji.EmojiPicker import chat.peptide.composables.generic.GroupIcon +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.PresenceBadge +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.generic.UserAvatar import chat.peptide.composables.generic.UserAvatarWidthPlaceholder import chat.peptide.composables.generic.presenceFromStatus @@ -337,6 +339,13 @@ fun ChannelScreen( scope.launch { try { val cameraProvider = cameraProviderFuture.get() + // Ensure there's a camera available to bind to + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + if (!cameraProvider.hasCamera(cameraSelector)) { + Toast.makeText(context, "No camera available", Toast.LENGTH_SHORT).show() + Log.e("ChannelScreen", "No camera available to bind") + return@launch + } cameraProvider.unbindAll() // Unbind previous use cases val name = "peptide-photo-${System.currentTimeMillis()}.jpg" @@ -353,6 +362,14 @@ fun ChannelScreen( ) .build() + // Bind use cases before taking picture + val preview = CameraPreview.Builder().build() // Dummy preview, not shown + cameraProvider.bindToLifecycle( + lifecycleOwner, + cameraSelector, + preview, // Bind a preview as it's often required + imageCapture + ) imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(context), @@ -1091,7 +1108,7 @@ fun ChannelScreen( Row( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - Button( + SquareButton( onClick = { viewModel.dismissPhysicalKeyboardSpark() }, @@ -1099,7 +1116,7 @@ fun ChannelScreen( ) { Text(stringResource(R.string.spark_keyboard_shortcuts_dismiss)) } - TextButton( + PepTextButton( onClick = { (context as Activity).requestShowKeyboardShortcuts() }, diff --git a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenAgeGate.kt b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenAgeGate.kt index ab470085..d03c5ed5 100644 --- a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenAgeGate.kt +++ b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenAgeGate.kt @@ -32,6 +32,8 @@ import androidx.compose.ui.unit.dp import chat.peptide.R import chat.peptide.composables.generic.DobPicker import chat.peptide.composables.generic.DobRegion +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import kotlinx.datetime.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone @@ -171,11 +173,11 @@ fun ChannelScreenAgeGate( Row( horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), ) { - Button(onClick = { onDeny() }) { + SquareButton(onClick = { onDeny() }) { Text(stringResource(R.string.channel_age_gate_dob_cancel)) } - TextButton(onClick = { onAccept() }, enabled = dobValid) { + PepTextButton(onClick = { onAccept() }, enabled = dobValid) { Text(stringResource(R.string.channel_age_gate_dob_proceed)) } } diff --git a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenGeoGate.kt b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenGeoGate.kt index f6dd504f..eef90b15 100644 --- a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenGeoGate.kt +++ b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreenGeoGate.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R import chat.peptide.api.settings.GeoStateProvider +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.vectorassets.GeoGateUX @Composable @@ -68,7 +69,7 @@ fun ChannelScreenGeoGate( } } - Button(onClick = { onAcknowledge() }) { + SquareButton(onClick = { onAcknowledge() }) { Text(stringResource(R.string.geogate_acknowledge)) } } diff --git a/app/src/main/java/chat/peptide/screens/labs/LabsRootScreen.kt b/app/src/main/java/chat/peptide/screens/labs/LabsRootScreen.kt index 6e6b507f..52030a99 100644 --- a/app/src/main/java/chat/peptide/screens/labs/LabsRootScreen.kt +++ b/app/src/main/java/chat/peptide/screens/labs/LabsRootScreen.kt @@ -12,6 +12,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import chat.peptide.api.settings.FeatureFlags +import chat.peptide.composables.generic.PepTextButton import chat.peptide.screens.labs.ui.mockups.NewLoginExperienceMockup import chat.peptide.screens.labs.ui.sandbox.CoreLibSandbox import chat.peptide.screens.labs.ui.sandbox.GradientEditorSandbox @@ -26,7 +27,7 @@ fun LabsGuard(onTurnBack: () -> Unit = {}, content: @Composable () -> Unit) { AlertDialog( onDismissRequest = { onTurnBack() }, confirmButton = { - TextButton(onClick = { onTurnBack() }) { + PepTextButton(onClick = { onTurnBack() }) { Text("Turn back") } }, diff --git a/app/src/main/java/chat/peptide/screens/labs/ui/mockups/NewLoginExperienceMockup.kt b/app/src/main/java/chat/peptide/screens/labs/ui/mockups/NewLoginExperienceMockup.kt index a32971b0..0972b99c 100644 --- a/app/src/main/java/chat/peptide/screens/labs/ui/mockups/NewLoginExperienceMockup.kt +++ b/app/src/main/java/chat/peptide/screens/labs/ui/mockups/NewLoginExperienceMockup.kt @@ -38,6 +38,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import chat.peptide.R import chat.peptide.api.settings.LoadedSettings +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.ui.theme.Theme @OptIn(ExperimentalMaterial3ExpressiveApi::class) @@ -126,13 +128,13 @@ fun NewLoginExperienceMockup(navController: NavController) { .padding(horizontal = 16.dp) .widthIn(max = 150.dp) ) { - Button( + SquareButton( onClick = { loginNav.navigate("login") }, modifier = Modifier.fillMaxWidth() ) { Text("Log In") } - TextButton( + PepTextButton( onClick = { navController.popBackStack() }, modifier = Modifier.fillMaxWidth() ) { @@ -149,7 +151,7 @@ fun NewLoginExperienceMockup(navController: NavController) { .fillMaxSize() .background(Color.Blue), Alignment.Center ) { - Button(onClick = { loginNav.navigate("greet") }) { + SquareButton(onClick = { loginNav.navigate("greet") }) { Text("Greetings!") } } diff --git a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/CoreLibSandbox.kt b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/CoreLibSandbox.kt index ac7069c1..6db1888a 100644 --- a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/CoreLibSandbox.kt +++ b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/CoreLibSandbox.kt @@ -1,6 +1,5 @@ package chat.peptide.screens.labs.ui.sandbox -import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -11,6 +10,7 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.navigation.NavController +import chat.peptide.composables.generic.SquareButton import chat.peptide.settings.dsl.SettingsPage import chat.peptide.ui.theme.FragmentMono @@ -33,7 +33,7 @@ fun CoreLibSandbox(navController: NavController) { ) } ) { - Button(onClick = { + SquareButton(onClick = { greeting = librevolt.greet() }) { Text("Greet") diff --git a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/GradientEditorSandbox.kt b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/GradientEditorSandbox.kt index 41076628..9a51ff9a 100644 --- a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/GradientEditorSandbox.kt +++ b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/GradientEditorSandbox.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.navigation.NavController import chat.peptide.composables.generic.GradientStopEditor +import chat.peptide.composables.generic.PepTextButton import chat.peptide.settings.dsl.SettingsPage @Composable @@ -35,7 +36,7 @@ fun GradientEditorSandbox(navController: NavController) { Row( verticalAlignment = Alignment.CenterVertically ) { - TextButton( + PepTextButton( onClick = { if (segmentCount >= 2) segmentCount-- @@ -50,7 +51,7 @@ fun GradientEditorSandbox(navController: NavController) { modifier = Modifier.weight(1f), textAlign = TextAlign.Center ) - TextButton( + PepTextButton( onClick = { segmentCount++ }, modifier = Modifier.weight(1f) ) { diff --git a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/JBMSandbox.kt b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/JBMSandbox.kt index f9a4144c..ce49b0b5 100644 --- a/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/JBMSandbox.kt +++ b/app/src/main/java/chat/peptide/screens/labs/ui/sandbox/JBMSandbox.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import chat.peptide.composables.generic.PepTextButton import chat.peptide.markdown.jbm.JBM import chat.peptide.markdown.jbm.JBMRenderer import chat.peptide.markdown.jbm.LocalJBMarkdownTreeState @@ -65,12 +66,12 @@ fun JBMSandbox(navController: NavController) { modifier = Modifier.fillMaxWidth() ) - TextButton(onClick = { + PepTextButton(onClick = { submitMdSource = mdSource }) { Text("Submit") } - TextButton(onClick = { + PepTextButton(onClick = { submitMdSource = """# Full range of MD now supported! 1. Text with **bold**, *italics*, and ***both***! 2. You ~~can't see me~~. diff --git a/app/src/main/java/chat/peptide/screens/login/LoginGreetingScreen.kt b/app/src/main/java/chat/peptide/screens/login/LoginGreetingScreen.kt index cd073678..0d426574 100644 --- a/app/src/main/java/chat/peptide/screens/login/LoginGreetingScreen.kt +++ b/app/src/main/java/chat/peptide/screens/login/LoginGreetingScreen.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.generic.SquareElevatedButton @@ -151,7 +152,7 @@ fun LoginGreetingScreen(navController: NavController) { AnimatedVisibility(showBoringButton) { Spacer(modifier = Modifier.height(10.dp)) - TextButton( + PepTextButton( onClick = { navController.navigate("login2/init") }, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/chat/peptide/screens/login/MfaScreen.kt b/app/src/main/java/chat/peptide/screens/login/MfaScreen.kt index e2f7912d..3c920dd3 100644 --- a/app/src/main/java/chat/peptide/screens/login/MfaScreen.kt +++ b/app/src/main/java/chat/peptide/screens/login/MfaScreen.kt @@ -44,6 +44,7 @@ import chat.peptide.api.routes.account.authenticateWithMfaRecoveryCode import chat.peptide.api.routes.account.authenticateWithMfaTotpCode import chat.peptide.composables.generic.CollapsibleCard import chat.peptide.composables.generic.FormTextField +import chat.peptide.composables.generic.SquareButton import chat.peptide.persistence.KVStorage import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -255,7 +256,7 @@ fun MfaScreen( value = viewModel.totpCode ) - Button( + SquareButton( onClick = { viewModel.tryAuthorizeTotp(mfaTicket) }, modifier = Modifier .padding(horizontal = 20.dp, vertical = 10.dp) @@ -309,7 +310,7 @@ fun MfaScreen( value = viewModel.recoveryCode ) - Button( + SquareButton( onClick = { viewModel.tryAuthorizeRecovery(mfaTicket) }, modifier = Modifier .padding(horizontal = 20.dp, vertical = 10.dp) diff --git a/app/src/main/java/chat/peptide/screens/login2/InitScreen.kt b/app/src/main/java/chat/peptide/screens/login2/InitScreen.kt index c95927b5..cb5728b9 100644 --- a/app/src/main/java/chat/peptide/screens/login2/InitScreen.kt +++ b/app/src/main/java/chat/peptide/screens/login2/InitScreen.kt @@ -38,6 +38,8 @@ import chat.peptide.R import chat.peptide.api.PeptideAPI import chat.peptide.api.settings.LoadedSettings import chat.peptide.composables.generic.AnyLink +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.generic.Weblink import chat.peptide.ui.theme.Theme import com.chuckerteam.chucker.api.Chucker @@ -140,13 +142,13 @@ private fun LinkPart(windowSizeClass: WindowSizeClass) { .fillMaxWidth() ) { Column(Modifier.widthIn(max = 150.dp)) { - Button( + SquareButton( onClick = {/* navController.navigate("login2/existing/details") */ }, modifier = Modifier.fillMaxWidth() ) { Text("Log In") // FIXME hardcoded string } - TextButton( + PepTextButton( onClick = {/* navController.navigate("login2/new/details") */ }, modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/java/chat/peptide/screens/register/RegisterGreetingScreen.kt b/app/src/main/java/chat/peptide/screens/register/RegisterGreetingScreen.kt index f33fd338..27d86f56 100644 --- a/app/src/main/java/chat/peptide/screens/register/RegisterGreetingScreen.kt +++ b/app/src/main/java/chat/peptide/screens/register/RegisterGreetingScreen.kt @@ -27,6 +27,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton @Composable fun RegisterGreetingScreen(navController: NavController) { @@ -83,7 +85,7 @@ fun RegisterGreetingScreen(navController: NavController) { } Row { - TextButton(onClick = { + PepTextButton(onClick = { navController.popBackStack() }) { Text(text = stringResource(R.string.back)) @@ -91,7 +93,7 @@ fun RegisterGreetingScreen(navController: NavController) { Spacer(modifier = Modifier.width(10.dp)) - Button(onClick = { + SquareButton(onClick = { navController.navigate("register/details") }) { Text(text = stringResource(R.string.next)) diff --git a/app/src/main/java/chat/peptide/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/AppearanceSettingsScreen.kt index 3bb4ad1d..8719efaa 100644 --- a/app/src/main/java/chat/peptide/screens/settings/AppearanceSettingsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/AppearanceSettingsScreen.kt @@ -8,7 +8,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.clickable -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -31,7 +30,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.rememberModalBottomSheetState @@ -63,6 +61,7 @@ import chat.peptide.api.PeptideJson import chat.peptide.api.settings.LoadedSettings import chat.peptide.api.settings.SyncedSettings import chat.peptide.composables.generic.ListHeader +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.screens.settings.appearance.ColourChip import chat.peptide.composables.screens.settings.appearance.CornerRadiusPicker import chat.peptide.sheets.ColourPickerSheet @@ -317,23 +316,12 @@ fun AppearanceSettingsScreen( ColourChip( color = Color(0xff333642), text = stringResource(id = R.string.settings_appearance_theme_peptide), - selected = LoadedSettings.theme == Theme.None, + selected = LoadedSettings.theme == Theme.PEPTIDE_DARK, modifier = Modifier .weight(1f) .testTag("set_theme_peptide") ) { - viewModel.saveNewTheme(Theme.None) - } - - ColourChip( - color = if (isSystemInDarkTheme()) Color(0xff333642) else Color(0xfff7f7f7), - text = stringResource(id = R.string.settings_appearance_theme_none), - selected = LoadedSettings.theme == Theme.None, - modifier = Modifier - .weight(1f) - .testTag("set_theme_none") - ) { - viewModel.saveNewTheme(Theme.None) + viewModel.saveNewTheme(Theme.PEPTIDE_DARK) } if (systemSupportsDynamicColors()) { @@ -425,7 +413,7 @@ fun AppearanceSettingsScreen( .fillMaxWidth() .padding(horizontal = 20.dp) ) { - TextButton( + PepTextButton( onClick = { filePicker.launch(arrayOf("*/*")) }, @@ -443,7 +431,7 @@ fun AppearanceSettingsScreen( ) } - TextButton( + PepTextButton( onClick = { fileSaver.launch("${SyncedSettings.android.theme}-colours.rato") }, diff --git a/app/src/main/java/chat/peptide/screens/settings/ChatSettingsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/ChatSettingsScreen.kt index 7e16920f..4ebaef25 100644 --- a/app/src/main/java/chat/peptide/screens/settings/ChatSettingsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/ChatSettingsScreen.kt @@ -43,6 +43,7 @@ import chat.peptide.api.settings.MessageReplyStyle import chat.peptide.api.settings.SpecialEmbedSettings import chat.peptide.api.settings.SyncedSettings import chat.peptide.composables.generic.ListHeader +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.RadioItem import kotlinx.coroutines.launch @@ -141,7 +142,7 @@ fun ChatSettingsScreen( for (key in LoadedSettings.poorlyFormedSettingsKeys.filter { it in setOf("ordering", "android", "notifications") }) { - TextButton( + PepTextButton( onClick = { scope.launch { when (key) { diff --git a/app/src/main/java/chat/peptide/screens/settings/DebugSettingsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/DebugSettingsScreen.kt index b26fa1d1..e1228dfe 100644 --- a/app/src/main/java/chat/peptide/screens/settings/DebugSettingsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/DebugSettingsScreen.kt @@ -50,6 +50,8 @@ import androidx.lifecycle.viewModelScope import androidx.navigation.NavController import chat.peptide.R import chat.peptide.api.routes.push.subscribePush +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.dialogs.NotificationRationaleDialog import chat.peptide.persistence.Database import chat.peptide.persistence.KVStorage @@ -178,7 +180,7 @@ fun DebugSettingsScreen( } }, confirmButton = { - TextButton(onClick = { + PepTextButton(onClick = { showC2dmDataDialogue = false }) { Text("OK") @@ -239,7 +241,7 @@ fun DebugSettingsScreen( ElevatedButton(onClick = { viewModel.forgetSwipeToReplySpark() }) { Text("Forget swipe to reply spark") } - Button(onClick = { viewModel.forgetAllSparks() }) { + SquareButton(onClick = { viewModel.forgetAllSparks() }) { Text("Forget all sparks") } } diff --git a/app/src/main/java/chat/peptide/screens/settings/ExperimentsSettingsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/ExperimentsSettingsScreen.kt index 7f2b3383..f02f5be7 100644 --- a/app/src/main/java/chat/peptide/screens/settings/ExperimentsSettingsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/ExperimentsSettingsScreen.kt @@ -25,6 +25,8 @@ import chat.peptide.BuildConfig import chat.peptide.PeptideApplication import chat.peptide.api.settings.Experiments import chat.peptide.api.settings.LoadedSettings +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.persistence.KVStorage import chat.peptide.settings.dsl.SettingsPage import chat.peptide.settings.dsl.SubcategoryContentInsets @@ -121,7 +123,7 @@ fun ExperimentsSettingsScreen( Text("The changes you made require a restart to take effect.") }, confirmButton = { - Button( + SquareButton( onClick = { viewModel.restartApp(context) } @@ -130,7 +132,7 @@ fun ExperimentsSettingsScreen( } }, dismissButton = { - TextButton( + PepTextButton( onClick = { viewModel.showNeedsRestartAlert.value = false } diff --git a/app/src/main/java/chat/peptide/screens/settings/ProfileSettngsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/ProfileSettngsScreen.kt index 56987ed5..ff498b5a 100644 --- a/app/src/main/java/chat/peptide/screens/settings/ProfileSettngsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/ProfileSettngsScreen.kt @@ -53,6 +53,7 @@ import chat.peptide.api.routes.user.fetchUserProfile import chat.peptide.api.routes.user.patchSelf import chat.peptide.api.schemas.Profile import chat.peptide.composables.generic.InlineMediaPicker +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.screens.settings.RawUserOverview import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext @@ -415,7 +416,7 @@ fun ProfileSettingsScreen( Spacer(Modifier.height(8.dp)) - TextButton( + PepTextButton( onClick = { viewModel.saveBio() }, diff --git a/app/src/main/java/chat/peptide/screens/settings/SessionSettngsScreen.kt b/app/src/main/java/chat/peptide/screens/settings/SessionSettngsScreen.kt index 3410e24c..f34471b1 100644 --- a/app/src/main/java/chat/peptide/screens/settings/SessionSettngsScreen.kt +++ b/app/src/main/java/chat/peptide/screens/settings/SessionSettngsScreen.kt @@ -14,10 +14,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton @@ -27,7 +24,6 @@ import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -55,6 +51,8 @@ import chat.peptide.api.routes.auth.logoutAllSessions import chat.peptide.api.routes.auth.logoutSessionById import chat.peptide.api.schemas.Session import chat.peptide.composables.generic.ListHeader +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.markdown.RichMarkdown import chat.peptide.composables.settings.sessions.SessionItem import kotlinx.coroutines.launch @@ -114,7 +112,7 @@ fun SessionSettingsScreen( ) }, confirmButton = { - Button( + SquareButton( onClick = { viewModel.showLogoutOtherConfirmation = false viewModel.logoutOtherSessions() @@ -124,7 +122,7 @@ fun SessionSettingsScreen( } }, dismissButton = { - TextButton( + PepTextButton( onClick = { viewModel.showLogoutOtherConfirmation = false } diff --git a/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsHome.kt b/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsHome.kt index bdb8c36c..3724687c 100644 --- a/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsHome.kt +++ b/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsHome.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -19,7 +18,6 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -43,6 +41,8 @@ import chat.peptide.api.internals.hasPermission import chat.peptide.api.routes.channel.leaveDeleteOrCloseChannel import chat.peptide.api.schemas.ChannelType import chat.peptide.api.settings.FeatureFlags +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.extensions.rememberChannelPermissions import chat.peptide.screens.settings.SettingsIcon import kotlinx.coroutines.launch @@ -64,14 +64,14 @@ fun ChannelSettingsHome(navController: NavController, channelId: String) { title = { Text(stringResource(R.string.channel_settings_delete_confirm)) }, text = { Text(stringResource(R.string.channel_settings_delete_confirm_description)) }, dismissButton = { - TextButton(onClick = { + PepTextButton(onClick = { showDeletionConfirmation = false }) { Text(stringResource(R.string.channel_settings_delete_confirm_no)) } }, confirmButton = { - Button(onClick = { + SquareButton(onClick = { showDeletionConfirmation = false scope.launch { leaveDeleteOrCloseChannel(channelId) diff --git a/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsOverview.kt b/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsOverview.kt index b454c6f6..ea854112 100644 --- a/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsOverview.kt +++ b/app/src/main/java/chat/peptide/screens/settings/channel/ChannelSettingsOverview.kt @@ -64,6 +64,8 @@ import chat.peptide.api.routes.microservices.autumn.uploadToAutumn import chat.peptide.api.schemas.Channel import chat.peptide.composables.generic.InlineMediaPicker import chat.peptide.composables.generic.ListHeader +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import io.ktor.http.ContentType @@ -257,7 +259,7 @@ fun ChannelSettingsOverview( } }, confirmButton = { - Button( + SquareButton( onClick = { viewModel.toggleNsfw() } @@ -270,7 +272,7 @@ fun ChannelSettingsOverview( } }, dismissButton = { - TextButton( + PepTextButton( onClick = { viewModel.showNsfwToggleDialogue = false } diff --git a/app/src/main/java/chat/peptide/sheets/AddServerSheet.kt b/app/src/main/java/chat/peptide/sheets/AddServerSheet.kt index acbd28e0..997cec74 100644 --- a/app/src/main/java/chat/peptide/sheets/AddServerSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/AddServerSheet.kt @@ -58,6 +58,7 @@ import chat.peptide.api.PeptideAPI import chat.peptide.api.routes.server.createServer import chat.peptide.callbacks.Action import chat.peptide.callbacks.ActionChannel +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.sheets.SheetSelection import chat.peptide.composables.vectorassets.CreateServer import chat.peptide.composables.vectorassets.JoinServer @@ -307,7 +308,7 @@ fun AddServerSheet(onDismiss: () -> Unit) { .padding(vertical = 16.dp, horizontal = 8.dp) ) - Button( + SquareButton( onClick = { val intent = Intent(context, InviteActivity::class.java) intent.data = if (inviteState.text.startsWith("https://")) { @@ -319,7 +320,7 @@ fun AddServerSheet(onDismiss: () -> Unit) { "Invalid URL: ${inviteState.text}", e ) - return@Button + return@SquareButton } } else { "https://${PeptideAPI.getCurrentAppUrl()}/invite/${inviteState.text}".toUri() @@ -452,7 +453,7 @@ fun AddServerSheet(onDismiss: () -> Unit) { .padding(vertical = 16.dp, horizontal = 8.dp) ) - Button( + SquareButton( onClick = { serverCreationError = false diff --git a/app/src/main/java/chat/peptide/sheets/ColourPickerSheet.kt b/app/src/main/java/chat/peptide/sheets/ColourPickerSheet.kt index a3a1e379..f3e0edfc 100644 --- a/app/src/main/java/chat/peptide/sheets/ColourPickerSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/ColourPickerSheet.kt @@ -29,7 +29,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -40,7 +39,6 @@ import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SliderState import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -62,6 +60,8 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.TailwindColourScheme import org.intellij.lang.annotations.Language @@ -629,7 +629,7 @@ fun ColumnScope.ColourPickerSheet( }, ) - TextButton( + PepTextButton( onClick = { try { color = Color(android.graphics.Color.parseColor(hex)) @@ -671,7 +671,7 @@ fun ColumnScope.ColourPickerSheet( Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp) ) { - TextButton(onClick = onUseDefaultColour, Modifier.fillMaxWidth()) { + PepTextButton(onClick = onUseDefaultColour, Modifier.fillMaxWidth()) { Icon(Icons.Default.CheckCircle, null) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.colour_picker_use_default)) @@ -681,12 +681,12 @@ fun ColumnScope.ColourPickerSheet( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - TextButton(onClick = onDismiss, modifier = Modifier.weight(1f)) { + PepTextButton(onClick = onDismiss, modifier = Modifier.weight(1f)) { Icon(Icons.Default.Close, null) Spacer(Modifier.width(8.dp)) Text(stringResource(R.string.colour_picker_cancel)) } - Button( + SquareButton( onClick = { onColourSelected(color.toArgb()) }, modifier = Modifier.weight(1f) ) { diff --git a/app/src/main/java/chat/peptide/sheets/EarlyAccessSheet.kt b/app/src/main/java/chat/peptide/sheets/EarlyAccessSheet.kt index bb5d1ac2..386c1536 100644 --- a/app/src/main/java/chat/peptide/sheets/EarlyAccessSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/EarlyAccessSheet.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton @Composable fun EarlyAccessSheet(onClose: () -> Unit) { @@ -55,7 +56,7 @@ fun EarlyAccessSheet(onClose: () -> Unit) { style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) - TextButton(onClick = onClose, modifier = Modifier.fillMaxWidth()) { + PepTextButton(onClick = onClose, modifier = Modifier.fillMaxWidth()) { Text(stringResource(R.string.spark_early_access_cta)) } } diff --git a/app/src/main/java/chat/peptide/sheets/MessageContextSheet.kt b/app/src/main/java/chat/peptide/sheets/MessageContextSheet.kt index 75df2d79..5e862cd1 100644 --- a/app/src/main/java/chat/peptide/sheets/MessageContextSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/MessageContextSheet.kt @@ -10,14 +10,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -44,7 +42,9 @@ import chat.peptide.api.routes.channel.react import chat.peptide.api.settings.Experiments import chat.peptide.callbacks.UiCallbacks import chat.peptide.composables.chat.Message +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.SheetButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.Platform import kotlinx.coroutines.launch @@ -76,7 +76,7 @@ fun MessageContextSheet( var showDeleteMessageConfirmation by remember { mutableStateOf(false) } var showInspectASTSheet by remember { mutableStateOf(false) } val showInspectASTSheetButton = - BuildConfig.DEBUG || (message.content != null && Experiments.useKotlinBasedMarkdownRenderer.isEnabled) + BuildConfig.DEBUG && (message.content != null && Experiments.useKotlinBasedMarkdownRenderer.isEnabled) if (showShareSheet) { val shareSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -280,7 +280,7 @@ fun MessageContextSheet( ) }, confirmButton = { - Button( + SquareButton( onClick = { showDeleteMessageConfirmation = false coroutineScope.launch { @@ -295,7 +295,7 @@ fun MessageContextSheet( } }, dismissButton = { - TextButton( + PepTextButton( onClick = { showDeleteMessageConfirmation = false } diff --git a/app/src/main/java/chat/peptide/sheets/ReactionInfoSheet.kt b/app/src/main/java/chat/peptide/sheets/ReactionInfoSheet.kt index f8b02436..f77b818e 100644 --- a/app/src/main/java/chat/peptide/sheets/ReactionInfoSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/ReactionInfoSheet.kt @@ -16,14 +16,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow import androidx.compose.material3.Tab import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -52,7 +50,9 @@ import chat.peptide.api.schemas.Emoji import chat.peptide.api.schemas.User import chat.peptide.api.settings.LoadedSettings import chat.peptide.composables.chat.MemberListItem +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.RemoteImage +import chat.peptide.composables.generic.SquareButton import chat.peptide.internals.text.MessageProcessor import chat.peptide.persistence.KVStorage import kotlinx.coroutines.launch @@ -181,7 +181,7 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { title = { Text("Traveller, you may not unsee your knowledge...") }, text = { Text("Experimental features are already unlocked.") }, confirmButton = { - TextButton(onClick = { showEnabledAlreadyAlert = false }) { + PepTextButton(onClick = { showEnabledAlreadyAlert = false }) { Text("OK") } } @@ -194,7 +194,7 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { title = { Text("You hear a faint whisper in the wind...") }, text = { Text("Would you like to enable experimental features? They may be unstable.") }, confirmButton = { - TextButton(onClick = { + PepTextButton(onClick = { showEnabledConfirmAlert = false LoadedSettings.experimentsEnabled = true scope.launch { @@ -205,7 +205,7 @@ fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) { } }, dismissButton = { - Button(onClick = { showEnabledConfirmAlert = false }) { + SquareButton(onClick = { showEnabledConfirmAlert = false }) { Text("I shall not risk it.") } } diff --git a/app/src/main/java/chat/peptide/sheets/ServerContextSheet.kt b/app/src/main/java/chat/peptide/sheets/ServerContextSheet.kt index a88ff719..b1a4a0a8 100644 --- a/app/src/main/java/chat/peptide/sheets/ServerContextSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/ServerContextSheet.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider @@ -22,7 +21,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -43,7 +41,9 @@ import androidx.core.net.toUri import chat.peptide.R import chat.peptide.api.PeptideAPI import chat.peptide.api.routes.server.leaveOrDeleteServer +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.SheetButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.markdown.RichMarkdown import chat.peptide.composables.screens.settings.ServerOverview import chat.peptide.composables.sheets.SheetSelection @@ -116,7 +116,7 @@ fun ServerContextSheet( } }, confirmButton = { - Button( + SquareButton( onClick = { coroutineScope.launch { onHideSheet() @@ -134,7 +134,7 @@ fun ServerContextSheet( } }, dismissButton = { - TextButton( + PepTextButton( onClick = { showLeaveConfirmation = false } diff --git a/app/src/main/java/chat/peptide/sheets/StatusSheet.kt b/app/src/main/java/chat/peptide/sheets/StatusSheet.kt index f2624157..73ad4817 100644 --- a/app/src/main/java/chat/peptide/sheets/StatusSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/StatusSheet.kt @@ -13,14 +13,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -36,11 +32,13 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R import chat.peptide.api.PeptideAPI import chat.peptide.api.routes.user.patchSelf import chat.peptide.api.schemas.User +import chat.peptide.composables.generic.PepTextButton import chat.peptide.composables.generic.SheetButton import chat.peptide.composables.generic.asApiName import chat.peptide.composables.generic.presenceFromStatus @@ -101,7 +99,7 @@ fun StatusTextEditDialog( } }, confirmButton = { - TextButton( + PepTextButton( enabled = isConfirmEnabled, onClick = { errorText = null @@ -110,11 +108,11 @@ fun StatusTextEditDialog( } else { if (fieldState.text == initialStatus) { onDismiss() - return@TextButton + return@PepTextButton } else if (fieldState.text.isBlank()) { if (selfUser.status?.text == null) { onDismiss() - return@TextButton + return@PepTextButton } scope.launch { try { @@ -150,7 +148,7 @@ fun StatusTextEditDialog( } }, dismissButton = { - TextButton( + PepTextButton( onClick = onDismiss ) { Text( @@ -161,6 +159,17 @@ fun StatusTextEditDialog( ) } +@Composable +@Preview +fun StatusTextEditDialogPreview() { + StatusTextEditDialog( + selfUser = User.getPlaceholder("01G00000000000000000000000"), + initialStatus = "This is a test status", + onDismiss = {} + ) +} + + @Composable fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) { val selfUser = PeptideAPI.userCache[PeptideAPI.selfId]!! @@ -253,3 +262,12 @@ fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) { } + +@Preview +@Composable +fun StatusSheetPreview() { + StatusSheet( + onBeforeNavigation = {}, + onGoSettings = {} + ) +} diff --git a/app/src/main/java/chat/peptide/sheets/UserCardSheet.kt b/app/src/main/java/chat/peptide/sheets/UserCardSheet.kt index 2a5f5742..eaac638d 100644 --- a/app/src/main/java/chat/peptide/sheets/UserCardSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/UserCardSheet.kt @@ -14,11 +14,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -40,6 +38,8 @@ import chat.peptide.BuildConfig import chat.peptide.R import chat.peptide.api.PeptideAPI import chat.peptide.api.schemas.User +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.profile.UserCard import chat.peptide.internals.Platform import kotlinx.coroutines.Dispatchers @@ -200,7 +200,7 @@ fun UserCardSheet(user: User?) { horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() ) { - Button( + SquareButton( onClick = { scope.launch { shareCard() @@ -220,7 +220,7 @@ fun UserCardSheet(user: User?) { ) } - TextButton( + PepTextButton( onClick = { scope.launch { copyCard() diff --git a/app/src/main/java/chat/peptide/sheets/WebHookUserSheet.kt b/app/src/main/java/chat/peptide/sheets/WebHookUserSheet.kt index c4b9939a..696362e8 100644 --- a/app/src/main/java/chat/peptide/sheets/WebHookUserSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/WebHookUserSheet.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R +import chat.peptide.composables.generic.SquareButton @Composable fun WebHookUserSheet(modifier: Modifier = Modifier) { @@ -45,7 +46,7 @@ fun WebHookUserSheet(modifier: Modifier = Modifier) { textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() ) - Button( + SquareButton( onClick = { Toast(context).apply { setText(context.getString(R.string.comingsoon_toast)) diff --git a/app/src/main/java/chat/peptide/sheets/spark/SwipeToReplySparkSheet.kt b/app/src/main/java/chat/peptide/sheets/spark/SwipeToReplySparkSheet.kt index cb8fa27e..d1637983 100644 --- a/app/src/main/java/chat/peptide/sheets/spark/SwipeToReplySparkSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/spark/SwipeToReplySparkSheet.kt @@ -8,10 +8,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -20,6 +18,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.peptide.R +import chat.peptide.composables.generic.PepTextButton +import chat.peptide.composables.generic.SquareButton import chat.peptide.composables.vectorassets.SwipeToReplySpark @Composable @@ -51,13 +51,13 @@ fun SwipeToReplySparkSheet( Column( verticalArrangement = Arrangement.spacedBy(8.dp) ) { - TextButton( + PepTextButton( onClick = onOpenOptions, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.spark_swipe_to_reply_customise)) } - Button( + SquareButton( onClick = onDismissSheet, modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/java/chat/peptide/ui/theme/AppColors.kt b/app/src/main/java/chat/peptide/ui/theme/AppColors.kt index f98cbe13..cc6b9771 100644 --- a/app/src/main/java/chat/peptide/ui/theme/AppColors.kt +++ b/app/src/main/java/chat/peptide/ui/theme/AppColors.kt @@ -5,13 +5,13 @@ import androidx.compose.ui.graphics.Color object AppColors { val PrimaryLight = Color(0xFF662D91) - val OnPrimaryLight = Color(0xFFFFFFFF) - val PrimaryContainerLight = Color(0xFFFFD9DC) - val OnPrimaryContainerLight = Color(0xFF910031) - val SecondaryLight = Color(0xFFC0001F) - val OnSecondaryLight = Color(0xFFFFFFFF) - val SecondaryContainerLight = Color(0xFFFFDAD7) - val OnSecondaryContainerLight = Color(0xFF930015) + val OnPrimaryLight = Color(0xFFFFFFFD) + val PrimaryContainerLight = Color(0xFF662D91) + val OnPrimaryContainerLight = Color(0xFFFFFFFF) + val SecondaryLight = Color(0xFFFFDE17) + val OnSecondaryLight = Color(0xFF000000) + val SecondaryContainerLight = Color(0xFFCBB2E5) + val OnSecondaryContainerLight = Color(0xFFFFDE17) val TertiaryLight = Color(0xFF0060A9) val OnTertiaryLight = Color(0xFFFFFFFF) val TertiaryContainerLight = Color(0xFFD3E4FF) @@ -40,14 +40,14 @@ object AppColors { val SurfaceContainerHighLight = Color(0xFFE4E8F8) val SurfaceContainerHighestLight = Color(0xFFDEE2F2) - val PrimaryDark = Color(0xFF3B1954) - val OnPrimaryDark = Color(0xFFFFFFFD) - val PrimaryContainerDark = Color(0xFF662D91) - val OnPrimaryContainerDark = Color(0xFFFFFFFF) - val SecondaryDark = Color(0xFFFFDE17) - val OnSecondaryDark = Color(0xFF000000) - val SecondaryContainerDark = Color(0xFF332E36) - val OnSecondaryContainerDark = Color(0xFFFFDE17) + val PrimaryDark = Color(0xFFFFDE17) + val OnPrimaryDark = Color(0xFF000000) + val PrimaryContainerDark = Color(0xFF332E36) + val OnPrimaryContainerDark = Color(0xFFFFDE17) + val SecondaryDark = Color(0xFF662D91) + val OnSecondaryDark = Color(0xFFFFFFFD) + val SecondaryContainerDark = Color(0xFF662D91) + val OnSecondaryContainerDark = Color(0xFFFFFFFF) val TertiaryDark = Color(0xFFA2C9FF) val OnTertiaryDark = Color(0xFF00315B) val TertiaryContainerDark = Color(0xFF004881) diff --git a/app/src/main/java/chat/peptide/ui/theme/Theme.kt b/app/src/main/java/chat/peptide/ui/theme/Theme.kt index b1b1d011..3b91fc26 100644 --- a/app/src/main/java/chat/peptide/ui/theme/Theme.kt +++ b/app/src/main/java/chat/peptide/ui/theme/Theme.kt @@ -97,7 +97,7 @@ private val PeptideColorScheme = darkColorScheme( ) enum class Theme { - None, + PEPTIDE_DARK, M3Dynamic, } @@ -120,15 +120,12 @@ fun getColorScheme( context ) - requestedTheme == Theme.None && systemInDarkTheme -> PeptideColorScheme -// requestedTheme == Theme.None && !systemInDarkTheme -> LightColorScheme + requestedTheme == Theme.PEPTIDE_DARK -> PeptideColorScheme else -> PeptideColorScheme }.copy() val colorSchemeIsDark = when { m3Supported && requestedTheme == Theme.M3Dynamic -> isSystemInDarkTheme() - requestedTheme == Theme.None && systemInDarkTheme -> true - requestedTheme == Theme.None && !systemInDarkTheme -> false else -> true } @@ -140,6 +137,7 @@ fun getColorScheme( @Suppress("DEPRECATION") ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = !colorSchemeIsDark + } } @@ -176,15 +174,13 @@ fun systemSupportsDynamicColors(): Boolean { } fun getDefaultTheme(): Theme { - return when { - systemSupportsDynamicColors() -> Theme.M3Dynamic - else -> Theme.None - } + return Theme.PEPTIDE_DARK } fun isThemeDark(theme: Theme, systemIsDark: Boolean): Boolean { return when (theme) { - Theme.M3Dynamic, Theme.None -> systemIsDark + Theme.M3Dynamic, + Theme.PEPTIDE_DARK -> true } } diff --git a/app/src/main/res/drawable/usercard_heading.xml b/app/src/main/res/drawable/usercard_heading.xml index 20dd1723..4eb95a07 100644 --- a/app/src/main/res/drawable/usercard_heading.xml +++ b/app/src/main/res/drawable/usercard_heading.xml @@ -1,30 +1,15 @@ - - - - - - - + android:width="194dp" + android:height="32dp" + android:viewportWidth="194" + android:viewportHeight="32"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a56eb6bd..07facf1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,6 +485,13 @@ Server name can be up to %1$d characters in length. An error occurred while creating your server. Please try again later. Create + Add to server + Invite code or link + Invite code or link + Join + Create a new server + Create a new server + This feature is currently under construction. Discover Peptide @@ -695,7 +702,7 @@ Peptide Light Pure Black - Material You + Material Material You (unsupported) Material You is not supported on this device.