diff --git a/app/build.gradle b/app/build.gradle
index 0e2c1ac..9681432 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fd9efa0..5755373 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,11 @@
+
+
+
+
+
diff --git a/app/src/main/java/com/jbselfcompany/tyr/service/YggmailService.kt b/app/src/main/java/com/jbselfcompany/tyr/service/YggmailService.kt
index 784931c..19606aa 100644
--- a/app/src/main/java/com/jbselfcompany/tyr/service/YggmailService.kt
+++ b/app/src/main/java/com/jbselfcompany/tyr/service/YggmailService.kt
@@ -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)
diff --git a/app/src/main/java/com/jbselfcompany/tyr/ui/AboutActivity.kt b/app/src/main/java/com/jbselfcompany/tyr/ui/AboutActivity.kt
index 06e5f0e..5cf13ba 100644
--- a/app/src/main/java/com/jbselfcompany/tyr/ui/AboutActivity.kt
+++ b/app/src/main/java/com/jbselfcompany/tyr/ui/AboutActivity.kt
@@ -33,10 +33,6 @@ class AboutActivity : AppCompatActivity() {
findViewById(R.id.button_github).setOnClickListener {
openLink("https://github.com/JB-SelfCompany/Tyr")
}
-
- findViewById(R.id.button_website).setOnClickListener {
- openLink("https://business.shd.company")
- }
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
diff --git a/app/src/main/java/com/jbselfcompany/tyr/ui/MainActivity.kt b/app/src/main/java/com/jbselfcompany/tyr/ui/MainActivity.kt
index 42dda95..2903303 100644
--- a/app/src/main/java/com/jbselfcompany/tyr/ui/MainActivity.kt
+++ b/app/src/main/java/com/jbselfcompany/tyr/ui/MainActivity.kt
@@ -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)
diff --git a/app/src/main/java/com/jbselfcompany/tyr/utils/AutoconfigServer.kt b/app/src/main/java/com/jbselfcompany/tyr/utils/AutoconfigServer.kt
new file mode 100644
index 0000000..a7931e2
--- /dev/null
+++ b/app/src/main/java/com/jbselfcompany/tyr/utils/AutoconfigServer.kt
@@ -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()
+
+ /**
+ * 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)
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_background.xml b/app/src/main/res/drawable-v24/ic_launcher_background.xml
new file mode 100644
index 0000000..8d9ccfd
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_background.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..232c126
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 2c278bc..0000000
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index 30178be..c12f2d5 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -50,13 +50,13 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
-
@@ -70,15 +70,7 @@
android:contentDescription="@string/github"
android:src="@drawable/ic_github" />
-
-
+
-
+
+ android:layout_marginTop="8dp"
+ 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" />
+
+
diff --git a/app/src/main/res/layout/fragment_onboarding_password.xml b/app/src/main/res/layout/fragment_onboarding_password.xml
index b588348..48441fa 100644
--- a/app/src/main/res/layout/fragment_onboarding_password.xml
+++ b/app/src/main/res/layout/fragment_onboarding_password.xml
@@ -8,7 +8,7 @@
+ android:padding="@dimen/onboarding_horizontal_padding">
+ android:textSize="16sp" />
@@ -90,7 +90,7 @@
android:inputType="textPassword"
android:paddingTop="20dp"
android:paddingBottom="20dp"
- android:textAppearance="?attr/textAppearanceBodyLarge" />
+ android:textSize="16sp" />
@@ -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" />
diff --git a/app/src/main/res/layout/fragment_onboarding_peers.xml b/app/src/main/res/layout/fragment_onboarding_peers.xml
index e79f07d..7c2f81f 100644
--- a/app/src/main/res/layout/fragment_onboarding_peers.xml
+++ b/app/src/main/res/layout/fragment_onboarding_peers.xml
@@ -8,7 +8,7 @@
+ android:padding="@dimen/onboarding_horizontal_padding">
@@ -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" />
diff --git a/app/src/main/res/layout/fragment_onboarding_welcome.xml b/app/src/main/res/layout/fragment_onboarding_welcome.xml
index ef404bb..97e9d48 100644
--- a/app/src/main/res/layout/fragment_onboarding_welcome.xml
+++ b/app/src/main/res/layout/fragment_onboarding_welcome.xml
@@ -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">
@@ -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" />
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 036d09b..bbd3e02 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 036d09b..bbd3e02 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index 4e22f22..d2b0330 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index 4e22f22..d2b0330 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index 4f426a9..4ae8c95 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index 4f426a9..4ae8c95 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index 379a79a..87d0d04 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index 379a79a..87d0d04 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index 20e3d2b..f081423 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index 20e3d2b..f081423 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index 638ae18..92150d2 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 638ae18..92150d2 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6cfef58..1eb0b6b 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -36,13 +36,6 @@
Ошибка
Не удалось запустить службу
-
- Инициализация службы
- Служба работает
- Остановка службы
- Служба не запущена
- Ошибка службы
-
Запуск…
Работает
@@ -56,11 +49,17 @@
Настройка почты
Копировать адрес
Адрес скопирован в буфер обмена
+ Настроить DeltaChat
+ Открываю DeltaChat с автоконфигурацией…
+ Ссылка DCACCOUNT скопирована! Откройте DeltaChat и вставьте её в экран настройки
+ Не удалось создать ссылку автоконфигурации. Убедитесь, что служба запущена.
+ DeltaChat не установлен
+ DeltaChat не установлен на этом устройстве.\n\nЕсли у вас всё же установлен DeltaChat, воспользуйтесь инструкцией ниже для ручной настройки.
SMTP: %1$s:%2$s
IMAP: %1$s:%2$s
Настройка DeltaChat
Нажмите для просмотра инструкций
- Настройка 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.
+ Настройка DeltaChat:\n\n1. Установите и откройте приложение DeltaChat\n2. Нажмите \"Создать новый профиль\"\n3. Введите имя, при необходимости выберите аватар в верхней части экрана\n4. Нажмите \"Использовать другой сервер\" (под полями входа)\n5. Введите ваш email-адрес, указанный выше\n6. Введите пароль, установленный при первоначальной настройке\n7. Нажмите \"✓\" в правом верхнем углу для начала общения\n\nУбедитесь, что служба Tyr запущена перед использованием DeltaChat.
Добро пожаловать в Tyr
diff --git a/app/src/main/res/values-sw320dp/dimens.xml b/app/src/main/res/values-sw320dp/dimens.xml
new file mode 100644
index 0000000..a288e00
--- /dev/null
+++ b/app/src/main/res/values-sw320dp/dimens.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ 20sp
+ 13sp
+
+
+ 16dp
+ 80dp
+ 48dp
+ 16dp
+ 12dp
+
diff --git a/app/src/main/res/values-sw360dp/dimens.xml b/app/src/main/res/values-sw360dp/dimens.xml
new file mode 100644
index 0000000..d1a5b43
--- /dev/null
+++ b/app/src/main/res/values-sw360dp/dimens.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ 24sp
+ 14sp
+
+
+ 24dp
+ 100dp
+ 60dp
+ 24dp
+ 16dp
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..403e7f1
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ 32sp
+ 16sp
+
+
+ 16dp
+ 16dp
+ 16dp
+
+
+ 32dp
+ 140dp
+ 80dp
+ 40dp
+ 20dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b7e591b..1fb2385 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,13 +36,6 @@
Error
Failed to start service
-
- Initializing service
- Service is running
- Shutting down service
- Service not running
- Service error
-
Starting…
Running
@@ -56,11 +49,17 @@
Mail Configuration
Copy Address
Address copied to clipboard
+ Setup DeltaChat
+ Opening DeltaChat with autoconfig…
+ DCACCOUNT link copied! Open DeltaChat and paste it in the setup screen
+ Failed to generate autoconfig link. Make sure the service is running.
+ DeltaChat Not Installed
+ DeltaChat is not installed on this device.\n\nIf you have DeltaChat installed, please use the manual setup instructions shown below to configure it.
SMTP: %1$s:%2$s
IMAP: %1$s:%2$s
DeltaChat Setup
Tap to view setup 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.
+ 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.
Welcome to Tyr
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0db40ef..daf81df 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -2,6 +2,8 @@