Merge branch 'release/1.0.0'
This commit is contained in:
commit
8e856e9b09
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Android
|
||||||
|
.android/
|
||||||
|
local.properties
|
||||||
|
*.apk
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
apk-output/
|
||||||
|
output/
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
FROM eclipse-temurin:17-jdk-jammy
|
||||||
|
|
||||||
|
# Install required dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV ANDROID_HOME=/opt/android-sdk
|
||||||
|
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools
|
||||||
|
|
||||||
|
# Download and install Android command line tools
|
||||||
|
RUN mkdir -p $ANDROID_HOME/cmdline-tools && \
|
||||||
|
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip && \
|
||||||
|
unzip -q /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools && \
|
||||||
|
mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest && \
|
||||||
|
rm /tmp/cmdline-tools.zip
|
||||||
|
|
||||||
|
# Accept licenses and install required SDK components
|
||||||
|
RUN yes | sdkmanager --licenses && \
|
||||||
|
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Make gradlew executable
|
||||||
|
RUN chmod +x gradlew
|
||||||
|
|
||||||
|
# Build the APK
|
||||||
|
CMD ["./gradlew", "assembleDebug", "--no-daemon"]
|
||||||
93
README.md
Normal file
93
README.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Call Blocker
|
||||||
|
|
||||||
|
Aplicativo Android para bloqueio automático de chamadas de números desconhecidos.
|
||||||
|
|
||||||
|
## Sobre
|
||||||
|
|
||||||
|
O **Call Blocker** bloqueia automaticamente chamadas recebidas de números que não estão salvos na sua lista de contatos. Utiliza a API CallScreeningService do Android (disponível a partir do Android 10) para interceptar e filtrar chamadas de forma transparente.
|
||||||
|
|
||||||
|
## Funcionalidades
|
||||||
|
|
||||||
|
- Bloqueio automático de chamadas de números desconhecidos
|
||||||
|
- Permite chamadas de números salvos nos contatos
|
||||||
|
- Interface simples com botão liga/desliga
|
||||||
|
- Integração nativa com o sistema de chamadas do Android
|
||||||
|
- Bloqueia chamadas com número oculto/indisponível
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
- Android 7.0 (API 24) ou superior
|
||||||
|
- Permissões necessárias: telefone, contatos e triagem de chamadas
|
||||||
|
|
||||||
|
## Estrutura do Projeto
|
||||||
|
|
||||||
|
```
|
||||||
|
call_blocker/
|
||||||
|
├── app/
|
||||||
|
│ └── src/main/
|
||||||
|
│ ├── java/com/callblocker/
|
||||||
|
│ │ ├── MainActivity.kt # Atividade principal com UI
|
||||||
|
│ │ └── CallBlockerService.kt # Serviço de triagem de chamadas
|
||||||
|
│ ├── res/ # Recursos (layouts, strings, cores)
|
||||||
|
│ └── AndroidManifest.xml
|
||||||
|
├── gradle/wrapper/ # Gradle wrapper
|
||||||
|
├── build.gradle.kts # Configuração do Gradle
|
||||||
|
├── settings.gradle.kts # Configuração de módulos
|
||||||
|
└── Dockerfile # Build via Docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build via Docker
|
||||||
|
|
||||||
|
### Pré-requisitos
|
||||||
|
|
||||||
|
- Docker instalado na máquina
|
||||||
|
|
||||||
|
### Construir a imagem
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t call-blocker-builder .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gerar o APK
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -v $(pwd)/output:/app/app/build/outputs call-blocker-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
O APK será gerado em `./output/apk/debug/app-debug.apk`.
|
||||||
|
|
||||||
|
### Comando único (build + extração)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t call-blocker-builder . && \
|
||||||
|
docker run --rm -v $(pwd)/output:/app/app/build/outputs call-blocker-builder && \
|
||||||
|
echo "APK gerado em: ./output/apk/debug/app-debug.apk"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Local (sem Docker)
|
||||||
|
|
||||||
|
### Pré-requisitos
|
||||||
|
|
||||||
|
- JDK 17
|
||||||
|
- Android SDK com platform-tools e build-tools 34.0.0
|
||||||
|
|
||||||
|
### Gerar o APK
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
O APK será gerado em `app/build/outputs/apk/debug/app-debug.apk`.
|
||||||
|
|
||||||
|
## Instalação
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb install app-debug.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou transfira o APK para o dispositivo e instale manualmente.
|
||||||
|
|
||||||
|
## Licença
|
||||||
|
|
||||||
|
Este projeto é de código aberto.
|
||||||
47
app/build.gradle.kts
Executable file
47
app/build.gradle.kts
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.callblocker"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.callblocker"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
}
|
||||||
6
app/proguard-rules.pro
vendored
Executable file
6
app/proguard-rules.pro
vendored
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in android SDK/tools/proguard/proguard-android.txt
|
||||||
|
|
||||||
|
# Keep the CallBlockerService
|
||||||
|
-keep class com.callblocker.CallBlockerService { *; }
|
||||||
43
app/src/main/AndroidManifest.xml
Executable file
43
app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Permissões necessárias -->
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
||||||
|
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@drawable/ic_launcher"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.CallBlocker"
|
||||||
|
tools:targetApi="31">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.CallBlocker">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<!-- Serviço de triagem de chamadas (Android 10+) -->
|
||||||
|
<service
|
||||||
|
android:name=".CallBlockerService"
|
||||||
|
android:permission="android.permission.BIND_SCREENING_SERVICE"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.telecom.CallScreeningService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
94
app/src/main/java/com/callblocker/CallBlockerService.kt
Executable file
94
app/src/main/java/com/callblocker/CallBlockerService.kt
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
package com.callblocker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.telecom.Call
|
||||||
|
import android.telecom.CallScreeningService
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
class CallBlockerService : CallScreeningService() {
|
||||||
|
|
||||||
|
override fun onScreenCall(callDetails: Call.Details) {
|
||||||
|
val prefs = getSharedPreferences(MainActivity.PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val isBlockerEnabled = prefs.getBoolean(MainActivity.KEY_BLOCKER_ENABLED, false)
|
||||||
|
|
||||||
|
if (!isBlockerEnabled) {
|
||||||
|
// Bloqueio desativado - permitir todas as chamadas
|
||||||
|
respondToCall(callDetails, CallResponse.Builder().build())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val phoneNumber = getPhoneNumber(callDetails)
|
||||||
|
|
||||||
|
if (phoneNumber.isNullOrEmpty()) {
|
||||||
|
// Número desconhecido/oculto - bloquear
|
||||||
|
blockCall(callDetails)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNumberInContacts(phoneNumber)) {
|
||||||
|
// Número está nos contatos - permitir
|
||||||
|
allowCall(callDetails)
|
||||||
|
} else {
|
||||||
|
// Número não está nos contatos - bloquear
|
||||||
|
blockCall(callDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPhoneNumber(callDetails: Call.Details): String? {
|
||||||
|
val handle = callDetails.handle ?: return null
|
||||||
|
return handle.schemeSpecificPart
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNumberInContacts(phoneNumber: String): Boolean {
|
||||||
|
val uri = Uri.withAppendedPath(
|
||||||
|
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
|
||||||
|
Uri.encode(phoneNumber)
|
||||||
|
)
|
||||||
|
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
try {
|
||||||
|
cursor = contentResolver.query(
|
||||||
|
uri,
|
||||||
|
arrayOf(ContactsContract.PhoneLookup._ID),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
return cursor != null && cursor.count > 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allowCall(callDetails: Call.Details) {
|
||||||
|
val response = CallResponse.Builder()
|
||||||
|
.setDisallowCall(false)
|
||||||
|
.setRejectCall(false)
|
||||||
|
.setSilenceCall(false)
|
||||||
|
.setSkipCallLog(false)
|
||||||
|
.setSkipNotification(false)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
respondToCall(callDetails, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun blockCall(callDetails: Call.Details) {
|
||||||
|
val response = CallResponse.Builder()
|
||||||
|
.setDisallowCall(true)
|
||||||
|
.setRejectCall(true)
|
||||||
|
.setSilenceCall(true)
|
||||||
|
.setSkipCallLog(false)
|
||||||
|
.setSkipNotification(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
respondToCall(callDetails, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
app/src/main/java/com/callblocker/MainActivity.kt
Executable file
153
app/src/main/java/com/callblocker/MainActivity.kt
Executable file
@ -0,0 +1,153 @@
|
|||||||
|
package com.callblocker
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.role.RoleManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.callblocker.databinding.ActivityMainBinding
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private lateinit var prefs: SharedPreferences
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PREFS_NAME = "CallBlockerPrefs"
|
||||||
|
const val KEY_BLOCKER_ENABLED = "blocker_enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requiredPermissions = arrayOf(
|
||||||
|
Manifest.permission.READ_PHONE_STATE,
|
||||||
|
Manifest.permission.READ_CALL_LOG,
|
||||||
|
Manifest.permission.READ_CONTACTS,
|
||||||
|
Manifest.permission.ANSWER_PHONE_CALLS
|
||||||
|
)
|
||||||
|
|
||||||
|
private val permissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
) { permissions ->
|
||||||
|
val allGranted = permissions.values.all { it }
|
||||||
|
if (allGranted) {
|
||||||
|
requestCallScreeningRole()
|
||||||
|
} else {
|
||||||
|
binding.switchBlocker.isChecked = false
|
||||||
|
updateStatus(false)
|
||||||
|
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val roleRequestLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
enableBlocker()
|
||||||
|
} else {
|
||||||
|
binding.switchBlocker.isChecked = false
|
||||||
|
updateStatus(false)
|
||||||
|
Toast.makeText(this, "Permissão de triagem de chamadas negada", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
setupSwitch()
|
||||||
|
loadSavedState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSwitch() {
|
||||||
|
binding.switchBlocker.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
checkAndRequestPermissions()
|
||||||
|
} else {
|
||||||
|
disableBlocker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadSavedState() {
|
||||||
|
val isEnabled = prefs.getBoolean(KEY_BLOCKER_ENABLED, false)
|
||||||
|
if (isEnabled && hasAllPermissions()) {
|
||||||
|
binding.switchBlocker.isChecked = true
|
||||||
|
updateStatus(true)
|
||||||
|
} else {
|
||||||
|
binding.switchBlocker.isChecked = false
|
||||||
|
updateStatus(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasAllPermissions(): Boolean {
|
||||||
|
return requiredPermissions.all {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAndRequestPermissions() {
|
||||||
|
val permissionsToRequest = requiredPermissions.filter {
|
||||||
|
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
if (permissionsToRequest.isNotEmpty()) {
|
||||||
|
permissionLauncher.launch(permissionsToRequest)
|
||||||
|
} else {
|
||||||
|
requestCallScreeningRole()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestCallScreeningRole() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
|
||||||
|
if (roleManager.isRoleAvailable(RoleManager.ROLE_CALL_SCREENING)) {
|
||||||
|
if (!roleManager.isRoleHeld(RoleManager.ROLE_CALL_SCREENING)) {
|
||||||
|
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING)
|
||||||
|
roleRequestLauncher.launch(intent)
|
||||||
|
} else {
|
||||||
|
enableBlocker()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enableBlocker()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enableBlocker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableBlocker() {
|
||||||
|
prefs.edit().putBoolean(KEY_BLOCKER_ENABLED, true).apply()
|
||||||
|
updateStatus(true)
|
||||||
|
Toast.makeText(this, "Bloqueio de chamadas ativado!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disableBlocker() {
|
||||||
|
prefs.edit().putBoolean(KEY_BLOCKER_ENABLED, false).apply()
|
||||||
|
updateStatus(false)
|
||||||
|
Toast.makeText(this, "Bloqueio de chamadas desativado", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateStatus(enabled: Boolean) {
|
||||||
|
binding.switchStatus.text = if (enabled) {
|
||||||
|
getString(R.string.status_enabled)
|
||||||
|
} else {
|
||||||
|
getString(R.string.status_disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
val color = if (enabled) {
|
||||||
|
ContextCompat.getColor(this, R.color.primary)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(this, R.color.text_secondary)
|
||||||
|
}
|
||||||
|
binding.iconShield.setColorFilter(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/src/main/res/drawable/ic_launcher.xml
Executable file
10
app/src/main/res/drawable/ic_launcher.xml
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#4CAF50"
|
||||||
|
android:pathData="M54,20L27,34v18c0,16.65 11.52,32.22 27,36 15.48,-3.78 27,-19.35 27,-36V34l-27,-14zM48,70l-12,-12 4.23,-4.23L48,61.51l19.77,-19.77L72,46l-24,24z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_shield.xml
Executable file
10
app/src/main/res/drawable/ic_shield.xml
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#4CAF50"
|
||||||
|
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4zM10,17l-4,-4 1.41,-1.41L10,14.17l6.59,-6.59L18,9l-8,8z"/>
|
||||||
|
</vector>
|
||||||
122
app/src/main/res/layout/activity_main.xml
Executable file
122
app/src/main/res/layout/activity_main.xml
Executable file
@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<!-- Ícone do escudo -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iconShield"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="80dp"
|
||||||
|
android:src="@drawable/ic_shield"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tint="@color/primary" />
|
||||||
|
|
||||||
|
<!-- Título -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/iconShield" />
|
||||||
|
|
||||||
|
<!-- Descrição -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/descriptionText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/description"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||||
|
|
||||||
|
<!-- Card com Switch -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/cardSwitch"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
app:cardBackgroundColor="@color/card_background"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/descriptionText">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/switchTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/blocker_title"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/switchStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="@string/status_disabled"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switchBlocker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<!-- Status das permissões -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/permissionStatus"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/permission_info"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cardSwitch" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
12
app/src/main/res/values/colors.xml
Executable file
12
app/src/main/res/values/colors.xml
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="primary">#4CAF50</color>
|
||||||
|
<color name="primary_dark">#388E3C</color>
|
||||||
|
<color name="accent">#8BC34A</color>
|
||||||
|
<color name="background">#F5F5F5</color>
|
||||||
|
<color name="card_background">#FFFFFF</color>
|
||||||
|
<color name="text_primary">#212121</color>
|
||||||
|
<color name="text_secondary">#757575</color>
|
||||||
|
<color name="white">#FFFFFF</color>
|
||||||
|
<color name="error">#F44336</color>
|
||||||
|
</resources>
|
||||||
10
app/src/main/res/values/strings.xml
Executable file
10
app/src/main/res/values/strings.xml
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Bloqueador de Chamadas</string>
|
||||||
|
<string name="description">Bloqueia automaticamente chamadas de números que não estão na sua lista de contatos</string>
|
||||||
|
<string name="blocker_title">Bloqueio Ativo</string>
|
||||||
|
<string name="status_enabled">Protegendo suas chamadas</string>
|
||||||
|
<string name="status_disabled">Desativado</string>
|
||||||
|
<string name="permission_info">Toque no switch para ativar e conceder as permissões necessárias</string>
|
||||||
|
<string name="permission_denied">Permissões necessárias não concedidas</string>
|
||||||
|
</resources>
|
||||||
9
app/src/main/res/values/themes.xml
Executable file
9
app/src/main/res/values/themes.xml
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.CallBlocker" parent="Theme.Material3.Light.NoActionBar">
|
||||||
|
<item name="colorPrimary">@color/primary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||||
|
<item name="colorAccent">@color/accent</item>
|
||||||
|
<item name="android:statusBarColor">@color/primary_dark</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
4
build.gradle.kts
Executable file
4
build.gradle.kts
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application") version "8.2.0" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
||||||
|
}
|
||||||
4
gradle.properties
Executable file
4
gradle.properties
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
android.useAndroidX=true
|
||||||
|
kotlin.code.style=official
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
27
gradlew
vendored
Executable file
27
gradlew
vendored
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add default JVM options here
|
||||||
|
DEFAULT_JVM_OPTS='-Xmx64m -Xms64m'
|
||||||
|
|
||||||
|
APP_HOME=$( cd "$( dirname "$0" )" && pwd -P ) || exit
|
||||||
|
|
||||||
|
# Setup classpath
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Execute Gradle
|
||||||
|
exec "$JAVACMD" $DEFAULT_JVM_OPTS -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
18
settings.gradle.kts
Executable file
18
settings.gradle.kts
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "CallBlocker"
|
||||||
|
include(":app")
|
||||||
Loading…
x
Reference in New Issue
Block a user