Compare commits
No commits in common. "main" and "v2.0.0" have entirely different histories.
13 changed files with 53 additions and 138 deletions
|
@ -73,7 +73,7 @@ Redirecting outgoing calls to E2EE apps.
|
||||||
|
|
||||||
In the app, search for "Pulse" and install it.
|
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
|
## Using Obtainium
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ Go to the [Releases page](https://weforge.xyz/partisan/Pulse/releases) and downl
|
||||||
|
|
||||||
Install it, and you’re done!
|
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
|
# Permissions
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId = "partisan.weforge.xyz.pulse"
|
applicationId = "partisan.weforge.xyz.pulse"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 16
|
versionCode = 14
|
||||||
versionName = "2.0.2"
|
versionName = "2.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,9 +89,7 @@ class ContactsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getContacts(): List<ContactEntry> {
|
private fun getContacts(): List<ContactEntry> {
|
||||||
val results = mutableListOf<ContactEntry>()
|
|
||||||
val resolver: ContentResolver = requireContext().contentResolver
|
val resolver: ContentResolver = requireContext().contentResolver
|
||||||
|
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||||
ContactsContract.CommonDataKinds.Phone.NUMBER
|
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||||
|
@ -105,16 +103,16 @@ class ContactsFragment : Fragment() {
|
||||||
"${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC"
|
"${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val results = mutableListOf<ContactEntry>()
|
||||||
|
|
||||||
cursor?.use {
|
cursor?.use {
|
||||||
val nameIndex = it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
|
val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
|
||||||
val numberIndex = it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)
|
val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
|
||||||
|
|
||||||
while (it.moveToNext()) {
|
while (it.moveToNext()) {
|
||||||
val name = it.getString(nameIndex)
|
val name = it.getString(nameIndex) ?: continue
|
||||||
val number = it.getString(numberIndex)
|
val number = it.getString(numberIndex) ?: continue
|
||||||
if (!name.isNullOrBlank() && !number.isNullOrBlank()) {
|
results.add(ContactEntry(name, number))
|
||||||
results.add(ContactEntry(name, number))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
package partisan.weforge.xyz.pulse
|
package partisan.weforge.xyz.pulse
|
||||||
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import nl.dionsegijn.konfetti.core.Party
|
import nl.dionsegijn.konfetti.core.Party
|
||||||
import nl.dionsegijn.konfetti.core.Position
|
import nl.dionsegijn.konfetti.core.Position
|
||||||
import nl.dionsegijn.konfetti.core.emitter.Emitter
|
import nl.dionsegijn.konfetti.core.emitter.Emitter
|
||||||
import nl.dionsegijn.konfetti.xml.KonfettiView
|
import nl.dionsegijn.konfetti.xml.KonfettiView
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainFragment : Fragment() {
|
class MainFragment : Fragment() {
|
||||||
|
|
||||||
|
@ -27,9 +24,9 @@ class MainFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val view = inflater.inflate(R.layout.fragment_main, container, false)
|
val view = inflater.inflate(R.layout.fragment_main, container, false)
|
||||||
prefs = Preferences(requireContext())
|
prefs = Preferences(requireContext())
|
||||||
|
@ -47,16 +44,14 @@ class MainFragment : Fragment() {
|
||||||
|
|
||||||
if (isNowChecked && SystemClock.elapsedRealtime() - lastConfettiTime > 500) {
|
if (isNowChecked && SystemClock.elapsedRealtime() - lastConfettiTime > 500) {
|
||||||
konfetti.start(
|
konfetti.start(
|
||||||
Party(
|
Party(
|
||||||
emitter =
|
emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS).perSecond(100),
|
||||||
Emitter(duration = 100, TimeUnit.MILLISECONDS)
|
speed = 30f,
|
||||||
.perSecond(100),
|
maxSpeed = 40f,
|
||||||
speed = 30f,
|
damping = 0.85f,
|
||||||
maxSpeed = 40f,
|
spread = 360,
|
||||||
damping = 0.85f,
|
position = Position.Relative(0.5, 0.5)
|
||||||
spread = 360,
|
)
|
||||||
position = Position.Relative(0.5, 0.5)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
lastConfettiTime = SystemClock.elapsedRealtime()
|
lastConfettiTime = SystemClock.elapsedRealtime()
|
||||||
}
|
}
|
||||||
|
@ -67,49 +62,6 @@ class MainFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val warningText = view.findViewById<TextView>(R.id.warningText)
|
|
||||||
val warnings = mutableListOf<String>()
|
|
||||||
|
|
||||||
// 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
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,11 +133,7 @@ class PopupSettingsFragment : Fragment() {
|
||||||
binding.popupEffectSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.popupEffectSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||||
val selectedEffect = allEffects[position]
|
val selectedEffect = allEffects[position]
|
||||||
if (!prefs.isDonationActivated &&
|
if (!prefs.isDonationActivated && selectedEffect !in prefs.getAvailablePopupEffects()) {
|
||||||
selectedEffect !in prefs.getAvailablePopupEffects() &&
|
|
||||||
selectedEffect != Preferences.PopupEffect.NONE &&
|
|
||||||
selectedEffect != Preferences.PopupEffect.RANDOM
|
|
||||||
) {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.donate_lock), Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), getString(R.string.donate_lock), Toast.LENGTH_SHORT).show()
|
||||||
binding.popupEffectSpinner.setSelection(prefs.popupEffect.ordinal)
|
binding.popupEffectSpinner.setSelection(prefs.popupEffect.ordinal)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -127,13 +127,10 @@ class SecretView @JvmOverloads constructor(
|
||||||
com.google.android.material.R.attr.colorPrimaryVariant,
|
com.google.android.material.R.attr.colorPrimaryVariant,
|
||||||
com.google.android.material.R.attr.colorSecondary
|
com.google.android.material.R.attr.colorSecondary
|
||||||
)
|
)
|
||||||
val ta = context.obtainStyledAttributes(colorAttrs)
|
context.obtainStyledAttributes(colorAttrs).use {
|
||||||
try {
|
playerPaint.color = it.getColor(0, Color.CYAN)
|
||||||
playerPaint.color = ta.getColor(0, Color.CYAN)
|
enemyPaint.color = it.getColor(0, Color.CYAN)
|
||||||
enemyPaint.color = ta.getColor(0, Color.CYAN)
|
colorSecondary = it.getColor(1, Color.GREEN)
|
||||||
colorSecondary = ta.getColor(1, Color.GREEN)
|
|
||||||
} finally {
|
|
||||||
ta.recycle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Choreographer.getInstance().postFrameCallback(this)
|
Choreographer.getInstance().postFrameCallback(this)
|
||||||
|
|
|
@ -34,17 +34,4 @@
|
||||||
app:iconGravity="textTop"
|
app:iconGravity="textTop"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
app:cornerRadius="48dp" />
|
app:cornerRadius="48dp" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/warningText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:background="#AAFF5252"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:text=""
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -47,10 +47,6 @@
|
||||||
<item>Random</item>
|
<item>Random</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<!-- Missing perms -->
|
|
||||||
<string name="warn_no_contacts">Unable to access contacts.</string>
|
|
||||||
<string name="warn_no_internet">No internet connection.</string>
|
|
||||||
|
|
||||||
<!-- Donate screen -->
|
<!-- Donate screen -->
|
||||||
<string name="donate_title">Support Pulse Development 💖</string>
|
<string name="donate_title">Support Pulse Development 💖</string>
|
||||||
<string name="donate_description">Pulse is free and open-source. You can support future development through Ko-fi. As a thank-you, donors get special popup animation effects!</string>
|
<string name="donate_description">Pulse is free and open-source. You can support future development through Ko-fi. As a thank-you, donors get special popup animation effects!</string>
|
||||||
|
|
|
@ -17,6 +17,6 @@ Fixes:
|
||||||
|
|
||||||
Misc:
|
Misc:
|
||||||
- Updated Gradle to v8.14.1
|
- Updated Gradle to v8.14.1
|
||||||
- Updated screenshots to reflect the new look of the app
|
- Updated screenshots to include new looks of the app.
|
||||||
- Updated description
|
- Updated description.
|
||||||
- Temporarily added "v2.0" to the store icon to indicate the new version
|
- Added temporary "v2.0" to store icon to indicate new version.
|
|
@ -1,2 +0,0 @@
|
||||||
v2.0.1
|
|
||||||
- Fixed lock warning on NONE and RANDOM popup effects
|
|
|
@ -1,3 +0,0 @@
|
||||||
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
|
|
|
@ -3,7 +3,6 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp.
|
||||||
---
|
---
|
||||||
|
|
||||||
**Features:**
|
**Features:**
|
||||||
|
|
||||||
- Material You design
|
- Material You design
|
||||||
- Popup with cancel option
|
- Popup with cancel option
|
||||||
- Extensive settings panel:
|
- Extensive settings panel:
|
||||||
|
@ -12,17 +11,15 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp.
|
||||||
- Allowlist specific contacts
|
- Allowlist specific contacts
|
||||||
- Change per-service priority
|
- Change per-service priority
|
||||||
- Customize popup position, animation, and duration
|
- Customize popup position, animation, and duration
|
||||||
- etc
|
...
|
||||||
|
|
||||||
**Supports:**
|
**Supports:**
|
||||||
|
|
||||||
- Signal
|
- Signal
|
||||||
- Telegram
|
- Telegram
|
||||||
- Threema
|
- Threema
|
||||||
- WhatsApp
|
- WhatsApp
|
||||||
|
|
||||||
**Permissions required:**
|
**Permissions required:**
|
||||||
|
|
||||||
- `CALL_PHONE` - initiate calls via messenger
|
- `CALL_PHONE` - initiate calls via messenger
|
||||||
- `READ_CONTACTS` - check contact compatibility
|
- `READ_CONTACTS` - check contact compatibility
|
||||||
- `READ_PHONE_NUMBERS` - detect outgoing call
|
- `READ_PHONE_NUMBERS` - detect outgoing call
|
||||||
|
@ -32,8 +29,5 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp.
|
||||||
|
|
||||||
Currently all of the permissions are required.
|
Currently all of the permissions are required.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**License:** GPL-3.0
|
**License:** GPL-3.0
|
||||||
|
|
||||||
Free and open source
|
Free and open source
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Redirecting outgoing calls to E2EE apps
|
Redirecting outgoing calls to E2EE apps.
|
Loading…
Add table
Add a link
Reference in a new issue