diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2598810 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md new file mode 100644 index 0000000..2fc626a --- /dev/null +++ b/PRIVACY_POLICY.md @@ -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). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5f90128..d4cc9ca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.android.application) + id("com.google.android.gms.oss-licenses-plugin") } android { @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/io/github/iso53/nothingcompass/OptionsActivity.java b/app/src/main/java/io/github/iso53/nothingcompass/OptionsActivity.java index 46249bd..07f1eab 100644 --- a/app/src/main/java/io/github/iso53/nothingcompass/OptionsActivity.java +++ b/app/src/main/java/io/github/iso53/nothingcompass/OptionsActivity.java @@ -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; @@ -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; @@ -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 @@ -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()); @@ -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(); } @@ -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))); @@ -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())); @@ -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() { @@ -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))); } } @@ -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); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 599acea..b6667f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" @@ -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" } diff --git a/settings.gradle.kts b/settings.gradle.kts index d5c07fc..2927cc6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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 {