Added spotless plugin

This commit is contained in:
Aidan Follestad 2017-06-10 12:30:15 -05:00
commit 8ec5280a01
26 changed files with 1467 additions and 1424 deletions

View file

@ -0,0 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
</profile>
</component>

View file

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

2
.idea/modules.xml generated
View file

@ -4,6 +4,8 @@
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/NockNock.iml" filepath="$PROJECT_DIR$/NockNock.iml" /> <module fileurl="file://$PROJECT_DIR$/NockNock.iml" filepath="$PROJECT_DIR$/NockNock.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" />
<module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" /> <module fileurl="file://$PROJECT_DIR$/nock-nock.iml" filepath="$PROJECT_DIR$/nock-nock.iml" />
</modules> </modules>
</component> </component>

View file

@ -4,8 +4,8 @@ android:
components: components:
- tools - tools
- platform-tools - platform-tools
- build-tools-24.0.1 - build-tools-26.0.0
- android-24 - android-26
- extra-android-support - extra-android-support
- extra-android-m2repository - extra-android-m2repository
- extra-google-m2repository - extra-google-m2repository

View file

@ -1,15 +1,15 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 24 compileSdkVersion versions.compileSdk
buildToolsVersion "24.0.1" buildToolsVersion versions.buildTools
defaultConfig { defaultConfig {
applicationId "com.afollestad.nocknock" applicationId "com.afollestad.nocknock"
minSdkVersion 21 minSdkVersion versions.minSdk
targetSdkVersion 24 targetSdkVersion versions.compileSdk
versionCode 14 versionCode versions.publishVersionCode
versionName "0.1.3.1" versionName versions.publishVersion
lintOptions { lintOptions {
abortOnError false abortOnError false
@ -33,11 +33,11 @@ android {
} }
dependencies { dependencies {
compile 'com.android.support:appcompat-v7:24.2.0' compile 'com.android.support:appcompat-v7:' + versions.supportLib
compile 'com.android.support:design:24.2.0' compile 'com.android.support:design:' + versions.supportLib
compile 'com.afollestad.material-dialogs:core:0.9.0.1' compile 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
compile 'com.afollestad.material-dialogs:commons:0.9.0.1' compile 'com.afollestad.material-dialogs:commons:' + versions.materialDialogs
compile 'com.afollestad:bridge:3.2.5' compile 'com.afollestad:bridge:' + versions.bridge
compile 'com.afollestad:inquiry:3.2.1' compile 'com.afollestad:inquiry:' + versions.inquiry
compile files('libs/rhino-1.7.7.1.jar') compile files('libs/rhino-1.7.7.1.jar')
} }

View file

@ -5,167 +5,165 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import com.afollestad.nocknock.R; import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerModel; import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ServerStatus; import com.afollestad.nocknock.api.ServerStatus;
import com.afollestad.nocknock.util.TimeUtil; import com.afollestad.nocknock.util.TimeUtil;
import com.afollestad.nocknock.views.StatusImageView; import com.afollestad.nocknock.views.StatusImageView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerVH> { public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerVH> {
private final Object LOCK = new Object(); private final Object LOCK = new Object();
private ArrayList<ServerModel> mServers; private ArrayList<ServerModel> mServers;
private ClickListener mListener; private ClickListener mListener;
public interface ClickListener { public interface ClickListener {
void onSiteSelected(int index, ServerModel model, boolean longClick); void onSiteSelected(int index, ServerModel model, boolean longClick);
}
public void performClick(int index, boolean longClick) {
if (mListener != null) {
mListener.onSiteSelected(index, mServers.get(index), longClick);
} }
}
public void performClick(int index, boolean longClick) { public ServerAdapter(ClickListener listener) {
if (mListener != null) { mListener = listener;
mListener.onSiteSelected(index, mServers.get(index), longClick); mServers = new ArrayList<>(2);
}
public void add(ServerModel model) {
mServers.add(model);
notifyItemInserted(mServers.size() - 1);
}
public void update(int index, ServerModel model) {
mServers.set(index, model);
notifyItemChanged(index);
}
public void update(ServerModel model) {
synchronized (LOCK) {
for (int i = 0; i < mServers.size(); i++) {
if (mServers.get(i).id == model.id) {
update(i, model);
break;
} }
}
} }
}
public ServerAdapter(ClickListener listener) { public void remove(int index) {
mListener = listener; mServers.remove(index);
mServers = new ArrayList<>(2); notifyItemRemoved(index);
} }
public void add(ServerModel model) { public void remove(ServerModel model) {
mServers.add(model); synchronized (LOCK) {
notifyItemInserted(mServers.size() - 1); for (int i = 0; i < mServers.size(); i++) {
} if (mServers.get(i).id == model.id) {
remove(i);
public void update(int index, ServerModel model) { break;
mServers.set(index, model);
notifyItemChanged(index);
}
public void update(ServerModel model) {
synchronized (LOCK) {
for (int i = 0; i < mServers.size(); i++) {
if (mServers.get(i).id == model.id) {
update(i, model);
break;
}
}
} }
}
}
}
public void set(ServerModel[] models) {
if (models == null || models.length == 0) {
mServers.clear();
return;
}
mServers = new ArrayList<>(models.length);
Collections.addAll(mServers, models);
notifyDataSetChanged();
}
public void clear() {
mServers.clear();
notifyDataSetChanged();
}
@Override
public ServerAdapter.ServerVH onCreateViewHolder(ViewGroup parent, int viewType) {
final View v =
LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_server, parent, false);
return new ServerVH(v, this);
}
@Override
public void onBindViewHolder(ServerAdapter.ServerVH holder, int position) {
final ServerModel model = mServers.get(position);
holder.textName.setText(model.name);
holder.textUrl.setText(model.url);
holder.iconStatus.setStatus(model.status);
switch (model.status) {
case ServerStatus.OK:
holder.textStatus.setText(R.string.everything_checks_out);
break;
case ServerStatus.WAITING:
holder.textStatus.setText(R.string.waiting);
break;
case ServerStatus.CHECKING:
holder.textStatus.setText(R.string.checking_status);
break;
case ServerStatus.ERROR:
holder.textStatus.setText(model.reason);
break;
} }
public void remove(int index) { if (model.checkInterval <= 0) {
mServers.remove(index); holder.textInterval.setText("");
notifyItemRemoved(index); } else {
final long now = System.currentTimeMillis();
final long nextCheck = model.lastCheck + model.checkInterval;
final long difference = nextCheck - now;
holder.textInterval.setText(TimeUtil.str(difference));
} }
}
public void remove(ServerModel model) { @Override
synchronized (LOCK) { public int getItemCount() {
for (int i = 0; i < mServers.size(); i++) { return mServers.size();
if (mServers.get(i).id == model.id) { }
remove(i);
break;
}
}
}
}
public void set(ServerModel[] models) { public static class ServerVH extends RecyclerView.ViewHolder
if (models == null || models.length == 0) { implements View.OnClickListener, View.OnLongClickListener {
mServers.clear();
return;
}
mServers = new ArrayList<>(models.length);
Collections.addAll(mServers, models);
notifyDataSetChanged();
}
public void clear() { final StatusImageView iconStatus;
mServers.clear(); final TextView textName;
notifyDataSetChanged(); final TextView textInterval;
final TextView textUrl;
final TextView textStatus;
final ServerAdapter adapter;
public ServerVH(View itemView, ServerAdapter adapter) {
super(itemView);
iconStatus = (StatusImageView) itemView.findViewById(R.id.iconStatus);
textName = (TextView) itemView.findViewById(R.id.textName);
textInterval = (TextView) itemView.findViewById(R.id.textInterval);
textUrl = (TextView) itemView.findViewById(R.id.textUrl);
textStatus = (TextView) itemView.findViewById(R.id.textStatus);
this.adapter = adapter;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
} }
@Override @Override
public ServerAdapter.ServerVH onCreateViewHolder(ViewGroup parent, int viewType) { public void onClick(View view) {
final View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_server, parent, false); adapter.performClick(getAdapterPosition(), false);
return new ServerVH(v, this);
} }
@Override @Override
public void onBindViewHolder(ServerAdapter.ServerVH holder, int position) { public boolean onLongClick(View view) {
final ServerModel model = mServers.get(position); adapter.performClick(getAdapterPosition(), true);
return false;
holder.textName.setText(model.name);
holder.textUrl.setText(model.url);
holder.iconStatus.setStatus(model.status);
switch (model.status) {
case ServerStatus.OK:
holder.textStatus.setText(R.string.everything_checks_out);
break;
case ServerStatus.WAITING:
holder.textStatus.setText(R.string.waiting);
break;
case ServerStatus.CHECKING:
holder.textStatus.setText(R.string.checking_status);
break;
case ServerStatus.ERROR:
holder.textStatus.setText(model.reason);
break;
}
if (model.checkInterval <= 0) {
holder.textInterval.setText("");
} else {
final long now = System.currentTimeMillis();
final long nextCheck = model.lastCheck + model.checkInterval;
final long difference = nextCheck - now;
holder.textInterval.setText(TimeUtil.str(difference));
}
}
@Override
public int getItemCount() {
return mServers.size();
}
public static class ServerVH extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
final StatusImageView iconStatus;
final TextView textName;
final TextView textInterval;
final TextView textUrl;
final TextView textStatus;
final ServerAdapter adapter;
public ServerVH(View itemView, ServerAdapter adapter) {
super(itemView);
iconStatus = (StatusImageView) itemView.findViewById(R.id.iconStatus);
textName = (TextView) itemView.findViewById(R.id.textName);
textInterval = (TextView) itemView.findViewById(R.id.textInterval);
textUrl = (TextView) itemView.findViewById(R.id.textUrl);
textStatus = (TextView) itemView.findViewById(R.id.textStatus);
this.adapter = adapter;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
adapter.performClick(getAdapterPosition(), false);
}
@Override
public boolean onLongClick(View view) {
adapter.performClick(getAdapterPosition(), true);
return false;
}
} }
}
} }

View file

@ -1,36 +1,23 @@
package com.afollestad.nocknock.api; package com.afollestad.nocknock.api;
import com.afollestad.inquiry.annotations.Column; import com.afollestad.inquiry.annotations.Column;
import java.io.Serializable; import java.io.Serializable;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class ServerModel implements Serializable { public class ServerModel implements Serializable {
public ServerModel() { public ServerModel() {}
}
@Column(name = "_id", primaryKey = true, notNull = true, autoIncrement = true) @Column(name = "_id", primaryKey = true, notNull = true, autoIncrement = true)
public long id; public long id;
@Column
public String name;
@Column
public String url;
@Column
@ServerStatus.Enum
public int status;
@Column
public long checkInterval;
@Column
public long lastCheck;
@Column
public String reason;
@Column @Column public String name;
@ValidationMode.Enum @Column public String url;
public int validationMode; @Column @ServerStatus.Enum public int status;
@Column @Column public long checkInterval;
public String validationContent; @Column public long lastCheck;
} @Column public String reason;
@Column @ValidationMode.Enum public int validationMode;
@Column public String validationContent;
}

View file

@ -1,21 +1,18 @@
package com.afollestad.nocknock.api; package com.afollestad.nocknock.api;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public final class ServerStatus { public final class ServerStatus {
public final static int OK = 1; public static final int OK = 1;
public final static int WAITING = 2; public static final int WAITING = 2;
public final static int CHECKING = 3; public static final int CHECKING = 3;
public final static int ERROR = 4; public static final int ERROR = 4;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({OK, WAITING, CHECKING, ERROR}) @IntDef({OK, WAITING, CHECKING, ERROR})
public @interface Enum {} public @interface Enum {}
} }

View file

@ -1,21 +1,17 @@
package com.afollestad.nocknock.api; package com.afollestad.nocknock.api;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public final class ValidationMode { public final class ValidationMode {
public final static int STATUS_CODE = 1; public static final int STATUS_CODE = 1;
public final static int TERM_SEARCH = 2; public static final int TERM_SEARCH = 2;
public final static int JAVASCRIPT = 3; public static final int JAVASCRIPT = 3;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({STATUS_CODE, TERM_SEARCH, JAVASCRIPT}) @IntDef({STATUS_CODE, TERM_SEARCH, JAVASCRIPT})
public @interface Enum { public @interface Enum {}
}
} }

View file

@ -6,28 +6,25 @@ import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Html; import android.text.Html;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.nocknock.R; import com.afollestad.nocknock.R;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class AboutDialog extends DialogFragment { public class AboutDialog extends DialogFragment {
public static void show(AppCompatActivity context) { public static void show(AppCompatActivity context) {
AboutDialog dialog = new AboutDialog(); AboutDialog dialog = new AboutDialog();
dialog.show(context.getSupportFragmentManager(), "[ABOUT_DIALOG]"); dialog.show(context.getSupportFragmentManager(), "[ABOUT_DIALOG]");
} }
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
return new MaterialDialog.Builder(getActivity()) return new MaterialDialog.Builder(getActivity())
.title(R.string.about) .title(R.string.about)
.positiveText(R.string.dismiss) .positiveText(R.string.dismiss)
.content(Html.fromHtml(getString(R.string.about_body))) .content(Html.fromHtml(getString(R.string.about_body)))
.contentLineSpacing(1.6f) .contentLineSpacing(1.6f)
.build(); .build();
} }
} }

View file

@ -3,26 +3,21 @@ package com.afollestad.nocknock.receivers;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.afollestad.inquiry.Inquiry; import com.afollestad.inquiry.Inquiry;
import com.afollestad.nocknock.api.ServerModel; import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.ui.MainActivity; import com.afollestad.nocknock.ui.MainActivity;
import com.afollestad.nocknock.util.AlarmUtil; import com.afollestad.nocknock.util.AlarmUtil;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
final Inquiry inq = Inquiry.newInstance(context, MainActivity.DB_NAME).build(false); final Inquiry inq = Inquiry.newInstance(context, MainActivity.DB_NAME).build(false);
ServerModel[] models = inq ServerModel[] models = inq.selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class).all();
.selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class) AlarmUtil.setSiteChecks(context, models);
.all(); inq.destroyInstance();
AlarmUtil.setSiteChecks(context, models);
inq.destroyInstance();
}
} }
}
} }

View file

@ -4,22 +4,19 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.afollestad.nocknock.services.CheckService; import com.afollestad.nocknock.services.CheckService;
import com.afollestad.nocknock.util.NetworkUtil; import com.afollestad.nocknock.util.NetworkUtil;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class ConnectivityReceiver extends BroadcastReceiver { public class ConnectivityReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
final boolean hasInternet = NetworkUtil.hasInternet(context); final boolean hasInternet = NetworkUtil.hasInternet(context);
Log.v("ConnectivityReceiver", "Connectivity state changed... has internet? " + hasInternet); Log.v("ConnectivityReceiver", "Connectivity state changed... has internet? " + hasInternet);
if (hasInternet) { if (hasInternet) {
context.startService(new Intent(context, CheckService.class) context.startService(
.putExtra(CheckService.ONLY_WAITING, true)); new Intent(context, CheckService.class).putExtra(CheckService.ONLY_WAITING, true));
}
} }
}
} }

View file

@ -3,7 +3,6 @@ package com.afollestad.nocknock.services;
import android.app.IntentService; import android.app.IntentService;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@ -13,8 +12,6 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import com.afollestad.bridge.Bridge; import com.afollestad.bridge.Bridge;
import com.afollestad.bridge.BridgeException; import com.afollestad.bridge.BridgeException;
import com.afollestad.bridge.Response; import com.afollestad.bridge.Response;
@ -29,213 +26,210 @@ import com.afollestad.nocknock.ui.MainActivity;
import com.afollestad.nocknock.ui.ViewSiteActivity; import com.afollestad.nocknock.ui.ViewSiteActivity;
import com.afollestad.nocknock.util.JsUtil; import com.afollestad.nocknock.util.JsUtil;
import com.afollestad.nocknock.util.NetworkUtil; import com.afollestad.nocknock.util.NetworkUtil;
import java.util.Locale; import java.util.Locale;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
@SuppressWarnings("CheckResult") @SuppressWarnings("CheckResult")
public class CheckService extends IntentService { public class CheckService extends IntentService {
public static String ACTION_CHECK_UPDATE = BuildConfig.APPLICATION_ID + ".CHECK_UPDATE"; public static String ACTION_CHECK_UPDATE = BuildConfig.APPLICATION_ID + ".CHECK_UPDATE";
public static String ACTION_RUNNING = BuildConfig.APPLICATION_ID + ".CHECK_RUNNING"; public static String ACTION_RUNNING = BuildConfig.APPLICATION_ID + ".CHECK_RUNNING";
public static String MODEL_ID = "model_id"; public static String MODEL_ID = "model_id";
public static String ONLY_WAITING = "only_waiting"; public static String ONLY_WAITING = "only_waiting";
public static int NOTI_ID = 3456; public static int NOTI_ID = 3456;
public CheckService() { public CheckService() {
super("NockNockCheckService"); super("NockNockCheckService");
}
private static void LOG(String msg, Object... format) {
if (format != null) msg = String.format(Locale.getDefault(), msg, format);
Log.v("NockNockService", msg);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onHandleIntent(Intent intent) {
Inquiry.newInstance(this, MainActivity.DB_NAME).build();
isRunning(true);
Bridge.config().defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
final Query<ServerModel, Integer> query =
Inquiry.get(this).selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class);
if (intent != null && intent.hasExtra(MODEL_ID)) {
query.where("_id = ?", intent.getLongExtra(MODEL_ID, -1));
} else if (intent != null && intent.getBooleanExtra(ONLY_WAITING, false)) {
query.where("status = ?", ServerStatus.WAITING);
}
final ServerModel[] sites = query.all();
if (sites == null || sites.length == 0) {
LOG("No sites added to check, service will terminate.");
isRunning(false);
stopSelf();
return;
} }
private static void LOG(String msg, Object... format) { LOG("Checking %d sites...", sites.length);
if (format != null) sendBroadcast(new Intent(ACTION_RUNNING));
msg = String.format(Locale.getDefault(), msg, format);
Log.v("NockNockService", msg); for (ServerModel site : sites) {
LOG("Updating %s (%s) status to WAITING...", site.name, site.url);
site.status = ServerStatus.WAITING;
updateStatus(site);
} }
@Nullable if (NetworkUtil.hasInternet(this)) {
@Override for (ServerModel site : sites) {
public IBinder onBind(Intent intent) { LOG("Checking %s (%s)...", site.name, site.url);
return null; site.status = ServerStatus.CHECKING;
} site.lastCheck = System.currentTimeMillis();
updateStatus(site);
@Override
protected void onHandleIntent(Intent intent) {
Inquiry.newInstance(this, MainActivity.DB_NAME).build();
isRunning(true);
Bridge.config()
.defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
final Query<ServerModel, Integer> query = Inquiry.get(this)
.selectFrom(MainActivity.SITES_TABLE_NAME, ServerModel.class);
if (intent != null && intent.hasExtra(MODEL_ID)) {
query.where("_id = ?", intent.getLongExtra(MODEL_ID, -1));
} else if (intent != null && intent.getBooleanExtra(ONLY_WAITING, false)) {
query.where("status = ?", ServerStatus.WAITING);
}
final ServerModel[] sites = query.all();
if (sites == null || sites.length == 0) {
LOG("No sites added to check, service will terminate.");
isRunning(false);
stopSelf();
return;
}
LOG("Checking %d sites...", sites.length);
sendBroadcast(new Intent(ACTION_RUNNING));
for (ServerModel site : sites) {
LOG("Updating %s (%s) status to WAITING...", site.name, site.url);
site.status = ServerStatus.WAITING;
updateStatus(site);
}
if (NetworkUtil.hasInternet(this)) {
for (ServerModel site : sites) {
LOG("Checking %s (%s)...", site.name, site.url);
site.status = ServerStatus.CHECKING;
site.lastCheck = System.currentTimeMillis();
updateStatus(site);
try {
final Response response = Bridge.get(site.url)
.throwIfNotSuccess()
.cancellable(false)
.request()
.response();
site.reason = null;
site.status = ServerStatus.OK;
if (site.validationMode == ValidationMode.TERM_SEARCH) {
final String body = response.asString();
if (body == null || !body.contains(site.validationContent)) {
site.status = ServerStatus.ERROR;
site.reason = "Term \"" + site.validationContent + "\" not found in response body.";
}
} else if (site.validationMode == ValidationMode.JAVASCRIPT) {
final String body = response.asString();
site.reason = JsUtil.exec(site.validationContent, body);
if (site.reason != null && !site.toString().isEmpty())
site.status = ServerStatus.ERROR;
}
if (site.status == ServerStatus.ERROR)
showNotification(this, site);
} catch (BridgeException e) {
processError(e, site);
}
updateStatus(site);
}
} else {
LOG("No internet connection, waiting.");
}
isRunning(false);
LOG("Service is finished!");
}
private void processError(BridgeException e, ServerModel site) {
site.status = ServerStatus.OK;
site.reason = null;
switch (e.reason()) {
case BridgeException.REASON_REQUEST_CANCELLED:
// Shouldn't happen
break;
case BridgeException.REASON_REQUEST_FAILED:
case BridgeException.REASON_RESPONSE_UNPARSEABLE:
case BridgeException.REASON_RESPONSE_UNSUCCESSFUL:
case BridgeException.REASON_RESPONSE_IOERROR:
//noinspection ConstantConditions
if (e.response() != null && e.response().code() == 401) {
// Don't consider 401 unsuccessful here
site.reason = null;
} else {
site.status = ServerStatus.ERROR;
site.reason = e.getMessage();
}
break;
case BridgeException.REASON_REQUEST_TIMEOUT:
site.status = ServerStatus.ERROR;
site.reason = getString(R.string.timeout);
break;
case BridgeException.REASON_RESPONSE_VALIDATOR_ERROR:
case BridgeException.REASON_RESPONSE_VALIDATOR_FALSE:
// Not used
break;
}
if (site.status != ServerStatus.OK) {
LOG("%s error: %s", site.name, site.reason);
showNotification(this, site);
}
}
private void updateStatus(ServerModel site) {
Inquiry.get(this)
.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", site.id)
.values(site)
.run();
sendBroadcast(new Intent(ACTION_CHECK_UPDATE)
.putExtra("model", site));
}
private void isRunning(boolean running) {
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean("check_service_running", running).commit();
}
public static boolean isRunning(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("check_service_running", false);
}
public static void isAppOpen(Context context, boolean open) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean("is_app_open", open).commit();
}
public static boolean isAppOpen(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("is_app_open", false);
}
private static void showNotification(Context context, ServerModel site) {
if (isAppOpen(context)) {
// Don't show notifications while the app is open
return;
}
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
final PendingIntent openIntent = PendingIntent.getActivity(context, 9669,
new Intent(context, ViewSiteActivity.class)
.putExtra("model", site)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_CANCEL_CURRENT);
final Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(site.name)
.setContentText(context.getString(R.string.something_wrong))
.setContentIntent(openIntent)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_VIBRATE)
.build();
nm.notify(site.url, NOTI_ID, noti);
}
@Override
public void onDestroy() {
try { try {
Inquiry.destroy(this); final Response response =
} catch (Throwable t2) { Bridge.get(site.url).throwIfNotSuccess().cancellable(false).request().response();
t2.printStackTrace();
site.reason = null;
site.status = ServerStatus.OK;
if (site.validationMode == ValidationMode.TERM_SEARCH) {
final String body = response.asString();
if (body == null || !body.contains(site.validationContent)) {
site.status = ServerStatus.ERROR;
site.reason = "Term \"" + site.validationContent + "\" not found in response body.";
}
} else if (site.validationMode == ValidationMode.JAVASCRIPT) {
final String body = response.asString();
site.reason = JsUtil.exec(site.validationContent, body);
if (site.reason != null && !site.toString().isEmpty()) site.status = ServerStatus.ERROR;
}
if (site.status == ServerStatus.ERROR) showNotification(this, site);
} catch (BridgeException e) {
processError(e, site);
} }
super.onDestroy(); updateStatus(site);
}
} else {
LOG("No internet connection, waiting.");
} }
isRunning(false);
LOG("Service is finished!");
}
private void processError(BridgeException e, ServerModel site) {
site.status = ServerStatus.OK;
site.reason = null;
switch (e.reason()) {
case BridgeException.REASON_REQUEST_CANCELLED:
// Shouldn't happen
break;
case BridgeException.REASON_REQUEST_FAILED:
case BridgeException.REASON_RESPONSE_UNPARSEABLE:
case BridgeException.REASON_RESPONSE_UNSUCCESSFUL:
case BridgeException.REASON_RESPONSE_IOERROR:
//noinspection ConstantConditions
if (e.response() != null && e.response().code() == 401) {
// Don't consider 401 unsuccessful here
site.reason = null;
} else {
site.status = ServerStatus.ERROR;
site.reason = e.getMessage();
}
break;
case BridgeException.REASON_REQUEST_TIMEOUT:
site.status = ServerStatus.ERROR;
site.reason = getString(R.string.timeout);
break;
case BridgeException.REASON_RESPONSE_VALIDATOR_ERROR:
case BridgeException.REASON_RESPONSE_VALIDATOR_FALSE:
// Not used
break;
}
if (site.status != ServerStatus.OK) {
LOG("%s error: %s", site.name, site.reason);
showNotification(this, site);
}
}
private void updateStatus(ServerModel site) {
Inquiry.get(this)
.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", site.id)
.values(site)
.run();
sendBroadcast(new Intent(ACTION_CHECK_UPDATE).putExtra("model", site));
}
private void isRunning(boolean running) {
PreferenceManager.getDefaultSharedPreferences(this)
.edit()
.putBoolean("check_service_running", running)
.commit();
}
public static boolean isRunning(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("check_service_running", false);
}
public static void isAppOpen(Context context, boolean open) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean("is_app_open", open)
.commit();
}
public static boolean isAppOpen(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("is_app_open", false);
}
private static void showNotification(Context context, ServerModel site) {
if (isAppOpen(context)) {
// Don't show notifications while the app is open
return;
}
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
final PendingIntent openIntent =
PendingIntent.getActivity(
context,
9669,
new Intent(context, ViewSiteActivity.class)
.putExtra("model", site)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_CANCEL_CURRENT);
final Notification noti =
new NotificationCompat.Builder(context)
.setContentTitle(site.name)
.setContentText(context.getString(R.string.something_wrong))
.setContentIntent(openIntent)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(
BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_VIBRATE)
.build();
nm.notify(site.url, NOTI_ID, noti);
}
@Override
public void onDestroy() {
try {
Inquiry.destroy(this);
} catch (Throwable t2) {
t2.printStackTrace();
}
super.onDestroy();
}
} }

View file

@ -20,236 +20,254 @@ import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import com.afollestad.nocknock.R; import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerModel; import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.api.ServerStatus; import com.afollestad.nocknock.api.ServerStatus;
import com.afollestad.nocknock.api.ValidationMode; import com.afollestad.nocknock.api.ValidationMode;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class AddSiteActivity extends AppCompatActivity implements View.OnClickListener { public class AddSiteActivity extends AppCompatActivity implements View.OnClickListener {
private View rootLayout; private View rootLayout;
private Toolbar toolbar; private Toolbar toolbar;
private TextInputLayout nameTiLayout; private TextInputLayout nameTiLayout;
private EditText inputName; private EditText inputName;
private TextInputLayout urlTiLayout; private TextInputLayout urlTiLayout;
private EditText inputUrl; private EditText inputUrl;
private EditText inputInterval; private EditText inputInterval;
private Spinner spinnerInterval; private Spinner spinnerInterval;
private TextView textUrlWarning; private TextView textUrlWarning;
private Spinner responseValidationSpinner; private Spinner responseValidationSpinner;
private boolean isClosing; private boolean isClosing;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_addsite); setContentView(R.layout.activity_addsite);
rootLayout = findViewById(R.id.rootView); rootLayout = findViewById(R.id.rootView);
nameTiLayout = (TextInputLayout) findViewById(R.id.nameTiLayout); nameTiLayout = (TextInputLayout) findViewById(R.id.nameTiLayout);
inputName = (EditText) findViewById(R.id.inputName); inputName = (EditText) findViewById(R.id.inputName);
urlTiLayout = (TextInputLayout) findViewById(R.id.urlTiLayout); urlTiLayout = (TextInputLayout) findViewById(R.id.urlTiLayout);
inputUrl = (EditText) findViewById(R.id.inputUrl); inputUrl = (EditText) findViewById(R.id.inputUrl);
textUrlWarning = (TextView) findViewById(R.id.textUrlWarning); textUrlWarning = (TextView) findViewById(R.id.textUrlWarning);
inputInterval = (EditText) findViewById(R.id.checkIntervalInput); inputInterval = (EditText) findViewById(R.id.checkIntervalInput);
spinnerInterval = (Spinner) findViewById(R.id.checkIntervalSpinner); spinnerInterval = (Spinner) findViewById(R.id.checkIntervalSpinner);
responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode); responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode);
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(view -> closeActivityWithReveal()); toolbar.setNavigationOnClickListener(view -> closeActivityWithReveal());
if (savedInstanceState == null) { if (savedInstanceState == null) {
rootLayout.setVisibility(View.INVISIBLE); rootLayout.setVisibility(View.INVISIBLE);
ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver(); ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver();
if (viewTreeObserver.isAlive()) { if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { viewTreeObserver.addOnGlobalLayoutListener(
@Override new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() { @Override
circularRevealActivity(); public void onGlobalLayout() {
rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); circularRevealActivity();
} rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}); }
} });
} }
ArrayAdapter<String> intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.interval_options));
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
spinnerInterval.setAdapter(intervalOptionsAdapter);
inputUrl.setOnFocusChangeListener((view, hasFocus) -> {
if (!hasFocus) {
final String inputStr = inputUrl.getText().toString().trim();
if (inputStr.isEmpty()) return;
final Uri uri = Uri.parse(inputStr);
if (uri.getScheme() == null) {
inputUrl.setText("http://" + inputStr);
textUrlWarning.setVisibility(View.GONE);
} else if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
textUrlWarning.setVisibility(View.VISIBLE);
textUrlWarning.setText(R.string.warning_http_url);
} else {
textUrlWarning.setVisibility(View.GONE);
}
}
});
ArrayAdapter<String> validationOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break;
case 1:
modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
findViewById(R.id.doneBtn).setOnClickListener(this);
} }
@Override ArrayAdapter<String> intervalOptionsAdapter =
public void onBackPressed() { new ArrayAdapter<>(
closeActivityWithReveal(); this,
} R.layout.list_item_spinner,
getResources().getStringArray(R.array.interval_options));
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
spinnerInterval.setAdapter(intervalOptionsAdapter);
private void closeActivityWithReveal() { inputUrl.setOnFocusChangeListener(
if (isClosing) return; (view, hasFocus) -> {
isClosing = true; if (!hasFocus) {
final int fabSize = getIntent().getIntExtra("fab_size", toolbar.getMeasuredHeight()); final String inputStr = inputUrl.getText().toString().trim();
final int cx = (int) getIntent().getFloatExtra("fab_x", rootLayout.getMeasuredWidth() / 2) + (fabSize / 2); if (inputStr.isEmpty()) return;
final int cy = (int) getIntent().getFloatExtra("fab_y", rootLayout.getMeasuredHeight() / 2) + toolbar.getMeasuredHeight() + (fabSize / 2); final Uri uri = Uri.parse(inputStr);
float initialRadius = Math.max(cx, cy); if (uri.getScheme() == null) {
inputUrl.setText("http://" + inputStr);
final Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, initialRadius, 0); textUrlWarning.setVisibility(View.GONE);
circularReveal.setDuration(300); } else if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
circularReveal.setInterpolator(new AccelerateInterpolator()); textUrlWarning.setVisibility(View.VISIBLE);
circularReveal.addListener(new AnimatorListenerAdapter() { textUrlWarning.setText(R.string.warning_http_url);
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
rootLayout.setVisibility(View.INVISIBLE);
finish();
overridePendingTransition(0, 0);
}
});
circularReveal.start();
}
private void circularRevealActivity() {
final int cx = rootLayout.getMeasuredWidth() / 2;
final int cy = rootLayout.getMeasuredHeight() / 2;
final float finalRadius = Math.max(cx, cy);
final Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, 0, finalRadius);
circularReveal.setDuration(300);
circularReveal.setInterpolator(new DecelerateInterpolator());
rootLayout.setVisibility(View.VISIBLE);
circularReveal.start();
}
// Done button
@Override
public void onClick(View view) {
isClosing = true;
ServerModel model = new ServerModel();
model.name = inputName.getText().toString().trim();
model.url = inputUrl.getText().toString().trim();
model.status = ServerStatus.WAITING;
if (model.name.isEmpty()) {
nameTiLayout.setError(getString(R.string.please_enter_name));
isClosing = false;
return;
} else {
nameTiLayout.setError(null);
}
if (model.url.isEmpty()) {
urlTiLayout.setError(getString(R.string.please_enter_url));
isClosing = false;
return;
} else {
urlTiLayout.setError(null);
if (!Patterns.WEB_URL.matcher(model.url).find()) {
urlTiLayout.setError(getString(R.string.please_enter_valid_url));
isClosing = false;
return;
} else { } else {
final Uri uri = Uri.parse(model.url); textUrlWarning.setVisibility(View.GONE);
if (uri.getScheme() == null)
model.url = "http://" + model.url;
} }
} }
});
String intervalStr = inputInterval.getText().toString().trim(); ArrayAdapter<String> validationOptionsAdapter =
if (intervalStr.isEmpty()) intervalStr = "0"; new ArrayAdapter<>(
model.checkInterval = Integer.parseInt(intervalStr); this,
R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
switch (spinnerInterval.getSelectedItemPosition()) { searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
case 0: // minutes javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
model.checkInterval *= (60 * 1000);
break;
case 1: // hours
model.checkInterval *= (60 * 60 * 1000);
break;
case 2: // days
model.checkInterval *= (60 * 60 * 24 * 1000);
break;
default: // weeks
model.checkInterval *= (60 * 60 * 24 * 7 * 1000);
break;
}
model.lastCheck = System.currentTimeMillis() - model.checkInterval; switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break;
case 1:
modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break;
}
}
switch (responseValidationSpinner.getSelectedItemPosition()) { @Override
case 0: public void onNothingSelected(AdapterView<?> adapterView) {}
model.validationMode = ValidationMode.STATUS_CODE; });
model.validationContent = null;
break;
case 1:
model.validationMode = ValidationMode.TERM_SEARCH;
model.validationContent = ((EditText) findViewById(R.id.responseValidationSearchTerm)).getText().toString().trim();
break;
case 2:
model.validationMode = ValidationMode.JAVASCRIPT;
model.validationContent = ((EditText) findViewById(R.id.responseValidationScriptInput)).getText().toString().trim();
break;
}
setResult(RESULT_OK, new Intent() findViewById(R.id.doneBtn).setOnClickListener(this);
.putExtra("model", model)); }
finish();
overridePendingTransition(R.anim.fade_out, R.anim.fade_out); @Override
public void onBackPressed() {
closeActivityWithReveal();
}
private void closeActivityWithReveal() {
if (isClosing) return;
isClosing = true;
final int fabSize = getIntent().getIntExtra("fab_size", toolbar.getMeasuredHeight());
final int cx =
(int) getIntent().getFloatExtra("fab_x", rootLayout.getMeasuredWidth() / 2) + (fabSize / 2);
final int cy =
(int) getIntent().getFloatExtra("fab_y", rootLayout.getMeasuredHeight() / 2)
+ toolbar.getMeasuredHeight()
+ (fabSize / 2);
float initialRadius = Math.max(cx, cy);
final Animator circularReveal =
ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, initialRadius, 0);
circularReveal.setDuration(300);
circularReveal.setInterpolator(new AccelerateInterpolator());
circularReveal.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
rootLayout.setVisibility(View.INVISIBLE);
finish();
overridePendingTransition(0, 0);
}
});
circularReveal.start();
}
private void circularRevealActivity() {
final int cx = rootLayout.getMeasuredWidth() / 2;
final int cy = rootLayout.getMeasuredHeight() / 2;
final float finalRadius = Math.max(cx, cy);
final Animator circularReveal =
ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, 0, finalRadius);
circularReveal.setDuration(300);
circularReveal.setInterpolator(new DecelerateInterpolator());
rootLayout.setVisibility(View.VISIBLE);
circularReveal.start();
}
// Done button
@Override
public void onClick(View view) {
isClosing = true;
ServerModel model = new ServerModel();
model.name = inputName.getText().toString().trim();
model.url = inputUrl.getText().toString().trim();
model.status = ServerStatus.WAITING;
if (model.name.isEmpty()) {
nameTiLayout.setError(getString(R.string.please_enter_name));
isClosing = false;
return;
} else {
nameTiLayout.setError(null);
} }
}
if (model.url.isEmpty()) {
urlTiLayout.setError(getString(R.string.please_enter_url));
isClosing = false;
return;
} else {
urlTiLayout.setError(null);
if (!Patterns.WEB_URL.matcher(model.url).find()) {
urlTiLayout.setError(getString(R.string.please_enter_valid_url));
isClosing = false;
return;
} else {
final Uri uri = Uri.parse(model.url);
if (uri.getScheme() == null) model.url = "http://" + model.url;
}
}
String intervalStr = inputInterval.getText().toString().trim();
if (intervalStr.isEmpty()) intervalStr = "0";
model.checkInterval = Integer.parseInt(intervalStr);
switch (spinnerInterval.getSelectedItemPosition()) {
case 0: // minutes
model.checkInterval *= (60 * 1000);
break;
case 1: // hours
model.checkInterval *= (60 * 60 * 1000);
break;
case 2: // days
model.checkInterval *= (60 * 60 * 24 * 1000);
break;
default: // weeks
model.checkInterval *= (60 * 60 * 24 * 7 * 1000);
break;
}
model.lastCheck = System.currentTimeMillis() - model.checkInterval;
switch (responseValidationSpinner.getSelectedItemPosition()) {
case 0:
model.validationMode = ValidationMode.STATUS_CODE;
model.validationContent = null;
break;
case 1:
model.validationMode = ValidationMode.TERM_SEARCH;
model.validationContent =
((EditText) findViewById(R.id.responseValidationSearchTerm))
.getText()
.toString()
.trim();
break;
case 2:
model.validationMode = ValidationMode.JAVASCRIPT;
model.validationContent =
((EditText) findViewById(R.id.responseValidationScriptInput))
.getText()
.toString()
.trim();
break;
}
setResult(RESULT_OK, new Intent().putExtra("model", model));
finish();
overridePendingTransition(R.anim.fade_out, R.anim.fade_out);
}
}

View file

@ -27,7 +27,6 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.animation.PathInterpolator; import android.view.animation.PathInterpolator;
import android.widget.TextView; import android.widget.TextView;
import com.afollestad.bridge.Bridge; import com.afollestad.bridge.Bridge;
import com.afollestad.inquiry.Inquiry; import com.afollestad.inquiry.Inquiry;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
@ -41,286 +40,299 @@ import com.afollestad.nocknock.util.AlarmUtil;
import com.afollestad.nocknock.util.MathUtil; import com.afollestad.nocknock.util.MathUtil;
import com.afollestad.nocknock.views.DividerItemDecoration; import com.afollestad.nocknock.views.DividerItemDecoration;
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener, ServerAdapter.ClickListener { public class MainActivity extends AppCompatActivity
implements SwipeRefreshLayout.OnRefreshListener,
View.OnClickListener,
ServerAdapter.ClickListener {
private final static int ADD_SITE_RQ = 6969; private static final int ADD_SITE_RQ = 6969;
private final static int VIEW_SITE_RQ = 6923; private static final int VIEW_SITE_RQ = 6923;
public final static String DB_NAME = "nock_nock"; public static final String DB_NAME = "nock_nock";
public final static String SITES_TABLE_NAME_OLD = "sites"; public static final String SITES_TABLE_NAME_OLD = "sites";
public final static String SITES_TABLE_NAME = "site_models"; public static final String SITES_TABLE_NAME = "site_models";
private FloatingActionButton mFab; private FloatingActionButton mFab;
private RecyclerView mList; private RecyclerView mList;
private ServerAdapter mAdapter; private ServerAdapter mAdapter;
private TextView mEmptyText; private TextView mEmptyText;
private SwipeRefreshLayout mRefreshLayout; private SwipeRefreshLayout mRefreshLayout;
private ObjectAnimator mFabAnimator; private ObjectAnimator mFabAnimator;
private float mOrigFabX; private float mOrigFabX;
private float mOrigFabY; private float mOrigFabY;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver =
new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.v("MainActivity", "Received " + intent.getAction()); Log.v("MainActivity", "Received " + intent.getAction());
if (CheckService.ACTION_RUNNING.equals(intent.getAction())) { if (CheckService.ACTION_RUNNING.equals(intent.getAction())) {
if (mRefreshLayout != null) if (mRefreshLayout != null) mRefreshLayout.setRefreshing(false);
mRefreshLayout.setRefreshing(false); } else {
} else { final ServerModel model = (ServerModel) intent.getSerializableExtra("model");
final ServerModel model = (ServerModel) intent.getSerializableExtra("model"); if (mAdapter != null && mList != null && model != null) {
if (mAdapter != null && mList != null && model != null) { mList.post(() -> mAdapter.update(model));
mList.post(() -> mAdapter.update(model));
}
} }
}
} }
}; };
@SuppressLint("CommitPrefEdits") @SuppressLint("CommitPrefEdits")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
mAdapter = new ServerAdapter(this); mAdapter = new ServerAdapter(this);
mEmptyText = (TextView) findViewById(R.id.emptyText); mEmptyText = (TextView) findViewById(R.id.emptyText);
mList = (RecyclerView) findViewById(R.id.list); mList = (RecyclerView) findViewById(R.id.list);
mList.setLayoutManager(new LinearLayoutManager(this)); mList.setLayoutManager(new LinearLayoutManager(this));
mList.setAdapter(mAdapter); mList.setAdapter(mAdapter);
mList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); mList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); mRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
mRefreshLayout.setOnRefreshListener(this); mRefreshLayout.setOnRefreshListener(this);
mRefreshLayout.setColorSchemeColors(ContextCompat.getColor(this, R.color.md_green), mRefreshLayout.setColorSchemeColors(
ContextCompat.getColor(this, R.color.md_yellow), ContextCompat.getColor(this, R.color.md_green),
ContextCompat.getColor(this, R.color.md_red)); ContextCompat.getColor(this, R.color.md_yellow),
ContextCompat.getColor(this, R.color.md_red));
mFab = (FloatingActionButton) findViewById(R.id.fab); mFab = (FloatingActionButton) findViewById(R.id.fab);
mFab.setOnClickListener(this); mFab.setOnClickListener(this);
Inquiry.newInstance(this, DB_NAME).build(); Inquiry.newInstance(this, DB_NAME).build();
Bridge.config() Bridge.config().defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
.defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean("migrated_db", false)) { if (!sp.getBoolean("migrated_db", false)) {
final Inquiry mdb = Inquiry.newInstance(this, DB_NAME) final Inquiry mdb =
.instanceName("migrate_db") Inquiry.newInstance(this, DB_NAME).instanceName("migrate_db").build(false);
.build(false); final ServerModel[] models =
final ServerModel[] models = Inquiry.get(this) Inquiry.get(this)
.selectFrom(SITES_TABLE_NAME_OLD, ServerModel.class) .selectFrom(SITES_TABLE_NAME_OLD, ServerModel.class)
.projection("_id", "name", "url", "status", "checkInterval", "lastCheck", "reason") .projection("_id", "name", "url", "status", "checkInterval", "lastCheck", "reason")
.all(); .all();
if (models != null) { if (models != null) {
Log.d("SiteMigration", "Migrating " + models.length + " sites to the new table."); Log.d("SiteMigration", "Migrating " + models.length + " sites to the new table.");
for (ServerModel model : models) { for (ServerModel model : models) {
model.validationMode = ValidationMode.STATUS_CODE; model.validationMode = ValidationMode.STATUS_CODE;
model.validationContent = null; model.validationContent = null;
}
//noinspection CheckResult
mdb.insertInto(SITES_TABLE_NAME, ServerModel.class)
.values(models)
.run();
mdb.dropTable(SITES_TABLE_NAME_OLD);
}
sp.edit().putBoolean("migrated_db", true).commit();
} }
//noinspection CheckResult
mdb.insertInto(SITES_TABLE_NAME, ServerModel.class).values(models).run();
mdb.dropTable(SITES_TABLE_NAME_OLD);
}
sp.edit().putBoolean("migrated_db", true).commit();
}
}
private void showRefreshTutorial() {
if (mAdapter.getItemCount() == 0) return;
final SharedPreferences pr = PreferenceManager.getDefaultSharedPreferences(this);
if (pr.getBoolean("shown_swipe_refresh_tutorial", false)) return;
mFab.hide();
final View tutorialView = findViewById(R.id.swipeRefreshTutorial);
tutorialView.setVisibility(View.VISIBLE);
tutorialView.setAlpha(0f);
tutorialView.animate().cancel();
tutorialView.animate().setDuration(300).alpha(1f).start();
findViewById(R.id.understoodBtn)
.setOnClickListener(
view -> {
view.setOnClickListener(null);
findViewById(R.id.swipeRefreshTutorial).setVisibility(View.GONE);
pr.edit().putBoolean("shown_swipe_refresh_tutorial", true).commit();
mFab.show();
});
}
@Override
protected void onResume() {
super.onResume();
CheckService.isAppOpen(this, true);
try {
final IntentFilter filter = new IntentFilter();
filter.addAction(CheckService.ACTION_CHECK_UPDATE);
filter.addAction(CheckService.ACTION_RUNNING);
registerReceiver(mReceiver, filter);
} catch (Throwable t) {
t.printStackTrace();
} }
private void showRefreshTutorial() { refreshModels();
if (mAdapter.getItemCount() == 0) return; }
final SharedPreferences pr = PreferenceManager.getDefaultSharedPreferences(this);
if (pr.getBoolean("shown_swipe_refresh_tutorial", false)) return;
mFab.hide(); @Override
final View tutorialView = findViewById(R.id.swipeRefreshTutorial); protected void onPause() {
tutorialView.setVisibility(View.VISIBLE); super.onPause();
tutorialView.setAlpha(0f); CheckService.isAppOpen(this, false);
tutorialView.animate().cancel();
tutorialView.animate().setDuration(300).alpha(1f).start();
findViewById(R.id.understoodBtn).setOnClickListener(view -> { if (isFinishing()) {
view.setOnClickListener(null); Inquiry.destroy(this);
findViewById(R.id.swipeRefreshTutorial).setVisibility(View.GONE); }
pr.edit().putBoolean("shown_swipe_refresh_tutorial", true).commit();
mFab.show(); NotificationManagerCompat.from(this).cancel(CheckService.NOTI_ID);
try {
unregisterReceiver(mReceiver);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void refreshModels() {
mAdapter.clear();
mEmptyText.setVisibility(View.VISIBLE);
Inquiry.get(this).selectFrom(SITES_TABLE_NAME, ServerModel.class).all(this::setModels);
}
private void setModels(ServerModel[] models) {
mAdapter.set(models);
mEmptyText.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
AlarmUtil.setSiteChecks(this, models);
showRefreshTutorial();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.about) {
AboutDialog.show(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRefresh() {
if (CheckService.isRunning(this)) {
mRefreshLayout.setRefreshing(false);
return;
}
startService(new Intent(this, CheckService.class));
}
// FAB clicked
@Override
public void onClick(View view) {
mOrigFabX = mFab.getX();
mOrigFabY = mFab.getY();
final Path curve = MathUtil.bezierCurve(mFab, mList);
if (mFabAnimator != null) mFabAnimator.cancel();
mFabAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, curve);
mFabAnimator.setInterpolator(new PathInterpolator(.5f, .5f));
mFabAnimator.setDuration(300);
mFabAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
startActivityForResult(
new Intent(MainActivity.this, AddSiteActivity.class)
.putExtra("fab_x", mOrigFabX)
.putExtra("fab_y", mOrigFabY)
.putExtra("fab_size", mFab.getMeasuredWidth())
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION),
ADD_SITE_RQ);
mFab.postDelayed(
() -> {
mFab.setX(mOrigFabX);
mFab.setY(mOrigFabY);
},
600);
}
}); });
} mFabAnimator.start();
}
@Override @Override
protected void onResume() { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onResume(); super.onActivityResult(requestCode, resultCode, data);
CheckService.isAppOpen(this, true); if (resultCode == RESULT_OK) {
final ServerModel model = (ServerModel) data.getSerializableExtra("model");
try { if (requestCode == ADD_SITE_RQ) {
final IntentFilter filter = new IntentFilter(); mAdapter.add(model);
filter.addAction(CheckService.ACTION_CHECK_UPDATE); mEmptyText.setVisibility(View.GONE);
filter.addAction(CheckService.ACTION_RUNNING);
registerReceiver(mReceiver, filter);
} catch (Throwable t) {
t.printStackTrace();
}
refreshModels();
}
@Override
protected void onPause() {
super.onPause();
CheckService.isAppOpen(this, false);
if (isFinishing()) {
Inquiry.destroy(this);
}
NotificationManagerCompat.from(this).cancel(CheckService.NOTI_ID);
try {
unregisterReceiver(mReceiver);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void refreshModels() {
mAdapter.clear();
mEmptyText.setVisibility(View.VISIBLE);
Inquiry.get(this) Inquiry.get(this)
.selectFrom(SITES_TABLE_NAME, ServerModel.class) .insertInto(SITES_TABLE_NAME, ServerModel.class)
.all(this::setModels); .values(model)
.run(
inserted -> {
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
});
} else if (requestCode == VIEW_SITE_RQ) {
mAdapter.update(model);
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
}
} }
}
private void setModels(ServerModel[] models) { public static void removeSite(
mAdapter.set(models); final Context context, final ServerModel model, final Runnable onRemoved) {
mEmptyText.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); new MaterialDialog.Builder(context)
AlarmUtil.setSiteChecks(this, models); .title(R.string.remove_site)
showRefreshTutorial(); .content(Html.fromHtml(context.getString(R.string.remove_site_prompt, model.name)))
} .positiveText(R.string.remove)
.negativeText(android.R.string.cancel)
.onPositive(
(dialog, which) -> {
AlarmUtil.cancelSiteChecks(context, model);
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.cancel(model.url, CheckService.NOTI_ID);
//noinspection CheckResult
final Inquiry rinq =
Inquiry.newInstance(context, DB_NAME).instanceName("remove_site").build(false);
//noinspection CheckResult
rinq.deleteFrom(SITES_TABLE_NAME, ServerModel.class).where("_id = ?", model.id).run();
rinq.destroyInstance();
if (onRemoved != null) onRemoved.run();
})
.show();
}
@Override public static void checkSite(Context context, ServerModel model) {
public boolean onCreateOptionsMenu(Menu menu) { context.startService(
getMenuInflater().inflate(R.menu.menu_main, menu); new Intent(context, CheckService.class).putExtra(CheckService.MODEL_ID, model.id));
return super.onCreateOptionsMenu(menu); }
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public void onSiteSelected(final int index, final ServerModel model, boolean longClick) {
if (item.getItemId() == R.id.about) { if (longClick) {
AboutDialog.show(this); new MaterialDialog.Builder(this)
return true; .title(R.string.options)
} .items(R.array.site_long_options)
return super.onOptionsItemSelected(item); .negativeText(android.R.string.cancel)
.itemsCallback(
(dialog, itemView, which, text) -> {
if (which == 0) {
checkSite(MainActivity.this, model);
} else {
removeSite(
MainActivity.this,
model,
() -> {
mAdapter.remove(index);
mEmptyText.setVisibility(
mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
});
}
})
.show();
} else {
startActivityForResult(
new Intent(this, ViewSiteActivity.class).putExtra("model", model),
VIEW_SITE_RQ,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
} }
}
@Override }
public void onRefresh() {
if (CheckService.isRunning(this)) {
mRefreshLayout.setRefreshing(false);
return;
}
startService(new Intent(this, CheckService.class));
}
// FAB clicked
@Override
public void onClick(View view) {
mOrigFabX = mFab.getX();
mOrigFabY = mFab.getY();
final Path curve = MathUtil.bezierCurve(mFab, mList);
if (mFabAnimator != null)
mFabAnimator.cancel();
mFabAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, curve);
mFabAnimator.setInterpolator(new PathInterpolator(.5f, .5f));
mFabAnimator.setDuration(300);
mFabAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
startActivityForResult(new Intent(MainActivity.this, AddSiteActivity.class)
.putExtra("fab_x", mOrigFabX)
.putExtra("fab_y", mOrigFabY)
.putExtra("fab_size", mFab.getMeasuredWidth())
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION), ADD_SITE_RQ);
mFab.postDelayed(() -> {
mFab.setX(mOrigFabX);
mFab.setY(mOrigFabY);
}, 600);
}
});
mFabAnimator.start();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
final ServerModel model = (ServerModel) data.getSerializableExtra("model");
if (requestCode == ADD_SITE_RQ) {
mAdapter.add(model);
mEmptyText.setVisibility(View.GONE);
Inquiry.get(this).insertInto(SITES_TABLE_NAME, ServerModel.class)
.values(model)
.run(inserted -> {
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
});
} else if (requestCode == VIEW_SITE_RQ) {
mAdapter.update(model);
AlarmUtil.setSiteChecks(MainActivity.this, model);
checkSite(MainActivity.this, model);
}
}
}
public static void removeSite(final Context context, final ServerModel model, final Runnable onRemoved) {
new MaterialDialog.Builder(context)
.title(R.string.remove_site)
.content(Html.fromHtml(context.getString(R.string.remove_site_prompt, model.name)))
.positiveText(R.string.remove)
.negativeText(android.R.string.cancel)
.onPositive((dialog, which) -> {
AlarmUtil.cancelSiteChecks(context, model);
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
nm.cancel(model.url, CheckService.NOTI_ID);
//noinspection CheckResult
final Inquiry rinq = Inquiry.newInstance(context, DB_NAME)
.instanceName("remove_site")
.build(false);
//noinspection CheckResult
rinq.deleteFrom(SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", model.id)
.run();
rinq.destroyInstance();
if (onRemoved != null)
onRemoved.run();
}).show();
}
public static void checkSite(Context context, ServerModel model) {
context.startService(new Intent(context, CheckService.class)
.putExtra(CheckService.MODEL_ID, model.id));
}
@Override
public void onSiteSelected(final int index, final ServerModel model, boolean longClick) {
if (longClick) {
new MaterialDialog.Builder(this)
.title(R.string.options)
.items(R.array.site_long_options)
.negativeText(android.R.string.cancel)
.itemsCallback((dialog, itemView, which, text) -> {
if (which == 0) {
checkSite(MainActivity.this, model);
} else {
removeSite(MainActivity.this, model, () -> {
mAdapter.remove(index);
mEmptyText.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
});
}
}).show();
} else {
startActivityForResult(new Intent(this, ViewSiteActivity.class)
.putExtra("model", model), VIEW_SITE_RQ,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
}
}
}

View file

@ -19,7 +19,6 @@ import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import com.afollestad.bridge.Bridge; import com.afollestad.bridge.Bridge;
import com.afollestad.inquiry.Inquiry; import com.afollestad.inquiry.Inquiry;
import com.afollestad.nocknock.R; import com.afollestad.nocknock.R;
@ -29,312 +28,333 @@ import com.afollestad.nocknock.api.ValidationMode;
import com.afollestad.nocknock.services.CheckService; import com.afollestad.nocknock.services.CheckService;
import com.afollestad.nocknock.util.TimeUtil; import com.afollestad.nocknock.util.TimeUtil;
import com.afollestad.nocknock.views.StatusImageView; import com.afollestad.nocknock.views.StatusImageView;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad) public class ViewSiteActivity extends AppCompatActivity
*/ implements View.OnClickListener, Toolbar.OnMenuItemClickListener {
public class ViewSiteActivity extends AppCompatActivity implements View.OnClickListener, Toolbar.OnMenuItemClickListener {
private StatusImageView iconStatus; private StatusImageView iconStatus;
private EditText inputName; private EditText inputName;
private EditText inputUrl; private EditText inputUrl;
private EditText inputCheckInterval; private EditText inputCheckInterval;
private Spinner checkIntervalSpinner; private Spinner checkIntervalSpinner;
private TextView textLastCheckResult; private TextView textLastCheckResult;
private TextView textNextCheck; private TextView textNextCheck;
private TextView textUrlWarning; private TextView textUrlWarning;
private Spinner responseValidationSpinner; private Spinner responseValidationSpinner;
private ServerModel mModel; private ServerModel mModel;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver =
new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.v("ViewSiteActivity", "Received " + intent.getAction()); Log.v("ViewSiteActivity", "Received " + intent.getAction());
final ServerModel model = (ServerModel) intent.getSerializableExtra("model"); final ServerModel model = (ServerModel) intent.getSerializableExtra("model");
if (model != null) { if (model != null) {
mModel = model; mModel = model;
update();
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewsite);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(view -> finish());
toolbar.inflateMenu(R.menu.menu_viewsite);
toolbar.setOnMenuItemClickListener(this);
iconStatus = (StatusImageView) findViewById(R.id.iconStatus);
inputName = (EditText) findViewById(R.id.inputName);
inputUrl = (EditText) findViewById(R.id.inputUrl);
textUrlWarning = (TextView) findViewById(R.id.textUrlWarning);
inputCheckInterval = (EditText) findViewById(R.id.checkIntervalInput);
checkIntervalSpinner = (Spinner) findViewById(R.id.checkIntervalSpinner);
textLastCheckResult = (TextView) findViewById(R.id.textLastCheckResult);
textNextCheck = (TextView) findViewById(R.id.textNextCheck);
responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode);
ArrayAdapter<String> intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.interval_options));
intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
checkIntervalSpinner.setAdapter(intervalOptionsAdapter);
inputUrl.setOnFocusChangeListener((view, hasFocus) -> {
if (!hasFocus) {
final String inputStr = inputUrl.getText().toString().trim();
if (inputStr.isEmpty()) return;
final Uri uri = Uri.parse(inputStr);
if (uri.getScheme() == null) {
inputUrl.setText("http://" + inputStr);
textUrlWarning.setVisibility(View.GONE);
} else if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
textUrlWarning.setVisibility(View.VISIBLE);
textUrlWarning.setText(R.string.warning_http_url);
} else {
textUrlWarning.setVisibility(View.GONE);
}
}
});
ArrayAdapter<String> validationOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break;
case 1:
modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break;
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
mModel = (ServerModel) getIntent().getSerializableExtra("model");
update();
Bridge.config()
.defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && intent.hasExtra("model")) {
mModel = (ServerModel) intent.getSerializableExtra("model");
update(); update();
}
} }
} };
@SuppressLint({"SetTextI18n", "SwitchIntDef"}) @Override
private void update() { protected void onCreate(@Nullable Bundle savedInstanceState) {
final SimpleDateFormat df = new SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault()); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewsite);
iconStatus.setStatus(mModel.status); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
inputName.setText(mModel.name); toolbar.setNavigationOnClickListener(view -> finish());
inputUrl.setText(mModel.url); toolbar.inflateMenu(R.menu.menu_viewsite);
toolbar.setOnMenuItemClickListener(this);
if (mModel.lastCheck == 0) { iconStatus = (StatusImageView) findViewById(R.id.iconStatus);
textLastCheckResult.setText(R.string.none); inputName = (EditText) findViewById(R.id.inputName);
} else { inputUrl = (EditText) findViewById(R.id.inputUrl);
switch (mModel.status) { textUrlWarning = (TextView) findViewById(R.id.textUrlWarning);
case ServerStatus.CHECKING: inputCheckInterval = (EditText) findViewById(R.id.checkIntervalInput);
textLastCheckResult.setText(R.string.checking_status); checkIntervalSpinner = (Spinner) findViewById(R.id.checkIntervalSpinner);
break; textLastCheckResult = (TextView) findViewById(R.id.textLastCheckResult);
case ServerStatus.ERROR: textNextCheck = (TextView) findViewById(R.id.textNextCheck);
textLastCheckResult.setText(mModel.reason); responseValidationSpinner = (Spinner) findViewById(R.id.responseValidationMode);
break;
case ServerStatus.OK:
textLastCheckResult.setText(R.string.everything_checks_out);
break;
case ServerStatus.WAITING:
textLastCheckResult.setText(R.string.waiting);
break;
}
}
if (mModel.checkInterval == 0) { ArrayAdapter<String> intervalOptionsAdapter =
textNextCheck.setText(R.string.none_turned_off); new ArrayAdapter<>(
inputCheckInterval.setText(""); this,
checkIntervalSpinner.setSelection(0); R.layout.list_item_spinner,
} else { getResources().getStringArray(R.array.interval_options));
long lastCheck = mModel.lastCheck; intervalOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
if (lastCheck == 0) lastCheck = System.currentTimeMillis(); checkIntervalSpinner.setAdapter(intervalOptionsAdapter);
textNextCheck.setText(df.format(new Date(lastCheck + mModel.checkInterval)));
if (mModel.checkInterval >= TimeUtil.WEEK) { inputUrl.setOnFocusChangeListener(
inputCheckInterval.setText(Integer.toString((int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.WEEK)))); (view, hasFocus) -> {
checkIntervalSpinner.setSelection(3); if (!hasFocus) {
} else if (mModel.checkInterval >= TimeUtil.DAY) { final String inputStr = inputUrl.getText().toString().trim();
inputCheckInterval.setText(Integer.toString((int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.DAY)))); if (inputStr.isEmpty()) return;
checkIntervalSpinner.setSelection(2); final Uri uri = Uri.parse(inputStr);
} else if (mModel.checkInterval >= TimeUtil.HOUR) { if (uri.getScheme() == null) {
inputCheckInterval.setText(Integer.toString((int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.HOUR)))); inputUrl.setText("http://" + inputStr);
checkIntervalSpinner.setSelection(1); textUrlWarning.setVisibility(View.GONE);
} else if (mModel.checkInterval >= TimeUtil.MINUTE) { } else if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
inputCheckInterval.setText(Integer.toString((int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.MINUTE)))); textUrlWarning.setVisibility(View.VISIBLE);
checkIntervalSpinner.setSelection(0); textUrlWarning.setText(R.string.warning_http_url);
} else { } else {
inputCheckInterval.setText("0"); textUrlWarning.setVisibility(View.GONE);
checkIntervalSpinner.setSelection(0);
} }
} }
});
responseValidationSpinner.setSelection(mModel.validationMode - 1); ArrayAdapter<String> validationOptionsAdapter =
switch (mModel.validationMode) { new ArrayAdapter<>(
case ValidationMode.TERM_SEARCH: this,
((TextView) findViewById(R.id.responseValidationSearchTerm)).setText(mModel.validationContent); R.layout.list_item_spinner,
getResources().getStringArray(R.array.response_validation_options));
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown);
responseValidationSpinner.setAdapter(validationOptionsAdapter);
responseValidationSpinner.setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
final View searchTerm = findViewById(R.id.responseValidationSearchTerm);
final View javascript = findViewById(R.id.responseValidationScript);
final TextView modeDesc = (TextView) findViewById(R.id.validationModeDescription);
searchTerm.setVisibility(i == 1 ? View.VISIBLE : View.GONE);
javascript.setVisibility(i == 2 ? View.VISIBLE : View.GONE);
switch (i) {
case 0:
modeDesc.setText(R.string.validation_mode_status_desc);
break; break;
case ValidationMode.JAVASCRIPT: case 1:
((TextView) findViewById(R.id.responseValidationScriptInput)).setText(mModel.validationContent); modeDesc.setText(R.string.validation_mode_term_desc);
break;
case 2:
modeDesc.setText(R.string.validation_mode_javascript_desc);
break; break;
}
findViewById(R.id.doneBtn).setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
try {
final IntentFilter filter = new IntentFilter();
filter.addAction(CheckService.ACTION_CHECK_UPDATE);
// filter.addAction(CheckService.ACTION_RUNNING);
registerReceiver(mReceiver, filter);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
try {
unregisterReceiver(mReceiver);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void performSave(boolean withValidation) {
mModel.name = inputName.getText().toString().trim();
mModel.url = inputUrl.getText().toString().trim();
mModel.status = ServerStatus.WAITING;
if (withValidation && mModel.name.isEmpty()) {
inputName.setError(getString(R.string.please_enter_name));
return;
} else {
inputName.setError(null);
}
if (withValidation && mModel.url.isEmpty()) {
inputUrl.setError(getString(R.string.please_enter_url));
return;
} else {
inputUrl.setError(null);
if (withValidation && !Patterns.WEB_URL.matcher(mModel.url).find()) {
inputUrl.setError(getString(R.string.please_enter_valid_url));
return;
} else {
final Uri uri = Uri.parse(mModel.url);
if (uri.getScheme() == null)
mModel.url = "http://" + mModel.url;
} }
} }
String intervalStr = inputCheckInterval.getText().toString().trim(); @Override
if (intervalStr.isEmpty()) intervalStr = "0"; public void onNothingSelected(AdapterView<?> adapterView) {}
mModel.checkInterval = Integer.parseInt(intervalStr); });
switch (checkIntervalSpinner.getSelectedItemPosition()) { mModel = (ServerModel) getIntent().getSerializableExtra("model");
case 0: // minutes update();
mModel.checkInterval *= (60 * 1000);
break;
case 1: // hours
mModel.checkInterval *= (60 * 60 * 1000);
break;
case 2: // days
mModel.checkInterval *= (60 * 60 * 24 * 1000);
break;
default: // weeks
mModel.checkInterval *= (60 * 60 * 24 * 7 * 1000);
break;
}
mModel.lastCheck = System.currentTimeMillis() - mModel.checkInterval; Bridge.config().defaultHeader("User-Agent", getString(R.string.app_name) + " (Android)");
}
switch (responseValidationSpinner.getSelectedItemPosition()) { @Override
case 0: protected void onNewIntent(Intent intent) {
mModel.validationMode = ValidationMode.STATUS_CODE; super.onNewIntent(intent);
mModel.validationContent = null; if (intent != null && intent.hasExtra("model")) {
break; mModel = (ServerModel) intent.getSerializableExtra("model");
case 1: update();
mModel.validationMode = ValidationMode.TERM_SEARCH; }
mModel.validationContent = ((EditText) findViewById(R.id.responseValidationSearchTerm)).getText().toString().trim(); }
break;
case 2:
mModel.validationMode = ValidationMode.JAVASCRIPT;
mModel.validationContent = ((EditText) findViewById(R.id.responseValidationScriptInput)).getText().toString().trim();
break;
}
final Inquiry inq = Inquiry.newInstance(this, MainActivity.DB_NAME) @SuppressLint({"SetTextI18n", "SwitchIntDef"})
.build(false); private void update() {
//noinspection CheckResult final SimpleDateFormat df = new SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault());
inq.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", mModel.id) iconStatus.setStatus(mModel.status);
.values(mModel) inputName.setText(mModel.name);
.run(); inputUrl.setText(mModel.url);
inq.destroyInstance();
if (mModel.lastCheck == 0) {
textLastCheckResult.setText(R.string.none);
} else {
switch (mModel.status) {
case ServerStatus.CHECKING:
textLastCheckResult.setText(R.string.checking_status);
break;
case ServerStatus.ERROR:
textLastCheckResult.setText(mModel.reason);
break;
case ServerStatus.OK:
textLastCheckResult.setText(R.string.everything_checks_out);
break;
case ServerStatus.WAITING:
textLastCheckResult.setText(R.string.waiting);
break;
}
} }
// Save button if (mModel.checkInterval == 0) {
@Override textNextCheck.setText(R.string.none_turned_off);
public void onClick(View view) { inputCheckInterval.setText("");
performSave(true); checkIntervalSpinner.setSelection(0);
setResult(RESULT_OK, new Intent().putExtra("model", mModel)); } else {
finish(); long lastCheck = mModel.lastCheck;
if (lastCheck == 0) lastCheck = System.currentTimeMillis();
textNextCheck.setText(df.format(new Date(lastCheck + mModel.checkInterval)));
if (mModel.checkInterval >= TimeUtil.WEEK) {
inputCheckInterval.setText(
Integer.toString(
(int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.WEEK))));
checkIntervalSpinner.setSelection(3);
} else if (mModel.checkInterval >= TimeUtil.DAY) {
inputCheckInterval.setText(
Integer.toString(
(int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.DAY))));
checkIntervalSpinner.setSelection(2);
} else if (mModel.checkInterval >= TimeUtil.HOUR) {
inputCheckInterval.setText(
Integer.toString(
(int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.HOUR))));
checkIntervalSpinner.setSelection(1);
} else if (mModel.checkInterval >= TimeUtil.MINUTE) {
inputCheckInterval.setText(
Integer.toString(
(int) Math.ceil(((float) mModel.checkInterval / (float) TimeUtil.MINUTE))));
checkIntervalSpinner.setSelection(0);
} else {
inputCheckInterval.setText("0");
checkIntervalSpinner.setSelection(0);
}
} }
@Override responseValidationSpinner.setSelection(mModel.validationMode - 1);
public boolean onMenuItemClick(MenuItem item) { switch (mModel.validationMode) {
switch (item.getItemId()) { case ValidationMode.TERM_SEARCH:
case R.id.refresh: ((TextView) findViewById(R.id.responseValidationSearchTerm))
performSave(false); .setText(mModel.validationContent);
MainActivity.checkSite(this, mModel); break;
return true; case ValidationMode.JAVASCRIPT:
case R.id.remove: ((TextView) findViewById(R.id.responseValidationScriptInput))
MainActivity.removeSite(this, mModel, this::finish); .setText(mModel.validationContent);
return true; break;
}
return false;
} }
findViewById(R.id.doneBtn).setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
try {
final IntentFilter filter = new IntentFilter();
filter.addAction(CheckService.ACTION_CHECK_UPDATE);
// filter.addAction(CheckService.ACTION_RUNNING);
registerReceiver(mReceiver, filter);
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
try {
unregisterReceiver(mReceiver);
} catch (Throwable t) {
t.printStackTrace();
}
}
private void performSave(boolean withValidation) {
mModel.name = inputName.getText().toString().trim();
mModel.url = inputUrl.getText().toString().trim();
mModel.status = ServerStatus.WAITING;
if (withValidation && mModel.name.isEmpty()) {
inputName.setError(getString(R.string.please_enter_name));
return;
} else {
inputName.setError(null);
}
if (withValidation && mModel.url.isEmpty()) {
inputUrl.setError(getString(R.string.please_enter_url));
return;
} else {
inputUrl.setError(null);
if (withValidation && !Patterns.WEB_URL.matcher(mModel.url).find()) {
inputUrl.setError(getString(R.string.please_enter_valid_url));
return;
} else {
final Uri uri = Uri.parse(mModel.url);
if (uri.getScheme() == null) mModel.url = "http://" + mModel.url;
}
}
String intervalStr = inputCheckInterval.getText().toString().trim();
if (intervalStr.isEmpty()) intervalStr = "0";
mModel.checkInterval = Integer.parseInt(intervalStr);
switch (checkIntervalSpinner.getSelectedItemPosition()) {
case 0: // minutes
mModel.checkInterval *= (60 * 1000);
break;
case 1: // hours
mModel.checkInterval *= (60 * 60 * 1000);
break;
case 2: // days
mModel.checkInterval *= (60 * 60 * 24 * 1000);
break;
default: // weeks
mModel.checkInterval *= (60 * 60 * 24 * 7 * 1000);
break;
}
mModel.lastCheck = System.currentTimeMillis() - mModel.checkInterval;
switch (responseValidationSpinner.getSelectedItemPosition()) {
case 0:
mModel.validationMode = ValidationMode.STATUS_CODE;
mModel.validationContent = null;
break;
case 1:
mModel.validationMode = ValidationMode.TERM_SEARCH;
mModel.validationContent =
((EditText) findViewById(R.id.responseValidationSearchTerm))
.getText()
.toString()
.trim();
break;
case 2:
mModel.validationMode = ValidationMode.JAVASCRIPT;
mModel.validationContent =
((EditText) findViewById(R.id.responseValidationScriptInput))
.getText()
.toString()
.trim();
break;
}
final Inquiry inq = Inquiry.newInstance(this, MainActivity.DB_NAME).build(false);
//noinspection CheckResult
inq.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
.where("_id = ?", mModel.id)
.values(mModel)
.run();
inq.destroyInstance();
}
// Save button
@Override
public void onClick(View view) {
performSave(true);
setResult(RESULT_OK, new Intent().putExtra("model", mModel));
finish();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
performSave(false);
MainActivity.checkSite(this, mModel);
return true;
case R.id.remove:
MainActivity.removeSite(this, mModel, this::finish);
return true;
}
return false;
}
} }

View file

@ -5,55 +5,57 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.afollestad.nocknock.api.ServerModel; import com.afollestad.nocknock.api.ServerModel;
import com.afollestad.nocknock.services.CheckService; import com.afollestad.nocknock.services.CheckService;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class AlarmUtil { public class AlarmUtil {
private final static int BASE_RQC = 69; private static final int BASE_RQC = 69;
public static PendingIntent getSiteIntent(Context context, ServerModel site) { public static PendingIntent getSiteIntent(Context context, ServerModel site) {
return PendingIntent.getService(context, return PendingIntent.getService(
BASE_RQC + (int) site.id, context,
new Intent(context, CheckService.class) BASE_RQC + (int) site.id,
.putExtra(CheckService.MODEL_ID, site.id), new Intent(context, CheckService.class).putExtra(CheckService.MODEL_ID, site.id),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
} }
private static AlarmManager am(Context context) { private static AlarmManager am(Context context) {
return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
} }
public static void cancelSiteChecks(Context context, ServerModel site) { public static void cancelSiteChecks(Context context, ServerModel site) {
PendingIntent pi = getSiteIntent(context, site); PendingIntent pi = getSiteIntent(context, site);
am(context).cancel(pi); am(context).cancel(pi);
} }
public static void setSiteChecks(Context context, ServerModel site) { public static void setSiteChecks(Context context, ServerModel site) {
cancelSiteChecks(context, site); cancelSiteChecks(context, site);
if (site.checkInterval <= 0) return; if (site.checkInterval <= 0) return;
if (site.lastCheck <= 0) if (site.lastCheck <= 0) site.lastCheck = System.currentTimeMillis();
site.lastCheck = System.currentTimeMillis(); final long nextCheck = site.lastCheck + site.checkInterval;
final long nextCheck = site.lastCheck + site.checkInterval; final AlarmManager aMgr = am(context);
final AlarmManager aMgr = am(context); final PendingIntent serviceIntent = getSiteIntent(context, site);
final PendingIntent serviceIntent = getSiteIntent(context, site); aMgr.setRepeating(AlarmManager.RTC_WAKEUP, nextCheck, site.checkInterval, serviceIntent);
aMgr.setRepeating(AlarmManager.RTC_WAKEUP, nextCheck, site.checkInterval, serviceIntent); final SimpleDateFormat df =
final SimpleDateFormat df = new SimpleDateFormat("EEE MMM dd hh:mm:ssa z yyyy", Locale.getDefault()); new SimpleDateFormat("EEE MMM dd hh:mm:ssa z yyyy", Locale.getDefault());
Log.d("AlarmUtil", String.format(Locale.getDefault(), "Set site check alarm for %s (%s), check interval: %d, next check: %s", Log.d(
site.name, site.url, site.checkInterval, df.format(new Date(nextCheck)))); "AlarmUtil",
} String.format(
Locale.getDefault(),
"Set site check alarm for %s (%s), check interval: %d, next check: %s",
site.name,
site.url,
site.checkInterval,
df.format(new Date(nextCheck))));
}
public static void setSiteChecks(Context context, ServerModel[] sites) { public static void setSiteChecks(Context context, ServerModel[] sites) {
if (sites == null || sites.length == 0) return; if (sites == null || sites.length == 0) return;
for (ServerModel site : sites) for (ServerModel site : sites) setSiteChecks(context, site);
setSiteChecks(context, site); }
} }
}

View file

@ -1,72 +1,68 @@
package com.afollestad.nocknock.util; package com.afollestad.nocknock.util;
import android.util.Log; import android.util.Log;
import org.mozilla.javascript.Context; import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException; import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Function; import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Scriptable;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class JsUtil { public class JsUtil {
public static String exec(String code, String response) { public static String exec(String code, String response) {
try { try {
final String func = String.format( final String func =
"function validate(response) { " + String.format(
"try { " + "function validate(response) { "
"%s " + + "try { "
"} catch(e) { " + + "%s "
"return e; " + + "} catch(e) { "
"} " + + "return e; "
"}", code.replace("\n", " ")); + "} "
+ "}",
code.replace("\n", " "));
// Every Rhino VM begins with the enter() // Every Rhino VM begins with the enter()
// This Context is not Android's Context // This Context is not Android's Context
Context rhino = Context.enter(); Context rhino = Context.enter();
// Turn off optimization to make Rhino Android compatible // Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1); rhino.setOptimizationLevel(-1);
try { try {
Scriptable scope = rhino.initStandardObjects(); Scriptable scope = rhino.initStandardObjects();
// Note the forth argument is 1, which means the JavaScript source has // Note the forth argument is 1, which means the JavaScript source has
// been compressed to only one line using something like YUI // been compressed to only one line using something like YUI
rhino.evaluateString(scope, func, "JavaScript", 1, null); rhino.evaluateString(scope, func, "JavaScript", 1, null);
// Get the functionName defined in JavaScriptCode // Get the functionName defined in JavaScriptCode
Function jsFunction = (Function) scope.get("validate", scope); Function jsFunction = (Function) scope.get("validate", scope);
// Call the function with params // Call the function with params
Object jsResult = jsFunction.call(rhino, scope, scope, new Object[]{response}); Object jsResult = jsFunction.call(rhino, scope, scope, new Object[] {response});
// Parse the jsResult object to a String
String result = Context.toString(jsResult);
boolean success = result != null && result.equals("true");
String message = "The script returned a value other than true!";
if (!success && result != null && !result.equals("false")) {
if (result.equals("undefined")) {
message = "The script did not return or throw anything!";
} else {
message = result;
}
}
Log.d("JsUtil", "Evaluated to " + message + " (" + success + "): " + code);
return !success ? message : null;
} finally {
Context.exit();
}
} catch (EvaluatorException e) {
return e.getMessage();
// Parse the jsResult object to a String
String result = Context.toString(jsResult);
boolean success = result != null && result.equals("true");
String message = "The script returned a value other than true!";
if (!success && result != null && !result.equals("false")) {
if (result.equals("undefined")) {
message = "The script did not return or throw anything!";
} else {
message = result;
}
} }
}
private JsUtil() { Log.d("JsUtil", "Evaluated to " + message + " (" + success + "): " + code);
return !success ? message : null;
} finally {
Context.exit();
}
} catch (EvaluatorException e) {
return e.getMessage();
} }
}
private JsUtil() {}
} }

View file

@ -4,29 +4,27 @@ import android.graphics.Path;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.view.View; import android.view.View;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public final class MathUtil { public final class MathUtil {
public static Path bezierCurve(FloatingActionButton fab, View rootView) { public static Path bezierCurve(FloatingActionButton fab, View rootView) {
final int fabCenterX = (int) (fab.getX() + fab.getMeasuredWidth() / 2); final int fabCenterX = (int) (fab.getX() + fab.getMeasuredWidth() / 2);
final int fabCenterY = (int) (fab.getY() + fab.getMeasuredHeight() / 2); final int fabCenterY = (int) (fab.getY() + fab.getMeasuredHeight() / 2);
final int endCenterX = (rootView.getMeasuredWidth() / 2) - (fab.getMeasuredWidth() / 2); final int endCenterX = (rootView.getMeasuredWidth() / 2) - (fab.getMeasuredWidth() / 2);
final int endCenterY = (rootView.getMeasuredHeight() / 2) - (fab.getMeasuredHeight() / 2); final int endCenterY = (rootView.getMeasuredHeight() / 2) - (fab.getMeasuredHeight() / 2);
final int halfX = (fabCenterX - endCenterX) / 2; final int halfX = (fabCenterX - endCenterX) / 2;
final int halfY = (fabCenterY - endCenterY) / 2; final int halfY = (fabCenterY - endCenterY) / 2;
int mControlX = endCenterX + halfX; int mControlX = endCenterX + halfX;
int mControlY = endCenterY + halfY; int mControlY = endCenterY + halfY;
mControlY -= halfY; mControlY -= halfY;
mControlX += halfX; mControlX += halfX;
Path path = new Path(); Path path = new Path();
path.moveTo(fab.getX(), fab.getY()); path.moveTo(fab.getX(), fab.getY());
path.quadTo(mControlX, mControlY, endCenterX, endCenterY); path.quadTo(mControlX, mControlY, endCenterX, endCenterY);
return path; return path;
} }
} }

View file

@ -4,15 +4,13 @@ import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class NetworkUtil { public class NetworkUtil {
public static boolean hasInternet(Context context) { public static boolean hasInternet(Context context) {
final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); final ConnectivityManager cm =
final NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return activeNetwork != null && final NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
activeNetwork.isConnectedOrConnecting(); return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
} }
} }

View file

@ -1,32 +1,30 @@
package com.afollestad.nocknock.util; package com.afollestad.nocknock.util;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class TimeUtil { public class TimeUtil {
public final static long SECOND = 1000; public static final long SECOND = 1000;
public final static long MINUTE = SECOND * 60; public static final long MINUTE = SECOND * 60;
public final static long HOUR = MINUTE * 60; public static final long HOUR = MINUTE * 60;
public final static long DAY = HOUR * 24; public static final long DAY = HOUR * 24;
public final static long WEEK = DAY * 7; public static final long WEEK = DAY * 7;
public final static long MONTH = WEEK * 4; public static final long MONTH = WEEK * 4;
public static String str(long duration) { public static String str(long duration) {
if (duration <= 0) { if (duration <= 0) {
return ""; return "";
} else if (duration >= MONTH) { } else if (duration >= MONTH) {
return (int) Math.ceil(((float) duration / (float) MONTH)) + "mo"; return (int) Math.ceil(((float) duration / (float) MONTH)) + "mo";
} else if (duration >= WEEK) { } else if (duration >= WEEK) {
return (int) Math.ceil(((float) duration / (float) WEEK)) + "w"; return (int) Math.ceil(((float) duration / (float) WEEK)) + "w";
} else if (duration >= DAY) { } else if (duration >= DAY) {
return (int) Math.ceil(((float) duration / (float) DAY)) + "d"; return (int) Math.ceil(((float) duration / (float) DAY)) + "d";
} else if (duration >= HOUR) { } else if (duration >= HOUR) {
return (int) Math.ceil(((float) duration / (float) HOUR)) + "h"; return (int) Math.ceil(((float) duration / (float) HOUR)) + "h";
} else if (duration >= MINUTE) { } else if (duration >= MINUTE) {
return (int) Math.ceil(((float) duration / (float) MINUTE)) + "m"; return (int) Math.ceil(((float) duration / (float) MINUTE)) + "m";
} else { } else {
return "<1m"; return "<1m";
}
} }
}
} }

View file

@ -26,79 +26,75 @@ import android.view.View;
*/ */
public class DividerItemDecoration extends RecyclerView.ItemDecoration { public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{ private static final int[] ATTRS = new int[] {android.R.attr.listDivider};
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider; private Drawable mDivider;
private int mOrientation; private int mOrientation;
public DividerItemDecoration(Context context, int orientation) { public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS); final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0); mDivider = a.getDrawable(0);
a.recycle(); a.recycle();
setOrientation(orientation); setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
} }
mOrientation = orientation;
}
public void setOrientation(int orientation) { @Override
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { public void onDraw(Canvas c, RecyclerView parent) {
throw new IllegalArgumentException("invalid orientation"); if (mOrientation == VERTICAL_LIST) {
} drawVertical(c, parent);
mOrientation = orientation; } else {
drawHorizontal(c, parent);
} }
}
@Override public void drawVertical(Canvas c, RecyclerView parent) {
public void onDraw(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft();
if (mOrientation == VERTICAL_LIST) { final int right = parent.getWidth() - parent.getPaddingRight();
drawVertical(c, parent);
} else { final int childCount = parent.getChildCount();
drawHorizontal(c, parent); for (int i = 0; i < childCount; i++) {
} final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
} }
}
public void drawVertical(Canvas c, RecyclerView parent) { public void drawHorizontal(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft(); final int top = parent.getPaddingTop();
final int right = parent.getWidth() - parent.getPaddingRight(); final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount(); final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i); final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
.getLayoutParams(); final int left = child.getRight() + params.rightMargin;
final int top = child.getBottom() + params.bottomMargin; final int right = left + mDivider.getIntrinsicHeight();
final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom);
mDivider.setBounds(left, top, right, bottom); mDivider.draw(c);
mDivider.draw(c);
}
} }
}
public void drawHorizontal(Canvas c, RecyclerView parent) { @Override
final int top = parent.getPaddingTop(); public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
final int bottom = parent.getHeight() - parent.getPaddingBottom(); if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
final int childCount = parent.getChildCount(); } else {
for (int i = 0; i < childCount; i++) { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
} }
}
@Override }
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

View file

@ -3,45 +3,42 @@ package com.afollestad.nocknock.views;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.ImageView; import android.widget.ImageView;
import com.afollestad.nocknock.R; import com.afollestad.nocknock.R;
import com.afollestad.nocknock.api.ServerStatus; import com.afollestad.nocknock.api.ServerStatus;
/** /** @author Aidan Follestad (afollestad) */
* @author Aidan Follestad (afollestad)
*/
public class StatusImageView extends ImageView { public class StatusImageView extends ImageView {
public StatusImageView(Context context) { public StatusImageView(Context context) {
super(context); super(context);
setStatus(ServerStatus.OK); setStatus(ServerStatus.OK);
} }
public StatusImageView(Context context, AttributeSet attrs) { public StatusImageView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setStatus(ServerStatus.OK); setStatus(ServerStatus.OK);
} }
public StatusImageView(Context context, AttributeSet attrs, int defStyleAttr) { public StatusImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
setStatus(ServerStatus.OK); setStatus(ServerStatus.OK);
} }
public void setStatus(@ServerStatus.Enum int status) { public void setStatus(@ServerStatus.Enum int status) {
switch (status) { switch (status) {
case ServerStatus.CHECKING: case ServerStatus.CHECKING:
case ServerStatus.WAITING: case ServerStatus.WAITING:
setImageResource(R.drawable.status_progress); setImageResource(R.drawable.status_progress);
setBackgroundResource(R.drawable.yellow_circle); setBackgroundResource(R.drawable.yellow_circle);
break; break;
case ServerStatus.ERROR: case ServerStatus.ERROR:
setImageResource(R.drawable.status_error); setImageResource(R.drawable.status_error);
setBackgroundResource(R.drawable.red_circle); setBackgroundResource(R.drawable.red_circle);
break; break;
case ServerStatus.OK: case ServerStatus.OK:
setImageResource(R.drawable.status_ok); setImageResource(R.drawable.status_ok);
setBackgroundResource(R.drawable.green_circle); setBackgroundResource(R.drawable.green_circle);
break; break;
}
} }
}
} }

View file

@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. apply from: './dependencies.gradle'
buildscript { buildscript {
apply from: './dependencies.gradle'
repositories { repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.android.tools.build:gradle:' + versions.gradlePlugin
classpath "com.diffplug.spotless:spotless-plugin-gradle:" + versions.spotlessPlugin
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
} }
} }
@ -17,6 +17,22 @@ allprojects {
jcenter() jcenter()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
} }
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
}
apply plugin: "com.diffplug.gradle.spotless"
spotless {
java {
target "**/*.java"
trimTrailingWhitespace()
removeUnusedImports()
googleJavaFormat()
endWithNewline()
}
}
} }
task clean(type: Delete) { task clean(type: Delete) {

13
dependencies.gradle Normal file
View file

@ -0,0 +1,13 @@
ext.versions = [
minSdk : 21,
compileSdk : 26,
buildTools : '26.0.0',
publishVersion : '0.1.3.1',
publishVersionCode: 14,
gradlePlugin : '2.3.2',
spotlessPlugin : '3.4.0',
supportLib : '25.3.1',
materialDialogs : '0.9.4.5',
bridge : '5.1.2',
inquiry : '5.0.0'
]

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip