init
This commit is contained in:
commit
0c422c9562
45 changed files with 1724 additions and 0 deletions
|
@ -0,0 +1,24 @@
|
|||
package me.lucky.re
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("me.lucky.re", appContext.packageName)
|
||||
}
|
||||
}
|
40
app/src/main/AndroidManifest.xml
Normal file
40
app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.lucky.re">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="true" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:name=".Application"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Re">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".CallRedirectionService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.CallRedirectionService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
</manifest>
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
12
app/src/main/java/me/lucky/re/Application.kt
Normal file
12
app/src/main/java/me/lucky/re/Application.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
package me.lucky.re
|
||||
|
||||
import android.app.Application
|
||||
import com.google.android.material.color.DynamicColors
|
||||
|
||||
@Suppress("unused")
|
||||
class Application : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
}
|
100
app/src/main/java/me/lucky/re/CallRedirectionService.kt
Normal file
100
app/src/main/java/me/lucky/re/CallRedirectionService.kt
Normal file
|
@ -0,0 +1,100 @@
|
|||
package me.lucky.re
|
||||
|
||||
import android.database.Cursor
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.telecom.CallRedirectionService
|
||||
import android.telecom.PhoneAccountHandle
|
||||
|
||||
class CallRedirectionService : CallRedirectionService() {
|
||||
companion object {
|
||||
private const val SIGNAL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call"
|
||||
private const val TELEGRAM_MIMETYPE = "vnd.android.cursor.item/vnd.org.telegram.messenger.android.call"
|
||||
}
|
||||
|
||||
private lateinit var prefs: Preferences
|
||||
private lateinit var dialog: DialogWindow
|
||||
private var connectivityManager: ConnectivityManager? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
prefs = Preferences(this)
|
||||
dialog = DialogWindow(this)
|
||||
connectivityManager = getSystemService(ConnectivityManager::class.java)
|
||||
}
|
||||
|
||||
override fun onPlaceCall(
|
||||
handle: Uri,
|
||||
initialPhoneAccount: PhoneAccountHandle,
|
||||
allowInteractiveResponse: Boolean,
|
||||
) {
|
||||
if (!prefs.isServiceEnabled || !hasInternet()) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
}
|
||||
val uri = getUriFromPhoneNumber(handle.schemeSpecificPart)
|
||||
if (uri != null) {
|
||||
dialog.show(uri)
|
||||
return
|
||||
}
|
||||
placeCallUnmodified()
|
||||
}
|
||||
|
||||
private fun getContactIdByPhoneNumber(phoneNumber: String): String? {
|
||||
var result: String? = null
|
||||
val cursor: Cursor?
|
||||
try {
|
||||
cursor = contentResolver.query(
|
||||
Uri.withAppendedPath(
|
||||
ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
|
||||
Uri.encode(phoneNumber)
|
||||
),
|
||||
arrayOf(ContactsContract.PhoneLookup._ID),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
} catch (exc: SecurityException) { return null }
|
||||
cursor?.apply {
|
||||
if (moveToFirst()) {
|
||||
result = getString(getColumnIndexOrThrow(ContactsContract.PhoneLookup._ID))
|
||||
}
|
||||
close()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getUriFromPhoneNumber(phoneNumber: String): Uri? {
|
||||
val contactId = getContactIdByPhoneNumber(phoneNumber) ?: return null
|
||||
var result: Uri? = null
|
||||
val cursor: Cursor?
|
||||
try {
|
||||
cursor = contentResolver.query(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
arrayOf(ContactsContract.Data._ID),
|
||||
"${ContactsContract.Data.CONTACT_ID} = ? AND " +
|
||||
"${ContactsContract.Data.MIMETYPE} IN (?, ?)",
|
||||
arrayOf(contactId, SIGNAL_MIMETYPE, TELEGRAM_MIMETYPE),
|
||||
null,
|
||||
)
|
||||
} catch (exc: SecurityException) { return null }
|
||||
cursor?.apply {
|
||||
if (moveToFirst()) {
|
||||
result = Uri.withAppendedPath(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
Uri.encode(getString(getColumnIndexOrThrow(ContactsContract.Data._ID))),
|
||||
)
|
||||
}
|
||||
close()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun hasInternet(): Boolean {
|
||||
return connectivityManager
|
||||
?.getNetworkCapabilities(connectivityManager?.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false
|
||||
}
|
||||
}
|
67
app/src/main/java/me/lucky/re/DialogWindow.kt
Normal file
67
app/src/main/java/me/lucky/re/DialogWindow.kt
Normal file
|
@ -0,0 +1,67 @@
|
|||
package me.lucky.re
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.PixelFormat
|
||||
import android.net.Uri
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.WindowManager
|
||||
import java.util.*
|
||||
import kotlin.concurrent.timerTask
|
||||
|
||||
class DialogWindow(private val service: CallRedirectionService) {
|
||||
companion object {
|
||||
private const val CANCEL_DELAY = 3000L
|
||||
}
|
||||
|
||||
private val windowManager = service
|
||||
.applicationContext
|
||||
.getSystemService(WindowManager::class.java)
|
||||
@Suppress("InflateParams")
|
||||
private val floatView = LayoutInflater
|
||||
.from(service.applicationContext)
|
||||
.inflate(R.layout.popup, null)
|
||||
private val layoutParams: WindowManager.LayoutParams = WindowManager.LayoutParams().apply {
|
||||
format = PixelFormat.TRANSLUCENT
|
||||
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
gravity = Gravity.BOTTOM
|
||||
width = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
y = 384
|
||||
}
|
||||
private var data: Uri? = null
|
||||
private var timer: Timer? = null
|
||||
|
||||
init {
|
||||
floatView.setOnClickListener {
|
||||
timer?.cancel()
|
||||
service.placeCallUnmodified()
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(uri: Uri?) {
|
||||
remove()
|
||||
timer?.cancel()
|
||||
timer = Timer()
|
||||
timer?.schedule(timerTask {
|
||||
service.cancelCall()
|
||||
remove()
|
||||
Intent(Intent.ACTION_VIEW).also {
|
||||
it.data = data
|
||||
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
service.startActivity(it)
|
||||
}
|
||||
data = null
|
||||
}, CANCEL_DELAY)
|
||||
data = uri
|
||||
windowManager?.addView(floatView, layoutParams)
|
||||
}
|
||||
|
||||
private fun remove() {
|
||||
try {
|
||||
windowManager?.removeView(floatView)
|
||||
} catch (exc: IllegalArgumentException) {}
|
||||
}
|
||||
}
|
100
app/src/main/java/me/lucky/re/MainActivity.kt
Normal file
100
app/src/main/java/me/lucky/re/MainActivity.kt
Normal file
|
@ -0,0 +1,100 @@
|
|||
package me.lucky.re
|
||||
|
||||
import android.Manifest
|
||||
import android.app.role.RoleManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
import me.lucky.re.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private val PERMISSIONS = arrayOf(
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.CALL_PHONE,
|
||||
)
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var prefs: Preferences
|
||||
private var roleManager: RoleManager? = null
|
||||
|
||||
private val registerForCallRedirectionRole =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {}
|
||||
|
||||
private val registerForGeneralPermissions =
|
||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {}
|
||||
|
||||
private val registerForDrawOverlays =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
init()
|
||||
setup()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
prefs = Preferences(this)
|
||||
roleManager = getSystemService(RoleManager::class.java)
|
||||
binding.apply {
|
||||
toggle.isChecked = prefs.isServiceEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun setup() {
|
||||
binding.apply {
|
||||
toggle.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked && !hasPermissions()) {
|
||||
toggle.isChecked = false
|
||||
requestPermissions()
|
||||
return@setOnCheckedChangeListener
|
||||
}
|
||||
prefs.isServiceEnabled = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
when {
|
||||
!hasGeneralPermissions() -> requestGeneralPermissions()
|
||||
!hasDrawOverlays() -> requestDrawOverlays()
|
||||
!hasCallRedirectionRole() -> requestCallRedirectionRole()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasPermissions(): Boolean {
|
||||
return hasGeneralPermissions() && hasDrawOverlays() && hasCallRedirectionRole()
|
||||
}
|
||||
|
||||
private fun requestDrawOverlays() {
|
||||
registerForDrawOverlays.launch(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))
|
||||
}
|
||||
|
||||
private fun requestGeneralPermissions() {
|
||||
registerForGeneralPermissions.launch(PERMISSIONS)
|
||||
}
|
||||
|
||||
private fun hasGeneralPermissions(): Boolean {
|
||||
return !PERMISSIONS.any { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }
|
||||
}
|
||||
|
||||
private fun hasDrawOverlays(): Boolean {
|
||||
return Settings.canDrawOverlays(this)
|
||||
}
|
||||
|
||||
private fun requestCallRedirectionRole() {
|
||||
registerForCallRedirectionRole
|
||||
.launch(roleManager?.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION))
|
||||
}
|
||||
|
||||
private fun hasCallRedirectionRole(): Boolean {
|
||||
return roleManager?.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION) ?: false
|
||||
}
|
||||
}
|
17
app/src/main/java/me/lucky/re/Preferences.kt
Normal file
17
app/src/main/java/me/lucky/re/Preferences.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
package me.lucky.re
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
class Preferences(ctx: Context) {
|
||||
companion object {
|
||||
private const val SERVICE_ENABLED = "service_enabled"
|
||||
}
|
||||
|
||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
|
||||
var isServiceEnabled: Boolean
|
||||
get() = prefs.getBoolean(SERVICE_ENABLED, false)
|
||||
set(value) = prefs.edit { putBoolean(SERVICE_ENABLED, value) }
|
||||
}
|
20
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
20
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.13710937"
|
||||
android:scaleY="0.13710937"
|
||||
android:translateX="18.9"
|
||||
android:translateY="18.9">
|
||||
<path
|
||||
android:pathData="M416,196c33.14,0 60,26.86 60,60c0,33.14 -26.86,60 -60,60s-60,-26.86 -60,-60C356,222.86 382.86,196 416,196z"
|
||||
android:fillColor="#57606F"/>
|
||||
<path
|
||||
android:pathData="M256,196c33.14,0 60,26.86 60,60c0,33.14 -26.86,60 -60,60c-33.14,0 -60,-26.86 -60,-60C196,222.86 222.86,196 256,196z"
|
||||
android:fillColor="#F73E54"/>
|
||||
<path
|
||||
android:pathData="M96,196c33.14,0 60,26.86 60,60c0,33.14 -26.86,60 -60,60s-60,-26.86 -60,-60C36,222.86 62.86,196 96,196z"
|
||||
android:fillColor="#57606F"/>
|
||||
</group>
|
||||
</vector>
|
29
app/src/main/res/layout/activity_main.xml
Normal file
29
app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?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:padding="32dp"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/toggle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="12dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
21
app/src/main/res/layout/popup.xml
Normal file
21
app/src/main/res/layout/popup.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
card_view:cardBackgroundColor="@android:color/transparent"
|
||||
card_view:cardCornerRadius="32dp"
|
||||
card_view:cardElevation="0dp"
|
||||
card_view:contentPadding="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/popup"
|
||||
android:padding="16dp"
|
||||
android:text="@string/info"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
12
app/src/main/res/values-night/themes.xml
Normal file
12
app/src/main/res/values-night/themes.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Re" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
6
app/src/main/res/values-ru/strings.xml
Normal file
6
app/src/main/res/values-ru/strings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Re</string>
|
||||
<string name="description">Приложение будет от всей души пытаться перенаправить исходящие вызовы в Сигнал или Телеграм, но иногда у него может не получаться. Для работы ему нужны тонны разрешений. Кликайте на переключатель и выдавайте разрешения пока он не включится.</string>
|
||||
<string name="info">Перенаправление</string>
|
||||
</resources>
|
10
app/src/main/res/values/colors.xml
Normal file
10
app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="popup">#E3E3E3E3</color>
|
||||
</resources>
|
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
6
app/src/main/res/values/strings.xml
Normal file
6
app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Re</string>
|
||||
<string name="description">The app will try to redirect outgoing calls to Signal or Telegram if available. For work it will require many permissions. Click on toggle and grant permissions until it turns ON.</string>
|
||||
<string name="info">Redirecting</string>
|
||||
</resources>
|
12
app/src/main/res/values/themes.xml
Normal file
12
app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Re" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
17
app/src/test/java/me/lucky/re/ExampleUnitTest.kt
Normal file
17
app/src/test/java/me/lucky/re/ExampleUnitTest.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
package me.lucky.re
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue