Update to 1.2.0 version. Add deltachat autoconfig button and func. Fix logo, notifications.
@@ -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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
36
app/src/main/res/drawable-v24/ic_launcher_background.xml
Normal 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>
|
||||
38
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.5 KiB |
@@ -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>
|
||||
|
||||
15
app/src/main/res/values-sw320dp/dimens.xml
Normal 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>
|
||||
15
app/src/main/res/values-sw360dp/dimens.xml
Normal 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>
|
||||
21
app/src/main/res/values/dimens.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||