recyclerview with notification list

This commit is contained in:
danijoo
2016-01-02 21:44:58 +01:00
parent 35c6bc10ed
commit 2de64dd24f
15 changed files with 465 additions and 30 deletions

View File

@@ -16,7 +16,7 @@
</intent-filter>
</activity>
<activity
android:name=".PreferenceActivity"
android:name=".preferences.PreferenceActivity"
android:parentActivityName=".MainActivity" />
<receiver
@@ -28,6 +28,7 @@
</receiver>
<service
android:label="@string/app_name"
android:name=".logger.NotificationLoggerService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>

View File

@@ -15,6 +15,8 @@ import android.view.View;
import net.headlezz.notificationlogger.createnotification.NotificationCreationFragment;
import net.headlezz.notificationlogger.logger.LoggerUtils;
import net.headlezz.notificationlogger.notificationlist.NotificationListFragment;
import net.headlezz.notificationlogger.preferences.PreferenceActivity;
import net.headlezz.notificationlogger.presenter.LoggerWarningPresenter;
import butterknife.Bind;

View File

@@ -1,23 +0,0 @@
package net.headlezz.notificationlogger;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class NotificationListFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setText(getClass().getSimpleName());
return textView;
}
}

View File

@@ -3,6 +3,10 @@ package net.headlezz.notificationlogger;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import timber.log.Timber;
public class PackageUtils {
@@ -12,5 +16,38 @@ public class PackageUtils {
return (String) pm.getApplicationLabel(appInfo);
}
/**
*
* @param context
* @param packageName
* @return Drawable or Null
*/
public static Drawable getApplicationLauncherIcon(Context context, String packageName) {
try {
return context.getPackageManager().getApplicationIcon(packageName);
} catch(PackageManager.NameNotFoundException e) {
Timber.w("package not found: " + packageName);
return null;
}
}
/**
*
* @param context
* @param packageName
* @param iconId
* @return Drawable or Null
*/
public static Drawable getApplicationDrawable(Context context, String packageName, int iconId) {
try {
Resources res = context.getPackageManager().getResourcesForApplication(packageName);
return res.getDrawable(iconId);
} catch (PackageManager.NameNotFoundException e) {
Timber.w("package not found: " + packageName);
return null;
}
}
}

View File

@@ -8,6 +8,8 @@ import ckm.simple.sql_provider.annotation.SimpleSQLTable;
@SimpleSQLTable(table="logged_notification", provider="NotificationProvider")
public class LoggedNotification {
@SimpleSQLColumn("_id")
public long id;
@SimpleSQLColumn("title")
public String title;

View File

@@ -0,0 +1,143 @@
package net.headlezz.notificationlogger.notificationlist;
/*
* Copyright (C) 2014 skyfish.jy@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.support.v7.widget.RecyclerView;
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public CursorRecyclerViewAdapter(Context context, Cursor cursor) {
mContext = context;
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
public Cursor getCursor() {
return mCursor;
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
@Override
public void onBindViewHolder(VH viewHolder, int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(viewHolder, mCursor);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
notifyDataSetChanged();
} else {
mRowIdColumn = -1;
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}

View File

@@ -0,0 +1,82 @@
package net.headlezz.notificationlogger.notificationlist;
import android.content.Context;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import net.headlezz.notificationlogger.PackageUtils;
import net.headlezz.notificationlogger.R;
import net.headlezz.notificationlogger.logger.LoggedNotification;
import net.headlezz.notificationlogger.logger.Logged_notificationTable;
import butterknife.Bind;
import butterknife.ButterKnife;
public class NotificationListAdapter extends CursorRecyclerViewAdapter<NotificationListAdapter.NotificationViewHolder> {
class NotificationViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.notification_item_message)
TextView tvMessage;
@Bind(R.id.notification_item_title)
TextView tvTitle;
@Bind(R.id.notification_item_appName)
TextView tvAppName;
@Bind(R.id.notification_item_date)
TextView tvDate;
@Bind(R.id.notification_item_appIcon)
ImageView ivAppIcon;
@Bind(R.id.notification_item_smallIcon)
ImageView ivSmallIcon;
public NotificationViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void setNotification(LoggedNotification n) {
Context context = itemView.getContext();
tvMessage.setText(n.message);
tvTitle.setText(n.title);
tvAppName.setText(n.appName);
CharSequence formattedDate = DateUtils.getRelativeTimeSpanString(n.date.getTime(), System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS, DateUtils.FORMAT_ABBREV_ALL);
tvDate.setText(formattedDate);
Drawable appIcon = PackageUtils.getApplicationLauncherIcon(context, n.packageName);
ivAppIcon.setImageDrawable(appIcon);
Drawable smallIcon = PackageUtils.getApplicationDrawable(context, n.packageName, n.smallIconId);
ivSmallIcon.setImageDrawable(smallIcon);
}
}
public NotificationListAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
@Override
public void onBindViewHolder(NotificationViewHolder viewHolder, Cursor cursor) {
LoggedNotification n = Logged_notificationTable.getRow(cursor, false);
viewHolder.setNotification(n);
}
@Override
public NotificationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.notification_list_item, parent, false);
return new NotificationViewHolder(v);
}
}

View File

@@ -0,0 +1,89 @@
package net.headlezz.notificationlogger.notificationlist;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import net.headlezz.notificationlogger.R;
import net.headlezz.notificationlogger.logger.Logged_notificationTable;
import butterknife.Bind;
import butterknife.ButterKnife;
import timber.log.Timber;
public class NotificationListFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
final int LOADER_ID = 124;
@Bind(R.id.nList_emptyView)
View mEmptyView;
@Bind(R.id.nList_notificationList)
RecyclerView mNotificationList;
private NotificationListAdapter mAdapter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.notification_list_fragment, container, false);
ButterKnife.bind(this, view);
mNotificationList.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new NotificationListAdapter(getContext(), null);
mNotificationList.setAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, Bundle.EMPTY, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Timber.d("Loader created.");
return new CursorLoader(
getContext(),
Logged_notificationTable.CONTENT_URI,
null,
null,
null,
null
);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Timber.d("Loader finished. " + data.getCount() + " items.");
mAdapter.changeCursor(data);
checkIfEmpty();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
Timber.d("Loader reset.");
mAdapter.changeCursor(null);
checkIfEmpty();
}
private void checkIfEmpty() {
if(mAdapter.getItemCount() == 0) {
mEmptyView.setVisibility(View.VISIBLE);
mNotificationList.setVisibility(View.GONE);
} else {
mEmptyView.setVisibility(View.GONE);
mNotificationList.setVisibility(View.VISIBLE);
}
}
}

View File

@@ -1,4 +1,4 @@
package net.headlezz.notificationlogger;
package net.headlezz.notificationlogger.preferences;
import android.app.Activity;
import android.support.v4.app.Fragment;

View File

@@ -1,4 +1,4 @@
package net.headlezz.notificationlogger;
package net.headlezz.notificationlogger.preferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -8,6 +8,8 @@ import android.support.v7.widget.Toolbar;
import com.mikepenz.aboutlibraries.LibsBuilder;
import com.mikepenz.aboutlibraries.ui.LibsSupportFragment;
import net.headlezz.notificationlogger.R;
import butterknife.Bind;
import butterknife.ButterKnife;

View File

@@ -1,4 +1,4 @@
package net.headlezz.notificationlogger;
package net.headlezz.notificationlogger.preferences;
import android.app.Activity;
import android.content.ContentResolver;
@@ -10,6 +10,7 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import net.headlezz.notificationlogger.R;
import net.headlezz.notificationlogger.logger.Logged_notificationTable;
public class PreferenceFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener {

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/nList_notificationList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/nList_emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/notification_list_empty" />
</FrameLayout>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:andorid="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:layout_marginEnd="16dp"
android:id="@+id/notification_item_appIcon"
android:layout_width="64dp"
android:layout_height="64dp"
tools:src="@mipmap/ic_launcher" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/notification_item_smallIcon"
android:layout_width="24dp"
android:layout_height="24dp"
tools:src="@drawable/ic_sd_card" />
<TextView
android:id="@+id/notification_item_appName"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:lines="1"
tools:text="A really long app name thats ellipsed"
android:ellipsize="end"
android:layout_toEndOf="@+id/notification_item_smallIcon"
android:layout_toStartOf="@+id/notification_item_date"/>
<TextView
android:lines="1"
android:ellipsize="end"
android:layout_marginTop="4dp"
android:id="@+id/notification_item_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@+id/notification_item_appName"
tools:text="Notification title" />
<TextView
android:layout_marginStart="4dp"
android:id="@+id/notification_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
tools:text="16 hours ago"
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
<TextView
android:lines="3"
android:ellipsize="end"
android:id="@+id/notification_item_message"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@+id/notification_item_title"
tools:text="Some messages wit lots of text and \nTwo lines" />
</RelativeLayout>
</LinearLayout>

View File

@@ -44,6 +44,7 @@
<string name="create_shedule_snack">Notification sheduled for %1$s</string>
<string name="create_shedule_snack_undo">Undo</string>
<string name="notification_list_empty">No notifications found.</string>
<string-array name="create_categories">
<item>Alarm</item>