diff --git a/app/build.gradle b/app/build.gradle
index 20b4628..8bfa502 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,6 +17,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+ packagingOptions {
+ exclude 'META-INF/proguard/androidx-annotations.pro'
+ }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -24,11 +27,15 @@ android {
}
dependencies {
+ implementation "androidx.cardview:cardview:$androidx_version"
+ implementation project(":delegateadapter")
implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'com.google.android.material:material:1.1.0-alpha01'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
- implementation 'com.google.android.material:material:1.1.0-alpha01'
+ implementation "androidx.recyclerview:recyclerview:$androidx_version"
+ implementation 'com.github.bumptech.glide:glide:4.8.0'
implementation 'androidx.vectordrawable:vectordrawable:1.0.1'
- implementation "com.github.bumptech.glide:glide:$glide_version"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c03d4d3..a35c696 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"
package="ru.nikijava.androidacademynewsapp">
+
+
+
-
+
-
-
\ No newline at end of file
+
+
+
+
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/DateFormatter.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/DateFormatter.java
new file mode 100644
index 0000000..d8b8a4c
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/DateFormatter.java
@@ -0,0 +1,23 @@
+package ru.nikijava.androidacademynewsapp;
+
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import java.util.Date;
+
+public class DateFormatter {
+
+ public static CharSequence formatDateTime(Context context, Date dateTime) {
+ return DateUtils.getRelativeDateTimeString(
+ context,
+ dateTime.getTime(),
+ HOUR_IN_MILLIS,
+ 5 * DAY_IN_MILLIS,
+ FORMAT_ABBREV_RELATIVE
+ );
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/data/Category.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/Category.java
new file mode 100644
index 0000000..e4c0d28
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/Category.java
@@ -0,0 +1,46 @@
+package ru.nikijava.androidacademynewsapp.data;
+
+public enum Category {
+
+ DARWIN_AWARDS(1, "Darwin Awards"),
+ CRIMINAL(2, "Criminal"),
+ ANIMALS(3, "Animals"),
+ MUSIC(4, "Music");
+
+ private final int id;
+ private final String name;
+
+ Category(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public static Category getById(int id) {
+ Category type;
+ switch (id) {
+ case 1:
+ type = DARWIN_AWARDS;
+ break;
+ case 2:
+ type = CRIMINAL;
+ break;
+ case 3:
+ type = ANIMALS;
+ break;
+ case 4:
+ type = MUSIC;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return type;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/data/News.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/News.java
new file mode 100644
index 0000000..1139bf2
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/News.java
@@ -0,0 +1,93 @@
+package ru.nikijava.androidacademynewsapp.data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+
+public class News implements Serializable {
+
+ @NonNull private final String title;
+ @NonNull private final String imageUrl;
+ @NonNull private final Category category;
+ @NonNull private final Date publishDate;
+ @NonNull private final String previewText;
+ @NonNull private final String fullText;
+
+ public News(
+ @NonNull String title,
+ @NonNull String imageUrl,
+ @NonNull Category category,
+ @NonNull Date publishDate,
+ @NonNull String previewText,
+ @NonNull String fullText
+ ) {
+ this.title = title;
+ this.imageUrl = imageUrl;
+ this.category = category;
+ this.publishDate = publishDate;
+ this.previewText = previewText;
+ this.fullText = fullText;
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "{" +
+ "title='" + title + '\'' +
+ ", imageUrl='" + imageUrl + '\'' +
+ ", category=" + category +
+ ", publishDate=" + publishDate +
+ ", previewText='" + previewText + '\'' +
+ ", fullText='" + fullText + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof News)) return false;
+ final News news = (News) o;
+ return Objects.equals(title, news.title) &&
+ Objects.equals(imageUrl, news.imageUrl) &&
+ category == news.category &&
+ Objects.equals(publishDate, news.publishDate) &&
+ Objects.equals(previewText, news.previewText) &&
+ Objects.equals(fullText, news.fullText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, imageUrl, category, publishDate, previewText, fullText);
+ }
+
+ @NonNull
+ public String getTitle() {
+ return title;
+ }
+
+ @NonNull
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ @NonNull
+ public Category getCategory() {
+ return category;
+ }
+
+ @NonNull
+ public Date getPublishDate() {
+ return publishDate;
+ }
+
+ @NonNull
+ public String getPreviewText() {
+ return previewText;
+ }
+
+ @NonNull
+ public String getFullText() {
+ return fullText;
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepository.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepository.java
new file mode 100644
index 0000000..21a0b4c
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepository.java
@@ -0,0 +1,8 @@
+package ru.nikijava.androidacademynewsapp.data;
+
+import java.util.List;
+
+public interface NewsRepository {
+
+ List getNews();
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepositoryImpl.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepositoryImpl.java
new file mode 100644
index 0000000..504cfd8
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/data/NewsRepositoryImpl.java
@@ -0,0 +1,189 @@
+package ru.nikijava.androidacademynewsapp.data;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+public class NewsRepositoryImpl implements NewsRepository {
+
+ public List getNews() {
+ List news = new ArrayList<>();
+ news.add(new News(
+ "Tourist filmed sitting on 5m-long crocodile",
+ "https://e3.365dm.com/18/09/736x414/skynews-crocodile-australia_4433218.jpg",
+ Category.DARWIN_AWARDS,
+ createDate(2018, 9, 26, 10, 34),
+ "\"It was dangerous, I know. It is a scary feeling sitting on something that "
+ + "could kill you in a fraction of a "
+ + "second,\" he says.",
+ "A Danish tourist has admitted he took his life in his hands by sitting on a "
+ + "large crocodile in Australia.\n\n"
+ + "Niels Jensen, 22, was on safari in a wildlife park east of Darwin in "
+ + "northern Australia when he "
+ + "encountered the predator, estimated to be 4.7m (15ft) long and "
+ + "weighing 653kg (1428 lbs).\n\n"
+ + "The wildlife management graduate is filmed enticing the large reptile,"
+ + " which had been relocated to the "
+ + "park after it was caught preying on livestock, with a wallaby carcass."
+ + "After leaving the bait on the ground and waiting for the crocodile to "
+ + "start eating, he astonishingly "
+ + "straddled the reptile's back, sitting just behind its rear legs.\n\n"
+ + "He touched some of the scales on the animal's back, and, after a few "
+ + "moments, rose and walked away.\n\n"
+ + "But a man out of shot told him to get back and give a thumbs-up, so he"
+ + " approached the animal for a second"
+ + " time, sat down again, turned towards the camera, smiled and put this "
+ + "thumb in the air.\n\n"
+ + "Mr Jensen admitted he took life in his hands by sitting on a live "
+ + "crocodile for the first time."
+ ));
+ news.add(new News(
+ "Police warn daredevil cliff jumpers who are 'risking their lives for likes'",
+ "https://e3.365dm.com/18/09/2048x1152/skynews-cliff-jumping-greg-milam_4433647.jpg",
+ Category.CRIMINAL,
+ createDate(2018, 9, 25, 12, 45),
+ "Police in Los Angeles say they are spending hundreds of thousands of dollars "
+ + "airlifting cliff jumpers out of "
+ + "dangerous spots.",
+ "Daredevils attempting dangerous cliff dives in a quest for likes has led to an "
+ + "increase in costly helicopter "
+ + "airlifts in California, police say.\n\n"
+ + "As young people pursue the perfect selfie or video for their social "
+ + "media pages, the Los Angeles County "
+ + "Sheriff's Department says it is spending hundreds of thousands of "
+ + "dollars plucking the injured and "
+ + "stranded from beauty spot locations. \"People have to understand: "
+ + "people die up in those mountains. "
+ + "For every rescue you see that we do, there are ones that we don't make"
+ + ". They're dead,\" said Deputy "
+ + "Stephen Doucette.\n\n"
+ + "A social media search for locations like Eaton Canyon, Hermit Falls "
+ + "and Malibu Creek Rock Pool reveal "
+ + "dozens of risky selfie videos. Two men were recently rescued after "
+ + "being injured while being filmed at "
+ + "Hermit Falls."
+ ));
+ news.add(new News(
+ "Bear saved after getting his head stuck in milk can",
+ "https://e3.365dm.com/18/09/2048x1152/skynews-bear-minnesota_4419111.jpg",
+ Category.ANIMALS,
+ createDate(2018, 9, 20, 14, 4),
+ "Firefighters used the Jaws of Life to free the young black bear, a tool which is"
+ + " normally used to extricate car"
+ + " accident victims.",
+ "A bear has been freed after getting his head stuck in a milk can.\n\n"
+ + "Firefighters were called to help after a conservation officer "
+ + "encountered the grizzly sight in "
+ + "Minnesota.\n\n"
+ + "The young black male bear's head was stuck inside an old 10 gallon (38"
+ + " litre) milk can.\n\n"
+ + "At first, rescuers tried to use cooking oil to free the animal. When "
+ + "that didn't work, they drilled three"
+ + " holes in the milk can so the panting bear could breathe.\n\n"
+ + "Two hours later, firefighters used the \"Jaws of Life\" - a tool which"
+ + " is normally used to extricate car "
+ + "accident victims - and a spreader to pry the can off.\n\n"
+ + "After being released, the seemingly healthy bear ran off into the woods."
+ ));
+ news.add(new News(
+ "Nearly $18m of cocaine seized in donated boxes of bananas",
+ "https://e3.365dm.com/18/09/2048x1152/skynews-texas-bananas-drugs_4430760.jpg",
+ Category.CRIMINAL,
+ createDate(2018, 9, 18, 4, 4),
+ "Massive quantities of the drug were found in boxes of fruit that had been "
+ + "donated to the Texas Department of "
+ + "Criminal Justice.",
+ "A huge haul of cocaine was discovered hidden in boxes of bananas donated to the "
+ + "Texas Department of Criminal "
+ + "Justice.\n\n"
+ + "Some 45 boxes of bananas from Ports of America in Freeport were given "
+ + "away to the agency because they "
+ + "were already ripe.\n\n"
+ + "According to a Facebook post on the TDCJ's page, when two sergeants of"
+ + " the Scott Unit arrived to pick "
+ + "them up they \"discovered something not quite right\".\n\n"
+ + "The post explains: \"One of the boxes felt different than the others"
+ + ".\n\n"
+ + "\"They snipped the straps, pulled free the box, and opened it up.\n\n"
+ + "\"Inside, under a bundle of bananas, he found another bundle! Inside "
+ + "that? What appeared to be a white "
+ + "powdery substance.\n\n"
+ + "\"They immediately notified port authorities and awaited their "
+ + "instruction.\""
+ + "US Customs arrived and tested the substance, which confirmed the "
+ + "powder was cocaine."
+ ));
+ news.add(new News(
+ "US government hacker jailed after losing secrets",
+ "https://e3.365dm"
+ + ".com/17/09/736x414/d55722dc4eb37f6959d2e047c14710d586aab99f90aa1e4acfd9f992125294f5_4107038.jpg",
+ Category.CRIMINAL,
+ createDate(2018, 9, 17, 12, 45),
+ "Nghia Hoang Pho, 68, who developed hacking tools for the National Security "
+ + "Agency, illegally stored material "
+ + "on his home computer.",
+ "A man who illegally took home hacking tools from his workplace at the National "
+ + "Security Agency, and then "
+ + "allegedly lost them to Russian intelligence, has been jailed for five "
+ + "years and six months.\n\n"
+ + "Nghia Hoang Pho, 68, developed hacking tools at the NSA's elite "
+ + "Tailored Access Operations (TAO) unit, "
+ + "which works on penetrating target computer networks for the US "
+ + "intelligence community.\n\n"
+ + "While employed by the NSA between 2010 and 2015, Pho took home what "
+ + "prosecutors described as \"massive "
+ + "troves of highly classified national defence information\" and stored "
+ + "those troves on his home computer "
+ + "network.\n\n"
+ + "Reports have alleged that while these tools were stored on his home "
+ + "computer, Pho installed Kaspersky "
+ + "Lab anti-virus software, which Russian intelligence then used to steal"
+ + " those tools for themselves.\n\n"
+ + "Although the company has vigorously denied claims its software was "
+ + "used by Russian intelligence to steal"
+ + " the data, the publicity damage has left Kaspersky Lab working to "
+ + "address customer fears in a global "
+ + "transparency initiative - including moving a significant portion of "
+ + "its operations from Russia to "
+ + "Switzerland.\n\n"
+ + "An internal investigation at the cyber security company into the "
+ + "incident prompted the company to suggest"
+ + " that an NSA employee had actually been hacked when he downloaded "
+ + "pirate software and disabled "
+ + "Kaspersky's anti-virus."
+ ));
+ news.add(new News(
+ "Wet Wet Wet announce Liberty X star Kevin Simm as new frontman",
+ "https://e3.365dm.com/18/09/2048x1152/skynews-wet-wet-wet-kevin-simm_4433314.jpg",
+ Category.MUSIC,
+ createDate(2018, 9, 17, 12, 45),
+ "The Voice 2016 winner says he was \"really taken aback\" by the opportunity "
+ + "after singing the band's songs "
+ + "early in his career.",
+ "The Scottish band, who are best-known for their 1994 cover of The Troggs' 1960s "
+ + "hit Love Is All Around, "
+ + "revealed the change in line-up on Tuesday.\n\n"
+ + "Simm, 38, who won The Voice in 2016, will take over singing duties "
+ + "after founding member Marti Pellow "
+ + "left the band last year.\n\n"
+ + "Simm, from Lancashire, first shot to fame on ITV talent show Popstars "
+ + "in 2001 after forming the group "
+ + "Liberty X with four other runner-up contestants.\n\n"
+ + "He has recalled singing Wet Wet Wet's songs early in his career.He "
+ + "said: \"I was really taken aback, the"
+ + " opportunity to join a band with such amazing songs and great guys and"
+ + " a great fanbase really "
+ + "excites me.\n\n"
+ + "\"When I first started gigging around the pubs and clubs up North, two"
+ + " songs that were always in my set "
+ + "were Goodnight Girl and Love Is All Around.\""
+ ));
+
+ return news;
+ }
+
+ private Date createDate(int year, int month, int date, int hrs, int min) {
+ return new GregorianCalendar(year, month - 1, date, hrs, min).getTime();
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/NewsAdapterItem.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/NewsAdapterItem.java
new file mode 100644
index 0000000..20ade48
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/NewsAdapterItem.java
@@ -0,0 +1,25 @@
+package ru.nikijava.androidacademynewsapp.presentation.news;
+
+import androidx.annotation.LayoutRes;
+import ru.nikijava.adapterdelegate.Item;
+import ru.nikijava.androidacademynewsapp.data.News;
+
+public class NewsAdapterItem implements Item {
+
+ private final News news;
+ private final @LayoutRes int layoutId;
+
+ public NewsAdapterItem(News news, @LayoutRes int layoutId) {
+ this.news = news;
+ this.layoutId = layoutId;
+ }
+
+ @Override
+ public int getLayoutId() {
+ return layoutId;
+ }
+
+ public News getNews() {
+ return news;
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/details/NewsDetailsActivity.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/details/NewsDetailsActivity.java
new file mode 100644
index 0000000..19f3664
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/details/NewsDetailsActivity.java
@@ -0,0 +1,84 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.details;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.widget.NestedScrollView;
+import ru.nikijava.androidacademynewsapp.DateFormatter;
+import ru.nikijava.androidacademynewsapp.R;
+import ru.nikijava.androidacademynewsapp.data.News;
+
+public class NewsDetailsActivity extends AppCompatActivity {
+
+ private static String NEWS_KEY = "news_key";
+
+ private News news;
+
+ private ImageView ivImage;
+ private TextView tvTitle;
+ private TextView tvDate;
+ private TextView tvDetails;
+ private NestedScrollView svContent;
+
+ public static void start(Context context, News news) {
+ Intent intent = new Intent(context, NewsDetailsActivity.class);
+ intent.putExtra(NEWS_KEY, news);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_news_details);
+ news = (News) getIntent().getSerializableExtra(NEWS_KEY);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getSupportActionBar().setTitle(news.getCategory().getName());
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ initViews();
+ showNews();
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ private void initViews() {
+ ivImage = findViewById(R.id.ivImage);
+ tvTitle = findViewById(R.id.tvTitle);
+ tvDate = findViewById(R.id.tvDate);
+ tvDetails = findViewById(R.id.tvDetails);
+ svContent = findViewById(R.id.svContent);
+ }
+
+ private void showNews() {
+ tvDate.setText(DateFormatter.formatDateTime(this, news.getPublishDate()));
+ tvDetails.setText(news.getFullText());
+ tvTitle.setText(news.getTitle());
+ setImage();
+ }
+
+ private void setImage() {
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == ORIENTATION_PORTRAIT) ivImage.post(this::recomputeAvatarSize);
+ Glide.with(this).load(news.getImageUrl()).into(ivImage);
+ }
+
+ private void recomputeAvatarSize() {
+ float layoutPosition = svContent.getHeight();
+ CollapsingToolbarLayout.LayoutParams params =
+ (CollapsingToolbarLayout.LayoutParams) ivImage.getLayoutParams();
+ params.height = (int) (layoutPosition * 0.4);
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BaseNewsViewHolder.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BaseNewsViewHolder.java
new file mode 100644
index 0000000..7834eb8
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BaseNewsViewHolder.java
@@ -0,0 +1,60 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.RequestManager;
+
+import androidx.annotation.NonNull;
+import ru.nikijava.adapterdelegate.BaseViewHolder;
+import ru.nikijava.androidacademynewsapp.DateFormatter;
+import ru.nikijava.androidacademynewsapp.R;
+import ru.nikijava.androidacademynewsapp.data.News;
+import ru.nikijava.androidacademynewsapp.presentation.news.NewsAdapterItem;
+
+public class BaseNewsViewHolder extends BaseViewHolder implements View.OnClickListener {
+
+ private static final String TAG = NewsViewHolder.class.getSimpleName();
+ @NonNull private final OnNewsClickListener onNewsClickListener;
+ @NonNull private final RequestManager requestManager;
+
+ private final TextView tvCategory;
+ private final TextView tvTitle;
+ private final TextView tvBody;
+ private final ImageView ivImage;
+ private final TextView tvDate;
+
+ private NewsAdapterItem item;
+
+ public BaseNewsViewHolder(
+ @NonNull View itemView,
+ @NonNull OnNewsClickListener onNewsClickListener,
+ @NonNull RequestManager requestManager
+ ) {
+ super(itemView);
+ itemView.setOnClickListener(this);
+ this.onNewsClickListener = onNewsClickListener;
+ this.requestManager = requestManager;
+ tvCategory = itemView.findViewById(R.id.tvCategory);
+ tvTitle = itemView.findViewById(R.id.tvTitle);
+ tvBody = itemView.findViewById(R.id.tvBody);
+ ivImage = itemView.findViewById(R.id.ivImage);
+ tvDate = itemView.findViewById(R.id.tvDate);
+ }
+ @Override
+ protected void bind(@NonNull NewsAdapterItem item) {
+ this.item = item;
+ News news = item.getNews();
+ tvCategory.setText(news.getCategory().getName());
+ tvTitle.setText(news.getTitle());
+ tvBody.setText(news.getFullText());
+ tvDate.setText(DateFormatter.formatDateTime(itemView.getContext(), news.getPublishDate()));
+ requestManager.load(news.getImageUrl()).into(ivImage);
+ }
+
+ @Override
+ public void onClick(View v) {
+ onNewsClickListener.onNewsClick(item.getNews());
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BigNewsViewHolder.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BigNewsViewHolder.java
new file mode 100644
index 0000000..2600af3
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/BigNewsViewHolder.java
@@ -0,0 +1,19 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.view.View;
+
+import com.bumptech.glide.RequestManager;
+
+import androidx.annotation.NonNull;
+import ru.nikijava.androidacademynewsapp.presentation.news.NewsAdapterItem;
+
+public class BigNewsViewHolder extends BaseNewsViewHolder {
+
+ public BigNewsViewHolder(
+ @NonNull View itemView,
+ @NonNull OnNewsClickListener onNewsClickListener,
+ @NonNull RequestManager requestManager
+ ) {
+ super(itemView, onNewsClickListener, requestManager);
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsAdapter.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsAdapter.java
new file mode 100644
index 0000000..e859181
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsAdapter.java
@@ -0,0 +1,55 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.view.View;
+
+import com.bumptech.glide.RequestManager;
+
+import java.util.List;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import ru.nikijava.adapterdelegate.BaseDelegateAdapter;
+import ru.nikijava.adapterdelegate.Item;
+import ru.nikijava.androidacademynewsapp.R;
+import ru.nikijava.androidacademynewsapp.presentation.news.NewsAdapterItem;
+
+public class NewsAdapter extends
+ BaseDelegateAdapter {
+
+ private static final String TAG = NewsAdapter.class.getSimpleName();
+ @NonNull private final RequestManager requestManager;
+ @NonNull private final OnNewsClickListener onNewsClickListener;
+ private final @LayoutRes int layoutId;
+
+ public NewsAdapter(
+ @NonNull OnNewsClickListener onNewsClickListener,
+ @NonNull RequestManager requestManager,
+ @LayoutRes int layoutId
+ ) {
+ this.onNewsClickListener = onNewsClickListener;
+ this.requestManager = requestManager;
+ this.layoutId = layoutId;
+ }
+
+ @Override
+ public int getLayoutId() {
+ return layoutId;
+ }
+
+ @Override
+ public boolean isForViewType(@NonNull List> items, int position) {
+ Item item = (Item) items.get(position);
+ return item instanceof NewsAdapterItem && item.getLayoutId() == layoutId;
+ }
+
+ @NonNull
+ @Override
+ public BaseNewsViewHolder createViewHolder(@NonNull View itemView) {
+ switch (layoutId) {
+ case R.layout.item_news_big:
+ return new BigNewsViewHolder(itemView, onNewsClickListener, requestManager);
+ default:
+ return new NewsViewHolder(itemView, onNewsClickListener, requestManager);
+ }
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsItemDecorationImpl.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsItemDecorationImpl.java
new file mode 100644
index 0000000..93177bf
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsItemDecorationImpl.java
@@ -0,0 +1,33 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class NewsItemDecorationImpl extends RecyclerView.ItemDecoration {
+
+ private final int offset;
+
+ public NewsItemDecorationImpl(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public void getItemOffsets(
+ @NonNull Rect outRect,
+ @NonNull View view,
+ @NonNull RecyclerView parent,
+ @NonNull RecyclerView.State state
+ ) {
+
+ final int position = parent.getChildLayoutPosition(view);
+
+ if (position != RecyclerView.NO_POSITION) {
+ outRect.set(offset, offset, offset, offset);
+ } else {
+ outRect.set(0, 0, 0, 0);
+ }
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsListActivity.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsListActivity.java
new file mode 100644
index 0000000..6fffaec
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsListActivity.java
@@ -0,0 +1,102 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.StaggeredGridLayoutManager;
+import ru.nikijava.adapterdelegate.CompositeDelegateAdapter;
+import ru.nikijava.adapterdelegate.Item;
+import ru.nikijava.androidacademynewsapp.R;
+import ru.nikijava.androidacademynewsapp.data.Category;
+import ru.nikijava.androidacademynewsapp.data.News;
+import ru.nikijava.androidacademynewsapp.data.NewsRepository;
+import ru.nikijava.androidacademynewsapp.data.NewsRepositoryImpl;
+import ru.nikijava.androidacademynewsapp.presentation.news.NewsAdapterItem;
+import ru.nikijava.androidacademynewsapp.presentation.news.details.NewsDetailsActivity;
+
+public class NewsListActivity extends AppCompatActivity implements OnNewsClickListener {
+
+ private static final String TAG = NewsListActivity.class.getSimpleName();
+ private final NewsRepository newsRepository = new NewsRepositoryImpl();
+
+ private RecyclerView rvNewsList;
+
+ protected void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_news_list);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getSupportActionBar().setTitle(getString(R.string.top_news));
+ rvNewsList = findViewById(R.id.rvNewsList);
+ initList();
+
+ List data = newsRepository.getNews();
+ List- mappedNews = mapNewsForPresentation(data);
+ getAdapter().swapData(mappedNews);
+ }
+
+ @Override
+ public void onNewsClick(News news) {
+ NewsDetailsActivity.start(this, news);
+ }
+
+ @SuppressWarnings("unchecked call")
+ private void initList() {
+ RequestOptions options = RequestOptions
+ .placeholderOf(R.drawable.ic_photo_camera_grey_24dp)
+ .error(R.drawable.ic_photo_camera_grey_24dp);
+ RequestManager glide = Glide.with(this)
+ .setDefaultRequestOptions(options);
+ CompositeDelegateAdapter adapter = new CompositeDelegateAdapter.Builder
- ()
+ .add(new NewsAdapter(this, glide, R.layout.item_news))
+ .add(new NewsAdapter(this, glide, R.layout.item_news_big))
+ .build();
+
+ rvNewsList.setAdapter(adapter);
+
+ int columnCounts;
+ switch (getResources().getConfiguration().orientation) {
+ case Configuration.ORIENTATION_LANDSCAPE: {
+ columnCounts = 2;
+ break;
+ }
+ default: {
+ columnCounts = 1;
+ }
+ }
+
+ RecyclerView.LayoutManager layoutManager = new StaggeredGridLayoutManager(columnCounts, StaggeredGridLayoutManager.VERTICAL);
+
+ rvNewsList.setLayoutManager(layoutManager);
+
+ rvNewsList.addItemDecoration(new NewsItemDecorationImpl(
+ getResources().getDimensionPixelSize(R.dimen.spacing_micro)));
+ }
+
+ private List
- mapNewsForPresentation(List news) {
+ List
- mappedNews = new ArrayList<>(news.size());
+ for (News newsItem : news) {
+ if (newsItem.getCategory() == Category.CRIMINAL) {
+ mappedNews.add(
+ new NewsAdapterItem(newsItem, R.layout.item_news_big));
+ } else {
+ mappedNews.add(new NewsAdapterItem(newsItem, R.layout.item_news));
+ }
+ }
+ return mappedNews;
+ }
+
+ @SuppressWarnings("unchecked")
+ private CompositeDelegateAdapter
- getAdapter() {
+ return (CompositeDelegateAdapter
- ) rvNewsList.getAdapter();
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsViewHolder.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsViewHolder.java
new file mode 100644
index 0000000..9eabad2
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/NewsViewHolder.java
@@ -0,0 +1,21 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import android.view.View;
+
+import com.bumptech.glide.RequestManager;
+
+import androidx.annotation.NonNull;
+import ru.nikijava.androidacademynewsapp.presentation.news.NewsAdapterItem;
+
+public class NewsViewHolder extends BaseNewsViewHolder implements View.OnClickListener {
+
+ private static final String TAG = NewsViewHolder.class.getSimpleName();
+
+ public NewsViewHolder(
+ @NonNull View itemView,
+ @NonNull OnNewsClickListener onNewsClickListener,
+ @NonNull RequestManager requestManager
+ ) {
+ super(itemView, onNewsClickListener, requestManager);
+ }
+}
diff --git a/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/OnNewsClickListener.java b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/OnNewsClickListener.java
new file mode 100644
index 0000000..eabadfb
--- /dev/null
+++ b/app/src/main/java/ru/nikijava/androidacademynewsapp/presentation/news/list/OnNewsClickListener.java
@@ -0,0 +1,8 @@
+package ru.nikijava.androidacademynewsapp.presentation.news.list;
+
+import ru.nikijava.androidacademynewsapp.data.News;
+
+public interface OnNewsClickListener {
+
+ void onNewsClick(News news);
+}
diff --git a/app/src/main/res/drawable/ic_photo_camera_grey_24dp.xml b/app/src/main/res/drawable/ic_photo_camera_grey_24dp.xml
new file mode 100644
index 0000000..42c963f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_photo_camera_grey_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_news_details.xml b/app/src/main/res/layout/activity_news_details.xml
new file mode 100644
index 0000000..aa0531f
--- /dev/null
+++ b/app/src/main/res/layout/activity_news_details.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_news_list.xml b/app/src/main/res/layout/activity_news_list.xml
new file mode 100644
index 0000000..e661e7c
--- /dev/null
+++ b/app/src/main/res/layout/activity_news_list.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_news.xml b/app/src/main/res/layout/item_news.xml
new file mode 100644
index 0000000..ba8e5e3
--- /dev/null
+++ b/app/src/main/res/layout/item_news.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_news_big.xml b/app/src/main/res/layout/item_news_big.xml
new file mode 100644
index 0000000..0d4623c
--- /dev/null
+++ b/app/src/main/res/layout/item_news_big.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index e292e2d..5c2b3b3 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -14,4 +14,6 @@
Ссылка на страницу разработчика в Facebook
Ссылка на страницу разработчика в Telegram
Логотип Wellmark Group - текущего места работы
+ Изображение для новости
+ Главные новости
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ed1bf02..383b552 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,5 +3,7 @@
#ffdfb840
#ffd4a515
#ff962326
+ #b4c6c5c5
?android:attr/colorBackground
+ @color/color_transparent_grey
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index dab13f3..c029692 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,5 +1,8 @@
+ 16dp
+ 8dp
+ 4dp
16sp
16dp
8dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 61f2bf1..c296574 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
+
New York Times
Nikita
@@ -15,4 +16,7 @@
Link to developer page on Facebook
Link to developer page on Telegram
Wellmark Group the current job logo
+
+ Image for the selected news
+ Top news
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 960165c..ead8dfe 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -10,6 +10,28 @@
- @android:color/white
+
+
+
+
+
+
+
+
+
+
+
+
+
+