mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-03 22:58:38 +00:00
Added spotless plugin
This commit is contained in:
parent
ac36b94233
commit
8ec5280a01
26 changed files with 1467 additions and 1424 deletions
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
2
.idea/modules.xml
generated
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
26
build.gradle
26
build.gradle
|
@ -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
13
dependencies.gradle
Normal 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'
|
||||||
|
]
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue