diff --git a/apps/call-blocker/.gitignore b/apps/call-blocker/.gitignore deleted file mode 100644 index 348c102..0000000 --- a/apps/call-blocker/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/bin diff --git a/apps/call-blocker/build.gradle.kts b/apps/call-blocker/build.gradle.kts deleted file mode 100644 index 05d0cac..0000000 --- a/apps/call-blocker/build.gradle.kts +++ /dev/null @@ -1,46 +0,0 @@ - -import com.android.build.api.dsl.ApplicationExtension - -plugins { - alias(libs.plugins.convention.android.app) - alias(libs.plugins.convention.compose) - alias(libs.plugins.convention.koin) -} - -extensions.getByType().apply { - namespace = "ru.shadowsparky.callblocker" - defaultConfig { - applicationId = namespace - versionCode = 1 - versionName = "1.0" - } - - buildTypes { - release { - isMinifyEnabled = true - } - } - buildFeatures { - buildConfig = true - } -} - -dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.datastore.preferences) - implementation(libs.koin.android) - implementation(libs.koin.androidx.compose) - implementation(libs.activity.compose) - implementation(libs.androidx.work.runtime.ktx) - implementation(libs.androidx.appcompat) - - implementation(project(":libs:base")) - implementation(project(":libs:ui")) - - debugImplementation(libs.ui.tooling) - compileOnly(libs.ui.tooling.preview.android) - compileOnly(project(":libs:android-stub")) - testImplementation(libs.koin.test) - testImplementation(libs.mockk) - testImplementation(kotlin("test-junit")) -} diff --git a/apps/call-blocker/src/main/AndroidManifest.xml b/apps/call-blocker/src/main/AndroidManifest.xml deleted file mode 100644 index da8dff8..0000000 --- a/apps/call-blocker/src/main/AndroidManifest.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/call-blocker/src/main/ic_launcher-playstore.png b/apps/call-blocker/src/main/ic_launcher-playstore.png deleted file mode 100644 index 01f49b3..0000000 Binary files a/apps/call-blocker/src/main/ic_launcher-playstore.png and /dev/null differ diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/AppModule.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/AppModule.kt deleted file mode 100644 index 6693d7a..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/AppModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.shadowsparky.callblocker - -import android.app.Application -import android.os.Build -import org.koin.core.annotation.ComponentScan -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import ru.shadowsparky.callblocker.data.LRejectCallDelegate -import ru.shadowsparky.callblocker.data.PRejectCallDelegate -import ru.shadowsparky.callblocker.domain.RejectCallDelegate - -@Module -@ComponentScan -class AppModule { - - @Factory - fun provideAlwaysRejectCallDelegate(app: Application): RejectCallDelegate { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - PRejectCallDelegate(app) - } else { - LRejectCallDelegate(app) - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/ContentProviderContactReader.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/ContentProviderContactReader.kt deleted file mode 100644 index 736700c..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/ContentProviderContactReader.kt +++ /dev/null @@ -1,51 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import android.content.Context -import android.database.Cursor -import android.provider.ContactsContract -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.koin.core.annotation.Factory -import ru.shadowsparky.callblocker.domain.ContactEntry -import ru.shadowsparky.callblocker.domain.ContactReader - -@Factory -class ContentProviderContactReader( - private val context: Context -) : ContactReader { - - override suspend fun read(): Set { - val projection = arrayOf( - ContactsContract.Contacts.DISPLAY_NAME, - ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER - ) - return withContext(Dispatchers.IO) { - context.contentResolver.query( - ContactsContract.Data.CONTENT_URI, - projection, - null, - null, - null - ).use { queryContacts(it ?: return@withContext emptySet()) } - } - } - - private fun queryContacts(cursor: Cursor): Set { - if (!cursor.moveToFirst()) return emptySet() - val result = mutableSetOf() - do { - val name = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) - val phone = cursor.getString(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER) - if (phone != null && name != null) { - result.add(ContactEntry(name, phone)) - } - } while (cursor.moveToNext()) - return result - } - - private fun Cursor.getString(column: String): String? { - val idx = getColumnIndex(column) - if (idx < 0) return null - return getString(idx) - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/LRejectCallDelegate.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/LRejectCallDelegate.kt deleted file mode 100644 index 3b91d02..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/LRejectCallDelegate.kt +++ /dev/null @@ -1,28 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import android.app.Application -import android.content.Context -import android.telephony.TelephonyManager -import android.util.Log -import com.android.internal.telephony.ITelephony -import ru.shadowsparky.callblocker.domain.RejectCallDelegate - -class LRejectCallDelegate(private val app: Application) : RejectCallDelegate { - - override suspend fun reject() { - getTeleService()?.endCall() - } - - private fun getTeleService(): ITelephony? { - val tm = app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - try { - val c = Class.forName(tm.javaClass.name) - val m = c.getDeclaredMethod("getITelephony") - m.isAccessible = true - return m.invoke(tm) as ITelephony - } catch (e: Exception) { - Log.e("LRejectCallDelegate", "err", e) - } - return null - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/NonContactsRejectCallDelegate.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/NonContactsRejectCallDelegate.kt deleted file mode 100644 index b7848a3..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/NonContactsRejectCallDelegate.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import android.util.Log -import ru.shadowsparky.callblocker.BuildConfig -import ru.shadowsparky.callblocker.domain.ContactReader -import ru.shadowsparky.callblocker.domain.RejectCallDelegate - -class NonContactsRejectCallDelegate( - private val contactReader: ContactReader, - private val number: String?, - private val wrapper: RejectCallDelegate -) : RejectCallDelegate { - - override suspend fun reject() { - log("reject called with number $number") - if (number == null) { - log("number is null!!! exiting...") - return - } - val contacts = contactReader.read() - log("contacts found ${contacts.joinToString()}") - if (!contacts.any { it.phoneNumber == number }) { - log("calling number not found in contacts. rejecting") - wrapper.reject() - } else { - log("calling number found in contacts!") - } - } - - private fun log(msg: String) { - if (BuildConfig.DEBUG) { - Log.d(TAG, msg) - } - } - - companion object { - private const val TAG = "NonContactsCallDelegate" - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PRejectCallDelegate.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PRejectCallDelegate.kt deleted file mode 100644 index ab5bc51..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PRejectCallDelegate.kt +++ /dev/null @@ -1,20 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Build -import android.telecom.TelecomManager -import android.util.Log -import androidx.annotation.RequiresApi -import androidx.core.content.getSystemService -import ru.shadowsparky.callblocker.domain.RejectCallDelegate - -@RequiresApi(Build.VERSION_CODES.P) -class PRejectCallDelegate(private val context: Context) : RejectCallDelegate { - @SuppressLint("MissingPermission") // пофиг - @Suppress("DEPRECATION") // еще больше пофиг, пока работает будем использовать! - override suspend fun reject() { - Log.e("PRejectCallDelegate", "reject called", RuntimeException()) - context.getSystemService()?.endCall() - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PolicyBasedRejectDelegate.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PolicyBasedRejectDelegate.kt deleted file mode 100644 index e070554..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PolicyBasedRejectDelegate.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import org.koin.core.annotation.Factory -import ru.shadowsparky.callblocker.domain.ContactReader -import ru.shadowsparky.callblocker.domain.RejectCallDelegate -import ru.shadowsparky.callblocker.domain.RejectPolicy -import ru.shadowsparky.callblocker.domain.RejectPolicyManager - -@Factory -class PolicyBasedRejectDelegateFactory( - private val rejectPolicyManager: RejectPolicyManager, - private val contactReader: ContactReader, - private val rejectDelegate: RejectCallDelegate -) { - fun create(callingNumber: String?): PolicyBasedRejectDelegate { - return PolicyBasedRejectDelegate( - rejectPolicyManager, - callingNumber, - contactReader, - rejectDelegate - ) - } -} - -class PolicyBasedRejectDelegate( - private val rejectPolicyManager: RejectPolicyManager, - private val callingNumber: String?, - private val contactReader: ContactReader, - private val rejectDelegate: RejectCallDelegate -) : RejectCallDelegate { - override suspend fun reject() { - val wrapper = when (rejectPolicyManager.get()) { - RejectPolicy.All -> rejectDelegate - RejectPolicy.NonContacts -> NonContactsRejectCallDelegate( - contactReader, - callingNumber, - rejectDelegate - ) - - RejectPolicy.None -> null - } - wrapper?.reject() - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PreferenceRejectPolicyManager.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PreferenceRejectPolicyManager.kt deleted file mode 100644 index bf54cf4..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/data/PreferenceRejectPolicyManager.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.shadowsparky.callblocker.data - -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import org.koin.core.annotation.Factory -import ru.shadowsparky.callblocker.domain.RejectPolicy -import ru.shadowsparky.callblocker.domain.RejectPolicyManager -import ru.shadowsparky.domain.KeyValueStorage - -@Factory -class PreferenceRejectPolicyManager( - private val keyValueStorage: KeyValueStorage -) : RejectPolicyManager { - override val flow = keyValueStorage.getInt(POLICY_KEY).map { - RejectPolicy.findByCode(it ?: RejectPolicy.None.code) - } - - override suspend fun set(newPolicy: RejectPolicy) { - keyValueStorage.putInt(POLICY_KEY, newPolicy.code) - } - - override suspend fun get(): RejectPolicy = flow.first() - - companion object { - private const val POLICY_KEY = "policy" - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/ContactReader.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/ContactReader.kt deleted file mode 100644 index c0374c2..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/ContactReader.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ru.shadowsparky.callblocker.domain - -interface ContactReader { - - suspend fun read(): Set -} - -data class ContactEntry( - val name: String, - val phoneNumber: String -) diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectCallDelegate.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectCallDelegate.kt deleted file mode 100644 index 68d0cc6..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectCallDelegate.kt +++ /dev/null @@ -1,5 +0,0 @@ -package ru.shadowsparky.callblocker.domain - -interface RejectCallDelegate { - suspend fun reject() -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectPolicyManager.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectPolicyManager.kt deleted file mode 100644 index cd2d546..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/domain/RejectPolicyManager.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.shadowsparky.callblocker.domain - -import kotlinx.coroutines.flow.Flow - -interface RejectPolicyManager { - val flow: Flow - - suspend fun set(newPolicy: RejectPolicy) - suspend fun get(): RejectPolicy -} - -sealed class RejectPolicy(val code: Int) { - data object None : RejectPolicy(0) - data object All : RejectPolicy(1) - data object NonContacts : RejectPolicy(2) - - companion object { - val values: Set get() = setOf(None, All, NonContacts) - - fun findByCode(code: Int): RejectPolicy { - return values.first { it.code == code } - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/App.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/App.kt deleted file mode 100644 index 1e14e72..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/App.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ru.shadowsparky.callblocker.presentation - -import android.app.Application -import org.koin.android.ext.koin.androidContext -import org.koin.core.annotation.KoinApplication -import org.koin.plugin.module.dsl.startKoin -import ru.shadowsparky.callblocker.AppModule - -@KoinApplication(modules = [AppModule::class]) -object CallBlocker - -class App : Application() { - override fun onCreate() { - super.onCreate() - startKoin { - androidContext(this@App) - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallReceiver.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallReceiver.kt deleted file mode 100644 index c0b05fc..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallReceiver.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.shadowsparky.callblocker.presentation - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.telephony.TelephonyManager -import android.util.Log -import ru.shadowsparky.callblocker.BuildConfig - -class RejectCallReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val keys = intent?.extras?.keySet() ?: return - val values = keys.map { it to intent.extras?.get(it).toString() } - - if (BuildConfig.DEBUG) { - Log.d("RejectCallReceiver", "start") - values.forEach { - Log.d("RejectCallReceiver", "key ${it.first}; value: ${it.second}") - } - Log.d("RejectCallReceiver", "end") - } - - if (intent.action == TelephonyManager.ACTION_PHONE_STATE_CHANGED && context != null) { - val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE) - if (state == TelephonyManager.EXTRA_STATE_RINGING) { - @Suppress("DEPRECATION") - val number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) - RejectCallWorker.start(context, number) - } - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallWorker.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallWorker.kt deleted file mode 100644 index c9063f7..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/RejectCallWorker.kt +++ /dev/null @@ -1,36 +0,0 @@ -package ru.shadowsparky.callblocker.presentation - -import android.content.Context -import androidx.work.CoroutineWorker -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import androidx.work.workDataOf -import ru.shadowsparky.callblocker.data.PolicyBasedRejectDelegateFactory -import ru.shadowsparky.koin - -class RejectCallWorker( - appContext: Context, - params: WorkerParameters -) : CoroutineWorker(appContext, params) { - private val delegateFactory by koin.inject() - - override suspend fun doWork(): Result { - val callingNumber = inputData.getString(NUMBER_KEY) - delegateFactory.create(callingNumber).reject() - return Result.success() - } - - companion object { - private const val NUMBER_KEY = "number" - - fun start(context: Context?, number: String?) { - context ?: return - val wm = WorkManager.getInstance(context) - val request = OneTimeWorkRequestBuilder() - .setInputData(workDataOf(NUMBER_KEY to number)) - .build() - wm.enqueue(request) - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/MainActivity.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/MainActivity.kt deleted file mode 100644 index a3100a8..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/MainActivity.kt +++ /dev/null @@ -1,42 +0,0 @@ -package ru.shadowsparky.callblocker.presentation.ui - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import ru.shadowsparky.callblocker.R -import ru.shadowsparky.callblocker.domain.RejectPolicyManager -import ru.shadowsparky.koin -import ru.shadowsparky.ui.AppScaffold - -class MainActivity : ComponentActivity() { - private val rejectPolicyManager by koin.inject() - - @OptIn(ExperimentalMaterial3Api::class) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - val permissionRequest = newPermissionRequest() - setContent { - AppScaffold( - appBar = { - TopAppBar(title = { Text(stringResource(R.string.app_name)) }) - } - ) { - CompositionLocalProvider(LocalPermissionRequest provides permissionRequest) { - Box(modifier = Modifier.padding(it)) { - PolicyContent(rejectPolicyManager) - } - } - } - } - } -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PermissionRequest.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PermissionRequest.kt deleted file mode 100644 index 0ce66db..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PermissionRequest.kt +++ /dev/null @@ -1,147 +0,0 @@ -package ru.shadowsparky.callblocker.presentation.ui - -import android.Manifest -import android.content.pm.PackageManager -import android.os.Build -import androidx.activity.ComponentActivity -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.RequiresApi -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.core.content.ContextCompat -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import ru.shadowsparky.callblocker.R - -interface PermissionRequest { - val hasPermission: StateFlow - - fun getTitle(): String - - fun getDescription(): String - - fun requestPermission() -} - -abstract class MutablePermissionRequest(private val hasPermissionCallback: () -> Boolean) : - PermissionRequest { - override val hasPermission = MutableStateFlow(hasPermissionCallback()) - - init { - initState() - } - - override fun requestPermission() { - if (!hasPermission.value) requestPermissionInternal() - } - - private fun initState() { - if (!hasPermission.value) { - this.hasPermission.tryEmit(hasPermissionCallback()) - } - } - - protected abstract fun requestPermissionInternal() -} - -class MultiplePermissionRequest(private val requests: List) : PermissionRequest { - override val hasPermission - get() = getLatestDeclinedRequest()?.hasPermission ?: MutableStateFlow(true) - - override fun getTitle(): String { - return getLatestDeclinedRequest()?.getTitle() ?: "null" - } - - override fun getDescription(): String { - return getLatestDeclinedRequest()?.getDescription() ?: "null" - } - - private fun getLatestDeclinedRequest(): PermissionRequest? { - return requests.firstOrNull { !it.hasPermission.value } - } - - override fun requestPermission() { - getLatestDeclinedRequest()?.requestPermission() - } -} - -val LocalPermissionRequest: ProvidableCompositionLocal = - staticCompositionLocalOf { throw IllegalStateException("request must be initialized here") } - -fun ComponentActivity.newPermissionRequest(): PermissionRequest { - val list = mutableListOf( - ReadContactsPermissionRequest(this), - ReadCallLogPermissionRequest(this), - newAnswerPhonePermissionRequest(), - ReadPhoneStatePermissionRequest(this), - ) - return MultiplePermissionRequest(list) -} - -abstract class AndroidPermissionRequest( - private val activity: ComponentActivity, - private val permission: String, - hasPermissionCallback: () -> Boolean = { - ContextCompat.checkSelfPermission( - activity, - permission - ) == PackageManager.PERMISSION_GRANTED - } -) : MutablePermissionRequest(hasPermissionCallback) { - private val launcher = - activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { - hasPermission.tryEmit(it) - } - - override fun requestPermissionInternal() { - launcher.launch(permission) - } - - protected fun getString(code: Int, vararg args: Any): String { - return activity.getString(code, args) - } -} - -class ReadContactsPermissionRequest( - activity: ComponentActivity -) : AndroidPermissionRequest(activity, Manifest.permission.READ_CONTACTS) { - override fun getDescription(): String = getString(R.string.read_contacts_desc) - override fun getTitle(): String = getString(R.string.read_contacts) -} - -class ReadCallLogPermissionRequest( - activity: ComponentActivity -) : AndroidPermissionRequest(activity, Manifest.permission.READ_CALL_LOG) { - override fun getDescription(): String = getString(R.string.read_call_log_desc) - override fun getTitle(): String = getString(R.string.read_call_log) -} - -class ReadPhoneStatePermissionRequest( - activity: ComponentActivity -) : AndroidPermissionRequest(activity, Manifest.permission.READ_PHONE_STATE) { - override fun getDescription(): String = getString(R.string.read_phone_state_desc) - override fun getTitle(): String = getString(R.string.read_phone_state) -} - -private fun ComponentActivity.newAnswerPhonePermissionRequest(): PermissionRequest { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AnswerPhoneCallPermissionRequest(this) - } else { - CallPhonePermissionRequest(this) - } -} - -class CallPhonePermissionRequest( - activity: ComponentActivity -) : AndroidPermissionRequest(activity, Manifest.permission.CALL_PHONE) { - override fun getDescription(): String = getString(R.string.answer_phone_calls_desc) - override fun getTitle(): String = getString(R.string.answer_phone_calls) -} - -@RequiresApi(Build.VERSION_CODES.O) -class AnswerPhoneCallPermissionRequest( - activity: ComponentActivity -) : AndroidPermissionRequest(activity, Manifest.permission.ANSWER_PHONE_CALLS) { - override fun getDescription(): String = getString(R.string.answer_phone_calls_desc) - override fun getTitle(): String = getString(R.string.answer_phone_calls) -} diff --git a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PolicyScreen.kt b/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PolicyScreen.kt deleted file mode 100644 index dbf3d0d..0000000 --- a/apps/call-blocker/src/main/kotlin/ru/shadowsparky/callblocker/presentation/ui/PolicyScreen.kt +++ /dev/null @@ -1,159 +0,0 @@ -package ru.shadowsparky.callblocker.presentation.ui - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch -import ru.shadowsparky.callblocker.R -import ru.shadowsparky.callblocker.domain.RejectPolicy -import ru.shadowsparky.callblocker.domain.RejectPolicyManager -import ru.shadowsparky.ui.components.ListPreference - -@Composable -fun PolicyContent(policyManager: RejectPolicyManager) { - val state = policyManager.flow.collectAsState(null) - if (state.value != null) { - InitializedPolicyContent(state, policyManager, LocalPermissionRequest.current) - } -} - -@Preview(showBackground = true) -@Composable -fun InitializedPolicyScreenPreview() { - val state = remember { mutableStateOf(RejectPolicy.None) } - InitializedPolicyContent( - state = state, - manager = object : RejectPolicyManager { - override val flow = MutableStateFlow(RejectPolicy.None) - override suspend fun set(newPolicy: RejectPolicy) = flow.emit(newPolicy) - override suspend fun get(): RejectPolicy = flow.value - }, - MultiplePermissionRequest( - listOf( - NoOpPermissionRequest("1"), - NoOpPermissionRequest("2"), - NoOpPermissionRequest("3") - ) - ) - ) -} - -private class NoOpPermissionRequest(private val prefix: String) : PermissionRequest { - override val hasPermission = MutableStateFlow(false) - - override fun getTitle(): String { - return "$prefix noop" - } - - override fun getDescription(): String { - return "$prefix noop" - } - - override fun requestPermission() { - hasPermission.tryEmit(true) - } -} - -@Composable -private fun InitializedPolicyContent( - state: State, - manager: RejectPolicyManager, - permissionRequest: PermissionRequest -) { - val coroutineScope = rememberCoroutineScope() - Column { - val items = RejectPolicy.values.associateWith { it.getTitle() } - val item = remember { mutableStateOf(state.value!!) } - val hasPermission by permissionRequest.hasPermission.collectAsState() - if (!hasPermission) { - MissingPermissionCard( - permissionRequest, - modifier = Modifier.padding(4.dp), - ) - } - ListPreference( - title = stringResource(id = R.string.selected_policy), - items = items, - selectedItem = item.value, - onItemSelected = { - item.value = it - coroutineScope.launch { - manager.set(it) - } - } - ) - } -} - -@Composable -private fun RejectPolicy.getTitle(): String { - val code = when (this) { - RejectPolicy.All -> R.string.reject_all - RejectPolicy.NonContacts -> R.string.reject_non_contacts - RejectPolicy.None -> R.string.reject_none - } - return stringResource(code) -} - -@Preview -@Composable -fun MissingPermissionCardPreview() { - MissingPermissionCard(NoOpPermissionRequest("1")) -} - -@Composable -fun MissingPermissionCard( - request: PermissionRequest, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer, - ) - ) { - Column(modifier = Modifier.padding(8.dp)) { - Text( - stringResource(id = R.string.warning), - style = MaterialTheme.typography.titleMedium - ) - Text( - stringResource( - id = R.string.mission_permission_text, - request.getTitle(), - request.getDescription() - ) - ) - Box( - contentAlignment = Alignment.CenterEnd, - modifier = Modifier.fillMaxWidth() - ) { - OutlinedButton( - onClick = { request.requestPermission() }, - ) { - Text(stringResource(id = R.string.grant_permission)) - } - } - } - } -} diff --git a/apps/call-blocker/src/main/res/drawable/ic_launcher_background.xml b/apps/call-blocker/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/apps/call-blocker/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/call-blocker/src/main/res/drawable/ic_launcher_foreground.xml b/apps/call-blocker/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index cb40282..0000000 --- a/apps/call-blocker/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/apps/call-blocker/src/main/res/drawable/ic_launcher_monochrome.xml b/apps/call-blocker/src/main/res/drawable/ic_launcher_monochrome.xml deleted file mode 100644 index 1305eda..0000000 --- a/apps/call-blocker/src/main/res/drawable/ic_launcher_monochrome.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index c78bee3..0000000 --- a/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index c78bee3..0000000 --- a/apps/call-blocker/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 26260d8..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 4e3e8be..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 06aeb53..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 350fdef..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 58a414b..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index daefa2a..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index bbd7f15..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b713377..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 1b797c6..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index f631722..0000000 Binary files a/apps/call-blocker/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/call-blocker/src/main/res/values/colors.xml b/apps/call-blocker/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127..0000000 --- a/apps/call-blocker/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/apps/call-blocker/src/main/res/values/ic_launcher_background.xml b/apps/call-blocker/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899..0000000 --- a/apps/call-blocker/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FFFFFF - \ No newline at end of file diff --git a/apps/call-blocker/src/main/res/values/strings.xml b/apps/call-blocker/src/main/res/values/strings.xml deleted file mode 100644 index 03dd6e0..0000000 --- a/apps/call-blocker/src/main/res/values/strings.xml +++ /dev/null @@ -1,22 +0,0 @@ - - Блокировка звонков - Права не выданы - Выключено - Отменять любые звонки - Отменять звонки не из контактов - Режим работы - чтения контактов - распознавания находится ли номер в списке контактов или нет - Внимание! - Не выданы права для %1$s. Приложению эти права необходимы для %2$s. Пожалуйста, выдайте права, иначе приложение может работать некорректно" - просмотра номера звонящего - просмотра информации о звонке - получения уведомлений о звонках - чтение состояния телефона - сброса входящего звонка - ответа на звонок - Выдать права - Настройки - О приложении - Версия - \ No newline at end of file diff --git a/apps/call-blocker/src/main/res/values/themes.xml b/apps/call-blocker/src/main/res/values/themes.xml deleted file mode 100644 index 1de0704..0000000 --- a/apps/call-blocker/src/main/res/values/themes.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/apps/call-blocker/src/main/res/xml/backup_rules.xml b/apps/call-blocker/src/main/res/xml/backup_rules.xml deleted file mode 100644 index fa0f996..0000000 --- a/apps/call-blocker/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/call-blocker/src/main/res/xml/data_extraction_rules.xml b/apps/call-blocker/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997..0000000 --- a/apps/call-blocker/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt b/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt index 377775f..4399a15 100644 --- a/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt +++ b/build-logic/convention/src/main/kotlin/ru/shadowsparky/plugin/AndroidApplication.kt @@ -30,9 +30,14 @@ class AndroidApplication : Plugin { } } } + lint { + checkReleaseBuilds = false + abortOnError = false + } buildTypes { getByName("release") { isMinifyEnabled = true + isShrinkResources = true if (keystorePropertiesFile.exists()) { signingConfig = signingConfigs.getByName("release") } diff --git a/gradle.properties b/gradle.properties index c157c29..de4512b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,14 @@ kotlin.code.style=official -xcodeproj=./iosApp android.useAndroidX=true -org.gradle.jvmargs=-Xms512m -Xmx4096m -XX:+HeapDumpOnOutOfMemoryError + +org.gradle.jvmargs=-Xms1g -Xmx3584m -XX:+UseParallelGC -Dfile.encoding=UTF-8 +org.gradle.daemon.idletimeout=60000 + org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true android.nonTransitiveRClass=false +android.r8.strictFullModeForKeepRules=true + org.gradle.caching=true org.gradle.parallel=true org.gradle.configuration-cache=true -android.uniquePackageNames=false -android.dependency.useConstraints=false -android.r8.strictFullModeForKeepRules=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e991604..392c16e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,21 +2,21 @@ activity-compose = "1.13.0" appcompat = "1.7.1" -coil-compose = "3.4.0" -compose-gradle-plugin = "1.11.1" -compose-version = "1.11.0-alpha03" +coil-compose = "3.5.0" +compose-gradle-plugin = "1.12.0-beta01" +compose-version = "1.12.0-alpha03" desugar_jdk_libs = "2.1.5" haze = "1.7.2" -java-jwt = "4.5.2" -kotlin-version = "2.4.0" -agp = "9.2.1" +java-jwt = "4.6.0" +kotlin-version = "2.4.10" +agp = "9.3.0" coroutines-extensions = "1.5.5" decompose-version = "3.5.0" image-loader = "1.10.0" junit = "4.13.2" lifecycleCoroutines = "2.5.0" -material = "1.11.3" +material = "1.11.4" materialVersion = "1.14.0" media3 = "1.10.1" mockk = "1.14.11" @@ -24,15 +24,15 @@ jsoup = "1.22.2" gradle-plugin = "1.5.5" kotlinx-coroutines-core = "1.11.0" kotlinx-serialization-json = "1.11.0" -ktor-plugin = "3.5.0" +ktor-plugin = "3.5.1" kxml2 = "2.4.1" logback-classic = "1.5.32" multiplatform-settings = "1.3.0" -postgresql = "42.7.11" +postgresql = "42.7.13" runner = "1.7.0" -sqlite = "2.6.2" +sqlite = "2.7.0" sqlite-driver = "2.3.2" -ui-tooling = "1.11.1" +ui-tooling = "1.11.4" essenty = "1.3.0" stately-common = "2.0.6" proj-target = "37" @@ -43,11 +43,11 @@ proj-server-java = "21" datastore-preferences-core = "1.2.1" ksp-version = "2.3.7" koin-core = "4.2.2" -koin-compiler = "1.0.1" +koin-compiler = "1.0.2" kotlin-dsl-version = "5.1.2" -coreKtx = "1.18.0" +coreKtx = "1.19.0" workRuntimeKtx = "2.11.2" -krpc = "0.10.2" +krpc = "0.10.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c3dd19e..a374128 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jun 23 20:20:20 MSK 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 95a2aa9..21914db 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,8 +34,6 @@ include(":apps:vbox:client:shared") include(":apps:vbox:client:web") include(":apps:vbox:backend") -include(":apps:call-blocker") - include(":apps:experimentarium:androidApp") include(":apps:experimentarium:shared") include(":apps:experimentarium:web")