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
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Android Release

on:
push:
branches: [ main ]

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Decode keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks

- name: Build APK + AAB
run: |
chmod +x gradlew
./gradlew assembleRelease bundleRelease

- name: Extract versionName
id: version
run: |
VERSION=$(grep versionName app/build.gradle.kts | cut -d '"' -f2)
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: v${{ steps.version.outputs.version }}
generate_release_notes: true
files: |
app/build/outputs/apk/release/*.apk

- name: Upload AAB to Play Store (internal)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: io.github.iso53.nothingcompass
releaseFiles: app/build/outputs/bundle/release/*.aab
track: internal
status: completed
29 changes: 29 additions & 0 deletions PRIVACY_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Privacy Policy for Nothing Compass

**Last Updated: February 1, 2026**

Nothing Compass ("the App") is developed as an Open Source project by Yusuf İhsan Şimşek ("the Developer"). This Privacy Policy explains how information is handled within the App.

## 1. No Data Collection
Nothing Compass is designed to respect your privacy. The App does not collect, store, or transmit any personal data or usage information to the Developer or any third party. All calculations and sensor data processing occur entirely on your device.

## 2. Device Sensors and Location Data
- **Sensors**: The App uses your device's sensors (such as the magnetometer and accelerometer) to provide compass and inclinometer functionality. This sensor data is processed locally on your device in real-time.
- **Location**: If you enable "True North" features, the App may request access to your device's location to calculate magnetic declination. This location data is used only for calculation within the App, is processed locally, and is never uploaded, stored, or shared.

## 3. Permissions
- **Vibrate**: Used to provide haptic feedback to enhance the user experience. This can be disabled in the App settings.

## 4. Third-Party Services
The App may use the following third-party services which may collect information used to identify you:
- **Google Play Services**: Used for features such as In-App Reviews. Google's privacy policy applies: [Google Privacy & Terms](https://policies.google.com/privacy)
- **GitHub**: The App's source code is hosted on GitHub. If you visit the project page or report an issue, GitHub's privacy policy applies.

## 5. User-Initiated Communication
If you choose to contact the Developer for feedback, support, or to report an issue (e.g., via email or GitHub), the information you provide—including any diagnostic information generated by the App—will only be used to address your inquiry and improve the App.

## 6. Changes to This Privacy Policy
The Developer may update this Privacy Policy from time to time. Any changes will be posted on this page with an updated "Last Updated" date.

## 7. Contact
If you have any questions or suggestions regarding this Privacy Policy, please contact the Developer at [ihsansimsek5335@gmail.com](mailto:ihsansimsek5335@gmail.com) or via the [GitHub repository](https://github.com/iso53/Nothing-Compass).
8 changes: 8 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
id("com.google.android.gms.oss-licenses-plugin")
}

android {
Expand Down Expand Up @@ -54,4 +55,11 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation(libs.play.services.oss.licenses)
}

tasks.register("printVersionName") {
doLast {
println(android.defaultConfig.versionName)
}
}
120 changes: 49 additions & 71 deletions app/src/main/java/io/github/iso53/nothingcompass/OptionsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
Expand All @@ -17,6 +16,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.gms.oss.licenses.OssLicensesMenuActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import java.util.ArrayList;
Expand All @@ -28,10 +28,6 @@

public class OptionsActivity extends AppCompatActivity {

private static void onClick(View v) {
// TODO: Show OSS licenses
}

@Override
protected void onCreate(Bundle savedInstanceState) {
// Apply theme before super.onCreate
Expand All @@ -46,14 +42,14 @@ protected void onCreate(Bundle savedInstanceState) {

ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.optionsToolbar),
(v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, 0);
return insets;
});
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, 0);
return insets;
});

// Handle bottom padding for RecyclerView to avoid navigation bar overlap
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.optionsRecyclerView), (v,
insets) -> {
insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(),
systemBars.bottom + v.getPaddingBottom());
Expand All @@ -62,8 +58,7 @@ protected void onCreate(Bundle savedInstanceState) {

// Setup Toolbar
findViewById(R.id.optionsToolbar).setOnClickListener(v -> finish());
((androidx.appcompat.widget.Toolbar) findViewById(R.id.optionsToolbar))
.setNavigationOnClickListener(v -> finish());
((androidx.appcompat.widget.Toolbar) findViewById(R.id.optionsToolbar)).setNavigationOnClickListener(v -> finish());

setupRecyclerView();
}
Expand All @@ -78,8 +73,8 @@ private void setupRecyclerView() {
items.add(new OptionItem(getString(R.string.category_preferences)));
items.add(new OptionItem(getString(R.string.item_theme), null, R.drawable.ic_settings,
v -> showThemeSelectionDialog()));
items.add(new OptionItem(getString(R.string.item_haptic_feedback), null, R.drawable.ic_vibration,
v -> showHapticFeedbackSelectionDialog()));
items.add(new OptionItem(getString(R.string.item_haptic_feedback), null,
R.drawable.ic_vibration, v -> showHapticFeedbackSelectionDialog()));

// Category: App
items.add(new OptionItem(getString(R.string.category_app)));
Expand All @@ -95,13 +90,14 @@ private void setupRecyclerView() {
// Category: Support
items.add(new OptionItem(getString(R.string.category_support)));
items.add(new OptionItem(getString(R.string.item_license), null, R.drawable.ic_license,
v -> openUrl("https://github.com/iso53/Nothing-Compass/blob/main/LICENSE.md")));
v -> openUrl("https://github.com/iso53/Nothing-Compass/blob/main/LICENSE")));
items.add(new OptionItem(getString(R.string.item_third_party_licenses), null,
R.drawable.ic_verified, OptionsActivity::onClick));
R.drawable.ic_verified, v -> startActivity(new Intent(this,
OssLicensesMenuActivity.class))));
items.add(new OptionItem(getString(R.string.item_manage_permission), null,
R.drawable.ic_permission, v -> openAppSettings()));
items.add(new OptionItem(getString(R.string.item_help_feedback), null, R.drawable.ic_help,
v -> sendFeedbackEmail()));
items.add(new OptionItem(getString(R.string.item_help_feedback), null, R.drawable.ic_help
, v -> sendFeedbackEmail()));
items.add(new OptionItem(getString(R.string.item_rate_app), null, R.drawable.ic_rate,
v -> openPlayStore()));

Expand All @@ -110,63 +106,50 @@ private void setupRecyclerView() {
}

private void showThemeSelectionDialog() {
String[] themes = {
getString(R.string.theme_light),
getString(R.string.theme_dark),
getString(R.string.theme_system)
};
String[] themes = {getString(R.string.theme_light), getString(R.string.theme_dark),
getString(R.string.theme_system)};

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int currentTheme = prefs.getInt(PreferenceConstants.THEME,
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);

int checkedItem = 2; // Default to System
if (currentTheme == AppCompatDelegate.MODE_NIGHT_NO)
checkedItem = 0;
else if (currentTheme == AppCompatDelegate.MODE_NIGHT_YES)
checkedItem = 1;

new MaterialAlertDialogBuilder(this)
.setTitle(R.string.item_theme)
.setSingleChoiceItems(themes, checkedItem, (dialog, which) -> {
int mode;
switch (which) {
case 0:
mode = AppCompatDelegate.MODE_NIGHT_NO;
break;
case 1:
mode = AppCompatDelegate.MODE_NIGHT_YES;
break;
default:
mode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
break;
}
prefs.edit().putInt(PreferenceConstants.THEME, mode).apply();
AppCompatDelegate.setDefaultNightMode(mode);
dialog.dismiss();
})
.show();
if (currentTheme == AppCompatDelegate.MODE_NIGHT_NO) checkedItem = 0;
else if (currentTheme == AppCompatDelegate.MODE_NIGHT_YES) checkedItem = 1;

new MaterialAlertDialogBuilder(this).setTitle(R.string.item_theme).setSingleChoiceItems(themes, checkedItem, (dialog, which) -> {
int mode;
switch (which) {
case 0:
mode = AppCompatDelegate.MODE_NIGHT_NO;
break;
case 1:
mode = AppCompatDelegate.MODE_NIGHT_YES;
break;
default:
mode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
break;
}
prefs.edit().putInt(PreferenceConstants.THEME, mode).apply();
AppCompatDelegate.setDefaultNightMode(mode);
dialog.dismiss();
}).show();
}

private void showHapticFeedbackSelectionDialog() {
String[] options = {
getString(R.string.haptic_feedback_on),
getString(R.string.haptic_feedback_off)
};
String[] options = {getString(R.string.haptic_feedback_on),
getString(R.string.haptic_feedback_off)};

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean currentHaptic = prefs.getBoolean(PreferenceConstants.HAPTIC_FEEDBACK, true);

int checkedItem = currentHaptic ? 0 : 1;

new MaterialAlertDialogBuilder(this)
.setTitle(R.string.item_haptic_feedback)
.setSingleChoiceItems(options, checkedItem, (dialog, which) -> {
boolean enabled = (which == 0);
prefs.edit().putBoolean(PreferenceConstants.HAPTIC_FEEDBACK, enabled).apply();
dialog.dismiss();
})
.show();
new MaterialAlertDialogBuilder(this).setTitle(R.string.item_haptic_feedback).setSingleChoiceItems(options, checkedItem, (dialog, which) -> {
boolean enabled = (which == 0);
prefs.edit().putBoolean(PreferenceConstants.HAPTIC_FEEDBACK, enabled).apply();
dialog.dismiss();
}).show();
}

private void openPlayStore() {
Expand All @@ -175,8 +158,8 @@ private void openPlayStore() {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageName)));
} catch (ActivityNotFoundException e) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=" + packageName)));
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google" +
".com/store/apps/details?id=" + packageName)));
}
}

Expand All @@ -199,18 +182,13 @@ private void sendFeedbackEmail() {
} catch (Exception ignored) {
}

String deviceInfo = "\n\n\n------------------------------" +
"\nDevice Diagnostics (Please do not delete):" +
"\nApp Version: " + appVersion +
"\nAndroid Version: " + android.os.Build.VERSION.RELEASE + " (SDK "
+ android.os.Build.VERSION.SDK_INT + ")" +
"\nManufacturer: " + android.os.Build.MANUFACTURER +
"\nModel: " + android.os.Build.MODEL +
"\nProduct: " + android.os.Build.PRODUCT;
String deviceInfo = "\n\n\n------------------------------" + "\nDevice Diagnostics " +
"(Please do not delete):" + "\nApp Version: " + appVersion + "\nAndroid Version: "
+ android.os.Build.VERSION.RELEASE + " (SDK " + android.os.Build.VERSION.SDK_INT + ")" + "\nManufacturer: " + android.os.Build.MANUFACTURER + "\nModel: " + android.os.Build.MODEL + "\nProduct: " + android.os.Build.PRODUCT;

Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { feedbackEmail });
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{feedbackEmail});
intent.putExtra(Intent.EXTRA_SUBJECT, "Feedback/Support - Nothing Compass");
intent.putExtra(Intent.EXTRA_TEXT, deviceInfo);

Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ material = "1.13.0"
activity = "1.12.2"
constraintlayout = "2.2.1"
fragment = "1.8.9"
playServicesOssLicenses = "17.3.0"
sdpAndroid = "1.1.1"
sspAndroid = "1.1.1"
viewpager2 = "1.0.0"
Expand All @@ -23,6 +24,7 @@ material = { group = "com.google.android.material", name = "material", version.r
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
play-services-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "playServicesOssLicenses" }
sdp-android = { module = "com.intuit.sdp:sdp-android", version.ref = "sdpAndroid" }
ssp-android = { module = "com.intuit.ssp:ssp-android", version.ref = "sspAndroid" }
viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" }
Expand Down
7 changes: 7 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ pluginManagement {
}
mavenCentral()
gradlePluginPortal()
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") {
useModule("com.google.android.gms:oss-licenses-plugin:0.10.10")
}
}
}
}
}
dependencyResolutionManagement {
Expand Down