Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d4031c6
Bump compileSdk and targetSdk to sdk 33, updated dependencies and mig…
Lej77 Dec 26, 2022
de3acb9
Request permission to send notifications on Android 13 (sdk 33)
Lej77 Dec 26, 2022
c30cacf
Allow more actions when selecting text
Lej77 Dec 26, 2022
2054e3b
Improve tests, ParentPinTest failed because of timeout on newer andro…
Lej77 Dec 26, 2022
41ce154
Fix issue #20 which caused issues when selecting text
Lej77 Dec 26, 2022
517eb9a
Follow system's night mode setting
Lej77 Dec 26, 2022
1c88fd9
Register event listener for "show notification actions" to correctly …
Lej77 Dec 26, 2022
8fca239
Handle overflow when text gets too long, fixes issue #19
Lej77 Dec 26, 2022
ef6b2a1
Create one notification channel for each visibility level and provide…
Lej77 Dec 26, 2022
58c7de1
Don't overlap drop down with the spinner it controls (requires API le…
Lej77 Dec 26, 2022
47c6e3e
Place text inputs vertically and some other small style improvements
Lej77 Dec 26, 2022
03601b6
Close activity when backing out of it
Lej77 Dec 26, 2022
48b77c1
Move package name into gradle file since having it in the manifest is…
Lej77 Dec 27, 2022
dfc6678
Remove label from activity, should fix the name mixup in issue #29
Lej77 Dec 27, 2022
b6b5678
Fix lint warnings
Lej77 Dec 27, 2022
9470fa9
actually restore notifications when app is opened and not just when d…
Lej77 Dec 28, 2022
df46fad
Restore notifications directly after an app update
Lej77 Dec 28, 2022
42c4155
Documentation for OnUpdateReceiver
Lej77 Dec 28, 2022
354b4b3
Don't make sounds when updating an existing notification
Lej77 Dec 28, 2022
3fadbca
Remove old notification channel
Lej77 Dec 29, 2022
1dcaf5f
Don't make sounds when creating notifications
Lej77 Dec 29, 2022
2f620e8
Don't show notification count as application icon badges in Launcher …
Lej77 Dec 29, 2022
b7beec2
simpler return type of helper function
Lej77 Dec 29, 2022
c302570
double check that notification doesn't exist before restoring it
Lej77 Dec 30, 2022
c02ba56
Use a different application id in debug builds
Lej77 Dec 30, 2022
661cc80
Move the namespace property to the right place in the build.gradle file
Lej77 Dec 30, 2022
71dbc6b
Never make sounds when creating notifications
Lej77 Dec 30, 2022
f1a3d19
Revert "Never make sounds when creating notifications"
Lej77 Dec 30, 2022
ef93c01
Merge branch 'dotWee:master' into master
Lej77 Aug 12, 2025
bf4a390
Update android.yml (allow running manually)
Lej77 Aug 12, 2025
c4dc483
Create release.yml
Lej77 Aug 12, 2025
51d1545
chore: signing config in "app/build.gradle"
Lej77 Aug 12, 2025
095de48
chore: make signing config optional for release builds
Lej77 Aug 12, 2025
93f78a3
chore: fix release.yml so that the built apk is actually released
Lej77 Aug 12, 2025
4dd9730
chore: better gradle build
Lej77 Aug 12, 2025
f239f50
chore: Update release.yml with correct keystore path
Lej77 Aug 12, 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
1 change: 1 addition & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
workflow_dispatch:

jobs:
build:
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: 'Publish new release with Andorid APK'

on:
push:
tags: ['v*']
# This workflow will trigger on each push of a tag that starts with a "v" to create or update a GitHub release, build your app, and upload the artifacts to the release.

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Decode Keystore from GitHub Secrets
env:
KEYSTORE_FILE: ${{ secrets.KEYSTORE_FILE }}
run: |
echo $KEYSTORE_FILE | base64 -d > app/my-release-key.keystore

- name: Build release APK with Gradle
run: ./gradlew build
env:
SIGNING_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}

- name: Post Build | Upload binary
uses: actions/upload-artifact@v4
with:
name: release-apk
path: app/build/outputs/apk/release
retention-days: 3
if-no-files-found: error

release:
name: Release
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- name: Download binary from previous job
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Display structure of downloaded files
run: ls artifacts

# Upload release asset: https://github.com/actions/upload-release-asset
# which recommends: https://github.com/softprops/action-gh-release
- name: Release
uses: softprops/action-gh-release@v2
if: github.ref_type == 'tag'
with:
files: artifacts/*
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,9 @@ captures/

# Keystore files
*.jks

# Grade Built files:
app/build/

# Intellij APK Release:
app/release/
39 changes: 30 additions & 9 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
compileSdkVersion 33
buildToolsVersion "30.0.3"
namespace 'de.dotwee.micropinner'

defaultConfig {
applicationId "de.dotwee.micropinner"
minSdkVersion 16
targetSdkVersion 27
targetSdkVersion 33
versionCode 29
versionName "v2.2.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

buildTypes {
signingConfigs {
def keystoreFilePath = System.getenv("SIGNING_STORE_FILE") ?: "my-release-key.keystore"

if (file(keystoreFilePath).exists() ||
System.getenv("SIGNING_STORE_PASSWORD") ||
System.getenv("SIGNING_KEY_ALIAS") ||
System.getenv("SIGNING_KEY_PASSWORD")) {
release {
storeFile file(keystoreFilePath)
storePassword System.getenv("SIGNING_STORE_PASSWORD")
keyAlias System.getenv("SIGNING_KEY_ALIAS")
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
}
}
}

buildTypes {
debug {
minifyEnabled false
applicationIdSuffix '.debug'
}

release {
// Apply signingConfig only if it was configured
if (signingConfigs.hasProperty('release')) {
signingConfig signingConfigs.release
}
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
Expand All @@ -34,20 +55,20 @@ android {
}

dependencies {
implementation 'com.android.support:appcompat-v7:27.1.0'
implementation 'androidx.appcompat:appcompat:1.5.1'

androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1') {
androidTestImplementation('androidx.test.espresso:espresso-core:3.5.0') {
// Necessary if your app targets Marshmallow (since Espresso
// hasn't moved to Marshmallow yet)
exclude group: 'com.android.support', module: 'support-annotations'
}

androidTestImplementation('com.android.support.test.espresso:espresso-intents:3.0.1') {
androidTestImplementation('androidx.test.espresso:espresso-intents:3.5.0') {
// Necessary to avoid version conflicts
exclude group: 'com.android.support', module: 'support-annotations'
}

androidTestImplementation('com.android.support.test:runner:1.0.1') {
androidTestImplementation('androidx.test.ext:junit:1.1.4') {
// Necessary if your app targets Marshmallow (since the test runner
// hasn't moved to Marshmallow yet)
exclude group: 'com.android.support', module: 'support-annotations'
Expand Down
18 changes: 14 additions & 4 deletions app/src/androidTest/java/de/dotwee/micropinner/tools/Matches.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package de.dotwee.micropinner.tools;

import android.graphics.drawable.ColorDrawable;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.test.espresso.intent.Checks;
import android.support.test.espresso.matcher.BoundedMatcher;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.test.espresso.Root;
import androidx.test.espresso.intent.Checks;
import androidx.test.espresso.matcher.BoundedMatcher;
import android.view.View;
import android.widget.TextView;

Expand All @@ -16,6 +17,15 @@
*/
public final class Matches {

@NonNull
public static Matcher<Root> isToast() {
return new ToastMatcher();
}
@NonNull
public static Matcher<Root> isToast(int maxRetries) {
return new ToastMatcher(maxRetries);
}

/**
* This matcher checks if a TextView displays its text in
* a specific color.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Rule;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.dotwee.micropinner.tools;

import android.support.test.rule.ActivityTestRule;
import androidx.test.rule.ActivityTestRule;

import de.dotwee.micropinner.view.MainDialog;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package de.dotwee.micropinner.tools;

import android.os.IBinder;
import androidx.test.espresso.Root;
import android.view.WindowManager.LayoutParams;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
* This class allows to match Toast messages in tests with Espresso.
*
* Idea taken from: <a href="https://stackoverflow.com/questions/28390574/checking-toast-message-in-android-espresso">Checking toast message in android espresso - Stack Overflow</a>
*
* Usage in test class:
*
* <pre>
* {@code
* import somepkg.ToastMatcher.Companion.onToast;
*
* // To assert a toast does *not* pop up:
* onView(withText("text")).inRoot(new ToastMatcher()).check(doesNotExist());
* onView(withText(textId)).inRoot(new ToastMatcher()).check(doesNotExist());
*
* // To assert a toast does pop up:
* onView(withText("text")).inRoot(new ToastMatcher()).check(matches(isDisplayed()));
* onView(withText(textId)).inRoot(new ToastMatcher()).check(matches(isDisplayed()));
* }
*/
public class ToastMatcher extends TypeSafeMatcher<Root> {

/** Default for maximum number of retries to wait for the toast to pop up */
private static final int DEFAULT_MAX_FAILURES = 5;

/** Restrict number of false results from matchesSafely to avoid endless loop */
private int failures = 0;
private final int maxFailures;

public ToastMatcher() {
this(DEFAULT_MAX_FAILURES);
}
public ToastMatcher(int maxFailures) {
this.maxFailures = maxFailures;
}

@Override
public void describeTo(Description description) {
description.appendText("is toast");
}

@Override
public boolean matchesSafely(Root root) {
int type = root.getWindowLayoutParams().get().type;
if (type == LayoutParams.TYPE_TOAST || type == LayoutParams.TYPE_APPLICATION_OVERLAY) {
IBinder windowToken = root.getDecorView().getWindowToken();
IBinder appToken = root.getDecorView().getApplicationWindowToken();
if (windowToken == appToken) {
// windowToken == appToken means this window isn't contained by any other windows.
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
return true;
}
}
// Method is called again if false is returned which is useful because a toast may take some time to pop up. But for
// obvious reasons an infinite wait isn't of help. So false is only returned as often as maxFailures specifies.
return (++failures >= maxFailures);
}

}
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
package de.dotwee.micropinner.view;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import de.dotwee.micropinner.R;
import de.dotwee.micropinner.database.PinDatabase;
import de.dotwee.micropinner.tools.Matches;
import de.dotwee.micropinner.tools.PreferencesHandler;

import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isFocusable;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withSpinnerText;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isFocusable;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withSpinnerText;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.dotwee.micropinner.tools.TestTools.getPreferencesHandler;
import static de.dotwee.micropinner.tools.TestTools.recreateActivity;
import static org.hamcrest.Matchers.allOf;
Expand Down Expand Up @@ -108,9 +109,12 @@ public void testEmptyTitleToast() throws Exception {
// click pin button
onView(withText(R.string.dialog_action_pin)).perform(click());

// can't see toast if another toast is already present
onView(withText(R.string.message_visibility_unsupported)).inRoot(Matches.isToast())
.check(doesNotExist());

// verify toast existence
onView(withText(R.string.message_empty_title)).inRoot(
withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
onView(withText(R.string.message_empty_title)).inRoot(Matches.isToast())
.check(matches(isDisplayed()));
}

Expand Down
Loading