Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ android {
}
}
buildFeatures {
viewBinding true // Required by ChooseActivityDialog (pending Compose migration)
compose true
buildConfig true
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@style/AppTheme.Dialog.NoActionBar" />
android:theme="@style/AppTheme.Translucent" />

<receiver
android:name=".appwidget.DDWidgetProvider"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/de/psdev/devdrawer/DevDrawerApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fun DevDrawerApp(
if (currentRoute !in topLevelRoutes) {
{
IconButton(onClick = { backDispatcher?.onBackPressed() }) {
Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = stringResource(R.string.back))
}
}
} else {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/de/psdev/devdrawer/DevDrawerHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ fun DevDrawerHost(
WidgetProfilesScreen(
onEditProfile = { profile ->
navigator.navigate(WidgetProfileEditorRoute(profile.id))
},
onProfileCreated = { profile ->
navigator.navigate(WidgetProfileEditorRoute(profile.id, isNew = true))
}
)
}
Expand Down Expand Up @@ -68,7 +71,8 @@ fun DevDrawerHost(
}
entry<WidgetProfileEditorRoute> { key ->
WidgetProfileEditor(
profileId = key.id
profileId = key.id,
isNew = key.isNew
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/de/psdev/devdrawer/Routes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ data class WidgetEditorRoute(val id: Int) : NavKey
data class WidgetSetupRoute(val widgetId: Int) : NavKey

@Serializable
data class WidgetProfileEditorRoute(val id: String) : NavKey
data class WidgetProfileEditorRoute(val id: String, val isNew: Boolean = false) : NavKey
63 changes: 35 additions & 28 deletions app/src/main/java/de/psdev/devdrawer/about/AboutScreen.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package de.psdev.devdrawer.about

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -19,16 +21,15 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import de.psdev.devdrawer.BuildConfig
import de.psdev.devdrawer.R
import de.psdev.devdrawer.ui.AppIcon

@Composable
fun AboutScreen(
Expand All @@ -48,52 +49,58 @@ fun AboutScreen(
}

@Composable
private fun AboutHeader() {
internal fun AboutHeader() {
val context = LocalContext.current
val appIcon = remember(context) {
context.packageManager.getApplicationIcon(context.packageName).toBitmap().asImageBitmap()
}
val appIcon = remember(context) { context.applicationInfo.loadIcon(context.packageManager) }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Image(
bitmap = appIcon,
contentDescription = null,
modifier = Modifier.size(96.dp)
)
AppIcon(icon = appIcon, size = 96.dp)
Text(
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.headlineMedium
)
Text(
text = "Version ${BuildConfig.VERSION_NAME}",
text = stringResource(R.string.about_version, BuildConfig.VERSION_NAME),
style = MaterialTheme.typography.bodyLarge
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
Button(onClick = {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = "https://play.google.com/store/apps/details?id=de.psdev.devdrawer".toUri()
setPackage("com.android.vending")
}
context.startActivity(intent)
}) {
Text("Play Store")
Button(onClick = { openPlayStore(context) }) {
Text(stringResource(R.string.about_play_store))
}
OutlinedButton(onClick = {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.intent.data = "https://github.com/PSDev/DevDrawer".toUri()
customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(customTabsIntent.intent)
}) {
Text("GitHub")
OutlinedButton(onClick = { openFirst(context, CustomTabsIntent.Builder().build().intent.setData(SOURCE_URL.toUri())) }) {
Text(stringResource(R.string.about_github))
}
}
}
}

private const val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=de.psdev.devdrawer"
private const val SOURCE_URL = "https://github.com/PSDev/DevDrawer"

/** The Play Store app if installed, otherwise its web page in a browser. */
private fun openPlayStore(context: Context) = openFirst(
context,
Intent(Intent.ACTION_VIEW, PLAY_STORE_URL.toUri()).setPackage("com.android.vending"),
Intent(Intent.ACTION_VIEW, PLAY_STORE_URL.toUri())
)

/** Starts the first intent something can handle, or says the device can't. */
private fun openFirst(context: Context, vararg intents: Intent) {
for (intent in intents) {
try {
context.startActivity(intent)
return
} catch (e: ActivityNotFoundException) {
// Try the next one.
}
}
Toast.makeText(context, R.string.action_unavailable, Toast.LENGTH_LONG).show()
}

This file was deleted.

6 changes: 4 additions & 2 deletions app/src/main/java/de/psdev/devdrawer/appwidget/AppInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ data class AppInfo(
val lastUpdateTime: Long,
val signatureHashSha256: String,
val versionName: String = "",
val versionCode: Long = 0
val versionCode: Long = 0,
val canUninstall: Boolean = true
)

fun PackageHashInfo.toAppInfo(context: Context): AppInfo? = try {
Expand All @@ -43,7 +44,8 @@ fun PackageHashInfo.toAppInfo(context: Context): AppInfo? = try {
lastUpdateTime = lastUpdateTime,
signatureHashSha256 = signatureHashSha256,
versionName = packageInfo.versionName ?: "",
versionCode = versionCode
versionCode = versionCode,
canUninstall = applicationInfo.canUninstall
)
} catch (e: Exception) {
logger.warn(e) { "Error: ${e.message}" }
Expand Down
97 changes: 97 additions & 0 deletions app/src/main/java/de/psdev/devdrawer/appwidget/AppScreens.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package de.psdev.devdrawer.appwidget

import android.content.pm.ActivityInfo
import android.graphics.drawable.Drawable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import de.psdev.devdrawer.R
import de.psdev.devdrawer.ui.AppIcon

/** One screen of an app that can be opened directly. */
data class AppScreen(
val className: String,
val label: String,
/** The class name relative to the app's package, like ".settings.SettingsActivity". */
val shortName: String,
/** The screen the launcher opens. */
val isMain: Boolean
)

/**
* The screens of [packageName] that another app may open: exported, enabled and not behind a permission.
* The launcher's screen comes first, then the rest by label.
*/
fun appScreens(
packageName: String,
activities: List<ActivityInfo>,
mainClassName: String?,
labelOf: (ActivityInfo) -> String
): List<AppScreen> = activities
.filter { it.exported && it.enabled && it.permission == null }
.map { activity ->
AppScreen(
className = activity.name,
label = labelOf(activity),
shortName = if (activity.name.startsWith("$packageName.")) activity.name.removePrefix(packageName) else activity.name,
isMain = activity.name == mainClassName
)
}
.sortedWith(compareByDescending<AppScreen> { it.isMain }.thenBy(String.CASE_INSENSITIVE_ORDER) { it.label })

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChooseScreenSheet(
appName: String,
appIcon: Drawable?,
screens: List<AppScreen>,
onScreenSelected: (AppScreen) -> Unit,
onDismiss: () -> Unit
) {
ModalBottomSheet(onDismissRequest = onDismiss) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
AppIcon(icon = appIcon, size = 40.dp)
Text(text = stringResource(R.string.open_app_screen, appName), style = MaterialTheme.typography.titleLarge)
}
LazyColumn(modifier = Modifier.padding(vertical = 8.dp)) {
items(screens, key = { it.className }) { screen ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { onScreenSelected(screen) }
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
Text(
text = screen.label,
style = MaterialTheme.typography.bodyLarge,
fontWeight = if (screen.isMain) FontWeight.Medium else null
)
Text(
text = if (screen.isMain) stringResource(R.string.main_screen, screen.shortName) else screen.shortName,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
Loading
Loading