Changes for updating to Yggdrasil 0.5. (#49)

This commit is contained in:
Revertron 2023-10-28 23:31:32 +02:00 committed by GitHub
parent 9df80c0612
commit f4e1a75cfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 189 additions and 78 deletions

View file

@ -4,15 +4,14 @@ plugins {
}
android {
compileSdkVersion 29
buildToolsVersion "30.0.3"
compileSdkVersion 33
defaultConfig {
applicationId "eu.neilalexander.yggdrasil"
minSdkVersion 21
targetSdkVersion 29
versionCode 13
versionName "0.1-013"
targetSdkVersion 33
versionCode 14
versionName "0.1-014"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -51,12 +50,12 @@ android {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.0'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.preference:preference-ktx:1.2.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View file

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
@ -23,8 +24,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SettingsActivity" android:parentActivityName=".MainActivity" />
<activity android:name=".PeersActivity" android:parentActivityName=".MainActivity" />
<activity android:name=".SettingsActivity" android:parentActivityName=".MainActivity" android:exported="false" />
<activity android:name=".PeersActivity" android:parentActivityName=".MainActivity" android:exported="false" />
<activity android:name=".DnsActivity" android:exported="false" />
<activity android:name=".TileServiceActivity" android:theme="@android:style/Theme.NoDisplay"
android:allowTaskReparenting="true"

View file

@ -32,14 +32,12 @@ object ConfigurationProxy {
val newJson = JSONObject(String(Mobile.generateConfigJSON()))
updateJSON { json ->
json.put("PrivateKey", newJson.getString("PrivateKey"))
json.put("PublicKey", newJson.getString("PublicKey"))
}
}
fun setKeys(privateKey: String, publicKey: String) {
fun setKeys(privateKey: String) {
updateJSON { json ->
json.put("PrivateKey", privateKey)
json.put("PublicKey", publicKey)
}
}
@ -62,7 +60,8 @@ object ConfigurationProxy {
{
"Regex": ".*",
"Beacon": true,
"Listen": true
"Listen": true,
"Password": ""
}
""".trimIndent()))
json.put("MulticastInterfaces", ar)
@ -94,4 +93,12 @@ object ConfigurationProxy {
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Beacon", value)
}
}
var multicastPassword: String
get() = (json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).getString("Password")
set(value) {
updateJSON { json ->
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Password", value)
}
}
}

View file

@ -47,8 +47,10 @@ class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
@RequiresApi(Build.VERSION_CODES.N)
override fun onStateChange(state: State) {
if (state != currentState) {
val componentName = ComponentName(this, YggTileService::class.java)
TileService.requestListeningState(this, componentName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val componentName = ComponentName(this, YggTileService::class.java)
TileService.requestListeningState(this, componentName)
}
if (state != State.Disabled) {
val notification = createServiceNotification(this, state)
@ -68,7 +70,11 @@ fun createServiceNotification(context: Context, state: State): Notification {
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
val text = when (state) {
State.Disabled -> context.getText(R.string.tile_disabled)
@ -91,7 +97,11 @@ fun createPermissionMissingNotification(context: Context): Notification {
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, flags)
return NotificationCompat.Builder(context, MAIN_CHANNEL_ID)
.setShowWhen(false)

View file

@ -24,7 +24,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var enabledLabel: TextView
private lateinit var ipAddressLabel: TextView
private lateinit var subnetLabel: TextView
private lateinit var coordinatesLabel: TextView
private lateinit var treeLengthLabel: TextView
private lateinit var peersLabel: TextView
private lateinit var peersRow: LinearLayoutCompat
private lateinit var dnsLabel: TextView
@ -53,7 +53,7 @@ class MainActivity : AppCompatActivity() {
enabledLabel = findViewById(R.id.yggdrasilStatusLabel)
ipAddressLabel = findViewById(R.id.ipAddressValue)
subnetLabel = findViewById(R.id.subnetValue)
coordinatesLabel = findViewById(R.id.coordinatesValue)
treeLengthLabel = findViewById(R.id.treeLengthValue)
peersLabel = findViewById(R.id.peersValue)
peersRow = findViewById(R.id.peersTableRow)
dnsLabel = findViewById(R.id.dnsValue)
@ -155,11 +155,11 @@ class MainActivity : AppCompatActivity() {
"state" -> {
enabledLabel.text = if (intent.getBooleanExtra("started", false)) {
var count = 0
if (intent.hasExtra("dht")) {
val dht = intent.getStringExtra("dht")
if (dht != null && dht != "null") {
val dhtState = JSONArray(dht)
count = dhtState.length()
if (intent.hasExtra("tree")) {
val tree = intent.getStringExtra("tree")
if (tree != null && tree != "null") {
val treeState = JSONArray(tree)
count = treeState.length()
}
}
if (count == 0) {
@ -175,7 +175,7 @@ class MainActivity : AppCompatActivity() {
}
ipAddressLabel.text = intent.getStringExtra("ip") ?: "N/A"
subnetLabel.text = intent.getStringExtra("subnet") ?: "N/A"
coordinatesLabel.text = intent.getStringExtra("coords") ?: "[]"
treeLengthLabel.text = intent.getStringExtra("coords") ?: "0"
if (intent.hasExtra("peers")) {
val peerState = JSONArray(intent.getStringExtra("peers") ?: "[]")
peersLabel.text = when (val count = peerState.length()) {

View file

@ -1,7 +1,8 @@
package eu.neilalexander.yggdrasil
import android.content.*
import android.content.Intent
import android.net.VpnService
import android.net.wifi.WifiManager
import android.os.Build
import android.os.ParcelFileDescriptor
import android.system.OsConstants
@ -42,6 +43,7 @@ open class PacketTunnelProvider: VpnService() {
private var parcel: ParcelFileDescriptor? = null
private var readerStream: FileInputStream? = null
private var writerStream: FileOutputStream? = null
private var multicastLock: WifiManager.MulticastLock? = null
override fun onCreate() {
super.onCreate()
@ -101,6 +103,13 @@ open class PacketTunnelProvider: VpnService() {
val notification = createServiceNotification(this, State.Enabled)
startForeground(SERVICE_NOTIFICATION_ID, notification)
// Acquire multicast lock
val wifi = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
multicastLock = wifi.createMulticastLock("Yggdrasil").apply {
setReferenceCounted(false)
acquire()
}
Log.d(TAG, config.getJSON().toString())
yggdrasil.startJSON(config.getJSONByteArray())
@ -163,16 +172,7 @@ open class PacketTunnelProvider: VpnService() {
updater()
}
var intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("coords", yggdrasil.coordsString)
intent.putExtra("peers", yggdrasil.peersJSON)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
intent = Intent(YGG_STATE_INTENT)
var intent = Intent(YGG_STATE_INTENT)
intent.putExtra("state", STATE_ENABLED)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
@ -221,6 +221,7 @@ open class PacketTunnelProvider: VpnService() {
stopForeground(true)
stopSelf()
multicastLock?.release()
}
private fun connect() {
@ -231,27 +232,37 @@ open class PacketTunnelProvider: VpnService() {
}
private fun updater() {
Thread.sleep(500)
var lastStateUpdate = System.currentTimeMillis()
updates@ while (started.get()) {
val treeJSON = yggdrasil.treeJSON
var treeLength = 0
if (treeJSON != null && treeJSON != "null") {
val treeState = JSONArray(treeJSON)
treeLength = treeState.length()
}
if ((application as GlobalApplication).needUiUpdates()) {
val intent = Intent(STATE_INTENT)
intent.putExtra("type", "state")
intent.putExtra("started", true)
intent.putExtra("ip", yggdrasil.addressString)
intent.putExtra("subnet", yggdrasil.subnetString)
intent.putExtra("coords", yggdrasil.coordsString)
intent.putExtra("pubkey", yggdrasil.publicKeyString)
intent.putExtra("coords", "$treeLength")
intent.putExtra("peers", yggdrasil.peersJSON)
intent.putExtra("dht", yggdrasil.dhtjson)
intent.putExtra("tree", treeJSON)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
val curTime = System.currentTimeMillis()
if (lastStateUpdate + 10000 < curTime) {
val intent = Intent(YGG_STATE_INTENT)
var state = STATE_ENABLED
val dht = yggdrasil.dhtjson
if (dht != null && dht != "null") {
val dhtState = JSONArray(dht)
val count = dhtState.length()
if (yggdrasil.routingEntries > 0) {
state = STATE_CONNECTED
}
if (treeJSON != null && treeJSON != "null") {
val treeState = JSONArray(treeJSON)
val count = treeState.length()
if (count > 1)
state = STATE_CONNECTED
}

View file

@ -7,10 +7,13 @@ import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.core.widget.doOnTextChanged
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.material.textfield.TextInputEditText
import org.json.JSONArray
@ -27,6 +30,7 @@ class PeersActivity : AppCompatActivity() {
private lateinit var configuredTableLabel: TextView
private lateinit var multicastListenSwitch: Switch
private lateinit var multicastBeaconSwitch: Switch
private lateinit var passwordEdit: EditText
private lateinit var addPeerButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
@ -62,6 +66,30 @@ class PeersActivity : AppCompatActivity() {
multicastListenPanel.setOnClickListener {
multicastListenSwitch.toggle()
}
passwordEdit = findViewById(R.id.passwordEdit)
passwordEdit.setText(config.multicastPassword)
passwordEdit.doOnTextChanged { text, _, _, _ ->
config.multicastPassword = text.toString()
}
passwordEdit.setOnKeyListener { _, keyCode, _ ->
(keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
}
findViewById<View>(R.id.passwordTableRow).setOnKeyListener { _, keyCode, event ->
Log.i("Key", keyCode.toString())
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
passwordEdit.requestFocus()
true
} else {
false
}
} else {
false
}
}
addPeerButton = findViewById(R.id.addPeerButton)
addPeerButton.setOnClickListener {
@ -156,7 +184,7 @@ class PeersActivity : AppCompatActivity() {
val view = inflater.inflate(R.layout.peers_connected, null)
val ip = peer.getString("IP")
view.findViewById<TextView>(R.id.addressLabel).text = ip
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("Remote")
view.findViewById<TextView>(R.id.detailsLabel).text = peer.getString("URI")
connectedTableLayout.addView(view)
}
}
@ -168,7 +196,9 @@ class PeersActivity : AppCompatActivity() {
when (intent.getStringExtra("type")) {
"state" -> {
if (intent.hasExtra("peers")) {
val peersArray = JSONArray(intent.getStringExtra("peers") ?: "[]")
val peers1 = intent.getStringExtra("peers")
//Log.i("PeersActivity", "Peers json: $peers1")
val peersArray = JSONArray(peers1 ?: "[]")
val array = Array(peersArray.length()) { i ->
peersArray.getJSONObject(i)
}

View file

@ -1,8 +1,12 @@
package eu.neilalexander.yggdrasil
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
@ -13,6 +17,7 @@ import android.view.View
import android.widget.*
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.widget.doOnTextChanged
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.json.JSONObject
class SettingsActivity : AppCompatActivity() {
@ -87,11 +92,10 @@ class SettingsActivity : AppCompatActivity() {
val view = inflater.inflate(R.layout.dialog_set_keys, null)
val builder: AlertDialog.Builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.Theme_MaterialComponents_DayNight_Dialog))
val privateKey = view.findViewById<EditText>(R.id.private_key)
val publicKey = view.findViewById<EditText>(R.id.public_key)
builder.setTitle(getString(R.string.set_keys))
builder.setView(view)
builder.setPositiveButton(getString(R.string.save)) { dialog, _ ->
config.setKeys(privateKey.text.toString(), publicKey.text.toString())
config.setKeys(privateKey.text.toString())
updateView()
dialog.dismiss()
}
@ -113,13 +117,40 @@ class SettingsActivity : AppCompatActivity() {
}
private fun updateView() {
val nodeinfo = config.getJSON().optJSONObject("NodeInfo")
val json = config.getJSON()
val nodeinfo = json.optJSONObject("NodeInfo")
if (nodeinfo != null) {
deviceNameEntry.setText(nodeinfo.getString("name"), TextView.BufferType.EDITABLE)
} else {
deviceNameEntry.setText("", TextView.BufferType.EDITABLE)
}
publicKeyLabel.text = config.getJSON().getString("PublicKey")
publicKeyLabel.text = json.optString("PublicKey")
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, IntentFilter(PacketTunnelProvider.STATE_INTENT)
)
(application as GlobalApplication).subscribe()
}
override fun onPause() {
super.onPause()
(application as GlobalApplication).unsubscribe()
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
// To be able to get public key from running Yggdrasil we use this receiver, as we don't have this field in config
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.hasExtra("pubkey")) {
val tree = intent.getStringExtra("pubkey")
if (tree != null && tree != "null") {
publicKeyLabel.text = intent.getStringExtra("pubkey")
}
}
}
}
}

View file

@ -210,11 +210,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/main_coordinates"
android:text="@string/main_tree_length"
android:textColor="?attr/textDefault" />
<TextView
android:id="@+id/coordinatesValue"
android:id="@+id/treeLengthValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
@ -224,7 +224,7 @@
android:scrollHorizontally="false"
android:selectAllOnFocus="true"
android:singleLine="true"
android:text="[]"
android:text="0"
android:textAlignment="viewEnd"
android:textIsSelectable="true" />
</androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -217,6 +217,37 @@
</TableRow>
<TableRow
android:id="@+id/passwordTableRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:paddingStart="4pt"
android:paddingTop="4pt"
android:paddingEnd="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/peers_multicast_password_hint"
android:textColor="?attr/textDefault" />
<EditText
android:id="@+id/passwordEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:ems="10"
android:hint="@string/tap_to_edit"
android:inputType="textVisiblePassword"
android:padding="0pt"
android:textAlignment="textEnd"
android:textSize="14sp" />
</TableRow>
</TableLayout>
<TextView

View file

@ -12,15 +12,6 @@
android:paddingTop="4pt"
android:paddingBottom="4pt">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/public_key_label" />
<EditText
android:id="@+id/public_key"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -46,7 +46,7 @@
<string name="main_not_available">Н</string>
<string name="main_ip">Адрес</string>
<string name="main_subnet">Подсеть</string>
<string name="main_coordinates">Координаты</string>
<string name="main_tree_length">Высота дерева</string>
<string name="main_configuration">Конфигурация</string>
<string name="main_peers">Пиры</string>
<string name="main_dns_servers">Серверы DNS</string>
@ -58,7 +58,8 @@
<string name="discoverable_over_multicast">Находимый через multicast</string>
<string name="search_for_multicast_peers">Искать пиров через multicast</string>
<string name="configured_peers_hint">Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.</string>
<string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string>
<string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. У них должен быть одинаковый пароль. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string>
<string name="peers_multicast_password_hint">Пароль</string>
<string name="node_info">Об узле</string>
<string name="device_name">Название устройства</string>
<string name="tap_to_edit">Нажмите для изменения</string>
@ -79,8 +80,7 @@
<string name="channel_description">Главный канал нотификаций сервиса</string>
<string name="permission_notification_text">Нажмите здесь чтобы включить Yggdrasil.</string>
<string name="add_peer_help">Введите полный URI пира для добавления. Yggdrasil будет автоматически подключаться к нему при запуске.</string>
<string name="public_key_label">Публичный ключ:</string>
<string name="private_key_label">Приватный ключ:</string>
<string name="set_keys">Установить свои ключи</string>
<string name="set_keys">Установить свой ключ</string>
<string name="save">Сохранить</string>
</resources>

View file

@ -46,7 +46,7 @@
<string name="main_not_available">N/A</string>
<string name="main_ip">IP</string>
<string name="main_subnet">Subnet</string>
<string name="main_coordinates">Coordinates</string>
<string name="main_tree_length">Tree length</string>
<string name="main_configuration">Configuration</string>
<string name="main_peers">Peers</string>
<string name="main_dns_servers">DNS servers</string>
@ -58,7 +58,8 @@
<string name="discoverable_over_multicast">Discoverable over multicast</string>
<string name="search_for_multicast_peers">Search for multicast peers</string>
<string name="configured_peers_hint">Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer.</string>
<string name="peer_connectivity_hint">Multicast peers will be discovered on the same Wi-Fi network or via USB. Data charges may apply when using mobile data. You can prevent data usage in the device settings.</string>
<string name="peer_connectivity_hint">Multicast peers will be discovered on the same Wi-Fi network or via USB. They must have the same password. Data charges may apply when using mobile data. You can prevent data usage in the device settings.</string>
<string name="peers_multicast_password_hint">Password</string>
<string name="node_info">Node Info</string>
<string name="device_name">Device Name</string>
<string name="tap_to_edit">Tap to edit</string>
@ -79,8 +80,7 @@
<string name="channel_description">Main channel for foreground notification</string>
<string name="permission_notification_text">Tap here to enable Yggdrasil.</string>
<string name="add_peer_help">Enter the full URI of the peer to add. Yggdrasil will automatically connect to this peer when started.</string>
<string name="public_key_label">Public key:</string>
<string name="private_key_label">Private key:</string>
<string name="set_keys">Set your own keys</string>
<string name="set_keys">Set your own key</string>
<string name="save">Save</string>
</resources>

View file

@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.7.20'
ext.kotlin_version = '1.9.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

View file

@ -1,6 +1,6 @@
#Mon Jun 14 15:11:35 BST 2021
#Sat Aug 26 21:38:34 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

@ -1 +1 @@
Subproject commit 14f1cd4696a37b0f7fdcb067fac337c46953f8af
Subproject commit 7f9d4f3f6d06262e212a8dd101d51fda134332da