Init login
@ -70,7 +70,10 @@
|
|||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Android SDK" jdkType="Android SDK" />
|
<orderEntry type="jdk" jdkName="Android SDK" jdkType="Android SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" exported="" name="android-async-http-1.4.4" level="project" />
|
||||||
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
|
<orderEntry type="library" exported="" name="support-v4-19.1.0" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="acra-4.5.0" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
@ -36,5 +36,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
compile 'com.android.support:support-v4:19.1.0'
|
compile 'com.android.support:support-v4:19.1.0'
|
||||||
|
compile 'ch.acra:acra:4.5.0'
|
||||||
|
compile 'com.loopj.android:android-async-http:1.4.4'
|
||||||
}
|
}
|
||||||
|
BIN
docs-android/app/libs/android-query.0.26.8.jar
Normal file
@ -2,27 +2,31 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.sismics.docs" >
|
package="com.sismics.docs" >
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme" >
|
android:theme="@style/AppTheme" >
|
||||||
<activity
|
<activity
|
||||||
android:name="com.sismics.docs.DocListActivity"
|
android:name=".activity.LoginActivity"
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.sismics.docs.DocDetailActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:label="@string/title_doc_detail"
|
android:label="@string/app_name"
|
||||||
android:parentActivityName="com.sismics.docs.DocListActivity" >
|
android:logo="@drawable/ic_launcher"
|
||||||
<meta-data
|
android:launchMode="singleTop">
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="com.sismics.docs.DocListActivity" />
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity representing a single Doc detail screen. This
|
|
||||||
* activity is only used on handset devices. On tablet-size devices,
|
|
||||||
* item details are presented side-by-side with a list of items
|
|
||||||
* in a {@link DocListActivity}.
|
|
||||||
* <p>
|
|
||||||
* This activity is mostly just a 'shell' activity containing nothing
|
|
||||||
* more than a {@link DocDetailFragment}.
|
|
||||||
*/
|
|
||||||
public class DocDetailActivity extends FragmentActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_doc_detail);
|
|
||||||
|
|
||||||
// Show the Up button in the action bar.
|
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
// savedInstanceState is non-null when there is fragment state
|
|
||||||
// saved from previous configurations of this activity
|
|
||||||
// (e.g. when rotating the screen from portrait to landscape).
|
|
||||||
// In this case, the fragment will automatically be re-added
|
|
||||||
// to its container so we don't need to manually add it.
|
|
||||||
// For more information, see the Fragments API guide at:
|
|
||||||
//
|
|
||||||
// http://developer.android.com/guide/components/fragments.html
|
|
||||||
//
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
// Create the detail fragment and add it to the activity
|
|
||||||
// using a fragment transaction.
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID,
|
|
||||||
getIntent().getStringExtra(DocDetailFragment.ARG_ITEM_ID));
|
|
||||||
DocDetailFragment fragment = new DocDetailFragment();
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.add(R.id.doc_detail_container, fragment)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == android.R.id.home) {
|
|
||||||
// This ID represents the Home or Up button. In the case of this
|
|
||||||
// activity, the Up button is shown. Use NavUtils to allow users
|
|
||||||
// to navigate up one level in the application structure. For
|
|
||||||
// more details, see the Navigation pattern on Android Design:
|
|
||||||
//
|
|
||||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
|
||||||
//
|
|
||||||
NavUtils.navigateUpTo(this, new Intent(this, DocListActivity.class));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.sismics.docs.dummy.DummyContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fragment representing a single Doc detail screen.
|
|
||||||
* This fragment is either contained in a {@link DocListActivity}
|
|
||||||
* in two-pane mode (on tablets) or a {@link DocDetailActivity}
|
|
||||||
* on handsets.
|
|
||||||
*/
|
|
||||||
public class DocDetailFragment extends Fragment {
|
|
||||||
/**
|
|
||||||
* The fragment argument representing the item ID that this fragment
|
|
||||||
* represents.
|
|
||||||
*/
|
|
||||||
public static final String ARG_ITEM_ID = "item_id";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The dummy content this fragment is presenting.
|
|
||||||
*/
|
|
||||||
private DummyContent.DummyItem mItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
|
||||||
* fragment (e.g. upon screen orientation changes).
|
|
||||||
*/
|
|
||||||
public DocDetailFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
|
||||||
// Load the dummy content specified by the fragment
|
|
||||||
// arguments. In a real-world scenario, use a Loader
|
|
||||||
// to load content from a content provider.
|
|
||||||
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_doc_detail, container, false);
|
|
||||||
|
|
||||||
// Show the dummy content as text in a TextView.
|
|
||||||
if (mItem != null) {
|
|
||||||
((TextView) rootView.findViewById(R.id.doc_detail)).setText(mItem.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity representing a list of Docs. This activity
|
|
||||||
* has different presentations for handset and tablet-size devices. On
|
|
||||||
* handsets, the activity presents a list of items, which when touched,
|
|
||||||
* lead to a {@link DocDetailActivity} representing
|
|
||||||
* item details. On tablets, the activity presents the list of items and
|
|
||||||
* item details side-by-side using two vertical panes.
|
|
||||||
* <p>
|
|
||||||
* The activity makes heavy use of fragments. The list of items is a
|
|
||||||
* {@link DocListFragment} and the item details
|
|
||||||
* (if present) is a {@link DocDetailFragment}.
|
|
||||||
* <p>
|
|
||||||
* This activity also implements the required
|
|
||||||
* {@link DocListFragment.Callbacks} interface
|
|
||||||
* to listen for item selections.
|
|
||||||
*/
|
|
||||||
public class DocListActivity extends FragmentActivity
|
|
||||||
implements DocListFragment.Callbacks {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
|
||||||
* device.
|
|
||||||
*/
|
|
||||||
private boolean mTwoPane;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_doc_list);
|
|
||||||
|
|
||||||
if (findViewById(R.id.doc_detail_container) != null) {
|
|
||||||
// The detail container view will be present only in the
|
|
||||||
// large-screen layouts (res/values-large and
|
|
||||||
// res/values-sw600dp). If this view is present, then the
|
|
||||||
// activity should be in two-pane mode.
|
|
||||||
mTwoPane = true;
|
|
||||||
|
|
||||||
// In two-pane mode, list items should be given the
|
|
||||||
// 'activated' state when touched.
|
|
||||||
((DocListFragment) getSupportFragmentManager()
|
|
||||||
.findFragmentById(R.id.doc_list))
|
|
||||||
.setActivateOnItemClick(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: If exposing deep links into your app, handle intents here.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method from {@link DocListFragment.Callbacks}
|
|
||||||
* indicating that the item with the given ID was selected.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(String id) {
|
|
||||||
if (mTwoPane) {
|
|
||||||
// In two-pane mode, show the detail view in this activity by
|
|
||||||
// adding or replacing the detail fragment using a
|
|
||||||
// fragment transaction.
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID, id);
|
|
||||||
DocDetailFragment fragment = new DocDetailFragment();
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.doc_detail_container, fragment)
|
|
||||||
.commit();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// In single-pane mode, simply start the detail activity
|
|
||||||
// for the selected item ID.
|
|
||||||
Intent detailIntent = new Intent(this, DocDetailActivity.class);
|
|
||||||
detailIntent.putExtra(DocDetailFragment.ARG_ITEM_ID, id);
|
|
||||||
startActivity(detailIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.ListFragment;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
|
|
||||||
import com.sismics.docs.dummy.DummyContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list fragment representing a list of Docs. This fragment
|
|
||||||
* also supports tablet devices by allowing list items to be given an
|
|
||||||
* 'activated' state upon selection. This helps indicate which item is
|
|
||||||
* currently being viewed in a {@link DocDetailFragment}.
|
|
||||||
* <p>
|
|
||||||
* Activities containing this fragment MUST implement the {@link Callbacks}
|
|
||||||
* interface.
|
|
||||||
*/
|
|
||||||
public class DocListFragment extends ListFragment {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The serialization (saved instance state) Bundle key representing the
|
|
||||||
* activated item position. Only used on tablets.
|
|
||||||
*/
|
|
||||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The fragment's current callback object, which is notified of list item
|
|
||||||
* clicks.
|
|
||||||
*/
|
|
||||||
private Callbacks mCallbacks = sDummyCallbacks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current activated item position. Only used on tablets.
|
|
||||||
*/
|
|
||||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback interface that all activities containing this fragment must
|
|
||||||
* implement. This mechanism allows activities to be notified of item
|
|
||||||
* selections.
|
|
||||||
*/
|
|
||||||
public interface Callbacks {
|
|
||||||
/**
|
|
||||||
* Callback for when an item has been selected.
|
|
||||||
*/
|
|
||||||
public void onItemSelected(String id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dummy implementation of the {@link Callbacks} interface that does
|
|
||||||
* nothing. Used only when this fragment is not attached to an activity.
|
|
||||||
*/
|
|
||||||
private static Callbacks sDummyCallbacks = new Callbacks() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(String id) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
|
||||||
* fragment (e.g. upon screen orientation changes).
|
|
||||||
*/
|
|
||||||
public DocListFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
// TODO: replace with a real list adapter.
|
|
||||||
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
|
|
||||||
getActivity(),
|
|
||||||
android.R.layout.simple_list_item_activated_1,
|
|
||||||
android.R.id.text1,
|
|
||||||
DummyContent.ITEMS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
// Restore the previously serialized activated item position.
|
|
||||||
if (savedInstanceState != null
|
|
||||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
|
||||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
// Activities containing this fragment must implement its callbacks.
|
|
||||||
if (!(activity instanceof Callbacks)) {
|
|
||||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
|
||||||
}
|
|
||||||
|
|
||||||
mCallbacks = (Callbacks) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
|
|
||||||
// Reset the active callbacks interface to the dummy implementation.
|
|
||||||
mCallbacks = sDummyCallbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
|
||||||
super.onListItemClick(listView, view, position, id);
|
|
||||||
|
|
||||||
// Notify the active callbacks interface (the activity, if the
|
|
||||||
// fragment is attached to one) that an item has been selected.
|
|
||||||
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
|
||||||
// Serialize and persist the activated item position.
|
|
||||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
|
||||||
* given the 'activated' state when touched.
|
|
||||||
*/
|
|
||||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
|
||||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
|
||||||
// give items the 'activated' state when touched.
|
|
||||||
getListView().setChoiceMode(activateOnItemClick
|
|
||||||
? ListView.CHOICE_MODE_SINGLE
|
|
||||||
: ListView.CHOICE_MODE_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setActivatedPosition(int position) {
|
|
||||||
if (position == ListView.INVALID_POSITION) {
|
|
||||||
getListView().setItemChecked(mActivatedPosition, false);
|
|
||||||
} else {
|
|
||||||
getListView().setItemChecked(position, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
mActivatedPosition = position;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.sismics.docs;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import com.androidquery.callback.BitmapAjaxCallback;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import org.acra.ACRA;
|
||||||
|
import org.acra.ReportingInteractionMode;
|
||||||
|
import org.acra.annotation.ReportsCrashes;
|
||||||
|
import org.acra.sender.HttpSender.Method;
|
||||||
|
import org.acra.sender.HttpSender.Type;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
@ReportsCrashes(formKey = "",
|
||||||
|
httpMethod = Method.PUT,
|
||||||
|
reportType = Type.JSON,
|
||||||
|
formUri = "http://acralyzer.sismics.com/docs-report",
|
||||||
|
formUriBasicAuthLogin = "reporter",
|
||||||
|
formUriBasicAuthPassword = "jOS9ezJR",
|
||||||
|
mode = ReportingInteractionMode.TOAST,
|
||||||
|
forceCloseDialogAfterToast = true,
|
||||||
|
resToastText = R.string.crash_toast_text)
|
||||||
|
public class MainApplication extends Application {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
ACRA.init(this);
|
||||||
|
|
||||||
|
// Fetching /user/info from cache
|
||||||
|
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
|
||||||
|
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||||
|
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLowMemory() {
|
||||||
|
BitmapAjaxCallback.clearCache();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import com.androidquery.AQuery;
|
||||||
|
import com.loopj.android.http.JsonHttpResponseHandler;
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
import com.sismics.docs.resource.UserResource;
|
||||||
|
import com.sismics.docs.ui.form.Validator;
|
||||||
|
import com.sismics.docs.ui.form.validator.Required;
|
||||||
|
import com.sismics.docs.util.DialogUtil;
|
||||||
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login activity.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class LoginActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User interface.
|
||||||
|
*/
|
||||||
|
private View loginForm;
|
||||||
|
private View progressBar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.login_activity);
|
||||||
|
|
||||||
|
AQuery aq = new AQuery(this);
|
||||||
|
aq.id(R.id.loginExplain)
|
||||||
|
.text(Html.fromHtml(getString(R.string.login_explain)))
|
||||||
|
.getTextView()
|
||||||
|
.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
|
||||||
|
final EditText txtServer = aq.id(R.id.txtServer).getEditText();
|
||||||
|
final EditText txtUsername = aq.id(R.id.txtUsername).getEditText();
|
||||||
|
final EditText txtPassword = aq.id(R.id.txtPassword).getEditText();
|
||||||
|
final Button btnConnect = aq.id(R.id.btnConnect).getButton();
|
||||||
|
loginForm = aq.id(R.id.loginForm).getView();
|
||||||
|
progressBar = aq.id(R.id.progressBar).getView();
|
||||||
|
|
||||||
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||||
|
|
||||||
|
loginForm.setVisibility(View.GONE);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
final Validator validator = new Validator(false);
|
||||||
|
validator.addValidable(this, txtServer, new Required());
|
||||||
|
validator.addValidable(this, txtUsername, new Required());
|
||||||
|
validator.addValidable(this, txtPassword, new Required());
|
||||||
|
validator.setOnValidationChanged(new CallbackListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
btnConnect.setEnabled(validator.isValidated());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preset saved server URL
|
||||||
|
String serverUrl = PreferenceUtil.getStringPreference(this, PreferenceUtil.PREF_SERVER_URL);
|
||||||
|
if (serverUrl != null) {
|
||||||
|
txtServer.setText(serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
tryConnect();
|
||||||
|
|
||||||
|
// Login button
|
||||||
|
btnConnect.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
loginForm.setVisibility(View.GONE);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
PreferenceUtil.setServerUrl(LoginActivity.this, txtServer.getText().toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
UserResource.login(getApplicationContext(), txtUsername.getText().toString(), txtPassword.getText().toString(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(JSONObject json) {
|
||||||
|
// Empty previous user caches
|
||||||
|
PreferenceUtil.resetUserCache(getApplicationContext());
|
||||||
|
|
||||||
|
// Getting user info and redirecting to main activity
|
||||||
|
ApplicationContext.getInstance().fetchUserInfo(LoginActivity.this, new CallbackListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (responseBytes != null && new String(responseBytes).contains("\"ForbiddenError\"")) {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.login_fail_title, R.string.login_fail);
|
||||||
|
} else {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Given URL is not valid
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
PreferenceUtil.setServerUrl(LoginActivity.this, null);
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.invalid_url_title, R.string.invalid_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get a "session".
|
||||||
|
*/
|
||||||
|
private void tryConnect() {
|
||||||
|
String serverUrl = PreferenceUtil.getStringPreference(this, PreferenceUtil.PREF_SERVER_URL);
|
||||||
|
if (serverUrl == null) {
|
||||||
|
// Server URL is empty
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApplicationContext.getInstance().isLoggedIn()) {
|
||||||
|
// If we are already connected (from cache data)
|
||||||
|
// redirecting to main activity
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
// Trying to get user data
|
||||||
|
UserResource.info(getApplicationContext(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final JSONObject json) {
|
||||||
|
if (json.optBoolean("anonymous", true)) {
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save user data in application context
|
||||||
|
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||||
|
|
||||||
|
// Redirecting to main activity
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.support.v4.view.GravityCompat;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main activity.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class MainActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
private ListView drawerList;
|
||||||
|
private ActionBarDrawerToggle drawerToggle;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle args) {
|
||||||
|
super.onCreate(args);
|
||||||
|
|
||||||
|
// Check if logged in
|
||||||
|
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the activity
|
||||||
|
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||||
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
|
// Cache view references
|
||||||
|
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
|
drawerList = (ListView) findViewById(R.id.drawer_list);
|
||||||
|
|
||||||
|
// Drawer item click listener
|
||||||
|
drawerList.setOnItemClickListener(new ListView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (drawerLayout != null) {
|
||||||
|
// Set a custom shadow that overlays the main content when the drawer opens
|
||||||
|
drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||||
|
|
||||||
|
// Enable ActionBar app icon to behave as action to toggle nav drawer
|
||||||
|
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getActionBar().setHomeButtonEnabled(true);
|
||||||
|
|
||||||
|
// ActionBarDrawerToggle ties together the the proper interactions
|
||||||
|
// between the sliding drawer and the action bar app icon
|
||||||
|
drawerToggle = new ActionBarDrawerToggle(
|
||||||
|
this, /* host Activity */
|
||||||
|
drawerLayout, /* DrawerLayout object */
|
||||||
|
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawerOpened(View drawerView) {
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawerClosed(View drawerView) {
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drawerLayout.setDrawerListener(drawerToggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
// The action bar home/up action should open or close the drawer.
|
||||||
|
// ActionBarDrawerToggle will take care of this.
|
||||||
|
if (drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
if (drawerToggle != null) {
|
||||||
|
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||||
|
drawerToggle.syncState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (drawerToggle != null) {
|
||||||
|
// Pass any configuration change to the drawer toggle
|
||||||
|
drawerToggle.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt("drawerItemSelected", drawerList.getCheckedItemPosition());
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
package com.sismics.docs.dummy;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for providing sample content for user interfaces created by
|
|
||||||
* Android template wizards.
|
|
||||||
* <p>
|
|
||||||
* TODO: Replace all uses of this class before publishing your app.
|
|
||||||
*/
|
|
||||||
public class DummyContent {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of sample (dummy) items.
|
|
||||||
*/
|
|
||||||
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of sample (dummy) items, by ID.
|
|
||||||
*/
|
|
||||||
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Add 3 sample items.
|
|
||||||
addItem(new DummyItem("1", "Item 1"));
|
|
||||||
addItem(new DummyItem("2", "Item 2"));
|
|
||||||
addItem(new DummyItem("3", "Item 3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addItem(DummyItem item) {
|
|
||||||
ITEMS.add(item);
|
|
||||||
ITEM_MAP.put(item.id, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dummy item representing a piece of content.
|
|
||||||
*/
|
|
||||||
public static class DummyItem {
|
|
||||||
public String id;
|
|
||||||
public String content;
|
|
||||||
|
|
||||||
public DummyItem(String id, String content) {
|
|
||||||
this.id = id;
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.sismics.docs.listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple listener.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public interface CallbackListener {
|
||||||
|
public void onComplete();
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package com.sismics.docs.model.application;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.JsonHttpResponseHandler;
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
import com.sismics.docs.resource.UserResource;
|
||||||
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global context of the application.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class ApplicationContext {
|
||||||
|
/**
|
||||||
|
* Singleton's instance.
|
||||||
|
*/
|
||||||
|
private static ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response of /user/info
|
||||||
|
*/
|
||||||
|
private JSONObject userInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*/
|
||||||
|
private ApplicationContext() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a singleton of ApplicationContext.
|
||||||
|
*
|
||||||
|
* @return Singleton of ApplicationContext
|
||||||
|
*/
|
||||||
|
public static ApplicationContext getInstance() {
|
||||||
|
if (applicationContext == null) {
|
||||||
|
applicationContext = new ApplicationContext();
|
||||||
|
}
|
||||||
|
return applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if current user is logged in.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
return userInfo != null && !userInfo.optBoolean("anonymous");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of userInfo
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public JSONObject getUserInfo() {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of userInfo
|
||||||
|
*
|
||||||
|
* @param json
|
||||||
|
*/
|
||||||
|
public void setUserInfo(Context context, JSONObject json) {
|
||||||
|
this.userInfo = json;
|
||||||
|
PreferenceUtil.setCachedJson(context, PreferenceUtil.PREF_CACHED_USER_INFO_JSON, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously get user info.
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
* @param callbackListener
|
||||||
|
*/
|
||||||
|
public void fetchUserInfo(final Activity activity, final CallbackListener callbackListener) {
|
||||||
|
UserResource.info(activity.getApplicationContext(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final JSONObject json) {
|
||||||
|
// Save data in application context
|
||||||
|
if (!json.optBoolean("anonymous", true)) {
|
||||||
|
setUserInfo(activity.getApplicationContext(), json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
if (callbackListener != null) {
|
||||||
|
callbackListener.onComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.androidquery.callback.AbstractAjaxCallback;
|
||||||
|
import com.loopj.android.http.AsyncHttpClient;
|
||||||
|
import com.loopj.android.http.PersistentCookieStore;
|
||||||
|
import com.sismics.docs.util.ApplicationUtil;
|
||||||
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for API access.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class BaseResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-Agent to use.
|
||||||
|
*/
|
||||||
|
private static String USER_AGENT = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept-Language header.
|
||||||
|
*/
|
||||||
|
private static String ACCEPT_LANGUAGE = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP client.
|
||||||
|
*/
|
||||||
|
protected static AsyncHttpClient client = new AsyncHttpClient();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 20sec default timeout
|
||||||
|
client.setTimeout(60000);
|
||||||
|
try {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
|
||||||
|
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||||
|
client.setSSLSocketFactory(sf);
|
||||||
|
AbstractAjaxCallback.setSSF(sf);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource initialization.
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
protected static void init(Context context) {
|
||||||
|
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
|
||||||
|
client.setCookieStore(cookieStore);
|
||||||
|
|
||||||
|
if (USER_AGENT == null) {
|
||||||
|
USER_AGENT = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
|
||||||
|
client.setUserAgent(USER_AGENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACCEPT_LANGUAGE == null) {
|
||||||
|
Locale locale = Locale.getDefault();
|
||||||
|
ACCEPT_LANGUAGE = locale.getLanguage() + "_" + locale.getCountry();
|
||||||
|
client.addHeader("Accept-Language", ACCEPT_LANGUAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket factory to allow self-signed certificates.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public static class MySSLSocketFactory extends SSLSocketFactory {
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
|
||||||
|
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(truststore);
|
||||||
|
|
||||||
|
TrustManager tm = new X509TrustManager() {
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sslContext.init(null, new TrustManager[] { tm }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
||||||
|
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket() throws IOException {
|
||||||
|
return sslContext.getSocketFactory().createSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cleaned API URL.
|
||||||
|
* @param context Context
|
||||||
|
* @return Cleaned API URL
|
||||||
|
*/
|
||||||
|
protected static String getApiUrl(Context context) {
|
||||||
|
String serverUrl = PreferenceUtil.getServerUrl(context);
|
||||||
|
|
||||||
|
if (serverUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverUrl + "/api";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.JsonHttpResponseHandler;
|
||||||
|
import com.loopj.android.http.RequestParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /user API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class UserResource extends BaseResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /user/login.
|
||||||
|
* @param context Context
|
||||||
|
* @param username Username
|
||||||
|
* @param password Password
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void login(Context context, String username, String password, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
params.put("username", username);
|
||||||
|
params.put("password", password);
|
||||||
|
params.put("remember", "true");
|
||||||
|
client.post(getApiUrl(context) + "/user/login", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /user.
|
||||||
|
* @param context Context
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void info(Context context, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
client.get(getApiUrl(context) + "/user", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /user/logout.
|
||||||
|
* @param context Context
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void logout(Context context, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
client.post(getApiUrl(context) + "/user/logout", params, responseHandler);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.sismics.docs.ui.form;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class Validable {
|
||||||
|
|
||||||
|
private View view;
|
||||||
|
|
||||||
|
private boolean isValidated = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of view.
|
||||||
|
* @return view
|
||||||
|
*/
|
||||||
|
public View getView() {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of view.
|
||||||
|
* @param view view
|
||||||
|
*/
|
||||||
|
public void setView(View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of isValidated.
|
||||||
|
* @return isValidated
|
||||||
|
*/
|
||||||
|
public boolean isValidated() {
|
||||||
|
return isValidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of isValidated.
|
||||||
|
* @param isValidated isValidated
|
||||||
|
*/
|
||||||
|
public void setValidated(boolean isValidated) {
|
||||||
|
this.isValidated = isValidated;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.sismics.docs.ui.form;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
import com.sismics.docs.ui.form.validator.ValidatorType;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for form validation.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Validator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of validable elements.
|
||||||
|
*/
|
||||||
|
private Map<View, Validable> validables = new HashMap<View, Validable>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when the validation of one element has changed.
|
||||||
|
*/
|
||||||
|
private CallbackListener onValidationChanged;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the validator show validation errors.
|
||||||
|
*/
|
||||||
|
private boolean showErrors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param showErrors True to display validation errors
|
||||||
|
*/
|
||||||
|
public Validator(boolean showErrors) {
|
||||||
|
this.showErrors = showErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of onValidationChanged.
|
||||||
|
* @param onValidationChanged onValidationChanged
|
||||||
|
*/
|
||||||
|
public void setOnValidationChanged(CallbackListener onValidationChanged) {
|
||||||
|
this.onValidationChanged = onValidationChanged;
|
||||||
|
onValidationChanged.onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a validable element.
|
||||||
|
*
|
||||||
|
* @param editText Edit text
|
||||||
|
* @param validatorTypes Validators
|
||||||
|
*/
|
||||||
|
public void addValidable(final Context context, final EditText editText, final ValidatorType...validatorTypes) {
|
||||||
|
final Validable validable = new Validable();
|
||||||
|
validables.put(editText, validable);
|
||||||
|
|
||||||
|
editText.addTextChangedListener(new TextWatcher() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
validable.setValidated(true);
|
||||||
|
for (ValidatorType validatorType : validatorTypes) {
|
||||||
|
if (!validatorType.validate(s.toString())) {
|
||||||
|
if (showErrors) {
|
||||||
|
editText.setError(validatorType.getErrorMessage(context));
|
||||||
|
}
|
||||||
|
validable.setValidated(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validable.isValidated()) {
|
||||||
|
editText.setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onValidationChanged != null) {
|
||||||
|
onValidationChanged.onComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the element is validated.
|
||||||
|
*
|
||||||
|
* @param view View
|
||||||
|
* @return True if the element is validated
|
||||||
|
*/
|
||||||
|
public boolean isValidated(View view) {
|
||||||
|
return validables.get(view).isValidated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if all elements are validated.
|
||||||
|
*
|
||||||
|
* @return True if all elements are validated
|
||||||
|
*/
|
||||||
|
public boolean isValidated() {
|
||||||
|
for (Validable validable : validables.values()) {
|
||||||
|
if (!validable.isValidated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alphanumeric validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Alphanumeric implements ValidatorType {
|
||||||
|
|
||||||
|
private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return ALPHANUMERIC_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getString(R.string.validate_error_alphanumeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Email implements ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern de validation.
|
||||||
|
*/
|
||||||
|
private static Pattern EMAIL_PATTERN = Pattern.compile(".+@.+\\..+");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return EMAIL_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getResources().getString(R.string.validate_error_email);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text length validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Length implements ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum length.
|
||||||
|
*/
|
||||||
|
private int minLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum length.
|
||||||
|
*/
|
||||||
|
private int maxLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the last validation error was about a string too short.
|
||||||
|
*/
|
||||||
|
private boolean tooShort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param minLength Minimum length
|
||||||
|
* @param maxLength Maximum length
|
||||||
|
*/
|
||||||
|
public Length(int minLength, int maxLength) {
|
||||||
|
this.minLength = minLength;
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
tooShort = text.trim().length() < minLength;
|
||||||
|
return text.trim().length() >= minLength && text.trim().length() <= maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
if (tooShort) {
|
||||||
|
return context.getResources().getString(R.string.validate_error_length_min, minLength);
|
||||||
|
}
|
||||||
|
return context.getResources().getString(R.string.validate_error_length_max, maxLength);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text presence validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Required implements ValidatorType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return text.trim().length() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getString(R.string.validate_error_required);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for validation types.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public interface ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the validator is validated.
|
||||||
|
* @param text
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean validate(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an error message.
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getErrorMessage(Context context);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class on general application data.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class ApplicationUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns version name.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @return Nom de la version
|
||||||
|
*/
|
||||||
|
public static String getVersionName(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
return packageInfo.versionName;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns version number.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @return Numéro de version
|
||||||
|
*/
|
||||||
|
public static int getVersionCode(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
return packageInfo.versionCode;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for dialogs.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DialogUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dialog with an OK button.
|
||||||
|
*
|
||||||
|
* @param activity Context activity
|
||||||
|
* @param title Dialog title
|
||||||
|
* @param message Dialog message
|
||||||
|
*/
|
||||||
|
public static void showOkDialog(Activity activity, int title, int message) {
|
||||||
|
if (activity == null || activity.isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
|
||||||
|
builder.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).create().show();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,162 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.loopj.android.http.PersistentCookieStore;
|
||||||
|
|
||||||
|
import org.apache.http.cookie.Cookie;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class on preferences.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class PreferenceUtil {
|
||||||
|
|
||||||
|
public static final String PREF_CACHED_USER_INFO_JSON = "pref_cachedUserInfoJson";
|
||||||
|
public static final String PREF_SERVER_URL = "pref_ServerUrl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of boolean type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static boolean getBooleanPreference(Context context, String key, boolean defaultValue) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return sharedPreferences.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of string type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static String getStringPreference(Context context, String key) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return sharedPreferences.getString(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of integer type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static int getIntegerPreference(Context context, String key, int defaultValue) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
try {
|
||||||
|
String pref = sharedPreferences.getString(key, "");
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(pref);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
return sharedPreferences.getInt(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update JSON cache.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @param json JSON data
|
||||||
|
*/
|
||||||
|
public static void setCachedJson(Context context, String key, JSONObject json) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
sharedPreferences.edit().putString(key, json != null ? json.toString() : null).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON cache.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return JSON data
|
||||||
|
*/
|
||||||
|
public static JSONObject getCachedJson(Context context, String key) {
|
||||||
|
try {
|
||||||
|
return new JSONObject(getStringPreference(context, key));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// The cache is not parsable, clean this up
|
||||||
|
setCachedJson(context, key, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update server URL.
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void setServerUrl(Context context, String serverUrl) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
sharedPreferences.edit().putString(PREF_SERVER_URL, serverUrl).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty user caches.
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void resetUserCache(Context context) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putString(PREF_CACHED_USER_INFO_JSON, null);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns auth token cookie from shared preferences.
|
||||||
|
* @return Auth token
|
||||||
|
*/
|
||||||
|
public static String getAuthToken(Context context) {
|
||||||
|
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
|
||||||
|
List<Cookie> cookieList = cookieStore.getCookies();
|
||||||
|
for (Cookie cookie : cookieList) {
|
||||||
|
if (cookie.getName().equals("auth_token")) {
|
||||||
|
return cookie.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cleaned server URL.
|
||||||
|
* @param context Context
|
||||||
|
* @return Server URL
|
||||||
|
*/
|
||||||
|
public static String getServerUrl(Context context) {
|
||||||
|
String serverUrl = getStringPreference(context, PREF_SERVER_URL);
|
||||||
|
if (serverUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim
|
||||||
|
serverUrl = serverUrl.trim();
|
||||||
|
|
||||||
|
if (!serverUrl.startsWith("http")) {
|
||||||
|
// Try to add http
|
||||||
|
serverUrl = "http://" + serverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverUrl.endsWith("/")) {
|
||||||
|
// Delete last /
|
||||||
|
serverUrl = serverUrl.substring(0, serverUrl.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove /api
|
||||||
|
if (serverUrl.endsWith("/api")) {
|
||||||
|
serverUrl = serverUrl.substring(0, serverUrl.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverUrl;
|
||||||
|
}
|
||||||
|
}
|
BIN
docs-android/app/src/main/res/drawable-hdpi/drawer_shadow.9.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
docs-android/app/src/main/res/drawable-hdpi/ic_drawer.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 970 B |
Before Width: | Height: | Size: 5.1 KiB |
BIN
docs-android/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
docs-android/app/src/main/res/drawable-xhdpi/ic_drawer.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 969 B |
@ -1,7 +0,0 @@
|
|||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/doc_detail_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context="com.sismics.docs.DocDetailActivity"
|
|
||||||
tools:ignore="MergeRootFrame" />
|
|
@ -1,10 +0,0 @@
|
|||||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/doc_list"
|
|
||||||
android:name="com.sismics.docs.DocListFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
tools:context="com.sismics.docs.DocListActivity"
|
|
||||||
tools:layout="@android:layout/list_content" />
|
|
@ -1,38 +0,0 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:baselineAligned="false"
|
|
||||||
android:divider="?android:attr/dividerHorizontal"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:showDividers="middle"
|
|
||||||
tools:context="com.sismics.docs.DocListActivity">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This layout is a two-pane layout for the Docs
|
|
||||||
master/detail flow. See res/values-large/refs.xml and
|
|
||||||
res/values-sw600dp/refs.xml for an example of layout aliases
|
|
||||||
that replace the single-pane version of the layout with
|
|
||||||
this two-pane version.
|
|
||||||
|
|
||||||
For more on layout aliases, see:
|
|
||||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
|
||||||
-->
|
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/doc_list"
|
|
||||||
android:name="com.sismics.docs.DocListFragment"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
tools:layout="@android:layout/list_content" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/doc_detail_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="3" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -1,9 +0,0 @@
|
|||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/doc_detail"
|
|
||||||
style="?android:attr/textAppearanceLarge"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
tools:context="com.sismics.docs.DocDetailFragment" />
|
|
117
docs-android/app/src/main/res/layout/login_activity.xml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_gravity="top|center_horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/loginForm"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:scrollbarStyle="insideOverlay"
|
||||||
|
android:visibility="gone" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginExplain"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:text="@string/server" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/txtServer"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textNoSuggestions">
|
||||||
|
</EditText>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:text="@string/username" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/txtUsername"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textNoSuggestions" >
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</EditText>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:text="@string/password" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/txtPassword"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.5"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textPassword" >
|
||||||
|
</EditText>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnConnect"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="50dip"
|
||||||
|
android:layout_marginTop="20dip"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/login"
|
||||||
|
android:textColor="@android:color/black" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="visible" >
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
33
docs-android/app/src/main/res/layout/main_activity.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:ignore="InconsistentLayout"
|
||||||
|
android:id="@+id/drawer_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/content_frame"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/left_drawer"
|
||||||
|
android:layout_width="240dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:background="@drawable/list_background_holo"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/drawer_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:choiceMode="singleChoice"
|
||||||
|
android:divider="@android:color/transparent"
|
||||||
|
android:dividerHeight="0dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v4.widget.DrawerLayout>
|
@ -1,10 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--
|
|
||||||
Layout alias to replace the single-pane version of the layout with a
|
|
||||||
two-pane version on Large screens.
|
|
||||||
|
|
||||||
For more on layout aliases, see:
|
|
||||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
|
||||||
-->
|
|
||||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
|
||||||
</resources>
|
|
@ -1,10 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<!--
|
|
||||||
Layout alias to replace the single-pane version of the layout with a
|
|
||||||
two-pane version on Large screens.
|
|
||||||
|
|
||||||
For more on layout aliases, see:
|
|
||||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
|
||||||
-->
|
|
||||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
|
||||||
</resources>
|
|
@ -2,6 +2,28 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="app_name">Sismics Docs</string>
|
<string name="app_name">Sismics Docs</string>
|
||||||
<string name="title_doc_detail">Doc Detail</string>
|
<string name="drawer_open">Open navigation drawer</string>
|
||||||
|
<string name="drawer_close">Close navigation drawer</string>
|
||||||
|
<string name="login_explain"><![CDATA[To start, you must download and install Sismics Docs Server on <a href="http://www.sismics.com/docs">www.sismics.com/docs</a> and enter your server URL below]]></string>
|
||||||
|
<string name="server">Server</string>
|
||||||
|
<string name="username">Username</string>
|
||||||
|
<string name="password">Password</string>
|
||||||
|
<string name="login">Login</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="login_fail_title">Login fail</string>
|
||||||
|
<string name="login_fail">Wrong username or password</string>
|
||||||
|
<string name="network_error_title">Network error</string>
|
||||||
|
<string name="network_error">Network error, please check internet connectivity and server URL</string>
|
||||||
|
<string name="invalid_url_title">Invalid URL</string>
|
||||||
|
<string name="invalid_url">Please check the server URL and try again</string>
|
||||||
|
<string name="crash_toast_text">A crash occurred, a report has been sent to resolve this problem</string>
|
||||||
|
|
||||||
|
<!-- Validation -->
|
||||||
|
<string name="validate_error_email">Invalid email</string>
|
||||||
|
<string name="validate_error_length_min">Too short (min. %d)</string>
|
||||||
|
<string name="validate_error_length_max">Too long (max. %d)</string>
|
||||||
|
<string name="validate_error_required">Required</string>
|
||||||
|
<string name="validate_error_alphanumeric">Only letters and numbers</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
3
docs-android/app/src/main/res/xml/preferences.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen>
|
||||||
|
</PreferenceScreen>
|