diff --git a/README.md b/README.md index 9ac9d9e..5bcb4a7 100644 --- a/README.md +++ b/README.md @@ -48,22 +48,22 @@ Redirecting outgoing calls to E2EE apps. # Features -- Material You design -- Popup with cancel option +- Material You design +- Popup with cancel option - Extensive settings panel: - - Toggle per-service support - - Redirection only on Wi-Fi/Data - - Allowlist specific contacts - - Change per-service priority - - Customize popup position, animation, and duration + - Toggle per-service support + - Redirection only on Wi-Fi/Data + - Allowlist specific contacts + - Change per-service priority + - Customize popup position, animation, and duration - ... # Supports -- Signal -- Telegram -- Threema -- WhatsApp +- Signal +- Telegram +- Threema +- WhatsApp # How to Install @@ -73,7 +73,7 @@ Redirecting outgoing calls to E2EE apps. In the app, search for "Pulse" and install it. -*Pulse uses the IzzyOnDroid repo. Some F-Droid clients, such as F-Droid itself, do not include it by default. Please add the IzzyOnDroid repo: https://apt.izzysoft.de/fdroid/repo* +_Pulse uses the IzzyOnDroid repo. Some F-Droid clients, such as F-Droid itself, do not include it by default. Please add the IzzyOnDroid repo: https://apt.izzysoft.de/fdroid/repo_ ## Using Obtainium @@ -81,8 +81,8 @@ In the app, search for "Pulse" and install it. In the “Add App” screen: -1. Add the following URL: https://weforge.xyz/partisan/Pulse -2. In **Override Source**, select **Forgejo (Codeberg)** +1. Add the following URL: https://weforge.xyz/partisan/Pulse +2. In **Override Source**, select **Forgejo (Codeberg)** 3. Tap the “Add” button at the very top, and you’re done! ## Install directly @@ -91,16 +91,16 @@ Go to the [Releases page](https://weforge.xyz/partisan/Pulse/releases) and downl Install it, and you’re done! -*Please note that when installing directly, the app will not receive automatic updates.* +_Please note that when installing directly, the app will not receive automatic updates._ # Permissions -- `ACCESS_NETWORK_STATE` – check connectivity -- `CALL_PHONE` – make a call via messenger -- `READ_CONTACTS` – check if contact has a messenger -- `READ_PHONE_NUMBERS` – detect outgoing call -- `SYSTEM_ALERT_WINDOW` – show redirecting popup and launch from background -- `INTERNET` – check connectivity and verify donates +- `ACCESS_NETWORK_STATE` – check connectivity +- `CALL_PHONE` – make a call via messenger +- `READ_CONTACTS` – check if contact has a messenger +- `READ_PHONE_NUMBERS` – detect outgoing call +- `SYSTEM_ALERT_WINDOW` – show redirecting popup and launch from background +- `INTERNET` – check connectivity and verify donates Currently all of the permissions are required. @@ -120,4 +120,4 @@ Licensed under the [Public Domain](https://www.svgrepo.com/page/licensing/#PD). ## Original Author -[This software](https://github.com/x13a/Red) was originally developed by [x13a](https://github.com/x13a), but it has been archived by the owner on Jun 22, 2022. \ No newline at end of file +[This software](https://github.com/x13a/Red) was originally developed by [x13a](https://github.com/x13a), but it has been archived by the owner on Jun 22, 2022. diff --git a/app/build.gradle b/app/build.gradle index 6de77a6..7b72933 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId = "partisan.weforge.xyz.pulse" minSdk = 29 targetSdk = 34 - versionCode = 14 - versionName = "2.0.0" + versionCode = 16 + versionName = "2.0.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt index 370281d..39d236a 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt @@ -89,7 +89,9 @@ class ContactsFragment : Fragment() { } private fun getContacts(): List { + val results = mutableListOf() val resolver: ContentResolver = requireContext().contentResolver + val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER @@ -103,16 +105,16 @@ class ContactsFragment : Fragment() { "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC" ) - val results = mutableListOf() - cursor?.use { - val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) - val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + val nameIndex = it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) + val numberIndex = it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER) while (it.moveToNext()) { - val name = it.getString(nameIndex) ?: continue - val number = it.getString(numberIndex) ?: continue - results.add(ContactEntry(name, number)) + val name = it.getString(nameIndex) + val number = it.getString(numberIndex) + if (!name.isNullOrBlank() && !number.isNullOrBlank()) { + results.add(ContactEntry(name, number)) + } } } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt index 9e8c3b4..72cc529 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt @@ -1,17 +1,20 @@ package partisan.weforge.xyz.pulse +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.os.Bundle import android.os.SystemClock import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.TextView import androidx.fragment.app.Fragment import com.google.android.material.button.MaterialButton +import java.util.concurrent.TimeUnit import nl.dionsegijn.konfetti.core.Party import nl.dionsegijn.konfetti.core.Position import nl.dionsegijn.konfetti.core.emitter.Emitter import nl.dionsegijn.konfetti.xml.KonfettiView -import java.util.concurrent.TimeUnit class MainFragment : Fragment() { @@ -24,9 +27,9 @@ class MainFragment : Fragment() { } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View { val view = inflater.inflate(R.layout.fragment_main, container, false) prefs = Preferences(requireContext()) @@ -44,14 +47,16 @@ class MainFragment : Fragment() { if (isNowChecked && SystemClock.elapsedRealtime() - lastConfettiTime > 500) { konfetti.start( - Party( - emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).perSecond(100), - speed = 30f, - maxSpeed = 40f, - damping = 0.85f, - spread = 360, - position = Position.Relative(0.5, 0.5) - ) + Party( + emitter = + Emitter(duration = 100, TimeUnit.MILLISECONDS) + .perSecond(100), + speed = 30f, + maxSpeed = 40f, + damping = 0.85f, + spread = 360, + position = Position.Relative(0.5, 0.5) + ) ) lastConfettiTime = SystemClock.elapsedRealtime() } @@ -62,6 +67,49 @@ class MainFragment : Fragment() { } } + val warningText = view.findViewById(R.id.warningText) + val warnings = mutableListOf() + + // 1. Check if contacts are available + val contactCursor = + requireContext() + .contentResolver + .query( + android.provider.ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + null, + null, + null + ) + + if (contactCursor != null) { + contactCursor.use { + if (!it.moveToFirst()) { + warnings.add(getString(R.string.warn_no_contacts)) + } + } + } else { + warnings.add(getString(R.string.warn_no_contacts)) + } + + // 2. Check internet connectivity + if (!hasInternet()) { + warnings.add(getString(R.string.warn_no_internet)) + } + + // Show warning if needed + if (warnings.isNotEmpty()) { + warningText.text = warnings.joinToString("\n") + warningText.visibility = View.VISIBLE + } + return view } + + private fun hasInternet(): Boolean { + val cm = requireContext().getSystemService(ConnectivityManager::class.java) + val capabilities = cm?.getNetworkCapabilities(cm.activeNetwork) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt index 5a504d3..1150450 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt @@ -133,7 +133,11 @@ class PopupSettingsFragment : Fragment() { binding.popupEffectSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { val selectedEffect = allEffects[position] - if (!prefs.isDonationActivated && selectedEffect !in prefs.getAvailablePopupEffects()) { + if (!prefs.isDonationActivated && + selectedEffect !in prefs.getAvailablePopupEffects() && + selectedEffect != Preferences.PopupEffect.NONE && + selectedEffect != Preferences.PopupEffect.RANDOM + ) { Toast.makeText(requireContext(), getString(R.string.donate_lock), Toast.LENGTH_SHORT).show() binding.popupEffectSpinner.setSelection(prefs.popupEffect.ordinal) } else { diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt b/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt index 64efcce..759e8aa 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt @@ -127,10 +127,13 @@ class SecretView @JvmOverloads constructor( com.google.android.material.R.attr.colorPrimaryVariant, com.google.android.material.R.attr.colorSecondary ) - context.obtainStyledAttributes(colorAttrs).use { - playerPaint.color = it.getColor(0, Color.CYAN) - enemyPaint.color = it.getColor(0, Color.CYAN) - colorSecondary = it.getColor(1, Color.GREEN) + val ta = context.obtainStyledAttributes(colorAttrs) + try { + playerPaint.color = ta.getColor(0, Color.CYAN) + enemyPaint.color = ta.getColor(0, Color.CYAN) + colorSecondary = ta.getColor(1, Color.GREEN) + } finally { + ta.recycle() } Choreographer.getInstance().postFrameCallback(this) diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 86b4dee..741fc5e 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -34,4 +34,17 @@ app:iconGravity="textTop" app:iconPadding="0dp" app:cornerRadius="48dp" /> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ebeab5..19b99a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,10 @@ Random + + Unable to access contacts. + No internet connection. + Support Pulse Development 💖 Pulse is free and open-source. You can support future development through Ko-fi. As a thank-you, donors get special popup animation effects! diff --git a/fastlane/metadata/android/en-US/changelogs/15.txt b/fastlane/metadata/android/en-US/changelogs/15.txt index 54dbb99..9f159d6 100644 --- a/fastlane/metadata/android/en-US/changelogs/15.txt +++ b/fastlane/metadata/android/en-US/changelogs/15.txt @@ -17,6 +17,6 @@ Fixes: Misc: - Updated Gradle to v8.14.1 -- Updated screenshots to include new looks of the app. -- Updated description. -- Added temporary "v2.0" to store icon to indicate new version. \ No newline at end of file +- Updated screenshots to reflect the new look of the app +- Updated description +- Temporarily added "v2.0" to the store icon to indicate the new version \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/16.txt b/fastlane/metadata/android/en-US/changelogs/16.txt new file mode 100644 index 0000000..19c988b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/16.txt @@ -0,0 +1,2 @@ +v2.0.1 +- Fixed lock warning on NONE and RANDOM popup effects \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt new file mode 100644 index 0000000..d18ecdb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/17.txt @@ -0,0 +1,3 @@ +v2.0.2 +- Added warning text in case the app does not have sufficient permissions +- Fixed a bug related to tapping the Pulse logo in the About section, specific to MIUI \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 09bf845..e63bda7 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -3,6 +3,7 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. --- **Features:** + - Material You design - Popup with cancel option - Extensive settings panel: @@ -11,15 +12,17 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. - Allowlist specific contacts - Change per-service priority - Customize popup position, animation, and duration - ... + - etc **Supports:** + - Signal - Telegram - Threema - WhatsApp **Permissions required:** + - `CALL_PHONE` - initiate calls via messenger - `READ_CONTACTS` - check contact compatibility - `READ_PHONE_NUMBERS` - detect outgoing call @@ -29,5 +32,8 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. Currently all of the permissions are required. +--- + **License:** GPL-3.0 + Free and open source diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index aa3476a..3ffffd0 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Redirecting outgoing calls to E2EE apps. \ No newline at end of file +Redirecting outgoing calls to E2EE apps \ No newline at end of file