diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d408354..b5b56df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + diff --git a/app/src/main/java/io/saytheirnames/activity/HomeSearchActivity.java b/app/src/main/java/io/saytheirnames/activity/HomeSearchActivity.java new file mode 100644 index 0000000..902d87d --- /dev/null +++ b/app/src/main/java/io/saytheirnames/activity/HomeSearchActivity.java @@ -0,0 +1,298 @@ +package io.saytheirnames.activity; + +import android.annotation.SuppressLint; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.paging.LoadState; +import androidx.paging.LoadStateAdapter; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.MergeAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.google.android.material.appbar.AppBarLayout; +import com.squareup.picasso.Picasso; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import io.saytheirnames.R; +import io.saytheirnames.adapters.HashtagAdapter; +import io.saytheirnames.adapters.HeaderCardRecyclerAdapter; +import io.saytheirnames.adapters.HomeHeaderAdapter; +import io.saytheirnames.adapters.MediaAdapter; +import io.saytheirnames.adapters.NewsAdapter; +import io.saytheirnames.adapters.PeopleAdapter; +import io.saytheirnames.adapters.PeopleSearchAdapter; +import io.saytheirnames.fragments.DonationFragment; +import io.saytheirnames.models.Hashtag; +import io.saytheirnames.models.Media; +import io.saytheirnames.models.News; +import io.saytheirnames.models.People; +import io.saytheirnames.models.PeopleData; +import io.saytheirnames.network.BackendInterface; +import io.saytheirnames.network.Utils; +import io.saytheirnames.utils.CustomTabUtil; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class HomeSearchActivity extends AppCompatActivity implements HeaderCardRecyclerAdapter.HeaderCardClickListener { + + public static final String EXTRA_ID = "identifier"; + + private AppBarLayout appBarLayout; + private LinearLayout linearLayout; + private LinearLayout search_message; + private LinearLayout search_no_record; + private EditText search_text; + private TextView cancelText; + + private MediaAdapter mediaAdapter; + private NewsAdapter newsAdapter; + private HashtagAdapter hashtagAdapter; + + private List newsList; + private List mediaList; + private List hashtagList; + + private ArrayList peopleList; + + private String personID, baseUrl; + + PeopleData peopleData; + + BackendInterface backendInterface; + ; + + private RecyclerView personRecyclerView; + private MergeAdapter mergeAdapter; + + private PeopleAdapter peopleAdapter; + private PeopleSearchAdapter peopleSearchAdapter; + private ProgressBar progressBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home_search); + + personID = getIntent().getStringExtra(EXTRA_ID); + peopleData = new PeopleData(); + + bindViews(); + initializeRecyclerView(); + } + + @SuppressLint("StaticFieldLeak") + @Override + public void onDestroy() { + super.onDestroy(); + + //clear Glide's disk cache whenever an activity is destroyed. Mechanism for helping against memory leaks/ Out of memory errors + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + // This method must be called on a background thread. + Glide.get(getApplicationContext()).clearDiskCache(); + + // DetailsActivity's richLinkViewer (in the NewsAdapter) internally uses Picasso + Picasso.get().shutdown(); + return null; + } + }; + } + + private void initializeBackend() { + backendInterface = Utils.getBackendService(); + } + + private void bindViews() { + personRecyclerView = findViewById(R.id.personRecyclerView); + progressBar = findViewById(R.id.progressBar); + appBarLayout = findViewById(R.id.app_bar_home); + search_message = findViewById(R.id.search_message); + search_no_record = findViewById(R.id.search_no_record); + linearLayout = appBarLayout.findViewById(R.id.linear); + search_text = linearLayout.findViewById(R.id.search_text); + cancelText = linearLayout.findViewById(R.id.cancel); + + peopleAdapter = new PeopleAdapter(); + peopleList = new ArrayList<>(); + peopleSearchAdapter = new PeopleSearchAdapter(peopleList, HomeSearchActivity.this); + + + mergeAdapter = new MergeAdapter(new HomeHeaderAdapter(this), peopleAdapter); + + + cancelText.setOnClickListener(v -> { + finish(); + }); + + search_text.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + loadData(s.toString()); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + } + + private void initializeRecyclerView() { + GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 1); + + /* + This makes the first item span two columns (the black cards) + */ + gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + return 1; + //return position == 0 ? 2 : 1; + } + }); + + /* + This triggers the showing/hiding of the progress bar. This looks really hacky + this way, though. + */ + + peopleAdapter.withLoadStateFooter(new LoadStateAdapter() { + @NotNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup viewGroup, @NotNull LoadState loadState) { + return new RecyclerView.ViewHolder(progressBar) { + }; + } + + @Override + public void onBindViewHolder(@NotNull RecyclerView.ViewHolder viewHolder, @NotNull LoadState loadState) { + if (loadState.equals(LoadState.Loading.INSTANCE)) { + progressBar.setVisibility(View.VISIBLE); + } else { + progressBar.setVisibility(View.GONE); + } + } + }); + + personRecyclerView.setLayoutManager(gridLayoutManager); + personRecyclerView.setAdapter(peopleSearchAdapter); + } + + private void loadData(String s) { + showProgress(true); + showNoRecord(false); + showSearchMessage(false); + peopleList.clear(); + if (s.length() == 0) { + showProgress(false); + showNoRecord(false); + showSearchMessage(true); + peopleSearchAdapter.notifyDataSetChanged(); + } else { + BackendInterface backendInterface = Utils.getBackendService(); + backendInterface.searchPeople(s).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + showProgress(false); + if (response.body() != null) { + if (response.body().getData().size() == 0) { + showNoRecord(true); + showSearchMessage(false); + } else { + showNoRecord(false); + showSearchMessage(false); + peopleList.addAll(response.body().getData()); + } + } + peopleSearchAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + showProgress(false); + showNoRecord(false); + showSearchMessage(true); + onGetPersonFailure(t); + } + }); + } + + + } + + private void onGetPersonFailure(Throwable throwable) { + //TODO: Get a better message. This could be full of dev jargon. + showSnackbar(throwable.getLocalizedMessage()); + } + + private void showSnackbar(String text) { + // Snackbar.make(toolbar, text, Snackbar.LENGTH_SHORT).show(); + } + + + private void showProgress(Boolean show) { + progressBar.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private void showSearchMessage(Boolean show) { + search_message.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private void showNoRecord(Boolean show) { + search_no_record.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void onHeaderClick() { + + Fragment donationFragment = DonationFragment.newInstance(); + String tag = "DonationFragment"; + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.container, donationFragment, tag); + ft.commit(); + ((MainActivity) getBaseContext()).updateBottomNavBasedOnTag(tag); + } + + + private String nullCheck(Integer inputString) { + if (inputString == null) { + return "N/A"; + } else { + return inputString.toString(); + } + } + + private void navigateToUrl(String url) { + CustomTabUtil.openCustomTabForUrl(this, url); + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/saytheirnames/adapters/NewsAdapter.java b/app/src/main/java/io/saytheirnames/adapters/NewsAdapter.java index 6acb411..712a4e3 100644 --- a/app/src/main/java/io/saytheirnames/adapters/NewsAdapter.java +++ b/app/src/main/java/io/saytheirnames/adapters/NewsAdapter.java @@ -49,6 +49,7 @@ public void onSuccess(boolean status) { } @Override public void onError(Exception e) { + Log.d("ERROR:::", String.valueOf(e.getLocalizedMessage())); } }); diff --git a/app/src/main/java/io/saytheirnames/adapters/PeopleSearchAdapter.java b/app/src/main/java/io/saytheirnames/adapters/PeopleSearchAdapter.java new file mode 100644 index 0000000..74d7790 --- /dev/null +++ b/app/src/main/java/io/saytheirnames/adapters/PeopleSearchAdapter.java @@ -0,0 +1,86 @@ +package io.saytheirnames.adapters; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import io.saytheirnames.R; +import io.saytheirnames.activity.DetailsActivity; +import io.saytheirnames.models.People; +import io.saytheirnames.models.PeopleData; +import io.saytheirnames.models.Person; + +public class PeopleSearchAdapter extends RecyclerView.Adapter { + + private List peopleList; + private Context context; + + + public PeopleSearchAdapter(List peopleList, Context context) { + super(); + this.peopleList = peopleList; + this.context = context; + + notifyDataSetChanged(); + } + + @NonNull + @Override + public PeopleSearchAdapter.FilterItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + + View convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.person_search_item, parent, false); + + + return new FilterItemHolder(convertView); + } + + @Override + public void onBindViewHolder(@NonNull PeopleSearchAdapter.FilterItemHolder holder, final int position) { + + People people = peopleList.get(position); + + holder.personName.setText(people.getFullName()); + + + holder.cardView.setOnClickListener(v -> { + ((Activity)context).finish(); + Intent intent = new Intent(context, DetailsActivity.class); + intent.putExtra(DetailsActivity.EXTRA_ID, people.getIdentifier()); + holder.itemView.getContext().startActivity(intent); + }); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return peopleList.size(); + } + + static class FilterItemHolder extends RecyclerView.ViewHolder { + TextView personName; + CardView cardView; + + public FilterItemHolder(@NonNull View itemView) { + super(itemView); + personName = itemView.findViewById(R.id.txtPersonName); + cardView = itemView.findViewById(R.id.cardView); + } + } +} diff --git a/app/src/main/java/io/saytheirnames/fragments/HomeFragment.java b/app/src/main/java/io/saytheirnames/fragments/HomeFragment.java index 0df05a8..ad6f622 100644 --- a/app/src/main/java/io/saytheirnames/fragments/HomeFragment.java +++ b/app/src/main/java/io/saytheirnames/fragments/HomeFragment.java @@ -1,13 +1,18 @@ package io.saytheirnames.fragments; +import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import androidx.paging.LoadState; @@ -16,11 +21,14 @@ import androidx.recyclerview.widget.MergeAdapter; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.appbar.AppBarLayout; + import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import io.saytheirnames.R; +import io.saytheirnames.activity.HomeSearchActivity; import io.saytheirnames.activity.MainActivity; import io.saytheirnames.adapters.FilterHomeAdapter; import io.saytheirnames.adapters.HeaderCardRecyclerAdapter; @@ -43,6 +51,11 @@ public class HomeFragment extends Fragment implements HeaderCardRecyclerAdapter. private ArrayList filterArrayList; private ProgressBar progressBar; + private AppBarLayout appBarLayout; + private Toolbar toolbar; + private RelativeLayout relativeLayout; + private ImageButton searchButton; + Resources resources; public static HomeFragment newInstance() { @@ -67,6 +80,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, personRecyclerView = mContent.findViewById(R.id.personRecyclerView); progressBar = mContent.findViewById(R.id.progressBar); + appBarLayout = mContent.findViewById(R.id.app_bar_home); + toolbar = appBarLayout.findViewById(R.id.toolbar); + relativeLayout = toolbar.findViewById(R.id.relative_layout); + searchButton = relativeLayout.findViewById(R.id.searchButton); + + peopleAdapter = new PeopleAdapter(); @@ -74,12 +93,21 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, filterHomeAdapter = new FilterHomeAdapter(filterArrayList, getActivity()); + searchButton.setOnClickListener(v -> { + doSearch(); + }); + initializeRecyclerView(); loadData(); return mContent; } + private void doSearch(){ + Intent intent = new Intent(getActivity(), HomeSearchActivity.class); + startActivity(intent); + } + private void initializeRecyclerView() { GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 2); diff --git a/app/src/main/java/io/saytheirnames/network/BackendInterface.java b/app/src/main/java/io/saytheirnames/network/BackendInterface.java index 436eafd..76ccdb2 100644 --- a/app/src/main/java/io/saytheirnames/network/BackendInterface.java +++ b/app/src/main/java/io/saytheirnames/network/BackendInterface.java @@ -17,6 +17,9 @@ public interface BackendInterface { @GET("/api/people") Call getPeople(@Query("page") Integer page); + @GET("/api/people") + Call searchPeople(@Query("name") String name); + @GET("/api/people/{id}") Call getPeopleById(@Path("id") String id); diff --git a/app/src/main/res/drawable/shadow.xml b/app/src/main/res/drawable/shadow.xml new file mode 100644 index 0000000..82d5bae --- /dev/null +++ b/app/src/main/res/drawable/shadow.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home_search.xml b/app/src/main/res/layout/activity_home_search.xml new file mode 100644 index 0000000..8a9c3e6 --- /dev/null +++ b/app/src/main/res/layout/activity_home_search.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 2fda1ec..871c90d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -21,6 +21,7 @@ app:layout_scrollFlags="scroll|enterAlways"> @@ -62,6 +63,7 @@ + + + + + + + + + + + \ 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 cf3ec7e..9cf344a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -7,4 +7,5 @@ 0dp 4dp 16dp + 24dp \ No newline at end of file