/** * Android ownCloud News * * @author David Luhmer * @copyright 2013 David Luhmer david-dev@live.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package de.luhmer.owncloudnewsreader; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.util.Base64; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageView; import android.widget.TextView; import com.google.gson.stream.JsonReader; import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import butterknife.Bind; import butterknife.ButterKnife; import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter; import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm; import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; import de.luhmer.owncloudnewsreader.interfaces.ExpListTextClicked; import de.luhmer.owncloudnewsreader.model.FolderSubscribtionItem; import de.luhmer.owncloudnewsreader.model.UserInfo; import de.luhmer.owncloudnewsreader.reader.HttpJsonRequest; import de.luhmer.owncloudnewsreader.reader.owncloud.API; import de.luhmer.owncloudnewsreader.reader.owncloud.OwnCloudReaderMethods; import okhttp3.HttpUrl; import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView; /** * A list fragment representing a list of NewsReader. This fragment also * supports tablet devices by allowing list items to be given an 'activated' * state upon selection. This helps indicate which item is currently being * viewed in a {@link NewsReaderDetailFragment}. * <p> * Activities containing this fragment MUST implement the {@link Callbacks} * interface. */ public class NewsReaderListFragment extends Fragment implements OnCreateContextMenuListener { @SuppressWarnings("unused") protected static final String TAG = "NewsReaderListFragment"; public void ListViewNotifyDataSetChanged() { lvAdapter.NotifyDataSetChangedAsync(); } public void ReloadAdapter() { lvAdapter.ReloadAdapterAsync(); } /** * The fragment's current callback object, which is notified of list item * clicks. */ private Callbacks mCallbacks = null; public void setRefreshing(boolean isRefreshing) { if(isRefreshing) { //headerLogo.setImageResource(R.drawable.ic_launcher_background); headerLogo.setVisibility(View.INVISIBLE); headerLogoProgress.setVisibility(View.VISIBLE); } else { //headerLogo.setImageResource(R.drawable.ic_launcher); headerLogo.setVisibility(View.VISIBLE); headerLogoProgress.setVisibility(View.INVISIBLE); } } /** * A callback interface that all activities containing this fragment must * implement. This mechanism allows activities to be notified of item * selections. */ public interface Callbacks { /** * Callback for when an item has been selected. */ void onChildItemClicked(long idFeed, Long optional_folder_id); void onTopItemClicked(long idFeed, boolean isFolder, Long optional_folder_id); void onChildItemLongClicked(long idFeed, Long optional_folder_id); void onTopItemLongClicked(long idFeed, boolean isFolder, Long optional_folder_id); } private SubscriptionExpandableListAdapter lvAdapter; @Bind(R.id.expandableListView) protected ExpandableListView eListView; @Bind(R.id.urlTextView) protected TextView urlTextView; @Bind(R.id.userTextView) protected TextView userTextView; @Bind(R.id.header_view) protected ViewGroup headerView; @Bind(R.id.header_logo) protected ImageView headerLogo; @Bind(R.id.header_logo_progress) protected View headerLogoProgress; /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ public NewsReaderListFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_newsreader_list, container, false); if(!ThemeChooser.isDarkTheme(getActivity())) { view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.slider_listview_background_color_light_theme)); } ButterKnife.bind(this, view); if(!Constants.IsNextCloud(getContext())) { // Set ownCloud view headerView.setBackgroundResource(R.drawable.left_drawer_header_background); } lvAdapter = new SubscriptionExpandableListAdapter(getActivity(), new DatabaseConnectionOrm(getActivity()), eListView); lvAdapter.setHandlerListener(expListTextClickedListener); eListView.setGroupIndicator(null); eListView.setOnChildClickListener(onChildClickListener); eListView.setOnItemLongClickListener(onItemLongClickListener); eListView.setClickable(true); eListView.setLongClickable(true); eListView.setAdapter(lvAdapter); headerView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ((NewsReaderListActivity) getActivity()).startSync(); } }); lvAdapter.notifyDataSetChanged(); ReloadAdapter(); return view; } @Override public void onAttach(Context context) { super.onAttach(context); // Activities containing this fragment must implement its callbacks. if (!(context instanceof Callbacks)) { throw new IllegalStateException( "Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) context; } @Override public void onDetach() { super.onDetach(); mCallbacks = null; } ExpListTextClicked expListTextClickedListener = new ExpListTextClicked() { @Override public void onTextClicked(long idFeed, boolean isFolder, Long optional_folder_id) { mCallbacks.onTopItemClicked(idFeed, isFolder, optional_folder_id); } @Override public void onTextLongClicked(long idFeed, boolean isFolder, Long optional_folder_id) { mCallbacks.onTopItemLongClicked(idFeed, isFolder, optional_folder_id); } }; public OnChildClickListener onChildClickListener = new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { long idItem = lvAdapter.getChildId(groupPosition, childPosition); Long optional_id_folder = null; FolderSubscribtionItem groupItem = (FolderSubscribtionItem) lvAdapter.getGroup(groupPosition); if(groupItem != null) optional_id_folder = groupItem.id_database; mCallbacks.onChildItemClicked(idItem, optional_id_folder); return false; } }; AdapterView.OnItemLongClickListener onItemLongClickListener = new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { int childPosition = ExpandableListView.getPackedPositionChild(id); mCallbacks.onChildItemLongClicked(childPosition, null); } return true; } }; public ExpandableListView getListView() { return eListView; } protected void showTapLogoToSyncShowcaseView() { new MaterialShowcaseView.Builder(getActivity()) .setTarget(headerLogo) .setDismissText("GOT IT") .setContentText("Tap this logo to sync with server") .setDelay(300) // optional but starting animations immediately in onCreate can make them choppy .singleUse("LOGO_SYNC") // provide a unique ID used to ensure it is only shown once .show(); } public void startAsyncTaskGetUserInfo() { AsyncTaskHelper.StartAsyncTask(new AsyncTaskGetUserInfo()); } private class AsyncTaskGetUserInfo extends AsyncTask<Void, Void, UserInfo> { @Override protected UserInfo doInBackground(Void... voids) { HttpUrl oc_root_url = HttpJsonRequest.getInstance().getRootUrl(); try { String appVersion = OwnCloudReaderMethods.GetVersionNumber(oc_root_url); API api = API.GetRightApiForVersion(appVersion, HttpJsonRequest.getInstance().getRootUrl()); int[] version = API.ExtractVersionNumberFromString(appVersion); if(version[0] < 6 || version[0] == 6 && version[1] <= 4) //Supported since 6.0.5 return null; //API NOT SUPPORTED! // Update shared prefs SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); mPrefs.edit().putString(Constants.NEWS_WEB_VERSION_NUMBER_STRING, appVersion).apply(); UserInfo.Builder ui = new UserInfo.Builder(); InputStream inputStream = HttpJsonRequest.getInstance().PerformJsonRequest(api.getUserUrl()); JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8")); reader.beginObject(); String currentName; while(reader.hasNext() && (currentName = reader.nextName()) != null) { switch(currentName) { case "userId": ui.setUserId(reader.nextString()); break; case "displayName": ui.setDisplayName(reader.nextString()); break; case "avatar": com.google.gson.stream.JsonToken jt = reader.peek(); if(jt == com.google.gson.stream.JsonToken.NULL) { Log.v(TAG, "No image available"); reader.skipValue(); //No image available } else { reader.beginObject(); while (reader.hasNext()) { currentName = reader.nextName(); if (currentName.equals("data")) { String encodedImage = reader.nextString(); byte[] decodedString = Base64.decode(encodedImage, Base64.DEFAULT); ui.setAvatar(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length)); Log.v(TAG, encodedImage); } else { reader.skipValue(); } } } break; default: Log.v(TAG, "Skipping value for: " + currentName); reader.skipValue(); break; } } reader.close(); return ui.build(); } catch (Exception e) { if(e.getMessage().equals("Method Not Allowed")) { //Remove if old version is used SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); mPrefs.edit().remove("USER_INFO").commit(); } e.printStackTrace(); } return null; } @Override protected void onPostExecute(UserInfo userInfo) { if(userInfo != null) { try { SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); mPrefs.edit().putString("USER_INFO", NewsReaderListFragment.toString(userInfo)).commit(); bindUserInfoToUI(); } catch(Exception ex) { ex.printStackTrace(); } } super.onPostExecute(userInfo); } } protected void bindUserInfoToUI() { bindUserInfoToUI(false); } public void bindUserInfoToUI(boolean testMode) { SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); String mUsername = mPrefs.getString(SettingsActivity.EDT_USERNAME_STRING, null); String mOc_root_path = mPrefs.getString(SettingsActivity.EDT_OWNCLOUDROOTPATH_STRING, getString(R.string.app_name)); mOc_root_path = mOc_root_path.replace("http://", "").replace("https://", ""); //Remove http:// or https:// userTextView.setText(mUsername); urlTextView.setText(mOc_root_path); if(testMode) { //Hide real url in test mode urlTextView.setText("example.com/ownCloud"); } String uInfo = mPrefs.getString("USER_INFO", null); if(uInfo == null) return; try { UserInfo userInfo = (UserInfo) fromString(uInfo); if (userInfo.mDisplayName != null) userTextView.setText(userInfo.mDisplayName); if (userInfo.mAvatar != null) { Resources r = getResources(); float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, r.getDisplayMetrics()); RoundedBitmapDisplayer.RoundedDrawable roundedAvatar = new RoundedBitmapDisplayer.RoundedDrawable(userInfo.mAvatar, (int) px, 0); headerLogo.setImageDrawable(roundedAvatar); } } catch (Exception ex) { ex.printStackTrace(); } } /** Read the object from Base64 string. */ public static Object fromString(String s) throws IOException, ClassNotFoundException { byte [] data = Base64.decode(s, Base64.DEFAULT); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream( data ) ); Object o = ois.readObject(); ois.close(); return o; } /** Write the object to a Base64 string. */ public static String toString(Serializable o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( baos ); oos.writeObject(o); oos.close(); return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); } }