Refactored MediaFileListFragment to support multi-/single-columns
Refactored AbstractListFragment to make it more generic and allow it to be used by MediaFileListFragment. This adds support for switching between a single- and multicolumn view. Created new abstract class AbstractCursorListFragment for list fragments using a cursoradapter.
This commit is contained in:
parent
e5a1e97b8d
commit
8bfe2665dd
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Martijn Brekhof. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.xbmc.kore.ui;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CursorAdapter;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.xbmc.kore.R;
|
||||||
|
import org.xbmc.kore.host.HostInfo;
|
||||||
|
import org.xbmc.kore.host.HostManager;
|
||||||
|
import org.xbmc.kore.jsonrpc.ApiException;
|
||||||
|
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
||||||
|
import org.xbmc.kore.service.LibrarySyncService;
|
||||||
|
import org.xbmc.kore.service.SyncUtils;
|
||||||
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
public abstract class AbstractCursorListFragment extends AbstractListFragment
|
||||||
|
implements LoaderManager.LoaderCallbacks<Cursor>,
|
||||||
|
SyncUtils.OnServiceListener,
|
||||||
|
SearchView.OnQueryTextListener,
|
||||||
|
SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
private static final String TAG = LogUtils.makeLogTag(AbstractCursorListFragment.class);
|
||||||
|
|
||||||
|
private ServiceConnection serviceConnection;
|
||||||
|
|
||||||
|
private HostInfo hostInfo;
|
||||||
|
private EventBus bus;
|
||||||
|
|
||||||
|
private CursorAdapter adapter;
|
||||||
|
|
||||||
|
// Loader IDs
|
||||||
|
private static final int LOADER = 0;
|
||||||
|
|
||||||
|
// The search filter to use in the loader
|
||||||
|
private String searchFilter = null;
|
||||||
|
|
||||||
|
abstract protected CursorLoader createCursorLoader();
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
bus = EventBus.getDefault();
|
||||||
|
HostManager hostManager = HostManager.getInstance(getActivity());
|
||||||
|
hostInfo = hostManager.getHostInfo();
|
||||||
|
|
||||||
|
swipeRefreshLayout.setEnabled(true);
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(this);
|
||||||
|
|
||||||
|
adapter = (CursorAdapter) getAdapter();
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated (Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(LOADER, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
serviceConnection = SyncUtils.connectToLibrarySyncService(getActivity(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
bus.register(this);
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
bus.unregister(this);
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
SyncUtils.disconnectFromLibrarySyncService(getActivity(), serviceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.abstractcursorlistfragment, menu);
|
||||||
|
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch(item.getItemId()) {
|
||||||
|
case R.id.action_refresh:
|
||||||
|
onRefresh();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swipe refresh layout callback
|
||||||
|
*/
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
if (hostInfo != null) {
|
||||||
|
showRefreshAnimation();
|
||||||
|
onSwipeRefresh();
|
||||||
|
} else {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return the {@link LibrarySyncService} SyncType that
|
||||||
|
* this list initiates
|
||||||
|
* @return {@link LibrarySyncService} SyncType
|
||||||
|
*/
|
||||||
|
abstract protected String getListSyncType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event bus post. Called when the syncing process ended
|
||||||
|
*
|
||||||
|
* @param event Refreshes data
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(MediaSyncEvent event) {
|
||||||
|
onSyncProcessEnded(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called each time a MediaSyncEvent is received.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
protected void onSyncProcessEnded(MediaSyncEvent event) {
|
||||||
|
boolean silentSync = false;
|
||||||
|
if (event.syncExtras != null) {
|
||||||
|
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.syncType.equals(getListSyncType())) {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
|
||||||
|
refreshList();
|
||||||
|
if (!silentSync) {
|
||||||
|
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
} else if (!silentSync) {
|
||||||
|
String msg = (event.errorCode == ApiException.API_ERROR) ?
|
||||||
|
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
|
||||||
|
getString(R.string.unable_to_connect_to_xbmc);
|
||||||
|
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(LibrarySyncService librarySyncService) {
|
||||||
|
if(SyncUtils.isLibrarySyncing(
|
||||||
|
librarySyncService,
|
||||||
|
HostManager.getInstance(getActivity()).getHostInfo(),
|
||||||
|
getListSyncType())) {
|
||||||
|
showRefreshAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSwipeRefresh() {
|
||||||
|
LogUtils.LOGD(TAG, "Swipe, starting sync for: " + getListSyncType());
|
||||||
|
// Start the syncing process
|
||||||
|
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
|
||||||
|
syncIntent.putExtra(getListSyncType(), true);
|
||||||
|
getActivity().startService(syncIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search view callbacks
|
||||||
|
*/
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextChange(String newText) {
|
||||||
|
searchFilter = newText;
|
||||||
|
getLoaderManager().restartLoader(LOADER, null, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextSubmit(String newText) {
|
||||||
|
// All is handled in onQueryTextChange
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loader callbacks
|
||||||
|
*/
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return createCursorLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
adapter.swapCursor(cursor);
|
||||||
|
// To prevent the empty text from appearing on the first load, set it now
|
||||||
|
emptyView.setText(getString(R.string.swipe_down_to_refresh));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||||
|
adapter.swapCursor(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return text entered in searchview
|
||||||
|
*/
|
||||||
|
public String getSearchFilter() {
|
||||||
|
return searchFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to reload the items in the list
|
||||||
|
*/
|
||||||
|
public void refreshList() {
|
||||||
|
getLoaderManager().restartLoader(LOADER, null, this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,19 +17,12 @@
|
||||||
package org.xbmc.kore.ui;
|
package org.xbmc.kore.ui;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
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.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.SearchView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -38,44 +31,21 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.CursorAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.xbmc.kore.R;
|
import org.xbmc.kore.R;
|
||||||
import org.xbmc.kore.Settings;
|
import org.xbmc.kore.Settings;
|
||||||
import org.xbmc.kore.host.HostInfo;
|
|
||||||
import org.xbmc.kore.host.HostManager;
|
|
||||||
import org.xbmc.kore.jsonrpc.ApiException;
|
|
||||||
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
|
|
||||||
import org.xbmc.kore.service.LibrarySyncService;
|
|
||||||
import org.xbmc.kore.service.SyncUtils;
|
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
import org.xbmc.kore.utils.Utils;
|
import org.xbmc.kore.utils.Utils;
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.InjectView;
|
import butterknife.InjectView;
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
|
|
||||||
public abstract class AbstractListFragment extends Fragment
|
public abstract class AbstractListFragment extends Fragment {
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>,
|
|
||||||
SyncUtils.OnServiceListener,
|
|
||||||
SearchView.OnQueryTextListener,
|
|
||||||
SwipeRefreshLayout.OnRefreshListener {
|
|
||||||
private static final String TAG = LogUtils.makeLogTag(AbstractListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(AbstractListFragment.class);
|
||||||
|
private BaseAdapter adapter;
|
||||||
private ServiceConnection serviceConnection;
|
|
||||||
private CursorAdapter adapter;
|
|
||||||
|
|
||||||
private HostInfo hostInfo;
|
|
||||||
private EventBus bus;
|
|
||||||
|
|
||||||
// Loader IDs
|
|
||||||
private static final int LOADER = 0;
|
|
||||||
|
|
||||||
// The search filter to use in the loader
|
|
||||||
private String searchFilter = null;
|
|
||||||
|
|
||||||
private boolean gridViewUsesMultipleColumns;
|
private boolean gridViewUsesMultipleColumns;
|
||||||
|
|
||||||
|
@ -84,8 +54,7 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
@InjectView(android.R.id.empty) TextView emptyView;
|
@InjectView(android.R.id.empty) TextView emptyView;
|
||||||
|
|
||||||
abstract protected AdapterView.OnItemClickListener createOnItemClickListener();
|
abstract protected AdapterView.OnItemClickListener createOnItemClickListener();
|
||||||
abstract protected CursorAdapter createAdapter();
|
abstract protected BaseAdapter createAdapter();
|
||||||
abstract protected CursorLoader createCursorLoader();
|
|
||||||
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -94,22 +63,25 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
|
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
|
||||||
ButterKnife.inject(this, root);
|
ButterKnife.inject(this, root);
|
||||||
|
|
||||||
bus = EventBus.getDefault();
|
swipeRefreshLayout.setEnabled(false);
|
||||||
HostManager hostManager = HostManager.getInstance(getActivity());
|
|
||||||
hostInfo = hostManager.getHostInfo();
|
|
||||||
|
|
||||||
swipeRefreshLayout.setOnRefreshListener(this);
|
gridView.setEmptyView(emptyView);
|
||||||
|
gridView.setOnItemClickListener(createOnItemClickListener());
|
||||||
|
|
||||||
|
// Configure the adapter and start the loader
|
||||||
|
adapter = createAdapter();
|
||||||
|
gridView.setAdapter(adapter);
|
||||||
|
|
||||||
//Listener added to be able to determine if multiple-columns is at all possible for the current grid
|
//Listener added to be able to determine if multiple-columns is at all possible for the current grid
|
||||||
//We use this information to enable/disable the menu item to switch between multiple and single columns
|
//We use this information to enable/disable the menu item to switch between multiple and single columns
|
||||||
gridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
gridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onGlobalLayout() {
|
public void onGlobalLayout() {
|
||||||
if(gridView.getNumColumns() > 1) {
|
if (gridView.getNumColumns() > 1) {
|
||||||
gridViewUsesMultipleColumns = true;
|
gridViewUsesMultipleColumns = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Utils.isJellybeanOrLater()) {
|
if (Utils.isJellybeanOrLater()) {
|
||||||
gridView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
gridView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
} else {
|
} else {
|
||||||
gridView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
gridView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||||
|
@ -119,47 +91,11 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
getActivity().invalidateOptionsMenu();
|
getActivity().invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated (Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
gridView.setEmptyView(emptyView);
|
|
||||||
gridView.setOnItemClickListener(createOnItemClickListener());
|
|
||||||
|
|
||||||
// Configure the adapter and start the loader
|
|
||||||
adapter = createAdapter();
|
|
||||||
gridView.setAdapter(adapter);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(LOADER, null, this);
|
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return root;
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
serviceConnection = SyncUtils.connectToLibrarySyncService(getActivity(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
bus.register(this);
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
bus.unregister(this);
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
SyncUtils.disconnectFromLibrarySyncService(getActivity(), serviceConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -193,9 +129,6 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch(item.getItemId()) {
|
switch(item.getItemId()) {
|
||||||
case R.id.action_refresh:
|
|
||||||
onRefresh();
|
|
||||||
break;
|
|
||||||
case R.id.action_multi_single_columns:
|
case R.id.action_multi_single_columns:
|
||||||
toggleAmountOfColumns(item);
|
toggleAmountOfColumns(item);
|
||||||
break;
|
break;
|
||||||
|
@ -219,139 +152,6 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
adapter.notifyDataSetChanged(); //force gridView to redraw
|
adapter.notifyDataSetChanged(); //force gridView to redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Swipe refresh layout callback
|
|
||||||
*/
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
if (hostInfo != null) {
|
|
||||||
showRefreshAnimation();
|
|
||||||
onSwipeRefresh();
|
|
||||||
} else {
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
|
||||||
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should return the {@link org.xbmc.kore.service.LibrarySyncService} SyncType that
|
|
||||||
* this list initiates
|
|
||||||
* @return {@link org.xbmc.kore.service.LibrarySyncService} SyncType
|
|
||||||
*/
|
|
||||||
abstract protected String getListSyncType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event bus post. Called when the syncing process ended
|
|
||||||
*
|
|
||||||
* @param event Refreshes data
|
|
||||||
*/
|
|
||||||
public void onEventMainThread(MediaSyncEvent event) {
|
|
||||||
onSyncProcessEnded(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called each time a MediaSyncEvent is received.
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
protected void onSyncProcessEnded(MediaSyncEvent event) {
|
|
||||||
boolean silentSync = false;
|
|
||||||
if (event.syncExtras != null) {
|
|
||||||
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.syncType.equals(getListSyncType())) {
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
|
||||||
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
|
|
||||||
refreshList();
|
|
||||||
if (!silentSync) {
|
|
||||||
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
} else if (!silentSync) {
|
|
||||||
String msg = (event.errorCode == ApiException.API_ERROR) ?
|
|
||||||
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
|
|
||||||
getString(R.string.unable_to_connect_to_xbmc);
|
|
||||||
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(LibrarySyncService librarySyncService) {
|
|
||||||
if(SyncUtils.isLibrarySyncing(
|
|
||||||
librarySyncService,
|
|
||||||
HostManager.getInstance(getActivity()).getHostInfo(),
|
|
||||||
getListSyncType())) {
|
|
||||||
showRefreshAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onSwipeRefresh() {
|
|
||||||
LogUtils.LOGD(TAG, "Swipe, starting sync for: " + getListSyncType());
|
|
||||||
// Start the syncing process
|
|
||||||
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
|
|
||||||
syncIntent.putExtra(getListSyncType(), true);
|
|
||||||
getActivity().startService(syncIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search view callbacks
|
|
||||||
*/
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String newText) {
|
|
||||||
searchFilter = newText;
|
|
||||||
getLoaderManager().restartLoader(LOADER, null, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String newText) {
|
|
||||||
// All is handled in onQueryTextChange
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader callbacks
|
|
||||||
*/
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
|
||||||
return createCursorLoader();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
|
||||||
adapter.swapCursor(cursor);
|
|
||||||
// To prevent the empty text from appearing on the first load, set it now
|
|
||||||
emptyView.setText(getString(R.string.swipe_down_to_refresh));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
|
||||||
adapter.swapCursor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return text entered in searchview
|
|
||||||
*/
|
|
||||||
public String getSearchFilter() {
|
|
||||||
return searchFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this to reload the items in the list
|
|
||||||
*/
|
|
||||||
public void refreshList() {
|
|
||||||
getLoaderManager().restartLoader(LOADER, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showRefreshAnimation() {
|
public void showRefreshAnimation() {
|
||||||
/**
|
/**
|
||||||
* Fixes issue with refresh animation not showing when using appcompat library (from version 20?)
|
* Fixes issue with refresh animation not showing when using appcompat library (from version 20?)
|
||||||
|
@ -364,4 +164,8 @@ public abstract class AbstractListFragment extends Fragment
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BaseAdapter getAdapter() {
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ import org.xbmc.kore.utils.Utils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the albums list
|
* Fragment that presents the albums list
|
||||||
*/
|
*/
|
||||||
public class AlbumListFragment extends AbstractListFragment {
|
public class AlbumListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(AlbumListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(AlbumListFragment.class);
|
||||||
|
|
||||||
public interface OnAlbumSelectedListener {
|
public interface OnAlbumSelectedListener {
|
||||||
|
|
|
@ -41,7 +41,6 @@ import android.widget.TextView;
|
||||||
import org.xbmc.kore.R;
|
import org.xbmc.kore.R;
|
||||||
import org.xbmc.kore.host.HostInfo;
|
import org.xbmc.kore.host.HostInfo;
|
||||||
import org.xbmc.kore.host.HostManager;
|
import org.xbmc.kore.host.HostManager;
|
||||||
import org.xbmc.kore.jsonrpc.method.Playlist;
|
|
||||||
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
import org.xbmc.kore.jsonrpc.type.PlaylistType;
|
||||||
import org.xbmc.kore.provider.MediaContract;
|
import org.xbmc.kore.provider.MediaContract;
|
||||||
import org.xbmc.kore.provider.MediaDatabase;
|
import org.xbmc.kore.provider.MediaDatabase;
|
||||||
|
@ -54,7 +53,7 @@ import org.xbmc.kore.utils.Utils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the artists list
|
* Fragment that presents the artists list
|
||||||
*/
|
*/
|
||||||
public class ArtistListFragment extends AbstractListFragment {
|
public class ArtistListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(ArtistListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(ArtistListFragment.class);
|
||||||
|
|
||||||
public interface OnArtistSelectedListener {
|
public interface OnArtistSelectedListener {
|
||||||
|
|
|
@ -50,7 +50,7 @@ import org.xbmc.kore.utils.UIUtils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the album genres list
|
* Fragment that presents the album genres list
|
||||||
*/
|
*/
|
||||||
public class AudioGenresListFragment extends AbstractListFragment {
|
public class AudioGenresListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(AudioGenresListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(AudioGenresListFragment.class);
|
||||||
|
|
||||||
public interface OnAudioGenreSelectedListener {
|
public interface OnAudioGenreSelectedListener {
|
||||||
|
|
|
@ -21,15 +21,12 @@ import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.GridView;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ListAdapter;
|
import android.widget.ListAdapter;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
|
@ -56,13 +53,10 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.InjectView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presents a list of files of different types (Video/Music)
|
* Presents a list of files of different types (Video/Music)
|
||||||
*/
|
*/
|
||||||
public class MediaFileListFragment extends Fragment {
|
public class MediaFileListFragment extends AbstractListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(MediaFileListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(MediaFileListFragment.class);
|
||||||
|
|
||||||
public static final String MEDIA_TYPE = "mediaType";
|
public static final String MEDIA_TYPE = "mediaType";
|
||||||
|
@ -80,15 +74,15 @@ public class MediaFileListFragment extends Fragment {
|
||||||
String mediaType = Files.Media.MUSIC;
|
String mediaType = Files.Media.MUSIC;
|
||||||
String parentDirectory = null;
|
String parentDirectory = null;
|
||||||
int playlistId = PlaylistType.MUSIC_PLAYLISTID; // this is the ID of the music player
|
int playlistId = PlaylistType.MUSIC_PLAYLISTID; // this is the ID of the music player
|
||||||
private MediaFileListAdapter adapter = null;
|
// private MediaFileListAdapter adapter = null;
|
||||||
boolean browseRootAlready = false;
|
boolean browseRootAlready = false;
|
||||||
|
|
||||||
ArrayList<FileLocation> rootFileLocation = new ArrayList<FileLocation>();
|
ArrayList<FileLocation> rootFileLocation = new ArrayList<FileLocation>();
|
||||||
Queue<FileLocation> mediaQueueFileLocation = new LinkedList<>();
|
Queue<FileLocation> mediaQueueFileLocation = new LinkedList<>();
|
||||||
|
|
||||||
@InjectView(R.id.list) GridView folderGridView;
|
// @InjectView(R.id.list) GridView folderGridView;
|
||||||
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
|
// @InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
|
||||||
@InjectView(android.R.id.empty) TextView emptyView;
|
// @InjectView(android.R.id.empty) TextView emptyView;
|
||||||
|
|
||||||
public static MediaFileListFragment newInstance(final String media) {
|
public static MediaFileListFragment newInstance(final String media) {
|
||||||
MediaFileListFragment fragment = new MediaFileListFragment();
|
MediaFileListFragment fragment = new MediaFileListFragment();
|
||||||
|
@ -103,7 +97,7 @@ public class MediaFileListFragment extends Fragment {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString(MEDIA_TYPE, mediaType);
|
outState.putString(MEDIA_TYPE, mediaType);
|
||||||
try {
|
try {
|
||||||
outState.putParcelableArrayList(PATH_CONTENTS, (ArrayList<FileLocation>)adapter.getFileItemList());
|
outState.putParcelableArrayList(PATH_CONTENTS, (ArrayList<FileLocation>) ((MediaFileListAdapter) getAdapter()).getFileItemList());
|
||||||
} catch (NullPointerException npe) {
|
} catch (NullPointerException npe) {
|
||||||
// adapter is null probably nothing was save in bundle because the directory is empty
|
// adapter is null probably nothing was save in bundle because the directory is empty
|
||||||
// ignore this so that the empty message would display later on
|
// ignore this so that the empty message would display later on
|
||||||
|
@ -112,8 +106,24 @@ public class MediaFileListFragment extends Fragment {
|
||||||
outState.putBoolean(ROOT_VISITED, browseRootAlready);
|
outState.putBoolean(ROOT_VISITED, browseRootAlready);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AdapterView.OnItemClickListener createOnItemClickListener() {
|
||||||
|
return new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
handleFileSelect(((MediaFileListAdapter) getAdapter()).getItem(position));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BaseAdapter createAdapter() {
|
||||||
|
return new MediaFileListAdapter(getActivity(), R.layout.grid_item_file);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null) {
|
if (args != null) {
|
||||||
mediaType = args.getString(MEDIA_TYPE, Files.Media.MUSIC);
|
mediaType = args.getString(MEDIA_TYPE, Files.Media.MUSIC);
|
||||||
|
@ -123,11 +133,8 @@ public class MediaFileListFragment extends Fragment {
|
||||||
playlistId = PlaylistType.PICTURE_PLAYLISTID;
|
playlistId = PlaylistType.PICTURE_PLAYLISTID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
|
|
||||||
ButterKnife.inject(this, root);
|
|
||||||
|
|
||||||
hostManager = HostManager.getInstance(getActivity());
|
hostManager = HostManager.getInstance(getActivity());
|
||||||
swipeRefreshLayout.setEnabled(false);
|
|
||||||
|
|
||||||
emptyView.setOnClickListener(new View.OnClickListener() {
|
emptyView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -135,18 +142,7 @@ public class MediaFileListFragment extends Fragment {
|
||||||
browseSources();
|
browseSources();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
folderGridView.setEmptyView(emptyView);
|
|
||||||
folderGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
handleFileSelect(adapter.getItem(position));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (adapter == null) {
|
|
||||||
adapter = new MediaFileListAdapter(getActivity(), R.layout.grid_item_file);
|
|
||||||
}
|
|
||||||
folderGridView.setAdapter(adapter);
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
mediaType = savedInstanceState.getString(MEDIA_TYPE);
|
mediaType = savedInstanceState.getString(MEDIA_TYPE);
|
||||||
//currentPath = savedInstanceState.getString(CURRENT_PATH);
|
//currentPath = savedInstanceState.getString(CURRENT_PATH);
|
||||||
|
@ -158,7 +154,7 @@ public class MediaFileListFragment extends Fragment {
|
||||||
ArrayList<FileLocation> list = savedInstanceState.getParcelableArrayList(PATH_CONTENTS);
|
ArrayList<FileLocation> list = savedInstanceState.getParcelableArrayList(PATH_CONTENTS);
|
||||||
rootFileLocation = savedInstanceState.getParcelableArrayList(ROOT_PATH_CONTENTS);
|
rootFileLocation = savedInstanceState.getParcelableArrayList(ROOT_PATH_CONTENTS);
|
||||||
browseRootAlready = savedInstanceState.getBoolean(ROOT_VISITED);
|
browseRootAlready = savedInstanceState.getBoolean(ROOT_VISITED);
|
||||||
adapter.setFilelistItems(list);
|
((MediaFileListAdapter) getAdapter()).setFilelistItems(list);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
browseSources();
|
browseSources();
|
||||||
|
@ -187,13 +183,13 @@ public class MediaFileListFragment extends Fragment {
|
||||||
|
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
// Emulate a click on ..
|
// Emulate a click on ..
|
||||||
handleFileSelect(adapter.getItem(0));
|
handleFileSelect(((MediaFileListAdapter) getAdapter()).getItem(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean atRootDirectory() {
|
public boolean atRootDirectory() {
|
||||||
if (adapter.getCount() == 0)
|
if (getAdapter().getCount() == 0)
|
||||||
return true;
|
return true;
|
||||||
FileLocation fl = adapter.getItem(0);
|
FileLocation fl = ((MediaFileListAdapter) getAdapter()).getItem(0);
|
||||||
if (fl == null)
|
if (fl == null)
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
|
@ -224,7 +220,7 @@ public class MediaFileListFragment extends Fragment {
|
||||||
|
|
||||||
browseRootAlready = true;
|
browseRootAlready = true;
|
||||||
emptyView.setText(getString(R.string.source_empty));
|
emptyView.setText(getString(R.string.source_empty));
|
||||||
adapter.setFilelistItems(rootFileLocation);
|
((MediaFileListAdapter) getAdapter()).setFilelistItems(rootFileLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -320,7 +316,7 @@ public class MediaFileListFragment extends Fragment {
|
||||||
for (ListType.ItemFile i : result) {
|
for (ListType.ItemFile i : result) {
|
||||||
flList.add(FileLocation.newInstanceFromItemFile(getActivity(), i));
|
flList.add(FileLocation.newInstanceFromItemFile(getActivity(), i));
|
||||||
}
|
}
|
||||||
adapter.setFilelistItems(flList);
|
((MediaFileListAdapter) getAdapter()).setFilelistItems(flList);
|
||||||
browseRootAlready = false;
|
browseRootAlready = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ import org.xbmc.kore.utils.Utils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the movie list
|
* Fragment that presents the movie list
|
||||||
*/
|
*/
|
||||||
public class MovieListFragment extends AbstractListFragment {
|
public class MovieListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class);
|
||||||
|
|
||||||
public interface OnMovieSelectedListener {
|
public interface OnMovieSelectedListener {
|
||||||
|
|
|
@ -50,7 +50,7 @@ import org.xbmc.kore.utils.Utils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the artists list
|
* Fragment that presents the artists list
|
||||||
*/
|
*/
|
||||||
public class MusicVideoListFragment extends AbstractListFragment {
|
public class MusicVideoListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(MusicVideoListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(MusicVideoListFragment.class);
|
||||||
|
|
||||||
public interface OnMusicVideoSelectedListener {
|
public interface OnMusicVideoSelectedListener {
|
||||||
|
|
|
@ -53,7 +53,7 @@ import org.xbmc.kore.utils.Utils;
|
||||||
/**
|
/**
|
||||||
* Fragment that presents the tv show list
|
* Fragment that presents the tv show list
|
||||||
*/
|
*/
|
||||||
public class TVShowListFragment extends AbstractListFragment {
|
public class TVShowListFragment extends AbstractCursorListFragment {
|
||||||
private static final String TAG = LogUtils.makeLogTag(TVShowListFragment.class);
|
private static final String TAG = LogUtils.makeLogTag(TVShowListFragment.class);
|
||||||
|
|
||||||
public interface OnTVShowSelectedListener {
|
public interface OnTVShowSelectedListener {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2015 Synced Synapse. All rights reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:id="@+id/action_refresh"
|
||||||
|
android:title="@string/refresh"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
</menu>
|
|
@ -20,7 +20,4 @@
|
||||||
<item android:id="@+id/action_multi_single_columns"
|
<item android:id="@+id/action_multi_single_columns"
|
||||||
android:title="@string/single_column"
|
android:title="@string/single_column"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
<item android:id="@+id/action_refresh"
|
|
||||||
android:title="@string/refresh"
|
|
||||||
app:showAsAction="never"/>
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|
Loading…
Reference in New Issue