Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
67c59d4
ci: Refactor APK signing process in GitHub workflow and update build.…
alexwilson1 Jul 20, 2025
d898d71
ci: Enhance version bump logic in GitHub workflow to handle initial r…
alexwilson1 Jul 20, 2025
6135d41
ci: Update signing configuration in build.gradle.kts to use property …
alexwilson1 Jul 20, 2025
ac7510a
refactor: Remove RootShell dependency and related code from Permissio…
alexwilson1 Jul 20, 2025
5ca4e09
fix: Update storeFile path in build.gradle.kts to ensure correct sign…
alexwilson1 Jul 20, 2025
894c3be
chore: bump version to v0.1.0 [skip ci]
actions-user Jul 20, 2025
6461009
refactor: Remove unused AppExceptionListType references and integrate…
alexwilson1 Jul 20, 2025
6bddc34
ci: Add logging for version bump determination in GitHub workflow
alexwilson1 Jul 20, 2025
5a10a93
ci: Refactor semantic version parsing in GitHub workflow for improved…
alexwilson1 Jul 20, 2025
a575ab6
ci: Enhance logging and clarity in version bump determination process…
alexwilson1 Jul 20, 2025
f225663
ci: Streamline version bump determination logging and enhance commit …
alexwilson1 Jul 20, 2025
71035a4
ci: Fix version increment logic in GitHub workflow for clarity and co…
alexwilson1 Jul 20, 2025
da8b279
chore: bump version to v0.1.1 [skip ci]
actions-user Jul 20, 2025
fc2dab7
refactor: Update OpenAppExceptionsTile to support dynamic title and l…
alexwilson1 Jul 20, 2025
d4b7dd8
chore: bump version to v0.1.2 [skip ci]
actions-user Jul 20, 2025
4b1e2a3
refactor: Integrate AppExceptionListType into AppExceptionsScreen and…
alexwilson1 Jul 20, 2025
c599146
chore: bump version to v0.1.3 [skip ci]
actions-user Jul 20, 2025
932233b
refactor: Adjust allowed daily color screen time range step from 15 t…
alexwilson1 Jul 20, 2025
d953b9e
chore: remove outdated changelog files for releases v0.1.0 to v0.1.3 …
alexwilson1 Jul 20, 2025
860aa89
docs: Update README to clarify the app's approach to digital detoxing…
alexwilson1 Jul 20, 2025
9d2b5cb
chore: bump version to v0.1.4 [skip ci]
actions-user Jul 20, 2025
52e6a8f
Delete fastlane/metadata/android/en-US/changelogs/104.txt
alexwilson1 Jul 21, 2025
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
84 changes: 60 additions & 24 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,50 +60,89 @@ jobs:
- name: Determine version bump
id: version
run: |
# Get the latest tag
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $latest_tag"
echo "Determining version bump..."

# Get the latest tag, or an empty string if none exist
echo "Checking for existing git tags..."
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "Latest tag (if any): $latest_tag"

# Determine the commit range for analysis
echo "Determining commit range for analysis..."
if [ -z "$latest_tag" ]; then
# No tags exist, so analyze all commits for the first release
commit_range="HEAD"
# Set a base version for the very first run
latest_version="0.0.0"
echo "No existing tags found, using base version: $latest_version"
else
# Analyze commits since the last tag
commit_range="$latest_tag..HEAD"
latest_version=${latest_tag#v}
echo "Found tag $latest_tag, extracted version: $latest_version"
fi
echo "Analyzing commits in range: $commit_range"

# Parse semantic version
latest_version=${latest_tag#v}
IFS='.' read -r major minor patch <<< "$latest_version"
echo "Parsing version components from: $latest_version"
major=$(echo "$latest_version" | cut -d. -f1)
minor=$(echo "$latest_version" | cut -d. -f2)
patch=$(echo "$latest_version" | cut -d. -f3)
echo "Parsed version - Major: $major, Minor: $minor, Patch: $patch"

# Default bump type based on conventional commits
bump_type="patch" # Default to patch
echo "Default bump type set to: $bump_type"

# Check for force bump input
if [ -n "${{ github.event.inputs.force-bump }}" ]; then
bump_type="${{ github.event.inputs.force-bump }}"
echo "Force bump detected: $bump_type"
else
# Get conventional commits since last tag
commits=$(git log $latest_tag..HEAD --pretty=format:"%s")
# Get conventional commits from the determined range
echo "Analyzing commit messages for conventional commit patterns..."
commits=$(git log $commit_range --pretty=format:"%s")
echo "Found $(echo "$commits" | wc -l) commits to analyze"

echo "Checking for feature commits..."
if echo "$commits" | grep -q "^feat\|^feat("; then
bump_type="minor"
echo "Feature commit detected. Bumping to minor version."
fi

echo "Checking for breaking changes..."
if echo "$commits" | grep -q "^BREAKING CHANGE\|^feat!:\|^fix!:"; then
bump_type="major"
echo "Breaking change detected. Bumping to major version."
fi
fi

echo "Final bump type determined: $bump_type"

# Increment version based on bump type
echo "Incrementing version numbers..."
if [ "$bump_type" = "patch" ]; then
((patch++))
echo "Incrementing patch version from $patch to $((patch + 1))"
patch=$((patch + 1))
elif [ "$bump_type" = "minor" ]; then
echo "Incrementing minor version from $minor to $((minor + 1)) and resetting patch to 0"
minor=$((minor + 1))
patch=0
elif [ "$bump_type" = "major" ]; then
echo "Incrementing major version from $major to $((major + 1)) and resetting minor/patch to 0"
major=$((major + 1))
minor=0
patch=0
fi

# Calculate version code (e.g., 10203 for 1.2.3)
echo "Calculating version code..."
version_code=$((major * 10000 + minor * 100 + patch))
echo "Calculated version code: $version_code"

# Set outputs
new_version="$major.$minor.$patch"
echo "Setting environment variables..."
echo "NEW_VERSION=$new_version" >> $GITHUB_ENV
echo "VERSION_CODE=$version_code" >> $GITHUB_ENV
echo "BUMP_TYPE=$bump_type" >> $GITHUB_ENV
Expand Down Expand Up @@ -169,15 +208,15 @@ jobs:
fi

# Handle Features
features=$(git log v2.0.6..HEAD --pretty=format:"%s" | grep -E "^feat(\(.*\))?:" | sed -E "s/^feat(\(.*\))?: //" | sed 's/^/- /' || true)
features=$(git log $git_range --pretty=format:"%s" | grep -E "^feat(\(.*\))?:" | sed -E "s/^feat(\(.*\))?: //" | sed 's/^/- /' || true)
if [ -n "$features" ]; then
echo "✨ New Features" >> "$changelog_file"
echo "$features" >> "$changelog_file"
echo "" >> "$changelog_file"
fi

# Handle Bug Fixes
fixes=$(git log v2.0.6..HEAD --pretty=format:"%s" | grep -E "^fix(\(.*\))?:" | sed -E "s/^fix(\(.*\))?: //" | sed 's/^/- /' || true)
fixes=$(git log $git_range --pretty=format:"%s" | grep -E "^fix(\(.*\))?:" | sed -E "s/^fix(\(.*\))?: //" | sed 's/^/- /' || true)
if [ -n "$fixes" ]; then
echo "🐛 Bug Fixes" >> "$changelog_file"
echo "$fixes" >> "$changelog_file"
Expand All @@ -186,24 +225,21 @@ jobs:

# Show the changelog
cat "$changelog_file"

- name: Decode Keystore
run: echo "${{ secrets.SIGNING_KEY }}" | base64 --decode > release.keystore

- name: Build APK
- name: Build and Sign Release APK
run: ./gradlew assembleRelease

- name: Sign APK
uses: ilharp/sign-android-release@nightly
id: sign_app
with:
releaseDir: app/build/outputs/apk/release
signingKey: ${{ secrets.SIGNING_KEY }}
keyAlias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
buildToolsVersion: 35.0.0
env:
SIGNING_STORE_FILE: "release.keystore"
SIGNING_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

- name: Rename signed APK
run: |
mv ${{ steps.sign_app.outputs.signedFile }} ./app-$NEW_VERSION.apk
mv app/build/outputs/apk/release/app-release.apk ./app-v${{ env.NEW_VERSION }}.apk

- name: Create Git tag
run: |
Expand All @@ -222,6 +258,6 @@ jobs:
tag_name: v${{ env.NEW_VERSION }}
name: Release v${{ env.NEW_VERSION }}
body_path: fastlane/metadata/android/en-US/changelogs/${{ env.VERSION_CODE }}.txt
files: ./app-${{ env.NEW_VERSION }}.apk
files: ./app-v${{ env.NEW_VERSION }}.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Usually, "Digital Detoxing" apps in Android are about opting-in for a contract to not use our phones for the next X minutes. Or that we will not use the app Y for more than Z minutes. These contracts are often reinforced with financial incentives, i.e. if we fail we will have to pay a fee of a few dollars to the developers.

This app follows a different approach by stripping away all the attention-grabbing and manipulative features of other apps. DetoxDroid does not require you to choose a time and place when you want to "detox", or punish you if you fail. Instead, it aims to enable you to use your phone rather than letting your phone use you.
This app follows a different approach by default, stripping away all the attention-grabbing and manipulative features of other apps. DetoxDroid does not require you to choose a time and place when you want to "detox", or punish you if you fail. Instead, it aims to enable you to use your phone rather than letting your phone use you.

## Features

Expand Down
27 changes: 22 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,29 @@ android {
namespace = "com.flx_apps.digitaldetox"
compileSdk = 35

signingConfigs {
create("release") {
// Provide these values via GitHub Secrets and environment variables
val storeFile = System.getenv("SIGNING_STORE_FILE")
val storePassword = System.getenv("SIGNING_STORE_PASSWORD")
val keyAlias = System.getenv("SIGNING_KEY_ALIAS")
val keyPassword = System.getenv("SIGNING_KEY_PASSWORD")

if (storeFile != null && !storeFile.isNullOrEmpty()) {
this.storeFile = file("$rootDir/$storeFile")
this.storePassword = storePassword
this.keyAlias = keyAlias
this.keyPassword = keyPassword
}
}
}

defaultConfig {
applicationId = "com.flx_apps.digitaldetox"
minSdk = 26
targetSdk = 33
versionCode = 20007
versionName = "2.0.7"
versionCode = 104
versionName = "0.1.4"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand All @@ -30,6 +47,8 @@ android {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
// Tell the release build to use signing config
signingConfig = signingConfigs.getByName("release")
}
}
compileOptions {
Expand Down Expand Up @@ -106,10 +125,8 @@ dependencies {
// Material Icons
implementation("androidx.compose.material:material-icons-extended")

// RootTools for running (adb) commands as root
implementation("com.github.Stericson:RootShell:1.6")
}

kotlin.sourceSets.all {
languageSettings.enableLanguageFeature("DataObjects")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.flx_apps.digitaldetox.R
import com.flx_apps.digitaldetox.feature_types.AppExceptionListType
import com.flx_apps.digitaldetox.feature_types.FeatureId
import com.flx_apps.digitaldetox.feature_types.SupportsAppExceptionsFeature
import com.flx_apps.digitaldetox.ui.screens.app_exceptions.AppExceptionItem
import com.flx_apps.digitaldetox.ui.screens.app_exceptions.AppExceptionsViewModel
import com.flx_apps.digitaldetox.ui.screens.nav_host.NavViewModel
Expand All @@ -72,6 +71,8 @@ fun ManageAppExceptionsScreen(
appExceptionsViewModel: AppExceptionsViewModel = AppExceptionsViewModel.withFeatureId(featureId),
) {
var showSearchBar by remember { mutableStateOf(false) }
val listType = appExceptionsViewModel.exceptionListType

Scaffold(topBar = {
TopAppBar(navigationIcon = {
IconButton(onClick = {
Expand Down Expand Up @@ -105,7 +106,7 @@ fun ManageAppExceptionsScreen(
}) {}
}
AnimatedVisibility(visible = !it) {
Text(stringResource(id = R.string.feature_settings_exceptions))
Text(stringResource(id = if (listType == AppExceptionListType.NOT_LIST) R.string.feature_settings_exceptions else R.string.feature_settings_inclusions))
}
}
}, actions = {
Expand Down Expand Up @@ -147,6 +148,8 @@ fun InstalledAppsList(
appExceptionsViewModel: AppExceptionsViewModel = viewModel()
) {
val appExceptions = appExceptionsViewModel.appExceptionItems.collectAsState().value
val listType = appExceptionsViewModel.exceptionListType

if (appExceptions.isNullOrEmpty()) {
Center {
if (appExceptions == null) {
Expand All @@ -158,7 +161,12 @@ fun InstalledAppsList(
} else {
LazyColumn {
item {
InfoCard(infoText = stringResource(id = R.string.feature_settings_exceptions_description))
val descriptionResId = if (listType == AppExceptionListType.NOT_LIST) {
R.string.feature_settings_exceptions_description
} else {
R.string.feature_settings_inclusions_description
}
InfoCard(infoText = stringResource(id = descriptionResId))
}
items(appExceptions, key = { item -> item.packageName }) { appException ->
AppExceptionListItem(appException)
Expand Down Expand Up @@ -283,24 +291,9 @@ fun AppExceptionsListSettingsSheet(
}
}
})
val listTypeMap = mapOf(
R.string.feature_settings_exceptions_listType_notList to AppExceptionListType.NOT_LIST,
R.string.feature_settings_exceptions_listType_onlyList to AppExceptionListType.ONLY_LIST
).filter { (viewModel.feature as SupportsAppExceptionsFeature).listTypes.contains(it.value) }
if (listTypeMap.size > 1) {
androidx.compose.material3.ListItem(headlineContent = { Text(stringResource(id = R.string.feature_settings_exceptions_listType)) },
supportingContent = {
Column {
Text(text = stringResource(id = R.string.feature_settings_exceptions_listType_description))
OptionsRow(options = listTypeMap,
selectedOption = viewModel.exceptionListType.collectAsState().value,
onOptionSelected = { viewModel.setExceptionListType(it as AppExceptionListType) })
}
})
}
// FIXME there should be a better way to do this, e.g. using Modifier.navigationBarsPadding(),
// but I couldn't get it to work for some reason
Box(modifier = Modifier.height(48.dp))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class AppExceptionsViewModel @Inject constructor(
*/
private val appExceptionsFeature = feature as SupportsAppExceptionsFeature

val exceptionListType: AppExceptionListType
get() = appExceptionsFeature.appExceptionListType

/**
* Holds the list of all installed apps.
*/
Expand Down Expand Up @@ -85,9 +88,6 @@ class AppExceptionsViewModel @Inject constructor(
private val _filteredAppExceptionItems = MutableStateFlow<List<AppExceptionItem>?>(null)
val appExceptionItems = _filteredAppExceptionItems.asStateFlow()

private val _exceptionListType = MutableStateFlow(appExceptionsFeature.appExceptionListType)
val exceptionListType = _exceptionListType.asStateFlow()

/**
* Whether the bottom sheet to configure the list should be shown.
*/
Expand Down Expand Up @@ -198,15 +198,6 @@ class AppExceptionsViewModel @Inject constructor(
filterApps()
}

/**
* Sets the type of the app exception list.
* @see AppExceptionListType
*/
fun setExceptionListType(type: AppExceptionListType) {
appExceptionsFeature.appExceptionListType = type
_exceptionListType.value = type
}

/**
* Sets whether the bottom sheet to configure the list should be shown.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,21 @@ import com.flx_apps.digitaldetox.ui.widgets.SimpleListTile
fun OpenAppExceptionsTile(
featureViewModel: FeatureViewModel = viewModel(),
navViewModel: NavViewModel = viewModel(viewModelStoreOwner = LocalContext.current as MainActivity),
titleText: String = stringResource(id = R.string.feature_settings_exceptions),
titleText: String? = null,
subtitleText: String? = null,
listType: AppExceptionListType? = null
) {
val feature = featureViewModel.feature as SupportsAppExceptionsFeature
SimpleListTile(titleText = titleText, subtitleText = subtitleText ?: stringResource(
id = if (feature.appExceptionListType == AppExceptionListType.NOT_LIST) R.string.feature_settings_exceptions__notListed
val currentListType = listType ?: feature.appExceptionListType

val finalTitleText = titleText ?: if (currentListType == AppExceptionListType.NOT_LIST) {
stringResource(id = R.string.feature_settings_exceptions)
} else {
stringResource(id = R.string.feature_settings_inclusions)
}

SimpleListTile(titleText = finalTitleText, subtitleText = subtitleText ?: stringResource(
id = if (currentListType == AppExceptionListType.NOT_LIST) R.string.feature_settings_exceptions__notListed
else R.string.feature_settings_exceptions__onlyListed, feature.appExceptions.size
), trailing = {
Icon(
Expand Down
Loading