Groups: interims commit

This commit is contained in:
badaix 2016-12-04 21:35:44 +01:00
parent 22f768311d
commit 0af508f6ee
14 changed files with 555 additions and 328 deletions

View file

@ -20,8 +20,8 @@ package de.badaix.snapcast;
import android.content.Context;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
@ -36,19 +36,21 @@ import de.badaix.snapcast.control.json.Volume;
public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, PopupMenu.OnMenuItemClickListener {
private static final String TAG = "ClientItem";
private TextView title;
private SeekBar volumeSeekBar;
private ImageButton ibMute;
private ImageButton ibOverflow;
private Client client;
private ServerStatus server;
private ClientInfoItemListener listener = null;
private ClientItemListener listener = null;
public ClientItem(Context context, ServerStatus server, Client client) {
super(context);
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi.inflate(R.layout.client_info, this);
vi.inflate(R.layout.client_item, this);
title = (TextView) findViewById(R.id.title);
volumeSeekBar = (SeekBar) findViewById(R.id.volumeSeekBar);
ibMute = (ImageButton) findViewById(R.id.ibMute);
@ -59,9 +61,11 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
setClient(client);
volumeSeekBar.setOnSeekBarChangeListener(this);
this.server = server;
update();
}
private void update() {
Log.d(TAG, "update: " + client.getVisibleName() + ", connected: " + client.isConnected());
title.setText(client.getVisibleName());
title.setEnabled(client.isConnected());
volumeSeekBar.setProgress(client.getConfig().getVolume().getPercent());
@ -80,7 +84,7 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
update();
}
public void setListener(ClientInfoItemListener listener) {
public void setListener(ClientItemListener listener) {
this.listener = listener;
}
@ -95,7 +99,7 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
@Override
public void onClick(View v) {
if (v == ibMute) {
/* TODO: group if (v == ibMute) {
Volume volume = client.getConfig().getVolume();
volume.setMuted(!volume.isMuted());
update();
@ -125,6 +129,7 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
popup.setOnMenuItemClickListener(this);
popup.show();
}
*/
}
@Override
@ -151,7 +156,7 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
}
}
public interface ClientInfoItemListener {
public interface ClientItemListener {
void onVolumeChanged(ClientItem clientItem, int percent);
void onMute(ClientItem clientItem, boolean mute);
@ -159,8 +164,6 @@ public class ClientItem extends LinearLayout implements SeekBar.OnSeekBarChangeL
void onDeleteClicked(ClientItem clientItem);
void onPropertiesClicked(ClientItem clientItem);
void onStreamClicked(ClientItem clientItem, Stream stream);
}
}

View file

@ -27,17 +27,15 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import de.badaix.snapcast.control.json.Client;
import de.badaix.snapcast.control.json.Group;
import de.badaix.snapcast.control.json.ServerStatus;
import de.badaix.snapcast.control.json.Stream;
/**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link ClientItem.ClientInfoItemListener} interface
* {@link GroupItem.GroupItemListener} interface
* to handle interaction events.
*/
public class ClientListFragment extends Fragment {
@ -49,14 +47,10 @@ public class ClientListFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private Stream stream;
private ClientItem.ClientInfoItemListener clientInfoItemListener;
private ClientInfoAdapter clientInfoAdapter;
private GroupItem.GroupItemListener groupItemListener;
private GroupAdapter groupAdapter;
private ServerStatus serverStatus = null;
private boolean hideOffline = false;
private TextView tvStreamState = null;
public ClientListFragment() {
// Required empty public constructor
@ -93,35 +87,27 @@ public class ClientListFragment extends Fragment {
// Inflate the layout for this fragment
Log.d(TAG, "onCreateView: " + this.toString());
View view = inflater.inflate(R.layout.fragment_client_list, container, false);
tvStreamState = (TextView) view.findViewById(R.id.tvStreamState);
ListView lvClient = (ListView) view.findViewById(R.id.lvClient);
clientInfoAdapter = new ClientInfoAdapter(getContext(), clientInfoItemListener);
clientInfoAdapter.setHideOffline(hideOffline);
clientInfoAdapter.updateServer(serverStatus);
lvClient.setAdapter(clientInfoAdapter);
ListView lvGroup = (ListView) view.findViewById(R.id.lvGroup);
groupAdapter = new GroupAdapter(getContext(), groupItemListener);
groupAdapter.setHideOffline(hideOffline);
groupAdapter.updateServer(serverStatus);
lvGroup.setAdapter(groupAdapter);
updateGui();
return view;
}
private void updateGui() {
Log.d(TAG, "(tvStreamState == null): " + (tvStreamState == null) + " " + this.toString());
if ((tvStreamState == null) || (stream == null))
return;
String codec = stream.getUri().getQuery().get("codec");
if (codec.contains(":"))
codec = codec.split(":")[0];
tvStreamState.setText(stream.getUri().getQuery().get("sampleformat") + " - " + codec + " - " + stream.getStatus().toString());
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof ClientItem.ClientInfoItemListener) {
clientInfoItemListener = (ClientItem.ClientInfoItemListener) context;
if (context instanceof GroupItem.GroupItemListener) {
groupItemListener = (GroupItem.GroupItemListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement ClientInfoItemListener");
+ " must implement GroupItemListener");
}
updateGui();
}
@ -129,39 +115,28 @@ public class ClientListFragment extends Fragment {
@Override
public void onDetach() {
super.onDetach();
clientInfoItemListener = null;
groupItemListener = null;
}
public void updateServer(ServerStatus serverStatus) {
this.serverStatus = serverStatus;
if (clientInfoAdapter != null)
clientInfoAdapter.updateServer(this.serverStatus);
if (groupAdapter != null)
groupAdapter.updateServer(this.serverStatus);
}
public void setHideOffline(boolean hide) {
this.hideOffline = hide;
if (clientInfoAdapter != null)
clientInfoAdapter.setHideOffline(hideOffline);
if (groupAdapter != null)
groupAdapter.setHideOffline(hideOffline);
}
public String getName() {
return stream.getName();
}
public void setStream(Stream stream) {
Log.d(TAG, "setStream: " + stream.getName() + ", status: " + stream.getStatus());
this.stream = stream;
updateGui();
}
public class ClientInfoAdapter extends ArrayAdapter<Client> {
public class GroupAdapter extends ArrayAdapter<Group> {
private Context context;
private ClientItem.ClientInfoItemListener listener;
private GroupItem.GroupItemListener listener;
private boolean hideOffline = false;
private ServerStatus serverStatus = new ServerStatus();
public ClientInfoAdapter(Context context, ClientItem.ClientInfoItemListener listener) {
public GroupAdapter(Context context, GroupItem.GroupItemListener listener) {
super(context, 0);
this.context = context;
this.listener = listener;
@ -169,22 +144,22 @@ public class ClientListFragment extends Fragment {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Client client = getItem(position);
final ClientItem clientItem;
Group group = getItem(position);
final GroupItem groupItem;
if (convertView != null) {
clientItem = (ClientItem) convertView;
clientItem.setClient(client);
groupItem = (GroupItem) convertView;
groupItem.setGroup(group);
} else {
clientItem = new ClientItem(context, serverStatus, client);
groupItem = new GroupItem(context, serverStatus, group);
}
clientItem.setListener(listener);
return clientItem;
groupItem.setListener(listener);
return groupItem;
}
public void updateServer(final ServerStatus serverStatus) {
if (serverStatus != null) {
ClientInfoAdapter.this.serverStatus = serverStatus;
GroupAdapter.this.serverStatus = serverStatus;
update();
}
}
@ -192,10 +167,16 @@ public class ClientListFragment extends Fragment {
public void update() {
clear();
for (Client client : ClientInfoAdapter.this.serverStatus.getClientInfos()) {
if ((client != null) && (!hideOffline || client.isConnected()) && !client.isDeleted() && client.getConfig().getStream().equals(ClientListFragment.this.stream.getId()))
// TODO: group
for (Group group : GroupAdapter.this.serverStatus.getGroups()) {
add(group);
/* for (Client client : group.getClients()) {
if ((client != null) && (!hideOffline || client.isConnected()) && !client.isDeleted())// && client.getConfig().getStream().equals(ClientListFragment.this.stream.getId()))
add(client);
}
*/
}
if (getActivity() != null) {
ClientListFragment.this.getActivity().runOnUiThread(new Runnable() {
@Override

View file

@ -94,6 +94,7 @@ public class ClientSettingsFragment extends PreferenceFragment {
prefStream = (ListPreference) findPreference("pref_client_stream");
prefStream.setEntries(streamNames);
prefStream.setEntryValues(streamIds);
/* TODO: group
for (int i = 0; i < streams.size(); ++i) {
if (streamIds[i].equals(client.getConfig().getStream())) {
prefStream.setSummary(streamNames[i]);
@ -116,6 +117,7 @@ public class ClientSettingsFragment extends PreferenceFragment {
return false;
}
});
*/
prefMac = (Preference) findPreference("pref_client_mac");
prefId = (Preference) findPreference("pref_client_id");
prefIp = (Preference) findPreference("pref_client_ip");

View file

@ -0,0 +1,237 @@
/*
* This file is part of snapcast
* Copyright (C) 2014-2016 Johannes Pohl
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.badaix.snapcast;
import android.content.Context;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import de.badaix.snapcast.control.json.Client;
import de.badaix.snapcast.control.json.Group;
import de.badaix.snapcast.control.json.ServerStatus;
import de.badaix.snapcast.control.json.Stream;
/**
* Created by johannes on 04.12.16.
*/
public class GroupItem extends LinearLayout implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, PopupMenu.OnMenuItemClickListener, ClientItem.ClientItemListener {
private static final String TAG = "GroupItem";
// private TextView title;
private SeekBar volumeSeekBar;
private ImageButton ibMute;
private ImageButton ibOverflow;
private LinearLayout llClient;
private Group group;
private ServerStatus server;
private TextView tvStreamState = null;
private GroupItemListener listener = null;
private Stream stream = null;
private LinearLayout llVolume;
public GroupItem(Context context, ServerStatus server, Group group) {
super(context);
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi.inflate(R.layout.group_item, this);
// title = (TextView) findViewById(R.id.title);
volumeSeekBar = (SeekBar) findViewById(R.id.volumeSeekBar);
ibMute = (ImageButton) findViewById(R.id.ibMute);
ibMute.setImageResource(R.drawable.ic_speaker_icon);
ibMute.setOnClickListener(this);
ibOverflow = (ImageButton) findViewById(R.id.ibOverflow);
ibOverflow.setOnClickListener(this);
llVolume = (LinearLayout) findViewById(R.id.llVolume);
llVolume.setVisibility(GONE);
llClient = (LinearLayout) findViewById(R.id.llClient);
llClient.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
tvStreamState = (TextView) findViewById(R.id.tvStreamState);
setGroup(group);
volumeSeekBar.setOnSeekBarChangeListener(this);
this.server = server;
stream = server.getStream(group.getStreamId());
update();
}
private void update() {
// title.setText(group.getName());
llClient.removeAllViews();
for (Client client : group.getClients()) {
ClientItem clientItem = new ClientItem(this.getContext(), server, client);
clientItem.setListener(this);
llClient.addView(clientItem);
}
Log.d(TAG, "(tvStreamState == null): " + (tvStreamState == null) + " " + this.toString());
if ((tvStreamState == null) || (stream == null))
return;
tvStreamState.setText(stream.getName());
/* String codec = stream.getUri().getQuery().get("codec");
if (codec.contains(":"))
codec = codec.split(":")[0];
tvStreamState.setText(stream.getUri().getQuery().get("sampleformat") + " - " + codec + " - " + stream.getStatus().toString());
*/
/* title.setEnabled(group.isConnected());
volumeSeekBar.setProgress(group.getConfig().getVolume().getPercent());
if (client.getConfig().getVolume().isMuted())
ibMute.setImageResource(R.drawable.ic_mute_icon);
else
ibMute.setImageResource(R.drawable.ic_speaker_icon);
*/
}
public Group getGroup() {
return group;
}
public void setGroup(final Group group) {
this.group = group;
update();
}
public void setStream(Stream stream) {
Log.d(TAG, "setStream: " + stream.getName() + ", status: " + stream.getStatus());
this.stream = stream;
update();
}
public void setListener(GroupItemListener listener) {
this.listener = listener;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/* if (fromUser && (listener != null)) {
Volume volume = new Volume(progress, false);
client.setVolume(volume);
listener.onVolumeChanged(this, volume.getPercent());
}
*/
}
@Override
public void onClick(View v) {
/* TODO: group if (v == ibMute) {
Volume volume = client.getConfig().getVolume();
volume.setMuted(!volume.isMuted());
update();
listener.onMute(this, volume.isMuted());
} else if (v == ibOverflow) {
PopupMenu popup = new PopupMenu(v.getContext(), v);
popup.getMenu().add(Menu.NONE, R.id.menu_details, 0, R.string.menu_details);
if (!client.isConnected())
popup.getMenu().add(Menu.NONE, R.id.menu_delete, 1, R.string.menu_delete);
if ((server != null) && (server.getStreams().size() > 1)) {
int pos = 2;
for (final Stream stream : server.getStreams()) {
if (client.getConfig().getStream().equals(stream.getId()))
continue;
final MenuItem menuItem = popup.getMenu().add(Menu.NONE, Menu.NONE, pos, stream.getName());
menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
listener.onStreamClicked(ClientItem.this, stream);
return true;
}
});
++pos;
}
}
popup.setOnMenuItemClickListener(this);
popup.show();
}
*/
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public boolean onMenuItemClick(MenuItem item) {
/* switch (item.getItemId()) {
case R.id.menu_details:
listener.onPropertiesClicked(this);
return true;
case R.id.menu_delete:
listener.onDeleteClicked(this);
return true;
default:
return false;
}
*/
return false;
}
@Override
public void onVolumeChanged(ClientItem clientItem, int percent) {
if (listener != null)
listener.onVolumeChanged(this, clientItem, percent);
}
@Override
public void onMute(ClientItem clientItem, boolean mute) {
if (listener != null)
listener.onMute(this, clientItem, mute);
}
@Override
public void onDeleteClicked(ClientItem clientItem) {
if (listener != null)
listener.onDeleteClicked(this, clientItem);
}
@Override
public void onPropertiesClicked(ClientItem clientItem) {
if (listener != null)
listener.onPropertiesClicked(this, clientItem);
}
public interface GroupItemListener {
void onVolumeChanged(GroupItem group, ClientItem clientItem, int percent);
void onMute(GroupItem group, ClientItem clientItem, boolean mute);
void onDeleteClicked(GroupItem group, ClientItem clientItem);
void onPropertiesClicked(GroupItem group, ClientItem clientItem);
void onStreamClicked(GroupItem group, Stream stream);
}
}

View file

@ -35,11 +35,6 @@ import android.os.IBinder;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@ -48,24 +43,20 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.UnknownHostException;
import java.util.Vector;
import de.badaix.snapcast.control.RemoteControl;
import de.badaix.snapcast.control.json.Client;
import de.badaix.snapcast.control.json.Group;
import de.badaix.snapcast.control.json.ServerStatus;
import de.badaix.snapcast.control.json.Stream;
import de.badaix.snapcast.utils.NsdHelper;
import de.badaix.snapcast.utils.Settings;
import de.badaix.snapcast.utils.Setup;
public class MainActivity extends AppCompatActivity implements ClientItem.ClientInfoItemListener, RemoteControl.RemoteControlListener, SnapclientService.SnapclientListener, NsdHelper.NsdHelperListener {
public class MainActivity extends AppCompatActivity implements GroupItem.GroupItemListener, RemoteControl.RemoteControlListener, SnapclientService.SnapclientListener, NsdHelper.NsdHelperListener {
static final int CLIENT_PROPERTIES_REQUEST = 1;
private static final String TAG = "Main";
@ -80,17 +71,12 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
private RemoteControl remoteControl = null;
private ServerStatus serverStatus = null;
private SnapclientService snapclientService;
private SectionsPagerAdapter sectionsPagerAdapter;
private ClientListFragment clientListFragment;
private TabLayout tabLayout;
private Snackbar warningSamplerateSnackbar = null;
private int nativeSampleRate = 0;
private CoordinatorLayout coordinatorLayout;
/**
* The {@link ViewPager} that will host the section contents.
*/
private ViewPager mViewPager;
/**
* Defines callbacks for service binding, passed to bindService()
@ -139,15 +125,8 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
setSupportActionBar(toolbar);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(sectionsPagerAdapter);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
mViewPager.setVisibility(View.GONE);
clientListFragment = (ClientListFragment) getSupportFragmentManager().findFragmentById(R.id.clientListFragment);
setActionbarSubtitle("Host: no Snapserver found");
@ -159,8 +138,6 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
Log.d(TAG, "done copying snapclient");
}
}).start();
sectionsPagerAdapter.setHideOffline(Settings.getInstance(this).getBoolean("hide_offline", false));
}
public void checkFirstRun() {
@ -195,7 +172,6 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
boolean isChecked = Settings.getInstance(this).getBoolean("hide_offline", false);
MenuItem menuItem = menu.findItem(R.id.action_hide_offline);
menuItem.setChecked(isChecked);
sectionsPagerAdapter.setHideOffline(isChecked);
// setHost(host, port, controlPort);
if (remoteControl != null) {
updateMenuItems(remoteControl.isConnected());
@ -241,7 +217,7 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
} else if (id == R.id.action_hide_offline) {
item.setChecked(!item.isChecked());
Settings.getInstance(this).put("hide_offline", item.isChecked());
sectionsPagerAdapter.setHideOffline(item.isChecked());
//TODO: group sectionsPagerAdapter.setHideOffline(item.isChecked());
return true;
} else if (id == R.id.action_refresh) {
startRemoteControl();
@ -407,7 +383,7 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
return;
}
if (requestCode == CLIENT_PROPERTIES_REQUEST) {
Client client = null;
/* TODO: group Client client = null;
try {
client = new Client(new JSONObject(data.getStringExtra("client")));
} catch (JSONException e) {
@ -432,17 +408,12 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
remoteControl.setLatency(client, client.getConfig().getLatency());
serverStatus.updateClient(client);
sectionsPagerAdapter.updateServer(serverStatus);
*/
}
}
@Override
public void onConnected(RemoteControl remoteControl) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mViewPager.setVisibility(View.VISIBLE);
}
});
setActionbarSubtitle(remoteControl.getHost());
remoteControl.getServerStatus();
updateMenuItems(true);
@ -457,7 +428,7 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
public void onDisconnected(RemoteControl remoteControl, Exception e) {
Log.d(TAG, "onDisconnected");
serverStatus = new ServerStatus();
sectionsPagerAdapter.updateServer(serverStatus);
//TODO: group sectionsPagerAdapter.updateServer(serverStatus);
if (e != null) {
if (e instanceof UnknownHostException)
setActionbarSubtitle("error: unknown host");
@ -466,38 +437,38 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
} else {
setActionbarSubtitle("not connected");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mViewPager.setVisibility(View.GONE);
}
});
updateMenuItems(false);
}
@Override
public void onClientEvent(RemoteControl remoteControl, Client client, RemoteControl.ClientEvent event) {
Log.d(TAG, "onClientEvent: " + event.toString());
remoteControl.getServerStatus();
/* TODO: group
if (event == RemoteControl.ClientEvent.deleted)
serverStatus.removeClient(client);
else
serverStatus.updateClient(client);
sectionsPagerAdapter.updateServer(serverStatus);
*/
}
@Override
public void onServerStatus(RemoteControl remoteControl, ServerStatus serverStatus) {
this.serverStatus = serverStatus;
for (Stream s : serverStatus.getStreams())
Log.d(TAG, s.toString());
sectionsPagerAdapter.updateServer(serverStatus);
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
clientListFragment.updateServer(MainActivity.this.serverStatus);
}
});
// TODO: group sectionsPagerAdapter.updateServer(serverStatus);
}
@Override
public void onStreamUpdate(RemoteControl remoteControl, Stream stream) {
serverStatus.updateStream(stream);
sectionsPagerAdapter.updateServer(serverStatus);
// TODO: group sectionsPagerAdapter.updateServer(serverStatus);
}
@ -558,19 +529,20 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
@Override
public void onVolumeChanged(ClientItem clientItem, int percent) {
public void onVolumeChanged(GroupItem groupItem, ClientItem clientItem, int percent) {
remoteControl.setVolume(clientItem.getClient(), percent);
}
@Override
public void onMute(ClientItem clientItem, boolean mute) {
public void onMute(GroupItem groupItem, ClientItem clientItem, boolean mute) {
remoteControl.setMute(clientItem.getClient(), mute);
}
@Override
public void onDeleteClicked(final ClientItem clientItem) {
public void onDeleteClicked(GroupItem groupItem, final ClientItem clientItem) {
final Client client = clientItem.getClient();
client.setDeleted(true);
/* TODO: group
serverStatus.updateClient(client);
sectionsPagerAdapter.updateServer(serverStatus);
Snackbar mySnackbar = Snackbar.make(findViewById(R.id.myCoordinatorLayout),
@ -595,10 +567,11 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
}
});
mySnackbar.show();
*/
}
@Override
public void onPropertiesClicked(ClientItem clientItem) {
public void onPropertiesClicked(GroupItem groupItem, ClientItem clientItem) {
Intent intent = new Intent(this, ClientSettingsActivity.class);
intent.putExtra("client", clientItem.getClient().toJson().toString());
intent.putExtra("streams", serverStatus.getJsonStreams().toString());
@ -607,78 +580,15 @@ public class MainActivity extends AppCompatActivity implements ClientItem.Client
}
@Override
public void onStreamClicked(ClientItem clientItem, Stream stream) {
public void onStreamClicked(GroupItem groupItem, Stream stream) {
/* TODO: group
Client client = clientItem.getClient();
client.getConfig().setStream(stream.getId());
remoteControl.setStream(client, client.getConfig().getStream());
serverStatus.updateClient(client);
sectionsPagerAdapter.updateServer(serverStatus);
*/
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
private Vector<ClientListFragment> fragments = new Vector<>();
private int streamCount = 0;
private boolean hideOffline = false;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
public void updateServer(final ServerStatus serverStatus) {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "updateServer: " + serverStatus.getStreams().size());
boolean changed = (serverStatus.getStreams().size() != streamCount);
while (serverStatus.getStreams().size() > fragments.size())
fragments.add(new ClientListFragment());
for (int i = 0; i < serverStatus.getStreams().size(); ++i) {
fragments.get(i).setStream(serverStatus.getStreams().get(i));
fragments.get(i).updateServer(serverStatus);
}
if (changed) {
streamCount = serverStatus.getStreams().size();
notifyDataSetChanged();
tabLayout.setTabsFromPagerAdapter(SectionsPagerAdapter.this);
}
setHideOffline(hideOffline);
}
});
}
public void setHideOffline(boolean hide) {
this.hideOffline = hide;
for (ClientListFragment clientListFragment : fragments)
clientListFragment.setHideOffline(hide);
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return streamCount;
}
@Override
public CharSequence getPageTitle(int position) {
return fragments.get(position).getName();
}
}
}

View file

@ -88,27 +88,28 @@ public class ServerStatus implements JsonSerialisable {
}
return false;
}
*/
public boolean updateClient(Client client) {
if (client == null)
return false;
for (int i = 0; i < clients.size(); ++i) {
Client clientInfo = clients.get(i);
if (clientInfo == null)
for (Group group : groups) {
for (int i = 0; i < group.getClients().size(); ++i) {
Client c = group.getClients().get(i);
if (c == null)
continue;
if (client.getId().equals(clientInfo.getId())) {
if (clientInfo.equals(client))
if (client.getId().equals(c.getId())) {
if (client.equals(c))
return true;
group.getClients().set(i, client);
return true;
}
}
}
return false;
clients.set(i, client);
return true;
}
}
clients.add(client);
return true;
}
*/
public boolean updateStream(Stream stream) {
if (stream == null)
return false;
@ -137,6 +138,13 @@ public class ServerStatus implements JsonSerialisable {
return streams;
}
public Stream getStream(String id) {
for (Stream s : streams)
if ((s != null) && (s.getId().equals(id)))
return s;
return null;
}
public JSONArray getJsonStreams() {
JSONArray jsonArray = new JSONArray();
for (Stream stream : streams)

View file

@ -44,53 +44,15 @@
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<!--
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="Info: this is a prototype"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<CheckBox
android:id="@+id/cbScreenWakelock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Keep screen on"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:visibility="gone"/>
-->
</LinearLayout>
android:layout_marginTop="?attr/actionBarSize"
android:name="de.badaix.snapcast.ClientListFragment"
android:id="@+id/clientListFragment"
tools:layout="@layout/fragment_client_list"/>
</android.support.design.widget.CoordinatorLayout>

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of snapcast
~ Copyright (C) 2014-2016 Johannes Pohl
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program 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 General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- android:descendantFocusability="afterDescendants"-->
<!-- android:paddingRight="?android:attr/scrollbarSize"-->
<!-- android:background="@drawable/big_card"-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/ibOverflow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:src="@drawable/abc_ic_menu_moreoverflow_mtrl_alpha"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/ibOverflow"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:paddingBottom="1dp"
android:paddingLeft="5dp"
android:paddingTop="2dp"
android:singleLine="true"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingBottom="4dp"
android:paddingLeft="3dp">
<ImageButton
android:id="@+id/ibMute"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="@null"
android:src="@drawable/ic_speaker_icon"/>
<SeekBar
android:id="@+id/volumeSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:max="100"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</FrameLayout>

View file

@ -23,13 +23,8 @@
android:layout_height="match_parent"
tools:context="de.badaix.snapcast.ClientListFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lvClient"
android:id="@+id/lvGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/tvStreamState"
@ -43,18 +38,4 @@
android:dividerHeight="1dp"
android:listSelector="@android:color/transparent"/>
<TextView
android:id="@+id/tvStreamState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
android:padding="5dp"
android:text="Stream state"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</RelativeLayout>
</FrameLayout>

View file

@ -20,7 +20,8 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
>
<!-- android:descendantFocusability="afterDescendants"-->
<!-- android:paddingRight="?android:attr/scrollbarSize"-->
<!-- android:background="@drawable/big_card"-->
@ -32,21 +33,20 @@
card_view:cardUseCompatPadding="true"
card_view:contentPadding="2dp">
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/ibOverflow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:src="@drawable/abc_ic_menu_moreoverflow_mtrl_alpha"/>
android:orientation="vertical">
<LinearLayout
android:id="@+id/llClient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<LinearLayout
android:id="@+id/llVolume"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
@ -54,6 +54,7 @@
android:orientation="vertical">
<!--
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
@ -66,7 +67,7 @@
android:singleLine="true"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceMedium"/>
-->
<LinearLayout
android:layout_width="match_parent"
@ -90,7 +91,33 @@
android:max="100"/>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/ibOverflow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null"
android:src="@drawable/abc_ic_menu_moreoverflow_mtrl_alpha"/>
<TextView
android:id="@+id/tvStreamState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
android:padding="5dp"
android:text="Stream state"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</FrameLayout>

View file

@ -93,11 +93,14 @@ ClientInfoPtr Config::getClientInfo(const std::string& clientId) const
if (clientId.empty())
return nullptr;
for (auto client: clients)
for (auto group: groups)
{
for (auto client: group->clients)
{
if (client->id == clientId)
return client;
}
}
return nullptr;
}
@ -109,13 +112,26 @@ ClientInfoPtr Config::addClientInfo(const std::string& clientId)
if (!client)
{
client = make_shared<ClientInfo>(clientId);
clients.push_back(client);
//TODO: strange contruct
getGroup(client);
}
return client;
}
GroupPtr Config::getGroup(const std::string& groupId) const
{
for (auto group: groups)
{
if (group->id == groupId)
return group;
}
return nullptr;
}
GroupPtr Config::getGroup(ClientInfoPtr client)
{
for (auto group: groups)
@ -127,8 +143,7 @@ GroupPtr Config::getGroup(ClientInfoPtr client)
}
}
GroupPtr group = std::make_shared<Group>();
group->id = generateUUID();
GroupPtr group = std::make_shared<Group>();//client);
group->clients.push_back(client);
groups.push_back(group);
@ -136,9 +151,9 @@ GroupPtr Config::getGroup(ClientInfoPtr client)
}
json Config::getServerStatus(const std::string& clientId, const json& streams) const
json Config::getServerStatus(const json& streams) const
{
json jClient = json::array();
/* json jClient = json::array();
if (clientId != "")
{
ClientInfoPtr client = getClientInfo(clientId);
@ -147,7 +162,7 @@ json Config::getServerStatus(const std::string& clientId, const json& streams) c
}
else
jClient = getClientInfos();
*/
Host host;
host.update();
//TODO: Set MAC and IP
@ -157,7 +172,7 @@ json Config::getServerStatus(const std::string& clientId, const json& streams) c
{"host", host.toJson()},//getHostName()},
{"snapserver", snapserver.toJson()}
}},
{"clients", jClient},
{"groups", getGroups()},
{"streams", streams}
};
@ -165,14 +180,14 @@ json Config::getServerStatus(const std::string& clientId, const json& streams) c
}
json Config::getClientInfos() const
/*json Config::getClientInfos() const
{
json result = json::array();
for (auto client: clients)
result.push_back(client->toJson());
return result;
}
*/
json Config::getGroups() const
{
@ -186,9 +201,9 @@ json Config::getGroups() const
void Config::remove(ClientInfoPtr client)
{
auto group = getGroup(client);
if (group->clients.size() == 1)
groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end());
auto clients = group->clients;
clients.erase(std::remove(clients.begin(), clients.end(), client), clients.end());
if (group->clients.empty())
groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end());
}

View file

@ -257,8 +257,11 @@ struct ClientInfo
struct Group
{
Group()
Group(const ClientInfoPtr client = nullptr)
{
if (client)
id = client->id;
id = generateUUID();
}
void fromJson(const json& j)
@ -314,15 +317,16 @@ public:
void remove(ClientInfoPtr client);
GroupPtr getGroup(ClientInfoPtr client);
GroupPtr getGroup(const std::string& groupId) const;
json getClientInfos() const;
// json getClientInfos() const;
json getGroups() const;
json getServerStatus(const std::string& clientId, const json& streams) const;
json getServerStatus(const json& streams) const;
void save();
std::vector<GroupPtr> groups;
std::vector<ClientInfoPtr> clients;
// std::vector<ClientInfoPtr> clients;
private:
Config();

View file

@ -115,9 +115,17 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
json response;
ClientInfoPtr clientInfo = nullptr;
GroupPtr group = nullptr;
msg::ServerSettings serverSettings;
serverSettings.setBufferMs(settings_.bufferMs);
if (request.method.find("Group.Set") == 0)
{
group = Config::instance().getGroup(request.getParam("group").get<string>());
if (group == nullptr)
throw JsonInternalErrorException("Group not found", request.id);
}
if (request.method.find("Client.Set") == 0)
{
clientInfo = Config::instance().getClientInfo(request.getParam("client").get<string>());
@ -127,8 +135,9 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
if (request.method == "Server.GetStatus")
{
/// TODO: rpc
string clientId = request.hasParam("client") ? request.getParam("client").get<string>() : "";
response = Config::instance().getServerStatus(clientId, streamManager_->toJson());
response = Config::instance().getServerStatus(/*clientId,*/ streamManager_->toJson());
// logO << response.dump(4);
}
else if (request.method == "Server.DeleteClient")
@ -153,24 +162,25 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
clientInfo->config.volume.muted = request.getParam<bool>("mute", false, true);
response = clientInfo->config.volume.muted;
}
else if (request.method == "Client.SetStream")
else if (request.method == "Group.SetStream")
{
/* TODO: Group.SetStream
string streamId = request.getParam("id").get<string>();
PcmStreamPtr stream = streamManager_->getStream(streamId);
if (stream == nullptr)
throw JsonInternalErrorException("Stream not found", request.id);
clientInfo->config.streamId = streamId;
response = clientInfo->config.streamId;
group->streamId = streamId;
response = group->streamId;
session_ptr session = getStreamSession(request.getParam("client").get<string>());
for (auto client: group->clients)
{
session_ptr session = getStreamSession(client->id);
if (session != nullptr)
{
session->sendAsync(stream->getHeader());
session->setPcmStream(stream);
}
*/
}
}
else if (request.method == "Client.SetLatency")
{
@ -247,7 +257,6 @@ void StreamServer::onMessageReceived(StreamSession* connection, const msg::BaseM
logD << "request kServerSettings: " << connection->clientId << "\n";
// std::lock_guard<std::mutex> mlock(mutex_);
ClientInfoPtr client = Config::instance().addClientInfo(connection->clientId);
GroupPtr group = Config::instance().getGroup(client);
logD << "request kServerSettings\n";
@ -289,6 +298,8 @@ void StreamServer::onMessageReceived(StreamSession* connection, const msg::BaseM
json notification = JsonNotification::getJson("Client.OnConnect", client->toJson());
// logO << notification.dump(4) << "\n";
controlServer_->send(notification.dump());
// cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n";
// cout << group->toJson().dump(4) << "\n";
}
}