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">