diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/AboutActivity.kt b/app/src/main/java/partisan/weforge/xyz/pulse/AboutActivity.kt index 7317054..77b8d2b 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/AboutActivity.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/AboutActivity.kt @@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity class AboutActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // placeholder until real UI is added + // placeholder setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item) } } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/ContactAdapter.kt b/app/src/main/java/partisan/weforge/xyz/pulse/ContactAdapter.kt new file mode 100644 index 0000000..bc69051 --- /dev/null +++ b/app/src/main/java/partisan/weforge/xyz/pulse/ContactAdapter.kt @@ -0,0 +1,35 @@ +package partisan.weforge.xyz.pulse + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class ContactAdapter( + private val prefs: Preferences, + private val contacts: List +) : RecyclerView.Adapter() { + + inner class ViewHolder(inflater: LayoutInflater, parent: ViewGroup) : + RecyclerView.ViewHolder(inflater.inflate(R.layout.item_contact, parent, false)) { + val contactName: TextView = itemView.findViewById(R.id.contactName) + val contactAllowed: CheckBox = itemView.findViewById(R.id.contactAllowed) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(LayoutInflater.from(parent.context), parent) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val contact = contacts[position] + holder.contactName.text = contact.name + holder.contactAllowed.isChecked = prefs.isContactWhitelisted(contact.phoneNumber) + + holder.contactAllowed.setOnCheckedChangeListener { _, isChecked -> + prefs.setContactWhitelisted(contact.phoneNumber, isChecked) + } + } + + override fun getItemCount(): Int = contacts.size +} diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/ContactEntry.kt b/app/src/main/java/partisan/weforge/xyz/pulse/ContactEntry.kt new file mode 100644 index 0000000..798f51a --- /dev/null +++ b/app/src/main/java/partisan/weforge/xyz/pulse/ContactEntry.kt @@ -0,0 +1,6 @@ +package partisan.weforge.xyz.pulse + +data class ContactEntry( + val name: String, + val phoneNumber: String +) diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt new file mode 100644 index 0000000..d7d9822 --- /dev/null +++ b/app/src/main/java/partisan/weforge/xyz/pulse/ContactsFragment.kt @@ -0,0 +1,75 @@ +package partisan.weforge.xyz.pulse + +import android.content.ContentResolver +import android.os.Bundle +import android.provider.ContactsContract +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import partisan.weforge.xyz.pulse.databinding.FragmentContactsBinding + +class ContactsFragment : Fragment() { + + private var _binding: FragmentContactsBinding? = null + private val binding get() = _binding!! + + private lateinit var prefs: Preferences + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentContactsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + prefs = Preferences(requireContext()) + + val contacts = getContacts() + val adapter = ContactAdapter(prefs, contacts) + binding.contactRecycler.layoutManager = LinearLayoutManager(requireContext()) + binding.contactRecycler.adapter = adapter + } + + private fun getContacts(): List { + val resolver: ContentResolver = requireContext().contentResolver + val projection = arrayOf( + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, + ContactsContract.CommonDataKinds.Phone.NUMBER + ) + + val cursor = resolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + projection, + null, + null, + "${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) + + while (it.moveToNext()) { + val name = it.getString(nameIndex) ?: continue + val number = it.getString(numberIndex) ?: continue + results.add(ContactEntry(name, number)) + } + } + + return results + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt b/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt index 0f9de9a..137fb96 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt @@ -33,6 +33,7 @@ class MainActivity : AppCompatActivity() { ) binding.drawerLayout.addDrawerListener(drawerToggle) drawerToggle.syncState() + supportFragmentManager.beginTransaction() .replace(R.id.fragmentContainer, MainFragment()) .commit() @@ -42,17 +43,21 @@ class MainActivity : AppCompatActivity() { R.id.action_popup_settings -> { supportFragmentManager.beginTransaction() .replace(R.id.fragmentContainer, PopupSettingsFragment()) + .addToBackStack(null) .commit() true } - R.id.action_settings -> { - startActivity(Intent(this, SettingsActivity::class.java)) - true - } R.id.action_about -> { startActivity(Intent(this, AboutActivity::class.java)) true } + R.id.action_contacts -> { + supportFragmentManager.beginTransaction() + .replace(R.id.fragmentContainer, ContactsFragment()) + .addToBackStack(null) + .commit() + true + } else -> false }.also { binding.drawerLayout.closeDrawers() diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/Permissions.kt b/app/src/main/java/partisan/weforge/xyz/pulse/Permissions.kt index f33ec8d..9b9eb78 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/Permissions.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/Permissions.kt @@ -1,4 +1,3 @@ - package partisan.weforge.xyz.pulse import android.Manifest @@ -25,4 +24,4 @@ fun hasDrawOverlays(context: Context): Boolean { fun hasCallRedirectionRole(context: Context): Boolean { val roleManager = context.getSystemService(RoleManager::class.java) return roleManager?.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION) ?: false -} +} \ No newline at end of file diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt index da09ad8..6616787 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt @@ -9,13 +9,12 @@ class Preferences(ctx: Context) { private const val ENABLED = "enabled" private const val REDIRECTION_DELAY = "redirection_delay" private const val POPUP_POSITION = "popup_position_y" + private const val POPUP_ENABLED = "popup_enabled" + private const val BLACKLISTED_CONTACTS = "blacklisted_contacts" private const val DEFAULT_REDIRECTION_DELAY = 2000L private const val DEFAULT_POPUP_POSITION = 333 - private const val SERVICE_ENABLED = "service_enabled" - - private const val POPUP_ENABLED = "popup_enabled" } private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx) @@ -39,23 +38,37 @@ class Preferences(ctx: Context) { private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype" private fun makeKeyPriority(mimetype: String) = "priority_$mimetype" - /** Whether this service is enabled */ fun isServiceEnabled(mimetype: String): Boolean { return prefs.getBoolean(makeKeyEnabled(mimetype), true) } - /** Current priority for this service (lower = higher priority) */ fun getServicePriority(mimetype: String): Int { return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE) } - /** Enable or disable individual service */ fun setServiceEnabled(mimetype: String, enabled: Boolean) { prefs.edit().putBoolean(makeKeyEnabled(mimetype), enabled).apply() } - /** Change priority for an individual service */ fun setServicePriority(mimetype: String, priority: Int) { prefs.edit().putInt(makeKeyPriority(mimetype), priority).apply() } + + var blacklistedContacts: Set + get() = prefs.getStringSet(BLACKLISTED_CONTACTS, emptySet()) ?: emptySet() + set(value) = prefs.edit { putStringSet(BLACKLISTED_CONTACTS, value) } + + fun isContactWhitelisted(phoneNumber: String): Boolean { + return !blacklistedContacts.contains(phoneNumber) + } + + fun setContactWhitelisted(phoneNumber: String, allowed: Boolean) { + val current = blacklistedContacts.toMutableSet() + if (!allowed) { + current.add(phoneNumber) + } else { + current.remove(phoneNumber) + } + blacklistedContacts = current + } } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/SettingsActivity.kt b/app/src/main/java/partisan/weforge/xyz/pulse/SettingsActivity.kt deleted file mode 100644 index ebac437..0000000 --- a/app/src/main/java/partisan/weforge/xyz/pulse/SettingsActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package partisan.weforge.xyz.pulse - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class SettingsActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // placeholder until real UI is added - setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item) - } -} diff --git a/app/src/main/res/drawable/group_24px.xml b/app/src/main/res/drawable/group_24px.xml new file mode 100644 index 0000000..556499a --- /dev/null +++ b/app/src/main/res/drawable/group_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/info_24px.xml b/app/src/main/res/drawable/info_24px.xml new file mode 100644 index 0000000..3186ebf --- /dev/null +++ b/app/src/main/res/drawable/info_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_contacts.xml b/app/src/main/res/layout/fragment_contacts.xml new file mode 100644 index 0000000..057c3ff --- /dev/null +++ b/app/src/main/res/layout/fragment_contacts.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/layout/item_contact.xml b/app/src/main/res/layout/item_contact.xml new file mode 100644 index 0000000..d7e3e51 --- /dev/null +++ b/app/src/main/res/layout/item_contact.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 48f3eee..b8a1808 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -1,14 +1,30 @@ + + - + android:id="@+id/section_settings" + android:title="Settings" + android:enabled="false" /> + + + + +