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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ Jenkinsfile

/encrypt.txt

google-play-api.json
google-play-api.json
/api-*.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fun AnalyticsOptInCard(
modifier = modifier
.fillMaxWidth()
.padding(8.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHighest)
) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = stringResource(R.string.analytics_opt_in_title), style = MaterialTheme.typography.titleMedium)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ fun DevDrawerWidgetContent(state: WidgetUiState) {
val background = GlanceModifier
.fillMaxSize()
.appWidgetBackground()
.background(GlanceTheme.colors.widgetBackground)
// Glance's widgetBackground derives from secondaryContainer, which is deep orange in DevDrawer's scheme.
.background(GlanceTheme.colors.background)
val rounded = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
background.cornerRadius(android.R.dimen.system_app_widget_background_radius)
} else {
Expand All @@ -76,7 +77,7 @@ fun DevDrawerWidgetContent(state: WidgetUiState) {
}
Header(state, showDetails = true)
if (state.apps.isEmpty()) {
EmptyState(context, state.appWidgetId)
EmptyState(context, state.appWidgetId, compact = size.height < ROOMY_EMPTY_HEIGHT)
} else {
val compact = size.width < NARROW_WIDTH
LazyColumn(modifier = GlanceModifier.fillMaxSize()) {
Expand All @@ -100,6 +101,9 @@ fun DevDrawerWidgetContent(state: WidgetUiState) {
/** Below this the widget shows its apps as a strip of icons. */
private val SHORT_HEIGHT = 130.dp

/** Below this the empty state leaves out its explanation so the button still fits. */
private val ROOMY_EMPTY_HEIGHT = 240.dp

/** Below this rows drop the package name and the actions. */
private val NARROW_WIDTH = 250.dp

Expand Down Expand Up @@ -170,22 +174,24 @@ private fun Header(state: WidgetUiState, showDetails: Boolean) {
}

@Composable
private fun EmptyState(context: Context, appWidgetId: Int) {
private fun EmptyState(context: Context, appWidgetId: Int, compact: Boolean) {
Column(
modifier = GlanceModifier.fillMaxSize().padding(24.dp),
modifier = GlanceModifier.fillMaxSize().padding(if (compact) 12.dp else 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = context.getString(R.string.widget_empty_title),
style = TextStyle(color = GlanceTheme.colors.onSurface, fontSize = 16.sp, fontWeight = FontWeight.Medium)
)
Spacer(GlanceModifier.height(4.dp))
Text(
text = context.getString(R.string.widget_empty_text),
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 14.sp)
)
Spacer(GlanceModifier.height(12.dp))
if (!compact) {
Spacer(GlanceModifier.height(4.dp))
Text(
text = context.getString(R.string.widget_empty_text),
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 14.sp)
)
}
Spacer(GlanceModifier.height(if (compact) 8.dp else 12.dp))
ChooseAppsButton(context, appWidgetId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.res.pluralStringResource
Expand Down Expand Up @@ -51,11 +55,18 @@ fun PackagePatternSheet(
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
val invalid = preview.pattern.isNotBlank() && !preview.isValid
// The field keeps its own text: the preview is computed asynchronously, and feeding it back into the
// field would drop or reorder characters typed quickly. It only describes the text once it caught up.
var text by rememberSaveable { mutableStateOf(preview.pattern) }
val upToDate = preview.pattern == text
val invalid = upToDate && text.isNotBlank() && !preview.isValid
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = preview.pattern,
onValueChange = onPatternChange,
value = text,
onValueChange = {
text = it
onPatternChange(it)
},
singleLine = true,
isError = invalid,
label = { Text(stringResource(R.string.packagefilter)) },
Expand All @@ -64,7 +75,7 @@ fun PackagePatternSheet(
invalid -> {
{ Text(stringResource(R.string.pattern_invalid)) }
}
preview.pattern.isNotBlank() -> {
upToDate && text.isNotBlank() -> {
{ Text(pluralStringResource(R.plurals.pattern_match_count, preview.matchCount, preview.matchCount)) }
}
else -> null
Expand All @@ -82,8 +93,8 @@ fun PackagePatternSheet(
}
Button(
modifier = Modifier.align(Alignment.End),
enabled = preview.isValid,
onClick = { onAdd(preview.pattern.trim()) }
enabled = upToDate && preview.isValid,
onClick = { onAdd(text.trim()) }
) {
Text(stringResource(R.string.add))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,17 @@ internal fun WidgetProfileEditor(
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// The field keeps its own text: the view state echoes the name back asynchronously, and feeding that into
// the field would drop or reorder characters typed quickly.
var name by rememberSaveable(widgetProfile.id) { mutableStateOf(viewState.widgetName ?: widgetProfile.name) }
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
singleLine = true,
value = viewState.widgetName ?: widgetProfile.name,
onValueChange = onNameChange,
value = name,
onValueChange = {
name = it
onNameChange(it)
},
label = { Text(text = stringResource(id = R.string.name)) },
// Every edit here changes these widgets, so say which they are.
supportingText = {
Expand Down
11 changes: 0 additions & 11 deletions app/src/main/java/de/psdev/devdrawer/ui/theme/Shape.kt

This file was deleted.

1 change: 0 additions & 1 deletion app/src/main/java/de/psdev/devdrawer/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ fun DevDrawerTheme(
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
shapes = Shapes,
content = content
)
}
1 change: 1 addition & 0 deletions app/src/main/play/contact-email.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
admin@psdev.de
1 change: 1 addition & 0 deletions app/src/main/play/contact-website.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://psdev.de
1 change: 1 addition & 0 deletions app/src/main/play/default-language.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
en-US
27 changes: 27 additions & 0 deletions app/src/main/play/listings/de-DE/full-description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
DevDrawer2 bringt die Apps, die du entwickelst, auf deinen Startbildschirm. Ein Widget listet jeden installierten Build, damit du ihn mit einem Tipp starten, seine App-Info öffnen oder ihn deinstallieren kannst.

SO FUNKTIONIERT ES
• Füge das Widget hinzu und wähle eine deiner Apps. Jede App, die mit demselben Schlüssel signiert ist, erscheint, auch solche, die du später installierst.
• Oder filtere Paketnamen mit einem Muster wie com.mycompany.*
• Fasse Filter in Profilen zusammen und nutze sie in mehreren Widgets.

GEMACHT FÜR DEN ENTWICKLERALLTAG
• Debug-, Beta- und Release-Builds nebeneinander
• Deinstallieren und App-Info direkt im Widget
• Sortierung nach zuletzt aktualisiert, zuerst installiert, Name oder Paketname
• Auf Wunsch wählen, welcher Bildschirm einer App geöffnet wird

DEIN WIDGET, WIE DU ES WILLST
• Neun Kopfzeilenfarben, auch die Farben deines Hintergrundbilds
• Heller und dunkler Modus
• Passt sich seiner Größe an: eine Symbolleiste, wenn es klein ist, ganze Zeilen, wenn es groß ist
• Beliebig viele Widgets, jedes mit eigenen Apps

PRIVAT UND OPEN SOURCE
• Keine Werbung, kein Konto
• Nutzungsanalyse nur, wenn du sie erlaubst (Firebase Analytics, keine personenbezogenen Daten). Absturzberichte werden über Firebase Crashlytics gesendet.
• Quellcode: https://github.com/PSDev/DevDrawer

Verfügbar auf Deutsch und Englisch.

Basiert auf dem ursprünglichen DevDrawer von Tony Owen, neu geschrieben in Kotlin.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/src/main/play/listings/de-DE/short-description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ein Widget für deine eigenen Apps: mit einem Tipp starten oder deinstallieren.
1 change: 1 addition & 0 deletions app/src/main/play/listings/de-DE/title.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DevDrawer2
27 changes: 27 additions & 0 deletions app/src/main/play/listings/en-US/full-description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
DevDrawer2 puts the apps you are developing on your home screen. One widget lists every build you have installed, so you can launch it, open its app info or uninstall it with a single tap.

HOW IT WORKS
• Add the widget and pick one of your apps. Every app signed with the same key appears, including ones you install later.
• Or match package names with a pattern like com.mycompany.*
• Combine filters in profiles and reuse them across widgets.

MADE FOR EVERYDAY DEVELOPMENT
• Debug, beta and release builds side by side
• Uninstall and app info right in the widget
• Sort by last updated, first installed, name or package name
• Optionally choose which screen of an app to open

YOUR WIDGET, YOUR WAY
• Nine header colours, including your wallpaper colours
• Light and dark mode
• Adapts to its size: a strip of icons when small, full rows when large
• As many widgets as you like, each with its own apps

PRIVATE AND OPEN SOURCE
• No ads, no account
• Usage analytics only if you allow them (Firebase Analytics, no personal data). Crash reports are sent via Firebase Crashlytics.
• Source code: https://github.com/PSDev/DevDrawer

Available in English and German.

Based on the original DevDrawer by Tony Owen, rewritten in Kotlin.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/src/main/play/listings/en-US/short-description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A home-screen widget for your own apps: launch, inspect or uninstall in one tap.
1 change: 1 addition & 0 deletions app/src/main/play/listings/en-US/title.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DevDrawer2
7 changes: 7 additions & 0 deletions app/src/main/play/release-notes/de-DE/default.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Komplett neu gestaltet:
• Ein neues Widget, das sich seiner Größe anpasst, mit neun Kopfzeilenfarben und dunklem Modus
• Geführte Einrichtung beim Hinzufügen eines Widgets
• Filter in verständlicher Sprache, mit Live-Vorschau der passenden Apps
• Änderungen werden automatisch gespeichert, gelöschte Profile lassen sich wiederherstellen
• Übersichtliche Auswahl, welcher Bildschirm einer App geöffnet wird
• Deutsche Übersetzung
7 changes: 7 additions & 0 deletions app/src/main/play/release-notes/en-US/default.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
A complete redesign:
• A new widget that adapts to its size, with nine header colours and dark mode
• Guided setup when you add a widget
• Filters in plain language, with a live preview of the apps they match
• Edits save automatically, and deleted profiles can be restored
• Pick which screen of an app to open from a clear list
• German translation
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/src/main/res/drawable-nodpi/devdrawer_widget_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<string name="pref_dynamic_color_title">Material-You-Farben</string>
<string name="pref_dynamic_color_summary">Farben aus deinem Hintergrundbild verwenden</string>
<string name="pref_feature_analytics_opted_in_title">Nutzungsanalyse teilen</string>
<string name="pref_feature_analytics_opted_in_summary">Welche Bildschirme du öffnest und Absturzberichte, über Firebase. Keine personenbezogenen Daten.</string>
<string name="pref_feature_analytics_opted_in_summary">Welche Bildschirme du öffnest, über Firebase Analytics. Keine personenbezogenen Daten. Absturzberichte werden immer gesendet.</string>
<string name="no_apps_available">Keine Apps verfügbar oder alle bereits als Filter hinzugefügt</string>
<string name="more">Mehr</string>
<string name="include_system_apps">System-Apps einbeziehen</string>
Expand All @@ -63,7 +63,7 @@

<string name="select_option">Option auswählen</string>
<string name="analytics_opt_in_title">Nutzungsanalyse</string>
<string name="analytics_opt_in_text">Hilf, DevDrawer zu verbessern, indem du teilst, welche Bildschirme du öffnest, und Absturzberichte sendest (über Firebase). Keine personenbezogenen Daten. Du kannst das jederzeit in den Einstellungen ändern.</string>
<string name="analytics_opt_in_text">Hilf, DevDrawer zu verbessern, indem du teilst, welche Bildschirme du öffnest (über Firebase Analytics). Keine personenbezogenen Daten. Du kannst das jederzeit in den Einstellungen ändern.</string>
<string name="analytics_opt_in">Erlauben</string>
<string name="analytics_opt_out">Nein danke</string>
<string name="analytics_opt_in_thanks">Danke! Du kannst deine Entscheidung jederzeit im Einstellungen-Tab ändern.</string>
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
<string name="pref_dynamic_color_title">Material You colors</string>
<string name="pref_dynamic_color_summary">Use colors generated from your wallpaper</string>
<string name="pref_feature_analytics_opted_in_title">Share usage analytics</string>
<string name="pref_feature_analytics_opted_in_summary">Which screens you open and crash reports, via Firebase. No personal data.</string>
<string name="pref_feature_analytics_opted_in_summary">Which screens you open, via Firebase Analytics. No personal data. Crash reports are sent either way.</string>
<string name="no_apps_available">No apps available or all already added as filter</string>
<string name="more">More</string>

Expand All @@ -103,7 +103,7 @@
<!-- Dialogs -->
<string name="select_option">Select option</string>
<string name="analytics_opt_in_title">Usage analytics</string>
<string name="analytics_opt_in_text">Help improve DevDrawer by sharing which screens you open and crash reports, via Firebase. No personal data. You can change this anytime in Settings.</string>
<string name="analytics_opt_in_text">Help improve DevDrawer by sharing which screens you open, via Firebase Analytics. No personal data. You can change this anytime in Settings.</string>
<string name="analytics_opt_in">Allow</string>
<string name="analytics_opt_out">No thanks</string>
<string name="analytics_opt_in_thanks">Thank you! You can change your decision anytime on the settings tab.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class AnalyticsOptInCardTest {
// When
composeTestRule.onNodeWithText("Usage analytics").assertExists()
composeTestRule.onNodeWithText("No personal data", substring = true).assertExists()
// Crash reports are sent regardless of this choice, so the card must not offer them.
composeTestRule.onNodeWithText("crash", substring = true, ignoreCase = true).assertDoesNotExist()
composeTestRule.onNodeWithText("No thanks").assertIsEnabled().performClick()
composeTestRule.onNodeWithText("Allow").assertIsEnabled().performClick()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,20 @@ class DevDrawerWidgetContentTest {
// Then
onNode(hasText("Choose apps")).assertExists()
}

@Test
fun `given a two-row widget without apps, when rendered, then the empty state drops its explanation so the button fits`() =
runGlanceAppWidgetUnitTest {
// Given
setContext(ApplicationProvider.getApplicationContext())
setAppWidgetSize(DpSize(300.dp, 180.dp))

// When
provideComposable { DevDrawerWidgetContent(state(emptyList())) }

// Then
onNode(hasText("No apps match this widget")).assertExists()
onNode(hasText("Choose apps")).assertExists()
onNode(hasText("Choose which of your apps it should list.")).assertDoesNotExist()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,30 @@ class PackagePatternSheetTest {
// Then
assertEquals("com.", typed.last())
}

@Test
fun `given a preview that lags behind, when typing, then the field still shows exactly what was typed`() {
// Given: the preview never catches up, like a slow recomputation
show(PatternPreview())

// When
composeTestRule.onNodeWithText("Package filter").performTextInput("com.example.*")

// Then
composeTestRule.onNodeWithText("com.example.*").assertExists()
}

@Test
fun `given a valid pattern that matches no app yet, when shown, then it can still be added`() {
// Given: e.g. a company prefix before any of its apps is installed
var added: String? = null
show(PatternPreview(pattern = "com.mycompany.*", isValid = true, matchCount = 0, apps = emptyList()), onAdd = { added = it })

// When
composeTestRule.onNodeWithText("0 apps match").assertExists()
composeTestRule.onNodeWithText("Add").assertIsEnabled().performClick()

// Then
assertEquals("com.mycompany.*", added)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.psdev.devdrawer.profiles.ui.editor

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTextReplacement
import de.psdev.devdrawer.database.WidgetProfile
import de.psdev.devdrawer.ui.theme.DevDrawerTheme
import org.junit.Rule
Expand Down Expand Up @@ -43,4 +44,16 @@ class WidgetProfileEditorScreenTest {

composeTestRule.onNodeWithText("Not used by any widget").assertExists()
}

@Test
fun `given a name update that lags behind, when typing, then the field still shows exactly what was typed`() {
// Given: the view state never reflects the typed name, like a slow combined flow
show(emptyList())

// When
composeTestRule.onNodeWithText("Signed like DevDrawer2").performTextReplacement("Work apps")

// Then
composeTestRule.onNodeWithText("Work apps").assertExists()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ class SettingsScreenTest {

// Then
composeTestRule.onNodeWithText("Share usage analytics").assertExists()
composeTestRule.onNodeWithText("Which screens you open and crash reports, via Firebase. No personal data.").assertExists()
composeTestRule.onNodeWithText("Which screens you open, via Firebase Analytics. No personal data. Crash reports are sent either way.").assertExists()
}
}
Loading
Loading