Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
id 'com.android.library' version '8.4.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
id "io.gitlab.arturbosch.detekt" version "1.21.0"
id 'org.jetbrains.kotlin.jvm' version '1.9.23' apply false
}

task clean(type: Delete) {
Expand Down
1 change: 1 addition & 0 deletions payload/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
43 changes: 43 additions & 0 deletions payload/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
}

android {
namespace = "ru.tilman.payload"
compileSdk = 34

defaultConfig {
minSdk = 23

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
Empty file added payload/consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions payload/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
14 changes: 14 additions & 0 deletions payload/src/main/java/ru/tilman/payload/Payload.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ru.tilman.payload

import android.os.Parcelable
import kotlinx.parcelize.Parcelize


const val PAYLOAD_KEY = "payload_key"

@Parcelize
data class Payload(
val title: String,
val year: String,
val description: String
) : Parcelable
1 change: 1 addition & 0 deletions receiver/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation project(':payload')
}
20 changes: 17 additions & 3 deletions receiver/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Activities" />
android:theme="@style/Theme.Activities">
<activity
android:name="otus.gpb.homework.activities.receiver.ReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,12 +1,62 @@
package otus.gpb.homework.activities.receiver

import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.IntentCompat
import ru.tilman.payload.PAYLOAD_KEY
import ru.tilman.payload.Payload

class ReceiverActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_receiver)
val payload = IntentCompat.getParcelableExtra(intent, PAYLOAD_KEY, Payload::class.java)
if (payload == null) {
Toast.makeText(this, getString(R.string.payliad_not_found), Toast.LENGTH_SHORT).show()
return
}

val imageVies = findViewById<ImageView>(R.id.posterImageView)
val imageId = runCatching { imageVies.getImageIdByTitle(payload) }
.getOrElse {
Toast.makeText(
this,
String.format(getString(R.string.image_not_found), payload.title),
Toast.LENGTH_SHORT
).show()
return
}
imageVies.setImageDrawable(getDrawableById(imageId))

findViewById<TextView>(R.id.titleTextView)
.apply { text = payload.title }
findViewById<TextView>(R.id.descriptionTextView)
.apply { text = payload.description }
findViewById<TextView>(R.id.yearTextView)
.apply { text = payload.year }

}

/**
* Отступление от задания по рекомендации IDE использован [AppCompatResources]
* */
private fun getDrawableById(imageId: Int) =
AppCompatResources.getDrawable(this, imageId)

private fun ImageView.getImageIdByTitle(it: Payload) = when (it.title) {
context.getString(R.string.niceguys_title) -> R.drawable.niceguys
context.getString(R.string.interstellar_title) -> R.drawable.interstellar
else -> throw IllegalArgumentException(
String.format(
context.getString(R.string.title_not_found),
it.title
)
)
}
}
5 changes: 5 additions & 0 deletions receiver/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<resources>
<string name="app_name">Receiver</string>
<string name="niceguys_title">Славные парни</string>
<string name="interstellar_title">Интерстеллар</string>
<string name="title_not_found">Фильм \'%s\' не найден(</string>
<string name="payliad_not_found">Не переданы данные для поиска</string>
<string name="image_not_found">Рисунок к фильму \'%s\' не найден</string>
</resources>
3 changes: 3 additions & 0 deletions sender/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-parcelize'
}

android {
Expand Down Expand Up @@ -40,4 +41,6 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation project(':payload')
// implementation 'androidx.activity:activity:1.10.1'
}
12 changes: 11 additions & 1 deletion sender/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Activities" />
android:theme="@style/Theme.Activities">
<activity
android:name="otus.gpb.homework.activities.sender.SenderActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package otus.gpb.homework.activities.sender

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import otus.gpb.homework.activities.receiver.R
import ru.tilman.payload.PAYLOAD_KEY
import ru.tilman.payload.Payload
import kotlin.random.Random

class SenderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_sender)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}

findViewById<Button>(R.id.to_google_maps_button).setOnClickListener {
val intent = createMapsIntent()
runCatching { startActivity(intent) }
.getOrElse {
handleOpenClientError(it, getString(R.string.no_maps_clients_installed))
}

}
findViewById<Button>(R.id.send_email_button).setOnClickListener {
val intent = createEmailIntent()
runCatching { startActivity(intent) }
.getOrElse {
handleOpenClientError(it, getString(R.string.no_email_clients_installed))
}
}
findViewById<Button>(R.id.open_receiver_button).setOnClickListener {
val intent = createSendPayloadIntent(payloadProvider.next())
runCatching { startActivity(intent) }
.getOrElse {
handleOpenClientError(it, getString(R.string.no_payload_clients_installed))
}
}

}

/**
* Формирует [Intent] для отправки [Payload]
* Тут отступил от задания, так как было интересно отправить более сложный объект, чем строку
* Попутно разобрался с созданием Android библиотеки
* для переиспользования класса данных и ключв передаваемого объекта
* */
private fun createSendPayloadIntent(payload: Payload): Intent {
return Intent(Intent.ACTION_SEND).apply {
setType("text/plain")
addCategory(Intent.CATEGORY_DEFAULT)
putExtra(PAYLOAD_KEY, payload)
}
}


private fun createMapsIntent(): Intent {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("geo:?q=Рестораны&z=18")
setPackage("com.google.android.apps.maps")
}
return intent
}

private fun handleOpenClientError(it: Throwable, viewClientError: String) {
Log.e("debug_activity", it.message, it)
if (it is ActivityNotFoundException)
Toast.makeText(this, viewClientError, Toast.LENGTH_SHORT).show()
else
Toast.makeText(this, "Unsupported error.", Toast.LENGTH_SHORT).show()
}

private fun createEmailIntent(): Intent {
return Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:")
putExtra(Intent.EXTRA_EMAIL, arrayOf("android@otus.ru"))
putExtra(Intent.EXTRA_SUBJECT, "Тестовое письмо")
putExtra(
Intent.EXTRA_TEXT, """
Добрый день!

Это тестовое письмо.
""".trimIndent()
)
}
}

/**
* Добавлен рандомайзе полезной нагрузки для отдладки разных вариантов,
* в том числе когда у получателя не найдены данные по нагрузке
* */
private val payloadProvider = object {
private val payloadArray: Array<Payload> = arrayOf(
Payload(
"Славные парни",
"2016",
"Что бывает, когда напарником брутального костолома становится субтильный лопух? Наемный охранник Джексон Хили и частный детектив Холланд Марч вынуждены работать в паре, чтобы распутать плевое дело о пропавшей девушке, которое оборачивается преступлением века. Смогут ли парни разгадать сложный ребус, если у каждого из них – свои, весьма индивидуальные методы."
),
Payload(
"Интерстеллар",
"2014",
"Когда засуха, пыльные бури и вымирание растений приводят человечество к продовольственному кризису, коллектив исследователей и учёных отправляется сквозь червоточину (которая предположительно соединяет области пространства-времени через большое расстояние) в путешествие, чтобы превзойти прежние ограничения для космических путешествий человека и найти планету с подходящими для человечества условиями."
),

Payload(
"Джентельмены удачи",
"1971",
"Заведующему детсадом Трошкину фатально не повезло: он оказался как две капли воды похож на бандита по кличке «Доцент», похитившего уникальный шлем Александра Македонского. Милиция внедряет добряка Трошкина в воровскую среду - и ему ничего не остается, кроме как старательно изображать своего двойника-злодея, путая всех окружающих. Со временем он настолько блестяще входит в роль, что сам начинает порой приходить в ужас. Между тем, жизни его угрожает смертельная опасность...."
)

)


fun next() = payloadArray[Random.nextInt(0, payloadArray.size)]
}
}

Loading