WIP camera list

This commit is contained in:
Daniele Verducci (Slimpenguin) 2022-01-21 08:56:04 +01:00
parent 505f8bf54b
commit 8dc6da8b98
15 changed files with 347 additions and 5 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -5,12 +5,14 @@
<map>
<entry key="app/src/main/res/layout/activity_main.xml" value="0.45" />
<entry key="app/src/main/res/layout/fragment_add_stream.xml" value="0.536" />
<entry key="app/src/main/res/layout/fragment_settings_item.xml" value="0.536" />
<entry key="app/src/main/res/layout/fragment_settings_item_list.xml" value="0.19010416666666666" />
<entry key="app/src/main/res/layout/fragment_surveillance.xml" value="0.16354166666666667" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -41,6 +41,8 @@ dependencies {
implementation 'androidx.navigation:navigation-ui:2.3.5'
//implementation 'org.videolan.android:libvlc-all:3.4.1'
implementation 'de.mrmaffen:libvlc-android:2.1.12@aar'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -14,8 +14,8 @@
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Ojo"
android:screenOrientation="landscape">
android:theme="@style/Theme.Ojo">
<!-- android:screenOrientation="landscape"> -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -0,0 +1,61 @@
package it.danieleverducci.ojo.ui;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import it.danieleverducci.ojo.R;
import it.danieleverducci.ojo.Settings;
import it.danieleverducci.ojo.entities.Camera;
import it.danieleverducci.ojo.ui.adapters.SettingsRecyclerViewAdapter;
import it.danieleverducci.ojo.utils.ItemMoveCallback;
/**
* A fragment representing a list of Items.
*/
public class SettingsFragment extends Fragment {
public SettingsFragment() {
}
public static SettingsFragment newInstance() {
SettingsFragment fragment = new SettingsFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_settings_item_list, container, false);
// Load cameras
Settings settings = Settings.fromDisk(getContext());
List<Camera> cams = settings.getCameras();
// Set the adapter
if (view instanceof RecyclerView) {
Context context = view.getContext();
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(context));
SettingsRecyclerViewAdapter adapter = new SettingsRecyclerViewAdapter(cams);
ItemTouchHelper.Callback callback =
new ItemMoveCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
adapter.setOnDragListener(touchHelper::startDrag);
recyclerView.setAdapter(adapter);
}
return view;
}
}

View File

@ -0,0 +1,106 @@
package it.danieleverducci.ojo.ui.adapters;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import it.danieleverducci.ojo.R;
import it.danieleverducci.ojo.databinding.FragmentSettingsItemBinding;
import it.danieleverducci.ojo.entities.Camera;
import it.danieleverducci.ojo.utils.ItemMoveCallback;
import java.util.Collections;
import java.util.List;
/**
* {@link RecyclerView.Adapter} that can display a {@link Camera}.
* TODO: Replace the implementation with code for your data type.
*/
public class SettingsRecyclerViewAdapter extends RecyclerView.Adapter<SettingsRecyclerViewAdapter.ViewHolder> implements ItemMoveCallback.ItemTouchHelperContract {
private final List<Camera> mValues;
private OnDragListener dragListener;
public SettingsRecyclerViewAdapter(List<Camera> items) {
mValues = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder vh = new ViewHolder(FragmentSettingsItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
vh.dragHandle.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
dragListener.onItemDrag(vh);
}
return false;
});
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
String cameraName = mValues.get(position).getName();
if (cameraName == null || cameraName.length() == 0)
cameraName = holder.name.getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (position+1)+"");
holder.name.setText(cameraName);
holder.url.setText(mValues.get(position).getRtspUrl());
}
@Override
public int getItemCount() {
return mValues.size();
}
// ============= Drag&Drop TouchHelper methods =============
@Override
public void onRowMoved(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mValues, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(mValues, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRowSelected(RecyclerView.ViewHolder myViewHolder) {
}
@Override
public void onRowClear(RecyclerView.ViewHolder myViewHolder) {
}
public void setOnDragListener(OnDragListener dragListener) {
this.dragListener = dragListener;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public TextView url;
public View dragHandle;
public ViewHolder(FragmentSettingsItemBinding binding) {
super(binding.getRoot());
this.name = binding.cameraName;
this.url = binding.cameraUrl;
this.dragHandle = binding.cameraDragHandle;
}
}
public interface OnDragListener {
void onItemDrag(ViewHolder vh);
}
}

View File

@ -0,0 +1,67 @@
package it.danieleverducci.ojo.utils;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
/**
* Implementation of recycleview drag&drop
*/
public class ItemMoveCallback extends ItemTouchHelper.Callback {
private final ItemTouchHelperContract mAdapter;
public ItemMoveCallback(ItemTouchHelperContract adapter) {
mAdapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onRowMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
mAdapter.onRowSelected(viewHolder);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onRowClear(viewHolder);
}
public interface ItemTouchHelperContract {
void onRowMoved(int fromPosition, int toPosition);
void onRowSelected(RecyclerView.ViewHolder myViewHolder);
void onRowClear(RecyclerView.ViewHolder myViewHolder);
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4V15z"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,14m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16,3.33c2.58,0 4.67,2.09 4.67,4.67H22c0,-3.31 -2.69,-6 -6,-6v1.33M16,6c1.11,0 2,0.89 2,2h1.33c0,-1.84 -1.49,-3.33 -3.33,-3.33V6"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17,9c0,-1.11 -0.89,-2 -2,-2L15,4L9,4L7.17,6L4,6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9h-5zM12,19c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/text_margin"
android:paddingBottom="@dimen/text_margin"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_weight="0"
app:srcCompat="@drawable/ic_network_camera"
app:tint="@color/design_default_color_primary"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/camera_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5sp"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="@color/design_default_color_primary"
android:lines="1"
android:text="@tools:sample/lorem/random"/>
<TextView
android:id="@+id/camera_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItemSecondary"
android:lines="1"
android:text="@tools:sample/lorem/random" />
</LinearLayout>
<ImageView
android:id="@+id/camera_drag_handle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_weight="0"
app:srcCompat="@drawable/ic_drag_handle"
app:tint="@color/purple_200" />
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:name="it.danieleverducci.ojo.ui.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ui.SettingsFragment"
tools:listitem="@layout/fragment_settings_item" />

View File

@ -13,13 +13,19 @@
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment" />
app:destination="@id/SettingsFragment" />
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="it.danieleverducci.ojo.ui.AddStreamFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_add_stream">
</fragment>
<fragment
android:id="@+id/SettingsFragment"
android:name="it.danieleverducci.ojo.ui.SettingsFragment"
android:label="fragment_settings_item_list"
tools:layout="@layout/fragment_settings_item_list" >
<action
android:id="@+id/action_SecondFragment_to_FirstFragment"

View File

@ -5,6 +5,8 @@
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="stream_list_default_camera_name">Videocamera n°{camNo}</string>
<string name="add_stream_placeholder_url">rtsp://username:password@192.168.1.123:554</string>
<string name="add_stream">Inserisci l\'url dello stream RTSP della tua IP Camera. Nota che questo differisce tra un modello e l\'altro. Consulta il pannello di configurazione o il manuale della tua IP Camera.</string>
<string name="add_stream_save">Salva</string>
@ -17,4 +19,5 @@
<string name="app_info_license_desc">Questa app è rilasciata sotto licenza GNU GENERAL PUBLIC LICENSE v3+. Puoi ottenerne una copia qui: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE</string>
<string name="app_info_repo_desc">Puoi trovare il codice sorgente al repository: https://github.com/penguin86/ojo</string>
<string name="app_info_lib_desc">Questa app è resa possibile dal magnifico lavoro dei team vlc and vlc-android! Per saperne di più o ottenere il codice sorgente: https://code.videolan.org/videolan/vlc-android</string>
</resources>

View File

@ -1,3 +1,4 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@ -4,6 +4,8 @@
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="stream_list_default_camera_name">Camera {camNo}</string>
<string name="add_stream_placeholder_url">rtsp://username:password@192.168.1.123:554</string>
<string name="add_stream">Please insert your camera\'s RTSP stream. Note that the URL differs from camera to camera: you can find the complete URL in your camera\'s settings or user manual.</string>
<string name="add_stream_save">Save</string>