diff --git a/.gitignore b/.gitignore index f55db1d..bf91284 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -reserve.md HELP.md .gradle build/ @@ -45,4 +44,4 @@ captures/ *.rar ### Gradle ### -!gradle/wrapper/gradle-wrapper.jar +!/gradle/wrapper/gradle-wrapper.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ + diff --git a/app/build.gradle b/app/build.gradle index bc6cc82..a907cb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,12 +4,12 @@ plugins { } android { - namespace 'ru.netology.nmedia' + namespace "ru.netology.nmedia" compileSdk 33 defaultConfig { - applicationId 'ru.netology.nmedia' - minSdk 26 + applicationId "ru.netology.nmedia" + minSdk 23 targetSdk 33 versionCode 1 versionName "1.0" @@ -17,6 +17,10 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + viewBinding true + } + buildTypes { release { minifyEnabled false @@ -33,12 +37,11 @@ android { } dependencies { - - implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.8.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/app/src/androidTest/java/ru/netology/nmedia/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ru/netology/nmedia/ExampleInstrumentedTest.kt index d759f87..039d3cf 100644 --- a/app/src/androidTest/java/ru/netology/nmedia/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/ru/netology/nmedia/ExampleInstrumentedTest.kt @@ -21,4 +21,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("ru.netology.nmedia", appContext.packageName) } -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fde7b7d..96181c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,17 +1,13 @@ - + + android:theme="@style/Theme.NMedia"> diff --git a/app/src/main/java/ru/netology/nmedia/MainActivity.kt b/app/src/main/java/ru/netology/nmedia/MainActivity.kt index b8bf709..d65f87b 100644 --- a/app/src/main/java/ru/netology/nmedia/MainActivity.kt +++ b/app/src/main/java/ru/netology/nmedia/MainActivity.kt @@ -2,10 +2,76 @@ package ru.netology.nmedia import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.util.Log +import ru.netology.nmedia.databinding.ActivityMainBinding +import ru.netology.nmedia.dto.Post +import ru.netology.nmedia.dto.PostService.formatNumber + class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + val binding = ActivityMainBinding.inflate(layoutInflater) //функция, которая на базе верстки выдувает наш интерфейс. layoutInflater - занимается выдуванием компонентов + setContentView(binding.root) //у любого binding есть специальная переменная root, которая всегда указывает на корневой элемент этого биндинга(ActivityMainBinding) + + + + val post = Post( + id = 1, + author = "Нетология. Университет интернет-профессий будущего", + content = "Привет, это новая Нетология! Когда-то Нетология начиналась с интенсивов по онлайн-маркетингу. Затем появились курсы по дизайну, разработке, аналитике и управлению. Мы растём сами и помогаем расти студентам: от новичков до уверенных профессионалов. Но самое важное остаётся с нами: мы верим, что в каждом уже есть сила, которая заставляет хотеть больше, целиться выше, бежать быстрее. Наша миссия — помочь встать на путь роста и начать цепочку перемен → http://netolo.gy/fyb", + published = "21 мая в 18:36", + likedByMe = false, + sharedByMe = false, + likes = 10, + shares = 9, + views = 1000000 + + ) + + binding.shareCount?.text = formatNumber(post.shares) + binding.likeCount?.text = formatNumber(post.likes) + binding.viewsCount?.text = formatNumber(post.views) + + + with(binding) { + author.text = post.author + published.text = post.published + content.text = post.content + if (post.likedByMe) { + like?.setImageResource(R.drawable.ic_liked_24) + } + likeCount?.text = post.likes.toString() //Если пост понравился пользователю, то устанавливается соответствующая иконка и обновляется значение количества лайков. + + + root.setOnClickListener { + Log.d("stuff", "stuff") + } + + avatar.setOnClickListener { + Log.d("stuff", "avatar") + } + + like?.setOnClickListener { //данный код настраивает обработчик клика для элемента like. При клике на элемент, выполняется изменение состояния лайка, обновление отображаемого изображения, обновление количества лайков и вывод сообщения в лог. + Log.d("stuff", "like") + post.likedByMe = !post.likedByMe //значение свойства likedByMe объекта post инвертируется. Если оно было true, то становится false, и наоборот. + like.setImageResource( //устанавливается ресурс изображения для элемента like. если likedByMe равно true, то устанавливается иконка с ресурсом R.drawable.ic_liked_24, иначе - иконка с ресурсом R.drawable.ic_like_24. + if (post.likedByMe) R.drawable.ic_liked_24 else R.drawable.ic_like_24 + ) + if (post.likedByMe) post.likes++ else post.likes-- //увеличивается или уменьшается значение свойства likes объекта post, в зависимости от значения likedByMe. Если likedByMe равно true, то значение likes увеличивается на 1, иначе - уменьшается на 1. + likeCount?.text = formatNumber(post.likes) + } + + share?.setOnClickListener { + Log.d("stuff", "share") + post.sharedByMe = !post.sharedByMe + post.shares++ + shareCount?.text = formatNumber(post.shares) + } + + } + } } + + diff --git a/app/src/main/java/ru/netology/nmedia/MainActivityPlain.kt b/app/src/main/java/ru/netology/nmedia/MainActivityPlain.kt new file mode 100644 index 0000000..0ca37f2 --- /dev/null +++ b/app/src/main/java/ru/netology/nmedia/MainActivityPlain.kt @@ -0,0 +1,20 @@ +package ru.netology.nmedia + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.ImageButton + +class MainActivityPlain : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) +// findViewById(R.id.like).setOnClickListener { +// if (it !is ImageButton) { +// return@setOnClickListener +// } +// +// it.setImageResource(R.drawable.ic_liked_24) +// } + } +} + diff --git a/app/src/main/java/ru/netology/nmedia/dto/Post.kt b/app/src/main/java/ru/netology/nmedia/dto/Post.kt new file mode 100644 index 0000000..de2c5c0 --- /dev/null +++ b/app/src/main/java/ru/netology/nmedia/dto/Post.kt @@ -0,0 +1,14 @@ +package ru.netology.nmedia.dto + +data class Post( + val id: Long, + val author: String, + val content: String, + val published: String, + var likes: Int = 0, + var likedByMe: Boolean = false, + var shares: Int = 0, + var sharedByMe: Boolean = false, + val views: Int = 0 +) + diff --git a/app/src/main/java/ru/netology/nmedia/dto/PostService.kt b/app/src/main/java/ru/netology/nmedia/dto/PostService.kt new file mode 100644 index 0000000..554a9fe --- /dev/null +++ b/app/src/main/java/ru/netology/nmedia/dto/PostService.kt @@ -0,0 +1,23 @@ +package ru.netology.nmedia.dto + +object PostService { + + fun formatNumber(number: Int): String { //принимает число, возвращает строку + return when { + number in 1000..9999 -> { + val hundreds = (number / 100) % 10 + val formattedHundreds = if (hundreds > 0) ".$hundreds" else "" + "${(number / 1000)}$formattedHundreds" + "K" + } + + number in 10000..999999 -> "${(number / 1000)}K" + + number >= 1000000 -> { + val tenThousands = (number / 100000) % 10 + val formattedtenThousands = if (tenThousands > 0) ".$tenThousands" else "" + "${(number / 1000000)}$formattedtenThousands" + "M" + } + else -> number.toString() + } + } +} diff --git a/app/src/main/java/ru/netology/nmedia/repository/PostRepository.kt b/app/src/main/java/ru/netology/nmedia/repository/PostRepository.kt new file mode 100644 index 0000000..bef5c24 --- /dev/null +++ b/app/src/main/java/ru/netology/nmedia/repository/PostRepository.kt @@ -0,0 +1,9 @@ +package ru.netology.nmedia.repository + +import androidx.lifecycle.LiveData +import ru.netology.nmedia.dto.Post + +interface PostRepository { + fun get() : LiveData + fun like() +} diff --git a/app/src/main/java/ru/netology/nmedia/repository/PostRepositoryInMemoryImpl.kt b/app/src/main/java/ru/netology/nmedia/repository/PostRepositoryInMemoryImpl.kt new file mode 100644 index 0000000..83dc552 --- /dev/null +++ b/app/src/main/java/ru/netology/nmedia/repository/PostRepositoryInMemoryImpl.kt @@ -0,0 +1,30 @@ +package ru.netology.nmedia.repository + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import ru.netology.nmedia.dto.Post + +class PostRepositoryInMemoryImpl: PostRepository { + + private var post = Post( + id = 1, + author = "Нетология. Университет интернет-профессий будущего", + content = "Привет, это новая Нетология! Когда-то Нетология начиналась с интенсивов по онлайн-маркетингу. Затем появились курсы по дизайну, разработке, аналитике и управлению. Мы растём сами и помогаем расти студентам: от новичков до уверенных профессионалов. Но самое важное остаётся с нами: мы верим, что в каждом уже есть сила, которая заставляет хотеть больше, целиться выше, бежать быстрее. Наша миссия — помочь встать на путь роста и начать цепочку перемен → http://netolo.gy/fyb", + published = "21 мая в 18:36", + likedByMe = false, + sharedByMe = false, + likes = 0 + ) + + private val data = MutableLiveData(post) // MutableLiveData - данные за которыми можно наблюдать. В конструктор ( ) ему передается начальное значение этих данных + + override fun get() = data //будет возвращать переменную data + + override fun like() { //будет изменять пост + post = post.copy(likedByMe = !post.likedByMe) //post является датаклассом со значениями val, поэтому, чтобы изменить какое-то значение, нам нужно создать копию + data.value = post //в переменную data записываем новое значение. + } + + // оператор ! - это инвертирование Boolean переменной. Если было true, то вернет false. Если было false, то вернет true + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file 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 07d5da9..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..a20fbec --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_like_24.xml b/app/src/main/res/drawable/ic_like_24.xml new file mode 100644 index 0000000..2950364 --- /dev/null +++ b/app/src/main/res/drawable/ic_like_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_liked_24.xml b/app/src/main/res/drawable/ic_liked_24.xml new file mode 100644 index 0000000..7b6e2c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_liked_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_vert_24.xml b/app/src/main/res/drawable/ic_more_vert_24.xml index 34b93ec..a2ffb9d 100644 --- a/app/src/main/res/drawable/ic_more_vert_24.xml +++ b/app/src/main/res/drawable/ic_more_vert_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="#999999"> diff --git a/app/src/main/res/drawable/ic_share_24.xml b/app/src/main/res/drawable/ic_share_24.xml new file mode 100644 index 0000000..9472e0f --- /dev/null +++ b/app/src/main/res/drawable/ic_share_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_views_24.xml b/app/src/main/res/drawable/ic_views_24.xml new file mode 100644 index 0000000..a7b1f81 --- /dev/null +++ b/app/src/main/res/drawable/ic_views_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..5dd4dcb --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 70520c3..46056a6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,33 +11,38 @@ android:id="@+id/avatar" android:layout_width="@dimen/posts_avatar_size" android:layout_height="@dimen/posts_avatar_size" + android:layout_marginBottom="@dimen/common_spacing" android:contentDescription="@string/description_post_author_avatar" - app:layout_constraintBottom_toBottomOf="@id/barrierBottom" + android:src="@drawable/ic_netology_48dp" + app:layout_constraintBottom_toBottomOf="@id/header" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_launcher_netology" - tools:srcCompat="@sample/posts_avatars" /> + tools:srcCompat="@sample/posts.json/data/authorAvatar" /> @@ -46,14 +51,15 @@ android:id="@+id/menu" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/common_spacing" android:background="@android:color/transparent" android:contentDescription="@string/description_post_menu" - android:src="@drawable/ic_baseline_more_vert_24" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_more_vert_24" /> + android:contentDescription="@string/description_post_like" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/footer" + app:srcCompat="@drawable/ic_like_24" /> + android:id="@+id/likeCount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="0" + android:layout_marginStart="@dimen/text_spacing" + android:layout_marginTop="@dimen/common_spacing" + app:layout_constraintTop_toBottomOf="@id/footer" + app:layout_constraintStart_toEndOf="@+id/like" + /> - - + android:contentDescription="@string/description_post_share" + app:layout_constraintTop_toBottomOf="@id/footer" + app:layout_constraintStart_toEndOf="@+id/likeCount" + app:srcCompat="@drawable/ic_share_24" /> + android:id="@+id/shareCount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="0" + android:layout_marginStart="@dimen/text_spacing" + android:layout_marginTop="@dimen/common_spacing" + app:layout_constraintTop_toBottomOf="@id/footer" + app:layout_constraintStart_toEndOf="@+id/share" + /> + android:contentDescription="@string/description_post_share" + app:layout_constraintTop_toBottomOf="@id/footer" + app:layout_constraintEnd_toStartOf="@id/viewsCount" + app:srcCompat="@drawable/ic_views_24" /> + 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 1328224..7353dbd 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 1328224..7353dbd 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/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 6e61029..55937ab 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,7 +1,16 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 493f5e3..879826b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,7 +1,9 @@ - НМедиа! - НМедиа! + NMedia + Привет, Мир! Аватар автора поста Меню поста - + Нравится + Поделиться + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1789de0..f8c6127 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,5 +1,10 @@ - #00BCD4 + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 #FFFFFFFF - + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index eb8eb28..3d8504f 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,6 +1,7 @@ 16dp - 24dp + 8dp + 48dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8244346..dabbe8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,9 +1,8 @@ - NMedia! - NMedia! - 10 - 5 - 5 + NMedia + Hello World! Post author avatar Post menu - + Like + Share + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2ad443b..7a75a84 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,9 +1,16 @@ - - -