Update to 1.2.0 version. Add deltachat autoconfig button and func. Fix logo, notifications.

This commit is contained in:
JB-SelfCompany
2025-11-24 14:08:33 +03:00
parent c04ee97f9a
commit ca26f2b348
34 changed files with 860 additions and 264 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.jbselfcompany.tyr"
minSdk 23
targetSdk 33
versionCode 4
versionName "1.1.0"
versionCode 5
versionName "1.2.0"
resourceConfigurations += ['en', 'ru']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -7,6 +7,11 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<!-- Query installed packages (Android 11+) -->
<queries>
<package android:name="com.b44t.messenger" />
</queries>
<!-- Foreground service permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

View File

@@ -346,11 +346,11 @@ class YggmailService : Service(), LogCallback {
)
val statusText = when (status) {
ServiceStatus.STARTING -> getString(R.string.notification_status_starting)
ServiceStatus.RUNNING -> getString(R.string.notification_status_running)
ServiceStatus.STOPPING -> getString(R.string.notification_status_stopping)
ServiceStatus.STOPPED -> getString(R.string.notification_status_stopped)
ServiceStatus.ERROR -> lastError ?: getString(R.string.notification_status_error)
ServiceStatus.STARTING -> getString(R.string.service_starting)
ServiceStatus.RUNNING -> getString(R.string.service_running)
ServiceStatus.STOPPING -> getString(R.string.service_stopping)
ServiceStatus.STOPPED -> getString(R.string.service_stopped)
ServiceStatus.ERROR -> lastError ?: getString(R.string.service_error)
}
return NotificationCompat.Builder(this, TyrApplication.CHANNEL_ID_SERVICE)

View File

@@ -33,10 +33,6 @@ class AboutActivity : AppCompatActivity() {
findViewById<AppCompatImageButton>(R.id.button_github).setOnClickListener {
openLink("https://github.com/JB-SelfCompany/Tyr")
}
findViewById<AppCompatImageButton>(R.id.button_website).setOnClickListener {
openLink("https://business.shd.company")
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@@ -26,7 +26,9 @@ import com.jbselfcompany.tyr.service.ServiceStatusListener
import com.jbselfcompany.tyr.service.YggmailService
import com.jbselfcompany.tyr.ui.onboarding.OnboardingActivity
import com.jbselfcompany.tyr.ui.settings.SettingsActivity
import com.jbselfcompany.tyr.utils.AutoconfigServer
import com.jbselfcompany.tyr.utils.PermissionManager
import android.util.Log
/**
* Main activity displaying service status and mail configuration.
@@ -36,23 +38,11 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
private lateinit var binding: ActivityMainBinding
private val configRepository by lazy { TyrApplication.instance.configRepository }
private val autoconfigServer by lazy { AutoconfigServer(this) }
private var yggmailService: YggmailService? = null
private var serviceBound = false
// Permission launcher for notification permission (Android 13+)
private val notificationPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// After notification permission, request battery optimization exclusion
requestBatteryOptimizationIfNeeded()
} else {
// Still request battery optimization even if notification was denied
requestBatteryOptimizationIfNeeded()
}
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as YggmailService.LocalBinder
@@ -87,8 +77,8 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
setupUI()
bindService()
// Request permissions if needed (only on first launch)
requestPermissionsIfNeeded()
// Don't request permissions automatically on first launch
// They will be shown as Snackbars in onResume() instead
}
override fun onResume() {
@@ -100,6 +90,8 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
override fun onDestroy() {
super.onDestroy()
unbindService()
// Stop autoconfig server when activity is destroyed
autoconfigServer.stop()
}
private fun setupUI() {
@@ -112,7 +104,12 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
}
}
// Copy mail address button
// Setup DeltaChat button with DCACCOUNT link
binding.buttonSetupDeltachat.setOnClickListener {
setupDeltaChat()
}
// Copy mail address button (legacy support)
binding.buttonCopyAddress.setOnClickListener {
val address = configRepository.getMailAddress()
if (!address.isNullOrEmpty()) {
@@ -156,9 +153,11 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
if (!mailAddress.isNullOrEmpty()) {
binding.textMailAddress.text = mailAddress
binding.textMailAddress.visibility = View.VISIBLE
binding.buttonSetupDeltachat.visibility = View.VISIBLE
binding.buttonCopyAddress.visibility = View.VISIBLE
} else {
binding.textMailAddress.visibility = View.GONE
binding.buttonSetupDeltachat.visibility = View.GONE
binding.buttonCopyAddress.visibility = View.GONE
}
@@ -176,6 +175,101 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
}
}
private fun setupDeltaChat() {
try {
// Get credentials
val email = configRepository.getMailAddress()
val password = configRepository.getPassword()
if (email.isNullOrEmpty() || password.isNullOrEmpty()) {
Snackbar.make(
binding.root,
R.string.dcaccount_error,
Snackbar.LENGTH_LONG
).show()
return
}
// Generate DCLOGIN URL (simpler, doesn't require HTTPS)
// DCLOGIN embeds credentials directly in the URI
val dcloginUrl = autoconfigServer.generateDcloginUrl(email, password)
Log.d("MainActivity", "Generated DCLOGIN URL: $dcloginUrl")
// Check if DeltaChat is installed
val isDeltaChatInstalled = try {
packageManager.getPackageInfo("com.b44t.messenger", 0)
true
} catch (e: Exception) {
false
}
if (isDeltaChatInstalled) {
// DeltaChat is installed, try to open it with DCLOGIN URL
try {
// First, try with package specified
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(dcloginUrl)
setPackage("com.b44t.messenger")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
Snackbar.make(
binding.root,
R.string.dcaccount_opened,
Snackbar.LENGTH_SHORT
).show()
} catch (e: Exception) {
Log.w("MainActivity", "Failed to open with package, trying without", e)
// Try without package specification
try {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(dcloginUrl)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
Snackbar.make(
binding.root,
R.string.dcaccount_opened,
Snackbar.LENGTH_SHORT
).show()
} catch (e2: Exception) {
Log.e("MainActivity", "Failed to open DCLOGIN URL", e2)
// Fallback: copy to clipboard
copyDcloginToClipboard(dcloginUrl)
}
}
} else {
// DeltaChat not installed - just show message, no clipboard copy
MaterialAlertDialogBuilder(this)
.setTitle(R.string.deltachat_not_installed_title)
.setMessage(R.string.deltachat_not_installed_message)
.setPositiveButton(R.string.ok, null)
.show()
}
} catch (e: Exception) {
Log.e("MainActivity", "Error setting up DeltaChat", e)
Snackbar.make(
binding.root,
R.string.dcaccount_error,
Snackbar.LENGTH_LONG
).show()
}
}
private fun copyDcloginToClipboard(dcloginUrl: String) {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("DCLOGIN", dcloginUrl)
clipboard.setPrimaryClip(clip)
Snackbar.make(
binding.root,
R.string.dcaccount_copied,
Snackbar.LENGTH_LONG
).show()
}
private fun showInstructionsDialog() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.deltachat_setup)
@@ -234,36 +328,8 @@ class MainActivity : AppCompatActivity(), ServiceStatusListener {
}
}
/**
* Request permissions if needed (first launch only)
*/
private fun requestPermissionsIfNeeded() {
// Check if this is the first time requesting permissions
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
val permissionsRequested = prefs.getBoolean("permissions_requested", false)
if (!permissionsRequested) {
// Mark as requested
prefs.edit().putBoolean("permissions_requested", true).apply()
// Request notification permission first (Android 13+)
if (!PermissionManager.hasNotificationPermission(this)) {
PermissionManager.requestNotificationPermission(this, notificationPermissionLauncher)
} else {
// If notification permission is already granted, request battery optimization
requestBatteryOptimizationIfNeeded()
}
}
}
/**
* Request battery optimization exclusion if needed
*/
private fun requestBatteryOptimizationIfNeeded() {
if (!PermissionManager.isBatteryOptimizationDisabled(this)) {
PermissionManager.requestBatteryOptimizationExclusion(this)
}
}
// Removed automatic permission requests on first launch
// Permissions are now only shown as Snackbar warnings in onResume()
/**
* Show Snackbar warnings for missing permissions (like in Mimir app)

View File

@@ -0,0 +1,336 @@
package com.jbselfcompany.tyr.utils
import android.content.Context
import android.util.Log
import com.jbselfcompany.tyr.TyrApplication
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.Socket
import java.nio.charset.StandardCharsets
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.thread
/**
* Simple HTTP server for DeltaChat autoconfig endpoint.
* Provides DCACCOUNT URL support by serving account configuration as JSON.
*/
class AutoconfigServer(private val context: Context) {
companion object {
private const val TAG = "AutoconfigServer"
private const val PORT = 8888
private const val TOKEN_EXPIRY_MS = 3600000L // 1 hour
}
private var serverSocket: ServerSocket? = null
private var running = false
private var serverThread: Thread? = null
// Store tokens with their creation time
private val tokens = ConcurrentHashMap<String, Long>()
/**
* Start the HTTP server on localhost
*/
fun start() {
if (running) {
Log.w(TAG, "Server already running")
return
}
try {
serverSocket = ServerSocket()
serverSocket?.reuseAddress = true
serverSocket?.bind(InetSocketAddress("127.0.0.1", PORT))
running = true
serverThread = thread(name = "AutoconfigServer") {
Log.i(TAG, "Autoconfig server started on port $PORT")
while (running) {
try {
val socket = serverSocket?.accept()
socket?.let { handleClient(it) }
} catch (e: Exception) {
if (running) {
Log.e(TAG, "Error accepting connection", e)
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to start server", e)
running = false
}
}
/**
* Stop the HTTP server
*/
fun stop() {
running = false
try {
serverSocket?.close()
serverSocket = null
serverThread?.interrupt()
serverThread = null
Log.i(TAG, "Autoconfig server stopped")
} catch (e: Exception) {
Log.e(TAG, "Error stopping server", e)
}
}
/**
* Check if server is running
*/
fun isRunning(): Boolean = running
/**
* Generate a new token for autoconfig URL
* @return token string
*/
fun generateToken(): String {
// Clean expired tokens
cleanExpiredTokens()
val token = UUID.randomUUID().toString().replace("-", "")
tokens[token] = System.currentTimeMillis()
return token
}
/**
* Generate DCACCOUNT URL with a new token
* @return DCACCOUNT URL string
*/
fun generateDcaccountUrl(): String {
val token = generateToken()
return "DCACCOUNT:https://127.0.0.1:$PORT/new_email?t=$token"
}
/**
* Generate DCLOGIN URL with embedded credentials (no HTTP server needed)
* This is a simpler alternative that doesn't require HTTPS
*
* Format: dclogin://user@host/?p=password&v=1&ih=imap_host&ip=imap_port&is=security&sh=smtp_host&sp=smtp_port&ss=security&ic=cert_checks
*
* @param email Mail address
* @param password Account password
* @return DCLOGIN URL string
*/
fun generateDcloginUrl(email: String, password: String): String {
// DCLOGIN format according to DeltaChat specification
// dclogin://email@domain/?p=password&v=1&ih=imap_host&ip=port&is=security&sh=smtp_host&sp=port&ss=security&ic=cert_checks
// URL encode the password to handle special characters
val encodedPassword = java.net.URLEncoder.encode(password, "UTF-8")
// Build DCLOGIN URL with IMAP and SMTP configuration
return buildString {
append("dclogin://")
append(email)
append("/?p=")
append(encodedPassword)
append("&v=1")
// IMAP configuration
append("&ih=127.0.0.1")
append("&ip=1143")
append("&is=plain") // No encryption for localhost
// SMTP configuration
append("&sh=127.0.0.1")
append("&sp=1025")
append("&ss=plain") // No encryption for localhost
// Certificate checks: 0 = automatic
append("&ic=0")
}
}
/**
* Clean expired tokens
*/
private fun cleanExpiredTokens() {
val now = System.currentTimeMillis()
tokens.entries.removeIf { (_, timestamp) ->
now - timestamp > TOKEN_EXPIRY_MS
}
}
/**
* Validate token
*/
private fun isValidToken(token: String?): Boolean {
if (token == null) return false
val timestamp = tokens[token] ?: return false
val now = System.currentTimeMillis()
// Check if token is expired
return (now - timestamp) <= TOKEN_EXPIRY_MS
}
/**
* Handle HTTP client connection
*/
private fun handleClient(socket: Socket) {
thread {
try {
val reader = BufferedReader(InputStreamReader(socket.getInputStream()))
val writer = OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)
// Read HTTP request line
val requestLine = reader.readLine() ?: ""
Log.d(TAG, "Request: $requestLine")
// Read headers (we don't need them, but we must consume them)
var line: String?
do {
line = reader.readLine()
} while (!line.isNullOrEmpty())
// Parse request
val parts = requestLine.split(" ")
if (parts.size >= 2) {
val method = parts[0]
val path = parts[1]
if (method == "GET" && path.startsWith("/new_email")) {
handleNewEmailRequest(path, writer)
} else {
send404(writer)
}
} else {
send400(writer)
}
writer.flush()
socket.close()
} catch (e: Exception) {
Log.e(TAG, "Error handling client", e)
}
}
}
/**
* Handle /new_email endpoint
*/
private fun handleNewEmailRequest(path: String, writer: OutputStreamWriter) {
try {
// Extract token from query string
val token = extractToken(path)
if (!isValidToken(token)) {
send401(writer, "Invalid or expired token")
return
}
// Get mail address and password from config
val configRepository = TyrApplication.instance.configRepository
val email = configRepository.getMailAddress()
val password = configRepository.getPassword()
if (email.isNullOrEmpty() || password.isNullOrEmpty()) {
send500(writer, "Account not configured")
return
}
// Build JSON response
val json = JSONObject()
json.put("email", email)
json.put("password", password)
val responseBody = json.toString()
// Send HTTP response
writer.write("HTTP/1.1 200 OK\r\n")
writer.write("Content-Type: application/json\r\n")
writer.write("Content-Length: ${responseBody.toByteArray(StandardCharsets.UTF_8).size}\r\n")
writer.write("Connection: close\r\n")
writer.write("\r\n")
writer.write(responseBody)
// Remove token after use (one-time use)
tokens.remove(token)
Log.i(TAG, "Served autoconfig for $email")
} catch (e: Exception) {
Log.e(TAG, "Error handling /new_email", e)
send500(writer, "Internal server error")
}
}
/**
* Extract token from query string
*/
private fun extractToken(path: String): String? {
val queryStart = path.indexOf('?')
if (queryStart == -1) return null
val query = path.substring(queryStart + 1)
val params = query.split('&')
for (param in params) {
val keyValue = param.split('=')
if (keyValue.size == 2 && keyValue[0] == "t") {
return keyValue[1]
}
}
return null
}
/**
* Send HTTP 400 Bad Request
*/
private fun send400(writer: OutputStreamWriter) {
writer.write("HTTP/1.1 400 Bad Request\r\n")
writer.write("Content-Length: 0\r\n")
writer.write("Connection: close\r\n")
writer.write("\r\n")
}
/**
* Send HTTP 401 Unauthorized
*/
private fun send401(writer: OutputStreamWriter, message: String) {
val json = JSONObject()
json.put("error", message)
val body = json.toString()
writer.write("HTTP/1.1 401 Unauthorized\r\n")
writer.write("Content-Type: application/json\r\n")
writer.write("Content-Length: ${body.toByteArray(StandardCharsets.UTF_8).size}\r\n")
writer.write("Connection: close\r\n")
writer.write("\r\n")
writer.write(body)
}
/**
* Send HTTP 404 Not Found
*/
private fun send404(writer: OutputStreamWriter) {
writer.write("HTTP/1.1 404 Not Found\r\n")
writer.write("Content-Length: 0\r\n")
writer.write("Connection: close\r\n")
writer.write("\r\n")
}
/**
* Send HTTP 500 Internal Server Error
*/
private fun send500(writer: OutputStreamWriter, message: String) {
val json = JSONObject()
json.put("error", message)
val body = json.toString()
writer.write("HTTP/1.1 500 Internal Server Error\r\n")
writer.write("Content-Type: application/json\r\n")
writer.write("Content-Length: ${body.toByteArray(StandardCharsets.UTF_8).size}\r\n")
writer.write("Connection: close\r\n")
writer.write("\r\n")
writer.write(body)
}
}

View File

@@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="210"
android:viewportHeight="297">
<group android:scaleX="0.7070707"
android:translateX="30.757576">
<path
android:pathData="M23.92,42.74L186.08,42.74A24.68,24.68 0,0 1,210.76 67.42L210.76,229.58A24.68,24.68 0,0 1,186.08 254.26L23.92,254.26A24.68,24.68 0,0 1,-0.76 229.58L-0.76,67.42A24.68,24.68 0,0 1,23.92 42.74z"
android:strokeLineJoin="bevel"
android:strokeWidth="4.99999"
android:strokeColor="#00000000"
android:strokeLineCap="round">
<aapt:attr name="android:fillColor">
<gradient
android:startX="105"
android:startY="42.74"
android:endX="105"
android:endY="254.26"
android:type="linear">
<item android:offset="0" android:color="#FF5EEFB8"/>
<item android:offset="0.5" android:color="#FF55DDAD"/>
<item android:offset="1" android:color="#FF4DCBA2"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m34.24,64.88v42.3l31.71,-14.05 23.73,14.05 -11.39,124.94h53.42l-11.39,-124.94 23.73,-14.05 31.71,14.05V64.88Z"
android:strokeLineJoin="bevel"
android:strokeWidth="4.97668"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -0,0 +1,38 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="210"
android:viewportHeight="297">
<group android:scaleX="0.5982906"
android:scaleY="0.84615386"
android:translateX="42.179485"
android:translateY="22.846153">
<path
android:pathData="M23.92,42.74L186.08,42.74A24.68,24.68 0,0 1,210.76 67.42L210.76,229.58A24.68,24.68 0,0 1,186.08 254.26L23.92,254.26A24.68,24.68 0,0 1,-0.76 229.58L-0.76,67.42A24.68,24.68 0,0 1,23.92 42.74z"
android:strokeLineJoin="bevel"
android:strokeWidth="4.99999"
android:strokeColor="#00000000"
android:strokeLineCap="round">
<aapt:attr name="android:fillColor">
<gradient
android:startX="105"
android:startY="42.74"
android:endX="105"
android:endY="254.26"
android:type="linear">
<item android:offset="0" android:color="#FF5EEFB8"/>
<item android:offset="0.5" android:color="#FF55DDAD"/>
<item android:offset="1" android:color="#FF4DCBA2"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m34.24,64.88v42.3l31.71,-14.05 23.73,14.05 -11.39,124.94h53.42l-11.39,-124.94 23.73,-14.05 31.71,14.05V64.88Z"
android:strokeLineJoin="bevel"
android:strokeWidth="4.97668"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:angle="90"
android:startColor="#49c29c"
android:endColor="#63fac0" />
<corners android:radius="6dp"/>
</shape>

View File

@@ -50,13 +50,13 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<LinearLayout
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/links_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:weightSum="2"
android:weightSum="1"
app:layout_constraintTop_toBottomOf="@id/links_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
@@ -70,15 +70,7 @@
android:contentDescription="@string/github"
android:src="@drawable/ic_github" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/button_website"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="16dp"
android:contentDescription="@string/website"
android:src="@drawable/ic_link" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
<TextView
android:id="@+id/libs_title"

View File

@@ -107,30 +107,44 @@
tools:text="abcd1234@yggmail"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_copy_address"
style="@style/Widget.Material3.Button.TextButton"
<!-- Server Info -->
<TextView
android:id="@+id/text_imap_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/copy_address"
android:visibility="gone"
tools:visibility="visible" />
android:layout_marginTop="8dp"
android:textAppearance="?attr/textAppearanceBody2"
tools:text="IMAP: 127.0.0.1:1143" />
<TextView
android:id="@+id/text_smtp_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="4dp"
android:textAppearance="?attr/textAppearanceBody2"
tools:text="SMTP: 127.0.0.1:1025" />
<TextView
android:id="@+id/text_imap_server"
<!-- Action Buttons -->
<com.google.android.material.button.MaterialButton
android:id="@+id/button_setup_deltachat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAppearance="?attr/textAppearanceBody2"
tools:text="IMAP: 127.0.0.1:1143" />
android:layout_marginTop="16dp"
android:text="@string/setup_deltachat"
android:visibility="gone"
app:icon="@drawable/ic_play_arrow"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_copy_address"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/copy_address"
android:visibility="gone"
app:icon="@drawable/ic_play_arrow"
tools:visibility="visible" />
</LinearLayout>

View File

@@ -8,7 +8,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="32dp">
android:padding="@dimen/onboarding_horizontal_padding">
<!-- Header Section -->
<TextView
@@ -18,7 +18,7 @@
android:layout_marginTop="32dp"
android:letterSpacing="0.01"
android:text="@string/setup_password"
android:textAppearance="?attr/textAppearanceHeadline4"
android:textSize="@dimen/text_size_onboarding_title"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
@@ -33,7 +33,7 @@
android:alpha="0.87"
android:lineSpacingExtra="2dp"
android:text="@string/password_description"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:textSize="@dimen/text_size_onboarding_description"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -63,7 +63,7 @@
android:inputType="textPassword"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:textAppearance="?attr/textAppearanceBodyLarge" />
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
@@ -90,7 +90,7 @@
android:inputType="textPassword"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:textAppearance="?attr/textAppearanceBodyLarge" />
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
@@ -111,7 +111,7 @@
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/password_security_info"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
android:textColor="?attr/colorOnSecondaryContainer" />
</com.google.android.material.card.MaterialCardView>

View File

@@ -8,7 +8,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="32dp">
android:padding="@dimen/onboarding_horizontal_padding">
<!-- Header Section -->
<TextView
@@ -18,7 +18,7 @@
android:layout_marginTop="32dp"
android:letterSpacing="0.01"
android:text="@string/configure_peers"
android:textAppearance="?attr/textAppearanceHeadline4"
android:textSize="@dimen/text_size_onboarding_title"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
@@ -33,7 +33,7 @@
android:alpha="0.87"
android:lineSpacingExtra="2dp"
android:text="@string/peers_description"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:textSize="@dimen/text_size_onboarding_description"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -96,7 +96,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_multicast"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
@@ -106,7 +106,7 @@
android:layout_marginTop="4dp"
android:alpha="0.87"
android:text="@string/multicast_description"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
@@ -130,7 +130,7 @@
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/peers_info"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
android:textColor="?attr/colorOnTertiaryContainer" />
</com.google.android.material.card.MaterialCardView>

View File

@@ -3,13 +3,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="32dp">
android:padding="@dimen/onboarding_horizontal_padding">
<!-- Modern Hero Section -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/hero_card"
android:layout_width="140dp"
android:layout_height="140dp"
android:layout_width="@dimen/onboarding_hero_size"
android:layout_height="@dimen/onboarding_hero_size"
android:layout_marginTop="48dp"
app:cardBackgroundColor="?attr/colorPrimaryContainer"
app:cardCornerRadius="32dp"
@@ -19,8 +19,8 @@
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_width="@dimen/onboarding_hero_icon_size"
android:layout_height="@dimen/onboarding_hero_icon_size"
android:layout_gravity="center"
android:contentDescription="@string/app_name"
android:src="@mipmap/ic_launcher" />
@@ -32,11 +32,11 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginTop="@dimen/onboarding_title_margin_top"
android:gravity="center"
android:letterSpacing="0.02"
android:text="@string/welcome_to_tyr"
android:textAppearance="?attr/textAppearanceHeadline3"
android:textSize="@dimen/text_size_onboarding_title"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
@@ -48,13 +48,13 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginTop="@dimen/onboarding_description_margin_top"
android:layout_marginEnd="16dp"
android:alpha="0.87"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:text="@string/welcome_description"
android:textAppearance="?attr/textAppearanceBodyLarge"
android:textSize="@dimen/text_size_onboarding_description"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -76,7 +76,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/feature_decentralized"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
app:chipBackgroundColor="?attr/colorSecondaryContainer"
app:chipStrokeWidth="0dp" />
@@ -85,7 +85,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/feature_encrypted"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
app:chipBackgroundColor="?attr/colorTertiaryContainer"
app:chipStrokeWidth="0dp" />
@@ -93,7 +93,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/feature_p2p"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="14sp"
app:chipBackgroundColor="?attr/colorPrimaryContainer"
app:chipStrokeWidth="0dp" />

View File

@@ -1,5 +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="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +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="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -36,13 +36,6 @@
<string name="service_error">Ошибка</string>
<string name="service_error_desc">Не удалось запустить службу</string>
<!-- Notification Status -->
<string name="notification_status_starting">Инициализация службы</string>
<string name="notification_status_running">Служба работает</string>
<string name="notification_status_stopping">Остановка службы</string>
<string name="notification_status_stopped">Служба не запущена</string>
<string name="notification_status_error">Ошибка службы</string>
<!-- Status Display -->
<string name="status_starting">Запуск…</string>
<string name="status_running">Работает</string>
@@ -56,11 +49,17 @@
<string name="mail_configuration">Настройка почты</string>
<string name="copy_address">Копировать адрес</string>
<string name="address_copied">Адрес скопирован в буфер обмена</string>
<string name="setup_deltachat">Настроить DeltaChat</string>
<string name="dcaccount_opened">Открываю DeltaChat с автоконфигурацией…</string>
<string name="dcaccount_copied">Ссылка DCACCOUNT скопирована! Откройте DeltaChat и вставьте её в экран настройки</string>
<string name="dcaccount_error">Не удалось создать ссылку автоконфигурации. Убедитесь, что служба запущена.</string>
<string name="deltachat_not_installed_title">DeltaChat не установлен</string>
<string name="deltachat_not_installed_message">DeltaChat не установлен на этом устройстве.\n\nЕсли у вас всё же установлен DeltaChat, воспользуйтесь инструкцией ниже для ручной настройки.</string>
<string name="smtp_server">SMTP: %1$s:%2$s</string>
<string name="imap_server">IMAP: %1$s:%2$s</string>
<string name="deltachat_setup">Настройка DeltaChat</string>
<string name="deltachat_setup_hint">Нажмите для просмотра инструкций</string>
<string name="deltachat_instructions">Настройка DeltaChat:\n\n1. Установите и откройте приложение DeltaChat\n2. Нажмите \"Добавить профиль\"\n3. Нажмите \"Дополнительно\" (под полями входа)\n4. Введите ваш email-адрес, указанный выше\n5. Введите пароль, установленный при первоначальной настройке\n6. В разделе IMAP-сервер:\n • Замените \"автоматически\" на: 127.0.0.1:1143\n • Безопасность: Нет/STARTTLS отключен\n7. В разделе SMTP-сервер:\n • Замените \"автоматически\" на: 127.0.0.1:1025\n • Безопасность: Нет/STARTTLS отключен\n8. Нажмите \"Войти\" для начала общения\n\nУбедитесь, что служба Tyr запущена перед использованием DeltaChat.</string>
<string name="deltachat_instructions">Настройка DeltaChat:\n\n1. Установите и откройте приложение DeltaChat\n2. Нажмите \"Создать новый профиль\"\n3. Введите имя, при необходимости выберите аватар в верхней части экрана\n4. Нажмите \"Использовать другой сервер\" (под полями входа)\n5. Введите ваш email-адрес, указанный выше\n6. Введите пароль, установленный при первоначальной настройке\n7. Нажмите \"✓\" в правом верхнем углу для начала общения\n\nУбедитесь, что служба Tyr запущена перед использованием DeltaChat.</string>
<!-- Onboarding -->
<string name="welcome_to_tyr">Добро пожаловать в Tyr</string>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Text sizes for very small screens (320dp width) -->
<!-- Onboarding text sizes - further reduced for very small screens -->
<dimen name="text_size_onboarding_title">20sp</dimen>
<dimen name="text_size_onboarding_description">13sp</dimen>
<!-- Minimal spacing for very compact screens -->
<dimen name="onboarding_horizontal_padding">16dp</dimen>
<dimen name="onboarding_hero_size">80dp</dimen>
<dimen name="onboarding_hero_icon_size">48dp</dimen>
<dimen name="onboarding_title_margin_top">16dp</dimen>
<dimen name="onboarding_description_margin_top">12dp</dimen>
</resources>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Text sizes for small screens (360dp width and similar) -->
<!-- Onboarding text sizes - reduced for small screens -->
<dimen name="text_size_onboarding_title">24sp</dimen>
<dimen name="text_size_onboarding_description">14sp</dimen>
<!-- Reduced spacing for compact screens -->
<dimen name="onboarding_horizontal_padding">24dp</dimen>
<dimen name="onboarding_hero_size">100dp</dimen>
<dimen name="onboarding_hero_icon_size">60dp</dimen>
<dimen name="onboarding_title_margin_top">24dp</dimen>
<dimen name="onboarding_description_margin_top">16dp</dimen>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Text sizes for different screen sizes -->
<!-- Default values for normal/large screens -->
<!-- Onboarding text sizes -->
<dimen name="text_size_onboarding_title">32sp</dimen>
<dimen name="text_size_onboarding_description">16sp</dimen>
<!-- Card content spacing -->
<dimen name="card_content_padding">16dp</dimen>
<dimen name="screen_horizontal_padding">16dp</dimen>
<dimen name="screen_vertical_padding">16dp</dimen>
<!-- Onboarding specific spacing -->
<dimen name="onboarding_horizontal_padding">32dp</dimen>
<dimen name="onboarding_hero_size">140dp</dimen>
<dimen name="onboarding_hero_icon_size">80dp</dimen>
<dimen name="onboarding_title_margin_top">40dp</dimen>
<dimen name="onboarding_description_margin_top">20dp</dimen>
</resources>

View File

@@ -36,13 +36,6 @@
<string name="service_error">Error</string>
<string name="service_error_desc">Failed to start service</string>
<!-- Notification Status -->
<string name="notification_status_starting">Initializing service</string>
<string name="notification_status_running">Service is running</string>
<string name="notification_status_stopping">Shutting down service</string>
<string name="notification_status_stopped">Service not running</string>
<string name="notification_status_error">Service error</string>
<!-- Status Display -->
<string name="status_starting">Starting…</string>
<string name="status_running">Running</string>
@@ -56,11 +49,17 @@
<string name="mail_configuration">Mail Configuration</string>
<string name="copy_address">Copy Address</string>
<string name="address_copied">Address copied to clipboard</string>
<string name="setup_deltachat">Setup DeltaChat</string>
<string name="dcaccount_opened">Opening DeltaChat with autoconfig…</string>
<string name="dcaccount_copied">DCACCOUNT link copied! Open DeltaChat and paste it in the setup screen</string>
<string name="dcaccount_error">Failed to generate autoconfig link. Make sure the service is running.</string>
<string name="deltachat_not_installed_title">DeltaChat Not Installed</string>
<string name="deltachat_not_installed_message">DeltaChat is not installed on this device.\n\nIf you have DeltaChat installed, please use the manual setup instructions shown below to configure it.</string>
<string name="smtp_server">SMTP: %1$s:%2$s</string>
<string name="imap_server">IMAP: %1$s:%2$s</string>
<string name="deltachat_setup">DeltaChat Setup</string>
<string name="deltachat_setup_hint">Tap to view setup instructions</string>
<string name="deltachat_instructions">To configure DeltaChat:\n\n1. Install and open DeltaChat app\n2. Tap \"Add Account\"\n3. Tap \"Advanced\" (below the login fields)\n4. Enter your email address shown above\n5. Enter the password you set during onboarding\n6. In IMAP Server section:\n • Replace \"automatic\" with: 127.0.0.1:1143\n • Security: None/STARTTLS disabled\n7. In SMTP Server section:\n • Replace \"automatic\" with: 127.0.0.1:1025\n • Security: None/STARTTLS disabled\n8. Tap \"Login\" to start chatting\n\nMake sure Tyr service is running before using DeltaChat.</string>
<string name="deltachat_instructions">DeltaChat setup:\n\n1. Install and open the DeltaChat app\n2. Click on "Create a new profile"\n3. Enter a name and select an avatar at the top of the screen if necessary\n4. Click on "Use a different server" (below the login fields)\n5. Enter your email address from above\n6. Enter the password set during the initial setup\n7. Click "✓" in the top right corner to start chatting. Make sure the Tyr service is running before using DeltaChat.</string>
<!-- Onboarding -->
<string name="welcome_to_tyr">Welcome to Tyr</string>

View File

@@ -2,6 +2,8 @@
<resources>
<!-- Base application theme (Light) -->
<style name="Base.Theme.Tyr" parent="Theme.Material3.Light.NoActionBar">
<!-- Support system font scaling -->
<item name="android:adjustViewBounds">true</item>
<item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>

296
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,69 +15,103 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

37
gradlew.bat vendored
View File

@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal