Add loading dialog on API load (after start or restart).

ref #47
This commit is contained in:
Felix Ableitner 2014-06-24 18:02:38 +02:00
parent d0e7f57812
commit 9d041d0bb3
12 changed files with 210 additions and 246 deletions

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nutomic.syncthingandroid.gui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
/**
* {@link android.support.v4.app.ListFragment} that shows a configurable loading text.
*/
public abstract class LoadingListFragment extends Fragment implements
SyncthingService.OnApiAvailableListener, AdapterView.OnItemClickListener {
private ListFragment mListFragment;
private View mListFragmentHolder;
private View mLoading;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mListFragment = (ListFragment) getChildFragmentManager()
.getFragment(savedInstanceState, ListFragment.class.getName());
}
else {
mListFragment = new ListFragment();
}
getChildFragmentManager()
.beginTransaction()
.add(R.id.list_fragment, mListFragment)
.commit();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.loading_list_fragment, container, false);
mListFragmentHolder = view.findViewById(R.id.list_fragment);
mLoading = view.findViewById(R.id.loading);
TextView loadingTextView = (TextView) view.findViewById(R.id.loading_text);
if (SyncthingService.isFirstStart(getActivity())) {
loadingTextView.setText(getString(R.string.web_gui_creating_key));
}
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getChildFragmentManager().putFragment(outState, ListFragment.class.getName(), mListFragment);
}
/**
* Sets adapter and empty text for {@link ListFragment}
* @param adapter Adapter to be used for {@link}ListFragment#setListAdapter}
* @param emptyText Resource id for text to be shown in the
* {@link ListFragment#setEmptyText(CharSequence)}.
*/
public void setListAdapter(ListAdapter adapter, int emptyText) {
mListFragment.setListAdapter(adapter);
mLoading.setVisibility(View.INVISIBLE);
mListFragmentHolder.setVisibility(View.VISIBLE);
mListFragment.setEmptyText(getString(emptyText));
}
@Override
public void onStart() {
super.onStart();
onApiAvailable();
}
/**
* Calls onInitAdapter if it has not yet been called, ListFragment is initialized,
* and SyncthingService is not null, and
*/
@Override
public void onApiAvailable() {
MainActivity activity = (MainActivity) getActivity();
if (getActivity() != null &&
activity.getApi() != null && mListFragment != null) {
onInitAdapter(activity);
getListView().setOnItemClickListener(this);
}
}
/**
* Called when the list adapter should be set.
*/
public abstract void onInitAdapter(MainActivity activity);
public ListView getListView() {
return mListFragment.getListView();
}
}

View File

@ -26,7 +26,7 @@ import java.util.TimerTask;
*/
public class LocalNodeInfoFragment extends Fragment
implements RestApi.OnReceiveSystemInfoListener, RestApi.OnReceiveConnectionsListener,
SyncthingService.OnApiAvailableListener {
SyncthingService.OnApiChangeListener {
private TextView mNodeId;
@ -97,7 +97,10 @@ public class LocalNodeInfoFragment extends Fragment
}
@Override
public void onApiAvailable() {
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
return;
updateGui();
}

View File

@ -1,8 +1,10 @@
package com.nutomic.syncthingandroid.gui;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
@ -19,8 +21,13 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.syncthing.RestApi;
@ -32,7 +39,7 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
* {@link LocalNodeInfoFragment} in the navigation drawer.
*/
public class MainActivity extends ActionBarActivity
implements SyncthingService.OnWebGuiAvailableListener {
implements SyncthingService.OnApiChangeListener {
private SyncthingService mSyncthingService;
@ -41,7 +48,10 @@ public class MainActivity extends ActionBarActivity
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
mSyncthingService = binder.getService();
mSyncthingService.registerOnWebGuiAvailableListener(MainActivity.this);
mSyncthingService.registerOnApiChangeListener(MainActivity.this);
mSyncthingService.registerOnApiChangeListener(mRepositoriesFragment);
mSyncthingService.registerOnApiChangeListener(mNodesFragment);
mSyncthingService.registerOnApiChangeListener(mLocalNodeInfoFragment);
}
public void onServiceDisconnected(ComponentName className) {
@ -49,14 +59,32 @@ public class MainActivity extends ActionBarActivity
}
};
private AlertDialog mLoadingDialog;
/**
* Causes population of repo and node lists, unlocks info drawer.
*/
@Override
public void onWebGuiAvailable() {
mSyncthingService.registerOnApiAvailableListener(mRepositoriesFragment);
mSyncthingService.registerOnApiAvailableListener(mNodesFragment);
mSyncthingService.registerOnApiAvailableListener(mLocalNodeInfoFragment);
@SuppressLint("InflateParams")
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
LayoutInflater inflater = getLayoutInflater();
View dialogLayout = inflater.inflate(R.layout.loading_dialog, null);
TextView loadingText = (TextView) dialogLayout.findViewById(R.id.loading_text);
loadingText.setText((SyncthingService.isFirstStart(this)
? R.string.web_gui_creating_key
: R.string.api_loading));
mLoadingDialog = new AlertDialog.Builder(this)
.setCancelable(false)
.setView(dialogLayout)
.show();
return;
}
if (mLoadingDialog != null) {
mLoadingDialog.dismiss();
}
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
mDrawerLayout.setDrawerListener(mDrawerToggle);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@ -170,13 +198,15 @@ public class MainActivity extends ActionBarActivity
.commit();
mDrawerToggle = mLocalNodeInfoFragment.new Toggle(this, mDrawerLayout,
R.drawable.ic_drawer);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
@Override
public void onDestroy() {
super.onDestroy();
unbindService(mSyncthingServiceConnection);
if (mLoadingDialog != null) {
mLoadingDialog.dismiss();
}
}
/**
@ -209,7 +239,7 @@ public class MainActivity extends ActionBarActivity
public boolean onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(findViewById(R.id.drawer));
menu.findItem(R.id.share_node_id).setVisible(drawerOpen);
return mSyncthingService != null && mSyncthingService.isWebGuiAvailable();
return true;
}
@Override

View File

@ -28,7 +28,7 @@ import java.util.Map;
*/
public class NodeSettingsActivity extends PreferenceActivity implements
Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
RestApi.OnReceiveConnectionsListener, SyncthingService.OnApiAvailableListener {
RestApi.OnReceiveConnectionsListener, SyncthingService.OnApiChangeListener {
public static final String ACTION_CREATE = "create";
@ -43,7 +43,7 @@ public class NodeSettingsActivity extends PreferenceActivity implements
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
mSyncthingService = binder.getService();
mSyncthingService.registerOnApiAvailableListener(NodeSettingsActivity.this);
mSyncthingService.registerOnApiChangeListener(NodeSettingsActivity.this);
}
public void onServiceDisconnected(ComponentName className) {
@ -89,7 +89,12 @@ public class NodeSettingsActivity extends PreferenceActivity implements
}
@Override
public void onApiAvailable() {
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
finish();
return;
}
if (getIntent().getAction().equals(ACTION_CREATE)) {
setTitle(R.string.create_node);
mNode = new RestApi.Node();

View File

@ -1,7 +1,11 @@
package com.nutomic.syncthingandroid.gui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
@ -15,31 +19,52 @@ import java.util.TimerTask;
/**
* Displays a list of all existing nodes.
*/
public class NodesFragment extends LoadingListFragment implements
SyncthingService.OnApiAvailableListener, ListView.OnItemClickListener {
public class NodesFragment extends ListFragment implements SyncthingService.OnApiChangeListener,
ListView.OnItemClickListener {
private NodesAdapter mAdapter;
private Timer mTimer;
private boolean mInitialized = false;
@Override
public void onResume() {
super.onResume();
setListShown(true);
}
@Override
public void onInitAdapter(MainActivity activity) {
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
return;
initAdapter();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initAdapter();
}
private void initAdapter() {
MainActivity activity = (MainActivity) getActivity();
if (activity == null || activity.getApi() == null)
return;
mAdapter = new NodesAdapter(activity);
mAdapter.add(activity.getApi().getNodes());
setListAdapter(mAdapter, R.string.nodes_list_empty);
mInitialized = true;
setListAdapter(mAdapter);
setEmptyText(getString(R.string.nodes_list_empty));
getListView().setOnItemClickListener(this);
}
private void updateList() {
if (!mInitialized)
if (mAdapter == null || getView() == null)
return;
MainActivity activity = (MainActivity) getActivity();
if (activity != null) {
mAdapter.updateConnections(activity.getApi(), getListView());
}
mAdapter.updateConnections(activity.getApi(), getListView());
}
@Override

View File

@ -31,7 +31,7 @@ import java.util.List;
*/
public class RepoSettingsActivity extends PreferenceActivity
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
SyncthingService.OnApiAvailableListener {
SyncthingService.OnApiChangeListener {
public static final String ACTION_CREATE = "create";
@ -48,7 +48,7 @@ public class RepoSettingsActivity extends PreferenceActivity
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
mSyncthingService = binder.getService();
mSyncthingService.registerOnApiAvailableListener(RepoSettingsActivity.this);
mSyncthingService.registerOnApiChangeListener(RepoSettingsActivity.this);
}
public void onServiceDisconnected(ComponentName className) {
@ -97,7 +97,12 @@ public class RepoSettingsActivity extends PreferenceActivity
}
@Override
public void onApiAvailable() {
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
finish();
return;
}
if (getIntent().getAction().equals(ACTION_CREATE)) {
setTitle(R.string.create_repo);
mRepo = new RestApi.Repository();

View File

@ -1,7 +1,11 @@
package com.nutomic.syncthingandroid.gui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import com.nutomic.syncthingandroid.R;
@ -14,31 +18,52 @@ import java.util.TimerTask;
/**
* Displays a list of all existing repositories.
*/
public class ReposFragment extends LoadingListFragment implements
SyncthingService.OnApiAvailableListener, AdapterView.OnItemClickListener {
public class ReposFragment extends ListFragment implements SyncthingService.OnApiChangeListener,
AdapterView.OnItemClickListener {
private ReposAdapter mAdapter;
private Timer mTimer;
private boolean mInitialized = false;
@Override
public void onResume() {
super.onResume();
setListShown(true);
}
@Override
public void onInitAdapter(MainActivity activity) {
public void onApiChange(boolean isAvailable) {
if (!isAvailable)
return;
initAdapter();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initAdapter();
}
private void initAdapter() {
MainActivity activity = (MainActivity) getActivity();
if (activity == null || activity.getApi() == null)
return;
mAdapter = new ReposAdapter(activity);
mAdapter.add(activity.getApi().getRepos());
setListAdapter(mAdapter, R.string.repositories_list_empty);
mInitialized = true;
setListAdapter(mAdapter);
setEmptyText(getString(R.string.repositories_list_empty));
getListView().setOnItemClickListener(this);
}
private void updateList() {
if (!mInitialized)
if (mAdapter == null || getView() == null)
return;
MainActivity activity = (MainActivity) getActivity();
if (activity != null) {
mAdapter.updateModel(activity.getApi(), getListView());
}
mAdapter.updateModel(activity.getApi(), getListView());
}
@Override

View File

@ -24,7 +24,7 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
public class SettingsActivity extends PreferenceActivity
implements Preference.OnPreferenceChangeListener {
implements SyncthingService.OnApiChangeListener, Preference.OnPreferenceChangeListener {
private static final String SYNCTHING_OPTIONS_KEY = "syncthing_options";
@ -48,23 +48,7 @@ public class SettingsActivity extends PreferenceActivity
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
mSyncthingService = binder.getService();
mVersion.setSummary(mSyncthingService.getApi().getVersion());
for (int i = 0; i < mOptionsScreen.getPreferenceCount(); i++) {
Preference pref = mOptionsScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value);
}
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value);
}
mSyncthingService.registerOnApiChangeListener(SettingsActivity.this);
}
public void onServiceDisconnected(ComponentName className) {
@ -72,6 +56,33 @@ public class SettingsActivity extends PreferenceActivity
}
};
@Override
public void onApiChange(boolean isAvailable) {
if (!isAvailable) {
finish();
return;
}
mVersion.setSummary(mSyncthingService.getApi().getVersion());
for (int i = 0; i < mOptionsScreen.getPreferenceCount(); i++) {
Preference pref = mOptionsScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_OPTIONS, pref.getKey());
applyPreference(pref, value);
}
for (int i = 0; i < mGuiScreen.getPreferenceCount(); i++) {
Preference pref = mGuiScreen.getPreference(i);
pref.setOnPreferenceChangeListener(SettingsActivity.this);
String value = mSyncthingService.getApi()
.getValue(RestApi.TYPE_GUI, pref.getKey());
applyPreference(pref, value);
}
}
/**
* Applies the given value to the preference.
*

View File

@ -209,12 +209,12 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
/**
* Increments mAvailableCount by one, and, if it reached TOTAL_STARTUP_CALLS,
* calls {@link SyncthingService#onApiAvailable()}.
* calls {@link SyncthingService#onApiChange(boolean)}.
*/
private void tryIsAvailable() {
int value = mAvailableCount.incrementAndGet();
if (value == TOTAL_STARTUP_CALLS) {
mSyncthingService.onApiAvailable();
mSyncthingService.onApiChange(true);
}
}

View File

@ -32,7 +32,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.locks.ReentrantLock;
/**
@ -95,22 +97,25 @@ public class SyncthingService extends Service {
public void onWebGuiAvailable();
}
private final LinkedList<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new LinkedList<OnWebGuiAvailableListener>();
private final ReentrantLock mOnWebGuiAvailableListenersLock = new ReentrantLock();
private final HashSet<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new HashSet<OnWebGuiAvailableListener>();
private boolean mIsWebGuiAvailable = false;
public interface OnApiAvailableListener {
public void onApiAvailable();
public interface OnApiChangeListener {
public void onApiChange(boolean isAvailable);
}
private final LinkedList<WeakReference<OnApiAvailableListener>> mOnApiAvailableListeners =
new LinkedList<WeakReference<OnApiAvailableListener>>();
private final HashSet<WeakReference<OnApiChangeListener>> mOnApiAvailableListeners =
new HashSet<WeakReference<OnApiChangeListener>>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && ACTION_RESTART.equals(intent.getAction())) {
mIsWebGuiAvailable = false;
onApiChange(false);
new PostTask() {
@Override
protected void onPostExecute(Void aVoid) {
@ -377,7 +382,7 @@ public class SyncthingService extends Service {
listener.onWebGuiAvailable();
}
else {
mOnWebGuiAvailableListeners.addLast(listener);
mOnWebGuiAvailableListeners.add(listener);
}
}
@ -429,34 +434,29 @@ public class SyncthingService extends Service {
}
/**
* Register a listener for the syncthing API becoming available..
* Register a listener for the syncthing API state changing.
*
* If the API is already available, listener will be called immediately.
*
* Listeners are kept around (as weak reference) and called again after any configuration
* changes to allow a data refresh.
* The listener is called immediately with the current state, and again whenever the state
* changes.
*/
public void registerOnApiAvailableListener(OnApiAvailableListener listener) {
if (mApi.isApiAvailable()) {
listener.onApiAvailable();
}
else {
mOnApiAvailableListeners.addLast(new WeakReference<OnApiAvailableListener>(listener));
}
public void registerOnApiChangeListener(OnApiChangeListener listener) {
listener.onApiChange((mApi != null) ? mApi.isApiAvailable() : false);
mOnApiAvailableListeners.add(new WeakReference<OnApiChangeListener>(listener));
}
/**
* Called by {@link RestApi} once it is fully initialized.
* Called when the state of the API changes.
*
* Must not be called from anywhere else.
* Must only be called from SyncthingService or {@link RestApi}.
*/
public void onApiAvailable() {
for (WeakReference<OnApiAvailableListener> listener : mOnApiAvailableListeners) {
public void onApiChange(boolean isAvailable) {
for (Iterator<WeakReference<OnApiChangeListener>> i = mOnApiAvailableListeners.iterator(); i.hasNext();) {
WeakReference<OnApiChangeListener> listener = i.next();
if (listener.get() != null) {
listener.get().onApiAvailable();
listener.get().onApiChange(isAvailable);
}
else {
mOnApiAvailableListeners.remove(listener);
i.remove();
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center"
android:padding="10dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/loading_text"
android:layout_toRightOf="@id/progress"
android:layout_alignBottom="@id/progress"
android:layout_alignTop="@id/progress"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/list_fragment"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ProgressBar
android:id="@+id/progress"
android:layout_centerInParent="true"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dip" />
<TextView
android:id="@+id/loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_below="@id/progress"
android:text="@string/api_loading" />
</RelativeLayout>
</LinearLayout>