diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4b0fd8ac4..043bfeaaa 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -158,7 +158,6 @@ dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}")
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}")
- implementation("io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:2.0.0")
implementation("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.google.android.material:material:1.1.0")
diff --git a/app/licenses.yml b/app/licenses.yml
index 815302d4b..71fb8fd19 100644
--- a/app/licenses.yml
+++ b/app/licenses.yml
@@ -495,11 +495,6 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
-- artifact: io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:+
- name: sectionedrecyclerviewadapter
- copyrightHolder: Gustavo Pagani
- license: MIT License
- licenseUrl: http://www.opensource.org/licenses/mit-license.php
- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
name: org.jetbrains.kotlin:kotlin-stdlib
copyrightHolder: JetBrains s.r.o.
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d8cf2eb11..a2adfaac1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -482,7 +482,7 @@
android:taskAffinity=""
android:theme="@style/TranslucentDialog"/>
-
+
diff --git a/app/src/main/assets/licenses.json b/app/src/main/assets/licenses.json
index a1e593452..e04cf63f3 100644
--- a/app/src/main/assets/licenses.json
+++ b/app/src/main/assets/licenses.json
@@ -1151,19 +1151,6 @@
"url": "http://tools.android.com",
"libraryName": "Android ConstraintLayout Solver"
},
- {
- "artifactId": {
- "name": "sectionedrecyclerviewadapter",
- "group": "io.github.luizgrp.sectionedrecyclerviewadapter",
- "version": "+"
- },
- "copyrightHolder": "Gustavo Pagani",
- "copyrightStatement": "Copyright © Gustavo Pagani. All rights reserved.",
- "license": "MIT License",
- "licenseUrl": "http://www.opensource.org/licenses/mit-license.php",
- "normalizedLicense": "mit",
- "libraryName": "sectionedrecyclerviewadapter"
- },
{
"artifactId": {
"name": "kotlin-stdlib",
diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.java b/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.java
new file mode 100644
index 000000000..2e196248b
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.java
@@ -0,0 +1,87 @@
+package org.tasks.activities.attribution;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Lists.newArrayList;
+
+import android.os.Bundle;
+import androidx.appcompat.widget.Toolbar;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimaps;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.tasks.R;
+import org.tasks.activities.attribution.AttributionViewModel.LibraryAttribution;
+import org.tasks.injection.ActivityComponent;
+import org.tasks.injection.ThemedInjectingAppCompatActivity;
+import timber.log.Timber;
+
+public class AttributionActivity extends ThemedInjectingAppCompatActivity {
+
+ @BindView(R.id.toolbar)
+ Toolbar toolbar;
+
+ @BindView(R.id.list)
+ RecyclerView recyclerView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_attributions);
+
+ ButterKnife.bind(this);
+
+ toolbar.setTitle(R.string.third_party_licenses);
+ toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px);
+ toolbar.setNavigationOnClickListener(v -> finish());
+ themeColor.apply(toolbar);
+
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ new ViewModelProvider(this)
+ .get(AttributionViewModel.class)
+ .observe(this, this::updateAttributions);
+ }
+
+ private void updateAttributions(List libraryAttributions) {
+ List rows = new ArrayList<>();
+ ImmutableListMultimap index =
+ Multimaps.index(libraryAttributions, LibraryAttribution::getLicense);
+ ArrayList licenses = new ArrayList<>(index.keySet());
+ Collections.sort(licenses);
+ for (String license : licenses) {
+ rows.add(new AttributionRow(license));
+ ImmutableList libraries = index.get(license);
+ ImmutableListMultimap idx =
+ Multimaps.index(libraries, LibraryAttribution::getCopyrightHolder);
+ List copyrightHolders = newArrayList(idx.keySet());
+ Collections.sort(copyrightHolders);
+ for (String copyrightHolder : copyrightHolders) {
+ List libs = newArrayList(transform(idx.get(copyrightHolder),
+ a -> "\u2022 " + a.getLibraryName()));
+ Collections.sort(libs);
+ rows.add(new AttributionRow(copyrightHolder, Joiner.on("\n").join(libs)));
+ }
+ }
+ recyclerView.setAdapter(new AttributionAdapter(rows));
+ Timber.d(libraryAttributions.toString());
+ }
+
+ @Override
+ public void inject(ActivityComponent component) {
+ component.inject(this);
+ }
+}
diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.java b/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.java
new file mode 100644
index 000000000..3f7e7eb11
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.java
@@ -0,0 +1,47 @@
+package org.tasks.activities.attribution;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import java.util.List;
+import org.tasks.R;
+
+public class AttributionAdapter extends RecyclerView.Adapter {
+
+ private final List rows;
+
+ AttributionAdapter(List rows) {
+ this.rows = rows;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ return viewType == 0
+ ? new LicenseHeader(inflater.inflate(R.layout.row_attribution_header, parent, false))
+ : new LicenseRow(inflater.inflate(R.layout.row_attribution, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ AttributionRow row = rows.get(position);
+ if (getItemViewType(position) == 0) {
+ ((LicenseHeader) holder).bind(row.getLicense());
+ } else {
+ ((LicenseRow) holder).bind(row.getCopyrightHolder(), row.getLibraries());
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return rows.get(position).isHeader() ? 0 : 1;
+ }
+
+ @Override
+ public int getItemCount() {
+ return rows.size();
+ }
+}
diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionRow.java b/app/src/main/java/org/tasks/activities/attribution/AttributionRow.java
new file mode 100644
index 000000000..af319a31c
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/AttributionRow.java
@@ -0,0 +1,39 @@
+package org.tasks.activities.attribution;
+
+public class AttributionRow {
+
+ private final boolean isHeader;
+ private final String license;
+ private final String copyrightHolder;
+ private final String libraries;
+
+ AttributionRow(String license) {
+ this.license = license;
+ isHeader = true;
+ copyrightHolder = null;
+ libraries = null;
+ }
+
+ AttributionRow(String copyrightHolder, String libraries) {
+ this.copyrightHolder = copyrightHolder;
+ this.libraries = libraries;
+ isHeader = false;
+ license = null;
+ }
+
+ boolean isHeader() {
+ return isHeader;
+ }
+
+ public String getLicense() {
+ return license;
+ }
+
+ String getCopyrightHolder() {
+ return copyrightHolder;
+ }
+
+ public String getLibraries() {
+ return libraries;
+ }
+}
diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.java b/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.java
new file mode 100644
index 000000000..8950fab3b
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.java
@@ -0,0 +1,77 @@
+package org.tasks.activities.attribution;
+
+import android.content.Context;
+import androidx.annotation.Keep;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+import com.google.gson.GsonBuilder;
+import io.reactivex.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.schedulers.Schedulers;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import timber.log.Timber;
+
+@SuppressWarnings({"WeakerAccess", "RedundantSuppression"})
+public class AttributionViewModel extends androidx.lifecycle.ViewModel {
+ private final MutableLiveData> attributions = new MutableLiveData<>();
+ private final CompositeDisposable disposables = new CompositeDisposable();
+ private boolean loaded;
+
+ void observe(AppCompatActivity activity, Observer> observer) {
+ attributions.observe(activity, observer);
+ load(activity);
+ }
+
+ private void load(Context context) {
+ if (loaded) {
+ return;
+ }
+ loaded = true;
+ disposables.add(
+ Single.fromCallable(
+ () -> {
+ InputStream licenses = context.getAssets().open("licenses.json");
+ InputStreamReader reader =
+ new InputStreamReader(licenses, StandardCharsets.UTF_8);
+ AttributionList list =
+ new GsonBuilder().create().fromJson(reader, AttributionList.class);
+ return list.libraries;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(attributions::setValue, Timber::e));
+ }
+
+ @Override
+ protected void onCleared() {
+ disposables.dispose();
+ }
+
+ static class AttributionList {
+ List libraries;
+ }
+
+ static class LibraryAttribution {
+ String copyrightHolder;
+ String license;
+ String libraryName;
+
+ String getCopyrightHolder() {
+ return copyrightHolder;
+ }
+
+ @Keep
+ String getLicense() {
+ return license;
+ }
+
+ String getLibraryName() {
+ return libraryName;
+ }
+ }
+}
diff --git a/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.java b/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.java
new file mode 100644
index 000000000..704b11f61
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.java
@@ -0,0 +1,23 @@
+package org.tasks.activities.attribution;
+
+import android.view.View;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import org.tasks.R;
+
+class LicenseHeader extends RecyclerView.ViewHolder {
+ @BindView(R.id.license_name)
+ TextView licenseName;
+
+ LicenseHeader(@NonNull View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+
+ void bind(String license) {
+ licenseName.setText(license);
+ }
+}
diff --git a/app/src/main/java/org/tasks/activities/attribution/LicenseRow.java b/app/src/main/java/org/tasks/activities/attribution/LicenseRow.java
new file mode 100644
index 000000000..26a0cd12a
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/attribution/LicenseRow.java
@@ -0,0 +1,28 @@
+package org.tasks.activities.attribution;
+
+import android.view.View;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import org.tasks.R;
+
+class LicenseRow extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.copyright_holder)
+ TextView copyrightHolder;
+
+ @BindView(R.id.libraries)
+ TextView libraries;
+
+ LicenseRow(@NonNull View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+
+ void bind(String copyrightHolder, String libraries) {
+ this.copyrightHolder.setText(copyrightHolder);
+ this.libraries.setText(libraries);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.java b/app/src/main/java/org/tasks/injection/ActivityComponent.java
index 9b8cbda8c..c847dfa06 100644
--- a/app/src/main/java/org/tasks/injection/ActivityComponent.java
+++ b/app/src/main/java/org/tasks/injection/ActivityComponent.java
@@ -25,7 +25,7 @@ import org.tasks.etesync.EteSyncCalendarSettingsActivity;
import org.tasks.locale.ui.activity.TaskerCreateTaskActivity;
import org.tasks.locale.ui.activity.TaskerSettingsActivity;
import org.tasks.location.LocationPickerActivity;
-import org.tasks.preferences.AttributionActivity;
+import org.tasks.activities.attribution.AttributionActivity;
import org.tasks.preferences.HelpAndFeedback;
import org.tasks.preferences.MainPreferences;
import org.tasks.preferences.ManageSpaceActivity;
diff --git a/app/src/main/java/org/tasks/preferences/AttributionActivity.java b/app/src/main/java/org/tasks/preferences/AttributionActivity.java
deleted file mode 100644
index e2aaac024..000000000
--- a/app/src/main/java/org/tasks/preferences/AttributionActivity.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.tasks.preferences;
-
-import android.content.Context;
-import android.os.Bundle;
-import androidx.annotation.Keep;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Multimaps;
-import com.google.gson.GsonBuilder;
-import io.github.luizgrp.sectionedrecyclerviewadapter.SectionedRecyclerViewAdapter;
-import io.reactivex.Single;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.schedulers.Schedulers;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.tasks.R;
-import org.tasks.injection.ActivityComponent;
-import org.tasks.injection.ThemedInjectingAppCompatActivity;
-import timber.log.Timber;
-
-public class AttributionActivity extends ThemedInjectingAppCompatActivity {
-
- @BindView(R.id.toolbar)
- Toolbar toolbar;
-
- @BindView(R.id.list)
- RecyclerView recyclerView;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_attributions);
-
- ButterKnife.bind(this);
-
- toolbar.setTitle(R.string.third_party_licenses);
- toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px);
- toolbar.setNavigationOnClickListener(v -> finish());
- themeColor.apply(toolbar);
-
- recyclerView.setLayoutManager(new LinearLayoutManager(this));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- new ViewModelProvider(this).get(ViewModel.class).observe(this, this::updateAttributions);
- }
-
- private void updateAttributions(List libraryAttributions) {
- SectionedRecyclerViewAdapter adapter = new SectionedRecyclerViewAdapter();
- ImmutableListMultimap index =
- Multimaps.index(libraryAttributions, LibraryAttribution::getLicense);
- ArrayList licenses = new ArrayList<>(index.keySet());
- Collections.sort(licenses);
- for (String license : licenses) {
- adapter.addSection(new AttributionSection(license, index.get(license)));
- }
- recyclerView.setAdapter(adapter);
- Timber.d(libraryAttributions.toString());
- }
-
- @Override
- public void inject(ActivityComponent component) {
- component.inject(this);
- }
-
- static class AttributionList {
- List libraries;
- }
-
- static class LibraryAttribution {
- String copyrightHolder;
- String license;
- String libraryName;
-
- String getCopyrightHolder() {
- return copyrightHolder;
- }
-
- @Keep
- String getLicense() {
- return license;
- }
-
- String getLibraryName() {
- return libraryName;
- }
- }
-
- @SuppressWarnings({"WeakerAccess", "RedundantSuppression"})
- public static class ViewModel extends androidx.lifecycle.ViewModel {
- private final MutableLiveData> attributions = new MutableLiveData<>();
- private final CompositeDisposable disposables = new CompositeDisposable();
- private boolean loaded;
-
- void observe(AppCompatActivity activity, Observer> observer) {
- attributions.observe(activity, observer);
- load(activity);
- }
-
- private void load(Context context) {
- if (loaded) {
- return;
- }
- loaded = true;
- disposables.add(
- Single.fromCallable(
- () -> {
- InputStream licenses = context.getAssets().open("licenses.json");
- InputStreamReader reader =
- new InputStreamReader(licenses, StandardCharsets.UTF_8);
- AttributionList list =
- new GsonBuilder().create().fromJson(reader, AttributionList.class);
- return list.libraries;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(attributions::setValue, Timber::e));
- }
-
- @Override
- protected void onCleared() {
- disposables.dispose();
- }
- }
-}
diff --git a/app/src/main/java/org/tasks/preferences/AttributionSection.java b/app/src/main/java/org/tasks/preferences/AttributionSection.java
deleted file mode 100644
index b2307b75a..000000000
--- a/app/src/main/java/org/tasks/preferences/AttributionSection.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package org.tasks.preferences;
-
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Maps.newLinkedHashMap;
-
-import android.view.View;
-import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Multimaps;
-import io.github.luizgrp.sectionedrecyclerviewadapter.SectionParameters;
-import io.github.luizgrp.sectionedrecyclerviewadapter.StatelessSection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import org.tasks.R;
-import org.tasks.preferences.AttributionActivity.LibraryAttribution;
-
-class AttributionSection extends StatelessSection {
-
- private final String license;
- private final List> attributions;
-
- AttributionSection(String license, List attributions) {
- super(
- SectionParameters.builder()
- .itemResourceId(R.layout.row_attribution)
- .headerResourceId(R.layout.row_attribution_header)
- .build());
- this.license = license;
- ImmutableListMultimap index =
- Multimaps.index(attributions, LibraryAttribution::getCopyrightHolder);
- List copyrightHolders = newArrayList(index.keySet());
- Collections.sort(copyrightHolders);
- Map map = newLinkedHashMap();
- for (String copyrightHolder : copyrightHolders) {
- List libraries = newArrayList(transform(index.get(copyrightHolder),
- a -> "\u2022 " + a.getLibraryName()));
- Collections.sort(libraries);
- map.put(copyrightHolder, Joiner.on("\n").join(libraries));
- }
- this.attributions = newArrayList(map.entrySet());
- }
-
- @Override
- public int getContentItemsTotal() {
- return attributions.size();
- }
-
- @Override
- public ViewHolder getHeaderViewHolder(View view) {
- return new LicenseHeader(view);
- }
-
- @Override
- public ViewHolder getItemViewHolder(View view) {
- return new LicenseRow(view);
- }
-
- @Override
- public void onBindHeaderViewHolder(ViewHolder holder) {
- ((LicenseHeader) holder).bind(license);
- }
-
- @Override
- public void onBindItemViewHolder(ViewHolder holder, int position) {
- Entry entry = attributions.get(position);
- ((LicenseRow) holder).bind(entry.getKey(), entry.getValue());
- }
-
- static class LicenseHeader extends RecyclerView.ViewHolder {
- @BindView(R.id.license_name)
- TextView licenseName;
-
- LicenseHeader(@NonNull View itemView) {
- super(itemView);
- ButterKnife.bind(this, itemView);
- }
-
- void bind(String license) {
- licenseName.setText(license);
- }
- }
-
- static class LicenseRow extends RecyclerView.ViewHolder {
-
- @BindView(R.id.copyright_holder)
- TextView copyrightHolder;
-
- @BindView(R.id.libraries)
- TextView libraries;
-
- LicenseRow(@NonNull View itemView) {
- super(itemView);
- ButterKnife.bind(this, itemView);
- }
-
- void bind(String copyrightHolder, String libraries) {
- this.copyrightHolder.setText(copyrightHolder);
- this.libraries.setText(libraries);
- }
- }
-}
diff --git a/app/src/main/res/xml/help_and_feedback.xml b/app/src/main/res/xml/help_and_feedback.xml
index 254a3a124..01c94931a 100644
--- a/app/src/main/res/xml/help_and_feedback.xml
+++ b/app/src/main/res/xml/help_and_feedback.xml
@@ -47,7 +47,7 @@
android:title="@string/third_party_licenses"
app:icon="@drawable/ic_outline_gavel_24px">