commit d91918002d47c4c286304a10be77439a02c41971 Author: aNNiMON Date: Wed Feb 14 00:02:51 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c4de58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/WebcamViewer.iml b/WebcamViewer.iml new file mode 100644 index 0000000..f1120b1 --- /dev/null +++ b/WebcamViewer.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..59dafff --- /dev/null +++ b/app/app.iml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..95162f2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,51 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'me.tatarka:gradle-retrolambda:3.2.4' + } +} +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' + +repositories { + jcenter() +} + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.annimon.webcamviewer" + minSdkVersion 14 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:support-v4:23.1.1' + compile 'com.android.support:recyclerview-v7:23.1.1' + compile 'com.android.support:design:23.1.1' + compile 'com.bignerdranch.android:expandablerecyclerview:2.0.4' + compile 'com.annimon:stream:1.0.6' + compile 'com.squareup.okhttp3:okhttp:3.1.2' + compile 'me.zhanghai.android.materialprogressbar:library:1.1.4' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..726a7fc --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,18 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:/Android/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-dontwarn java.lang.invoke.* \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..aa49957 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/annimon/webcamviewer/ExceptionHandler.java b/app/src/main/java/com/annimon/webcamviewer/ExceptionHandler.java new file mode 100644 index 0000000..2614aba --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/ExceptionHandler.java @@ -0,0 +1,39 @@ +package com.annimon.webcamviewer; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.Log; + +/** + * Handling exceptions. + * + * @author aNNiMON + */ +public class ExceptionHandler { + + private static final boolean DEBUG = true; + private static final String TAG = "webcamviewer"; + + public static void log(String message) { + if (DEBUG) { + Log.d(TAG, message); + } + } + + public static void log(String message, Throwable ex) { + if (DEBUG) { + Log.e(TAG, message, ex); + } + } + + public static void log(Exception ex) { + if (DEBUG) { + Log.e(TAG, getErrorMessage(ex)); + } + } + + private static String getErrorMessage(Exception ex) { + return ex.getMessage(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/annimon/webcamviewer/MainActivity.java b/app/src/main/java/com/annimon/webcamviewer/MainActivity.java new file mode 100644 index 0000000..a34ccfb --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/MainActivity.java @@ -0,0 +1,77 @@ +package com.annimon.webcamviewer; + +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +public class MainActivity extends AppCompatActivity implements NavigationDrawerCallbacks { + + private Toolbar mToolbar; + private View mRootView; + private NavigationDrawerFragment mNavigationDrawerFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar); + setSupportActionBar(mToolbar); + + mRootView = findViewById(R.id.root); + + mNavigationDrawerFragment = (NavigationDrawerFragment) + getSupportFragmentManager().findFragmentById(R.id.fragment_drawer); + new WebcamListLoader(this, mNavigationDrawerFragment).execute(WebcamListLoader.NORMAL); + mNavigationDrawerFragment.setup(R.id.fragment_drawer, (DrawerLayout) findViewById(R.id.drawer), mToolbar); + mNavigationDrawerFragment.configureNavHeader(); + } + + @Override + public void onNavigationDrawerItemSelected(int position, Webcam webcam) { + Snackbar.make(mRootView, "Menu item selected -> " + position + "\n" + webcam, Snackbar.LENGTH_SHORT).show(); + + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.container, WebcamViewerFragment.newInstance(webcam)) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .commit(); + mNavigationDrawerFragment.closeDrawer(); + } + + @Override + public void onBackPressed() { + if (mNavigationDrawerFragment.isDrawerOpen()) { + mNavigationDrawerFragment.closeDrawer(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (!mNavigationDrawerFragment.isDrawerOpen()) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + return true; + + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerCallbacks.java b/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerCallbacks.java new file mode 100644 index 0000000..c9d099b --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerCallbacks.java @@ -0,0 +1,5 @@ +package com.annimon.webcamviewer; + +public interface NavigationDrawerCallbacks { + void onNavigationDrawerItemSelected(int position, Webcam webcam); +} diff --git a/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerFragment.java b/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerFragment.java new file mode 100644 index 0000000..ff23030 --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/NavigationDrawerFragment.java @@ -0,0 +1,231 @@ +package com.annimon.webcamviewer; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.app.Fragment; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fragment used for managing interactions for and presentation of a navigation drawer. + * See the + * design guidelines for a complete explanation of the behaviors implemented here. + */ +public class NavigationDrawerFragment extends Fragment implements NavigationDrawerCallbacks { + + /** + * Remember the position of the selected item. + */ + private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position"; + + /** + * Per the design guidelines, you should show the drawer on launch until the user manually + * expands it. This shared preference tracks this. + */ + private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; + + /** + * A pointer to the current callbacks instance (the Activity). + */ + private NavigationDrawerCallbacks mCallbacks; + + /** + * Helper component that ties the action bar to the navigation drawer. + */ + private ActionBarDrawerToggle mActionBarDrawerToggle; + + private DrawerLayout mDrawerLayout; + private RecyclerView mDrawerList; + private View mFragmentContainerView; + + private int mCurrentSelectedPosition = 0; + private boolean mFromSavedInstanceState; + private boolean mUserLearnedDrawer; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Read in the flag indicating whether or not the user has demonstrated awareness of the + // drawer. See PREF_USER_LEARNED_DRAWER for details. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); + mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); + + if (savedInstanceState != null) { + mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION); + mFromSavedInstanceState = true; + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_navigation_drawer, container, false); + mDrawerList = (RecyclerView) view.findViewById(R.id.drawerList); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + mDrawerList.setLayoutManager(layoutManager); + mDrawerList.setHasFixedSize(true); + return view; + } + + public boolean isDrawerOpen() { + return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView); + } + + public ActionBarDrawerToggle getActionBarDrawerToggle() { + return mActionBarDrawerToggle; + } + + public DrawerLayout getDrawerLayout() { + return mDrawerLayout; + } + + @Override + public void onNavigationDrawerItemSelected(int position, Webcam webcam) { + selectItem(position, webcam); + } + + + public void updateAdapter(WebcamAdapter adapter) { + adapter.setNavigationDrawerCallbacks(this); + mDrawerList.setAdapter(adapter); +// selectItem(mCurrentSelectedPosition); + } + + /** + * Users of this fragment must call this method to set up the navigation drawer interactions. + * + * @param fragmentId The android:id of this fragment in its activity's layout. + * @param drawerLayout The DrawerLayout containing this fragment's UI. + * @param toolbar The Toolbar of the activity. + */ + public void setup(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) { + mFragmentContainerView = getActivity().findViewById(fragmentId); + mDrawerLayout = drawerLayout; + + mDrawerLayout.setStatusBarBackgroundColor(ContextCompat.getColor(getActivity(), R.color.primary)); + + mActionBarDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) { + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + if (!isAdded()) return; + + getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + if (!isAdded()) return; + if (!mUserLearnedDrawer) { + mUserLearnedDrawer = true; + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(getActivity()); + sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); + } + getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + }; + + // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer, + // per the navigation drawer design guidelines. + if (!mUserLearnedDrawer && !mFromSavedInstanceState) { + mDrawerLayout.openDrawer(mFragmentContainerView); + } + + // Defer code dependent on restoration of previous instance state. + mDrawerLayout.post(mActionBarDrawerToggle::syncState); + + mDrawerLayout.setDrawerListener(mActionBarDrawerToggle); + //selectItem(mCurrentSelectedPosition); + } + + private void selectItem(int position, Webcam webcam) { + mCurrentSelectedPosition = position; + if (mDrawerLayout != null) { + mDrawerLayout.closeDrawer(mFragmentContainerView); + } + if (mCallbacks != null) { + mCallbacks.onNavigationDrawerItemSelected(position, webcam); + } + ((WebcamAdapter) mDrawerList.getAdapter()).selectPosition(position); + } + + public void openDrawer() { + mDrawerLayout.openDrawer(mFragmentContainerView); + } + + public void closeDrawer() { + mDrawerLayout.closeDrawer(mFragmentContainerView); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + mCallbacks = (NavigationDrawerCallbacks) context; + } catch (ClassCastException e) { + throw new ClassCastException("Activity must implement NavigationDrawerCallbacks."); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Forward the new configuration the drawer toggle component. + mActionBarDrawerToggle.onConfigurationChanged(newConfig); + } + + public void configureNavHeader() { + MaterialProgressBar progressBar = (MaterialProgressBar) mFragmentContainerView.findViewById(R.id.progressBar); + mFragmentContainerView.findViewById(R.id.updateWebcams).setOnClickListener(v -> { + new WebcamListLoader(getActivity(), this, progressBar).execute(WebcamListLoader.UPDATE); + }); + } + + public View getGoogleDrawer() { + return mFragmentContainerView.findViewById(R.id.googleDrawer); + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/NavigationItem.java b/app/src/main/java/com/annimon/webcamviewer/NavigationItem.java new file mode 100644 index 0000000..dc5be5c --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/NavigationItem.java @@ -0,0 +1,33 @@ +package com.annimon.webcamviewer; + + +import android.graphics.drawable.Drawable; + +/** + * Created by poliveira on 24/10/2014. + */ +public class NavigationItem { + private String mText; + private Drawable mDrawable; + + public NavigationItem(String text, Drawable drawable) { + mText = text; + mDrawable = drawable; + } + + public String getText() { + return mText; + } + + public void setText(String text) { + mText = text; + } + + public Drawable getDrawable() { + return mDrawable; + } + + public void setDrawable(Drawable drawable) { + mDrawable = drawable; + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/Webcam.java b/app/src/main/java/com/annimon/webcamviewer/Webcam.java new file mode 100644 index 0000000..1814503 --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/Webcam.java @@ -0,0 +1,34 @@ +package com.annimon.webcamviewer; + +public class Webcam { + + private final String name, url; + private final int updateInterval; + + public Webcam(String name, String url) { + this(name, url, 1000); + } + + public Webcam(String name, String url, int updateInterval) { + this.name = name; + this.url = url; + this.updateInterval = updateInterval; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public int getUpdateInterval() { + return updateInterval; + } + + @Override + public String toString() { + return String.format("%s: %s", name, url); + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/WebcamAdapter.java b/app/src/main/java/com/annimon/webcamviewer/WebcamAdapter.java new file mode 100644 index 0000000..346c9e4 --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/WebcamAdapter.java @@ -0,0 +1,101 @@ +package com.annimon.webcamviewer; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.bignerdranch.expandablerecyclerview.Adapter.ExpandableRecyclerAdapter; +import com.bignerdranch.expandablerecyclerview.Model.ParentListItem; +import com.bignerdranch.expandablerecyclerview.ViewHolder.ChildViewHolder; +import com.bignerdranch.expandablerecyclerview.ViewHolder.ParentViewHolder; +import java.util.List; + +public class WebcamAdapter extends ExpandableRecyclerAdapter { + + private LayoutInflater mInflater; + private NavigationDrawerCallbacks mNavigationDrawerCallbacks; + private View mSelectedView; + private int mSelectedPosition; + + public WebcamAdapter(Context context, List data) { + super(data); + this.mInflater = LayoutInflater.from(context); + } + + public NavigationDrawerCallbacks getNavigationDrawerCallbacks() { + return mNavigationDrawerCallbacks; + } + + public void setNavigationDrawerCallbacks(NavigationDrawerCallbacks navigationDrawerCallbacks) { + mNavigationDrawerCallbacks = navigationDrawerCallbacks; + } + + public void selectPosition(int position) { + mSelectedPosition = position; + notifyItemChanged(position); + } + + @Override + public WebcamGroupViewHolder onCreateParentViewHolder(ViewGroup parent) { + final View view = mInflater.inflate(R.layout.drawer_group, parent, false); + return new WebcamGroupViewHolder(view); + } + + @Override + public WebcamViewHolder onCreateChildViewHolder(ViewGroup parent) { + final View view = mInflater.inflate(R.layout.drawer_row, parent, false); + final WebcamViewHolder viewHolder = new WebcamViewHolder(view); + + viewHolder.mNameTextView.setClickable(true); + viewHolder.mNameTextView.setOnClickListener(v -> { + if (mSelectedView != null) { + mSelectedView.setSelected(false); + } + final int adapterPosition = viewHolder.getAdapterPosition(); + mSelectedPosition = adapterPosition; + v.setSelected(true); + mSelectedView = v; + if (mNavigationDrawerCallbacks != null) { + mNavigationDrawerCallbacks.onNavigationDrawerItemSelected( + adapterPosition, + (Webcam) getListItem(adapterPosition) + ); + } + }); +// viewHolder.mNameTextView.setBackgroundResource(R.drawable.row_selector); + return viewHolder; + } + + @Override + public void onBindParentViewHolder(WebcamGroupViewHolder webcamGroupViewHolder, int i, ParentListItem parentListItem) { + final WebcamGroup webcamGroup = (WebcamGroup) parentListItem; + webcamGroupViewHolder.mGroupNameTextView.setText(webcamGroup.getName()); + } + + @Override + public void onBindChildViewHolder(WebcamViewHolder webcamViewHolder, int i, Object childListItem) { + final Webcam webcam = (Webcam) childListItem; + webcamViewHolder.mNameTextView.setText(webcam.getName()); + } + + + public static class WebcamGroupViewHolder extends ParentViewHolder { + public TextView mGroupNameTextView; + + public WebcamGroupViewHolder(View itemView) { + super(itemView); + mGroupNameTextView = (TextView) itemView.findViewById(R.id.item_name); + } + } + + public static class WebcamViewHolder extends ChildViewHolder { + public TextView mNameTextView; + + public WebcamViewHolder(View itemView) { + super(itemView); + mNameTextView = (TextView) itemView.findViewById(R.id.item_name); + } + } + +} diff --git a/app/src/main/java/com/annimon/webcamviewer/WebcamGroup.java b/app/src/main/java/com/annimon/webcamviewer/WebcamGroup.java new file mode 100644 index 0000000..a5f0f2d --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/WebcamGroup.java @@ -0,0 +1,49 @@ +package com.annimon.webcamviewer; + +import com.annimon.stream.Collectors; +import com.annimon.stream.Stream; +import com.bignerdranch.expandablerecyclerview.Model.ParentListItem; + +import java.util.List; + +public class WebcamGroup implements ParentListItem { + + private final String name; + private final boolean enabled; + private final List webcams; + private List mChildrenList; + + public WebcamGroup(String name, boolean enabled, List webcams) { + this.name = name; + this.enabled = enabled; + this.webcams = webcams; + mChildrenList = Stream.of(webcams).collect(Collectors.toList()); + } + + public String getName() { + return name; + } + + public boolean isEnabled() { + return enabled; + } + + public List getWebcams() { + return webcams; + } + + @Override + public String toString() { + return String.format("%s, %s", name, webcams); + } + + @Override + public List getChildItemList() { + return webcams; + } + + @Override + public boolean isInitiallyExpanded() { + return false; + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/WebcamListLoader.java b/app/src/main/java/com/annimon/webcamviewer/WebcamListLoader.java new file mode 100644 index 0000000..7893ca0 --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/WebcamListLoader.java @@ -0,0 +1,130 @@ +package com.annimon.webcamviewer; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import com.annimon.stream.Exceptional; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class WebcamListLoader extends AsyncTask> { + + private static final String WEBCAM_LIST_URL = "http://projects.annimon.com/projects/webcam-urls.json"; + private static final String WEBCAM_LIST_FILENAME = "webcam-urls.json"; + + public static final int NORMAL = 0, UPDATE = 1; + + private final Context mContext; + private final NavigationDrawerFragment mNavigationDrawerFragment; + private final ProgressBar mProgressBar; + + public WebcamListLoader(Context context, NavigationDrawerFragment fragment) { + this(context, fragment, null); + } + + public WebcamListLoader(Context context, NavigationDrawerFragment fragment, ProgressBar progressbar) { + this.mContext = context; + this.mNavigationDrawerFragment = fragment; + this.mProgressBar = progressbar; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (mProgressBar != null) { + mProgressBar.setIndeterminate(true); + mProgressBar.setVisibility(View.VISIBLE); + } + } + + @Override + protected List doInBackground(Integer... params) { + return Exceptional.of(() -> { + final File file = mContext.getFileStreamPath(WEBCAM_LIST_FILENAME); + ExceptionHandler.log(WEBCAM_LIST_FILENAME + " exists: " + file.exists()); + final String jsonRaw; + if (params[0] == NORMAL && file.exists()) { + // Load from internal storage + jsonRaw = loadFile(file); + } else { + // Retrieve from web + jsonRaw = loadUrl(WEBCAM_LIST_URL); + } + ExceptionHandler.log("JSONRAW length: " + jsonRaw.length()); + + final List webcamGroups = new ArrayList<>(); + final JSONArray groups = new JSONObject(jsonRaw).getJSONArray("groups"); + final int length = groups.length(); + for (int i = 0; i < length; i++) { + final JSONObject group = groups.getJSONObject(i); + final String groupName = group.getString("name"); + final boolean isGroupEnabled = group.optBoolean("enabled", true); + if (!isGroupEnabled) continue; + + final JSONArray urls = group.getJSONArray("urls"); + final int urlsLength = urls.length(); + final List webcams = new ArrayList<>(urlsLength); + for (int j = 0; j < urlsLength; j++) { + final JSONObject url = urls.getJSONObject(j); + webcams.add(new Webcam(url.getString("name"), url.getString("url"))); + } + + webcamGroups.add(new WebcamGroup(groupName, isGroupEnabled, webcams)); + } + + ExceptionHandler.log("Json parsed successfully"); + ExceptionHandler.log("Got " + webcamGroups.size() + " groups"); + + try (final OutputStream os = new FileOutputStream(file); + final OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8")) { + osw.write(jsonRaw); + osw.flush(); + } + + ExceptionHandler.log("Successfully wrote to internal file"); + + return webcamGroups; + }).ifException(e -> ExceptionHandler.log("WebcamListLoader: " + e.getMessage(), e)) + .getOrElse(new ArrayList<>()); + } + + private String loadUrl(String url) throws IOException { + final OkHttpClient client = new OkHttpClient(); + final Request request = new Request.Builder() + .url(url) + .build(); + final Response response = client.newCall(request).execute(); + return response.body().string(); + } + + private String loadFile(File file) throws IOException { + try (final InputStream is = new FileInputStream(file); + final InputStreamReader isr = new InputStreamReader(is, "UTF-8"); + final BufferedReader reader = new BufferedReader(isr)) { + final StringBuilder sb = new StringBuilder(); + String line; + while ( (line = reader.readLine()) != null ) { + sb.append(line); + } + return sb.toString(); + } + } + + @Override + protected void onPostExecute(List webcams) { + super.onPostExecute(webcams); + if (mProgressBar != null) { + mProgressBar.setVisibility(View.INVISIBLE); + mProgressBar.setIndeterminate(false); + } + mNavigationDrawerFragment.updateAdapter(new WebcamAdapter(mContext, webcams)); + } +} diff --git a/app/src/main/java/com/annimon/webcamviewer/WebcamViewerFragment.java b/app/src/main/java/com/annimon/webcamviewer/WebcamViewerFragment.java new file mode 100644 index 0000000..b80cf05 --- /dev/null +++ b/app/src/main/java/com/annimon/webcamviewer/WebcamViewerFragment.java @@ -0,0 +1,118 @@ +package com.annimon.webcamviewer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.blundell.tut.LoaderImageView; + +public class WebcamViewerFragment extends Fragment { + + private static final String ARG_URL = "url"; + + private String imageUrl; + private LoaderImageView contentImage; + + private boolean autoUpdate; + private Timer timer; + + public static WebcamViewerFragment newInstance(Webcam webcam) { + final WebcamViewerFragment instance = new WebcamViewerFragment(); + final Bundle args = new Bundle(); + args.putString(ARG_URL, webcam.getUrl()); + instance.setArguments(args); + return instance; + } + + public WebcamViewerFragment() { + autoUpdate = false; + timer = new Timer(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + setHasOptionsMenu(true); + contentImage = new LoaderImageView(getActivity(), (String) null); + imageUrl = getArguments().getString(ARG_URL); + update(); + return contentImage; + } + + /*@Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_auto_update: + autoUpdate = !autoUpdate; + autoUpdate(); + break; + case R.id.menu_update: + update(); + break; + case R.id.menu_save: + try { + save("image" + File.separator + "Донецк"); + Toast.makeText(getActivity(), "Сохранено", Toast.LENGTH_SHORT).show(); + } catch (IOException ex) { + ex.printStackTrace(); + } + break; + case R.id.menu_videos: + final String camimg = "/camimg/cam"; + final int start = imageUrl.indexOf(camimg) + camimg.length(); + String camId = imageUrl.substring(start, imageUrl.indexOf("-5.jpg")); + Intent intent = new Intent(getActivity(), VideoGetActivity.class); + intent.putExtra("camera_id", Integer.parseInt(camId)); + startActivity(intent); + break; + } + return true; + }*/ + + public void update() { + contentImage.post(() -> contentImage.setImageDrawable(imageUrl)); + } + + private void autoUpdate() { + TimerTask task = new TimerTask() { + + @Override + public void run() { + update(); + } + }; + if (autoUpdate) { + timer.schedule(task, 500, 100); + contentImage.setQuickMode(true); + } else { + timer.cancel(); + timer = new Timer(); + contentImage.setQuickMode(false); + } + } + + public void save(String path) throws IOException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"); + String fullPath = Environment.getExternalStorageDirectory() + + File.separator + path + + File.separator + sdf.format(new Date()) + ".jpg"; + + try (OutputStream out = new FileOutputStream(new File(fullPath))) { + Bitmap bitmap = ((BitmapDrawable) contentImage.getDrawable()).getBitmap(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out); + out.flush(); + } + } +} diff --git a/app/src/main/java/com/blundell/tut/LoaderImageView.java b/app/src/main/java/com/blundell/tut/LoaderImageView.java new file mode 100644 index 0000000..d8cd97a --- /dev/null +++ b/app/src/main/java/com/blundell/tut/LoaderImageView.java @@ -0,0 +1,214 @@ +package com.blundell.tut; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.annimon.webcamviewer.R; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; + +/** + * Free for anyone to use, just say thanks and share :-) + * @author Blundell + */ +public final class LoaderImageView extends FrameLayout { + + private static final int COMPLETE = 0; + private static final int FAILED = 1; + private static final int PROGRESS = 2; + + private Drawable mDrawable; + private MaterialProgressBar mProgressBar; + private ImageView mImage; + private boolean mQuickMode; + + /** + * This is used when creating the view in XML + * To have an image load in XML use the tag 'image="http://developer.android.com/images/dialog_buttons.png"' + * Replacing the url with your desired image + * Once you have instantiated the XML view you can call + * setImageDrawable(url) to change the image + * @param context + * @param attrSet + */ + public LoaderImageView(final Context context, final AttributeSet attrSet) { + super(context, attrSet); + final String url = attrSet.getAttributeValue(null, "image"); + instantiate(url); + } + + /** + * This is used when creating the view programmatically + * Once you have instantiated the view you can call + * setImageDrawable(url) to change the image + * @param context the Activity context + * @param imageUrl the Image URL you wish to load + */ + public LoaderImageView(final Context context, final String imageUrl) { + super(context); + instantiate(imageUrl); + } + + /** + * This is used when creating the view programmatically + * Once you have instantiated the view you can call + * setImageDrawable(url) to change the image + */ + public LoaderImageView(final Context context) { + super(context); + instantiate(null); + } + + /** + * First time loading of the LoaderImageView + * Sets up the LayoutParams of the view, you can change these to + * get the required effects you want + */ + private void instantiate(final String imageUrl) { + mQuickMode = false; + + mImage = new ImageView(getContext()); + mImage.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + mProgressBar = new MaterialProgressBar(getContext()); + mProgressBar.setLayoutParams(new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, + Gravity.CENTER)); + mProgressBar.setProgress(0); + mProgressBar.setVisibility(GONE); + + addView(mImage); + addView(mProgressBar); + + if (imageUrl != null) { + setImageDrawable(imageUrl); + } + } + + /** + * Set's the view's drawable, this uses the internet to retrieve the image + * don't forget to add the correct permissions to your manifest + * @param imageUrl the url of the image you wish to load + */ + public void setImageDrawable(final String imageUrl) { +// mDrawable = null; + if (!mQuickMode) { + mProgressBar.setVisibility(VISIBLE); + mProgressBar.setProgress(0); + mProgressBar.setIndeterminate(true); + } + new Thread() { + @Override + public void run() { + try { + mDrawable = getDrawableFromUrl(imageUrl); + imageLoadedHandler.sendEmptyMessage(COMPLETE); + } catch (MalformedURLException e) { + imageLoadedHandler.sendEmptyMessage(FAILED); + } catch (IOException e) { + imageLoadedHandler.sendEmptyMessage(FAILED); + } + }; + }.start(); + } + + public Drawable getDrawable() { + return mImage.getDrawable(); + } + + public boolean isQuickMode() { + return mQuickMode; + } + + public void setQuickMode(boolean quickMode) { + this.mQuickMode = quickMode; + } + + /** + * Callback that is received once the image has been downloaded + */ + private final Handler imageLoadedHandler = new Handler(new Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case COMPLETE: + mImage.setImageDrawable(mDrawable); + mProgressBar.setVisibility(GONE); + break; + case PROGRESS: + mProgressBar.setIndeterminate(false); + mProgressBar.setProgress(msg.arg1); + break; + case FAILED: + default: + // Could change image here to a 'failed' image + // otherwise will just keep on spinning + mImage.setImageResource(R.drawable.no_image); + mProgressBar.setIndeterminate(false); + mProgressBar.setVisibility(GONE); + break; + } + return true; + } + }); + + /** + * Pass in an image url to get a drawable object + * @return a drawable object + * @throws IOException + * @throws MalformedURLException + */ + private Drawable getDrawableFromUrl(final String imageUrl) throws IOException, MalformedURLException { + final URL url = new URL(imageUrl); + final URLConnection conection = url.openConnection(); + conection.connect(); + // this will be useful so that you can show a typical 0-100% progress bar + int lenghtOfFile = conection.getContentLength(); + if (lenghtOfFile <= 0 || mQuickMode) { + // try to load by system call + return Drawable.createFromStream((InputStream) url.getContent(), "name"); + } + + // download the file + final InputStream input = new BufferedInputStream(url.openStream(), 8192); + ByteArrayOutputStream output = new ByteArrayOutputStream(lenghtOfFile); + final byte[] buffer = new byte[4096]; + long total = 0; + int count; + while ((count = input.read(buffer)) != -1) { + total += count; + // publishing the progress.... + imageLoadedHandler.obtainMessage(PROGRESS, (int) ((total * 360) / lenghtOfFile), 0) + .sendToTarget(); + // writing data + output.write(buffer, 0, count); + } + // flushing output + output.flush(); + // closing streams + output.close(); + input.close(); + + byte[] data = output.toByteArray(); + output = null; + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + data = null; + return new BitmapDrawable(getContext().getResources(), bitmap); + } + +} diff --git a/app/src/main/res/drawable-hdpi/ic_menu_check.png b/app/src/main/res/drawable-hdpi/ic_menu_check.png new file mode 100644 index 0000000..8ca4009 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_check.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png new file mode 100644 index 0000000..4d4c517 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/no_image.png b/app/src/main/res/drawable-hdpi/no_image.png new file mode 100644 index 0000000..377cecf Binary files /dev/null and b/app/src/main/res/drawable-hdpi/no_image.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_check.png b/app/src/main/res/drawable-mdpi/ic_menu_check.png new file mode 100644 index 0000000..80e46c6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_check.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png new file mode 100644 index 0000000..f37c078 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_check.png b/app/src/main/res/drawable-xhdpi/ic_menu_check.png new file mode 100644 index 0000000..b71bc71 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_check.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png new file mode 100644 index 0000000..63f616d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu_check.png b/app/src/main/res/drawable-xxhdpi/ic_menu_check.png new file mode 100644 index 0000000..84aad60 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_menu_check.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png new file mode 100644 index 0000000..07a6372 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png new file mode 100644 index 0000000..c1ea1e2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png differ diff --git a/app/src/main/res/drawable/row_selector.xml b/app/src/main/res/drawable/row_selector.xml new file mode 100644 index 0000000..9d3138e --- /dev/null +++ b/app/src/main/res/drawable/row_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/wallpaper.png b/app/src/main/res/drawable/wallpaper.png new file mode 100644 index 0000000..ac23b4e Binary files /dev/null and b/app/src/main/res/drawable/wallpaper.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..beaf4ee --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/drawer_group.xml b/app/src/main/res/layout/drawer_group.xml new file mode 100644 index 0000000..c6f8933 --- /dev/null +++ b/app/src/main/res/layout/drawer_group.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/layout/drawer_row.xml b/app/src/main/res/layout/drawer_row.xml new file mode 100644 index 0000000..6a3b034 --- /dev/null +++ b/app/src/main/res/layout/drawer_row.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/layout/fragment_navigation_drawer.xml b/app/src/main/res/layout/fragment_navigation_drawer.xml new file mode 100644 index 0000000..872560a --- /dev/null +++ b/app/src/main/res/layout/fragment_navigation_drawer.xml @@ -0,0 +1,56 @@ + + + + +