The view site screen works. Added more custom graphics, not permanent yet.
|
@ -14,7 +14,9 @@
|
|||
android:theme="@style/AppTheme"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||
|
||||
<activity android:name=".ui.MainActivity">
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
@ -25,7 +27,16 @@
|
|||
<activity
|
||||
android:name=".ui.AddSiteActivity"
|
||||
android:label="@string/add_site"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.Transparent"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.ViewSiteActivity"
|
||||
android:label="@string/view_site"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.Ink"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
|
||||
<service
|
||||
android:name=".services.CheckService"
|
||||
|
|
|
@ -115,10 +115,14 @@ public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerVH>
|
|||
break;
|
||||
}
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
final long nextCheck = model.lastCheck + model.checkInterval;
|
||||
final long difference = nextCheck - now;
|
||||
holder.textInterval.setText(TimeUtil.str(difference));
|
||||
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
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package com.afollestad.nocknock.services;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -18,6 +23,7 @@ import com.afollestad.nocknock.R;
|
|||
import com.afollestad.nocknock.api.ServerModel;
|
||||
import com.afollestad.nocknock.api.ServerStatus;
|
||||
import com.afollestad.nocknock.ui.MainActivity;
|
||||
import com.afollestad.nocknock.ui.ViewSiteActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -29,6 +35,7 @@ public class CheckService extends Service {
|
|||
public static String ACTION_CHECK_UPDATE = BuildConfig.APPLICATION_ID + ".CHECK_UPDATE";
|
||||
public static String ACTION_RUNNING = BuildConfig.APPLICATION_ID + ".CHECK_RUNNING";
|
||||
public static String MODEL_ID = "model_id";
|
||||
public static int NOTI_ID = 3456;
|
||||
|
||||
private static void LOG(String msg, Object... format) {
|
||||
if (format != null)
|
||||
|
@ -43,6 +50,9 @@ public class CheckService extends Service {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -54,7 +64,6 @@ public class CheckService extends Service {
|
|||
//noinspection ConstantConditions
|
||||
if (e.response() != null && e.response().code() == 401) {
|
||||
// Don't consider 401 unsuccessful here
|
||||
site.status = ServerStatus.OK;
|
||||
site.reason = null;
|
||||
} else {
|
||||
site.status = ServerStatus.ERROR;
|
||||
|
@ -70,7 +79,11 @@ public class CheckService extends Service {
|
|||
// Not used
|
||||
break;
|
||||
}
|
||||
LOG("%s error: %s", site.name, site.reason);
|
||||
|
||||
if (site.status != ServerStatus.OK) {
|
||||
LOG("%s error: %s", site.name, site.reason);
|
||||
showNotification(this, site);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatus(ServerModel site) {
|
||||
|
@ -93,6 +106,40 @@ public class CheckService extends Service {
|
|||
.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)
|
||||
.setDefaults(Notification.DEFAULT_VIBRATE)
|
||||
.build();
|
||||
nm.notify(site.url, NOTI_ID, noti);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (isRunning(this)) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.afollestad.nocknock.ui;
|
|||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -10,6 +11,7 @@ import android.content.IntentFilter;
|
|||
import android.graphics.Path;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
@ -22,7 +24,6 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.animation.PathInterpolator;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.inquiry.Inquiry;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
@ -38,6 +39,7 @@ import com.afollestad.nocknock.views.DividerItemDecoration;
|
|||
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener, ServerAdapter.ClickListener {
|
||||
|
||||
private final static int ADD_SITE_RQ = 6969;
|
||||
private final static int VIEW_SITE_RQ = 6923;
|
||||
public final static String DB_NAME = "nock_nock";
|
||||
public final static String SITES_TABLE_NAME = "sites";
|
||||
|
||||
|
@ -90,12 +92,13 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
|||
mFab.setOnClickListener(this);
|
||||
|
||||
Inquiry.init(this, DB_NAME, 1);
|
||||
refreshModels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
CheckService.isAppOpen(this, true);
|
||||
|
||||
try {
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CheckService.ACTION_CHECK_UPDATE);
|
||||
|
@ -104,11 +107,16 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
|||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
refreshModels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
CheckService.isAppOpen(this, false);
|
||||
|
||||
NotificationManagerCompat.from(this).cancel(CheckService.NOTI_ID);
|
||||
try {
|
||||
unregisterReceiver(mReceiver);
|
||||
} catch (Throwable t) {
|
||||
|
@ -186,38 +194,52 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
|||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
ServerModel model = (ServerModel) data.getSerializableExtra("model");
|
||||
mAdapter.add(model);
|
||||
mEmptyText.setVisibility(View.GONE);
|
||||
|
||||
Inquiry.get().insertInto(SITES_TABLE_NAME, ServerModel.class)
|
||||
.values(model)
|
||||
.run(changed -> {
|
||||
AlarmUtil.setSiteChecks(MainActivity.this, model);
|
||||
checkSite(model);
|
||||
});
|
||||
final ServerModel model = (ServerModel) data.getSerializableExtra("model");
|
||||
if (requestCode == ADD_SITE_RQ) {
|
||||
mAdapter.add(model);
|
||||
mEmptyText.setVisibility(View.GONE);
|
||||
Inquiry.get().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) {
|
||||
Inquiry.get()
|
||||
.update(MainActivity.SITES_TABLE_NAME, ServerModel.class)
|
||||
.where("_id = ?", model.id)
|
||||
.values(model)
|
||||
.run(changed -> {
|
||||
mAdapter.update(model);
|
||||
AlarmUtil.setSiteChecks(MainActivity.this, model);
|
||||
checkSite(MainActivity.this, model);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSite(final int index, final ServerModel model) {
|
||||
Inquiry.init(this, DB_NAME, 1);
|
||||
new MaterialDialog.Builder(this)
|
||||
public static void removeSite(final Context context, final ServerModel model, final Runnable onRemoved) {
|
||||
Inquiry.init(context, DB_NAME, 1);
|
||||
new MaterialDialog.Builder(context)
|
||||
.title(R.string.remove_site)
|
||||
.content(Html.fromHtml(getString(R.string.remove_site_prompt, model.name)))
|
||||
.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(MainActivity.this, model);
|
||||
AlarmUtil.cancelSiteChecks(context, model);
|
||||
final NotificationManagerCompat nm = NotificationManagerCompat.from(context);
|
||||
nm.cancel(model.url, CheckService.NOTI_ID);
|
||||
Inquiry.get()
|
||||
.deleteFrom(SITES_TABLE_NAME, ServerModel.class)
|
||||
.where("_id = ?", model.id)
|
||||
.run();
|
||||
mAdapter.remove(index);
|
||||
if (onRemoved != null)
|
||||
onRemoved.run();
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void checkSite(ServerModel model) {
|
||||
startService(new Intent(this, CheckService.class)
|
||||
public static void checkSite(Context context, ServerModel model) {
|
||||
context.startService(new Intent(context, CheckService.class)
|
||||
.putExtra(CheckService.MODEL_ID, model.id));
|
||||
}
|
||||
|
||||
|
@ -230,13 +252,15 @@ public class MainActivity extends AppCompatActivity implements SwipeRefreshLayou
|
|||
.negativeText(android.R.string.cancel)
|
||||
.itemsCallback((dialog, itemView, which, text) -> {
|
||||
if (which == 0) {
|
||||
checkSite(model);
|
||||
checkSite(MainActivity.this, model);
|
||||
} else {
|
||||
removeSite(index, model);
|
||||
removeSite(MainActivity.this, model, null);
|
||||
}
|
||||
}).show();
|
||||
} else {
|
||||
Toast.makeText(this, "Coming soon", Toast.LENGTH_SHORT).show();
|
||||
startActivityForResult(new Intent(this, ViewSiteActivity.class)
|
||||
.putExtra("model", model), VIEW_SITE_RQ,
|
||||
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package com.afollestad.nocknock.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.util.Patterns;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.nocknock.R;
|
||||
import com.afollestad.nocknock.api.ServerModel;
|
||||
import com.afollestad.nocknock.api.ServerStatus;
|
||||
import com.afollestad.nocknock.services.CheckService;
|
||||
import com.afollestad.nocknock.util.TimeUtil;
|
||||
import com.afollestad.nocknock.views.StatusImageView;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
public class ViewSiteActivity extends AppCompatActivity implements View.OnClickListener, Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private StatusImageView iconStatus;
|
||||
private EditText inputName;
|
||||
private EditText inputUrl;
|
||||
private EditText inputCheckInterval;
|
||||
private Spinner checkIntervalSpinner;
|
||||
private TextView textLastCheckResult;
|
||||
private TextView textNextCheck;
|
||||
|
||||
private ServerModel mModel;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.v("ViewSiteActivity", "Received " + intent.getAction());
|
||||
final ServerModel model = (ServerModel) intent.getSerializableExtra("model");
|
||||
if (model != null) {
|
||||
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);
|
||||
inputCheckInterval = (EditText) findViewById(R.id.checkIntervalInput);
|
||||
checkIntervalSpinner = (Spinner) findViewById(R.id.checkIntervalSpinner);
|
||||
textLastCheckResult = (TextView) findViewById(R.id.textLastCheckResult);
|
||||
textNextCheck = (TextView) findViewById(R.id.textNextCheck);
|
||||
|
||||
ArrayAdapter<String> intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner,
|
||||
getResources().getStringArray(R.array.interval_options));
|
||||
checkIntervalSpinner.setAdapter(intervalOptionsAdapter);
|
||||
|
||||
mModel = (ServerModel) getIntent().getSerializableExtra("model");
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (intent != null && intent.hasExtra("model")) {
|
||||
mModel = (ServerModel) intent.getSerializableExtra("model");
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void update() {
|
||||
final SimpleDateFormat df = new SimpleDateFormat("MMMM dd, hh:mm:ss a", Locale.getDefault());
|
||||
|
||||
iconStatus.setStatus(mModel.status);
|
||||
inputName.setText(mModel.name);
|
||||
inputUrl.setText(mModel.url);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (mModel.checkInterval == 0) {
|
||||
textNextCheck.setText(R.string.none_turned_off);
|
||||
inputCheckInterval.setText("");
|
||||
checkIntervalSpinner.setSelection(0);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Save button
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mModel.name = inputName.getText().toString().trim();
|
||||
mModel.url = inputUrl.getText().toString().trim();
|
||||
mModel.status = ServerStatus.WAITING;
|
||||
|
||||
if (mModel.name.isEmpty()) {
|
||||
inputName.setError(getString(R.string.please_enter_name));
|
||||
return;
|
||||
} else {
|
||||
inputName.setError(null);
|
||||
}
|
||||
|
||||
if (mModel.url.isEmpty()) {
|
||||
inputUrl.setError(getString(R.string.please_enter_url));
|
||||
return;
|
||||
} else {
|
||||
inputUrl.setError(null);
|
||||
if (!Patterns.WEB_URL.matcher(mModel.url).find()) {
|
||||
inputUrl.setError(getString(R.string.please_enter_valid_url));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
setResult(RESULT_OK, new Intent().putExtra("model", mModel));
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.refresh:
|
||||
MainActivity.checkSite(this, mModel);
|
||||
return true;
|
||||
case R.id.remove:
|
||||
MainActivity.removeSite(this, mModel, () -> finish());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ public class AlarmUtil {
|
|||
|
||||
public static void setSiteChecks(Context context, ServerModel site) {
|
||||
cancelSiteChecks(context, site);
|
||||
if (site.checkInterval <= 0) return;
|
||||
if (site.lastCheck <= 0)
|
||||
site.lastCheck = System.currentTimeMillis();
|
||||
final long nextCheck = site.lastCheck + site.checkInterval;
|
||||
|
@ -46,7 +47,8 @@ public class AlarmUtil {
|
|||
final PendingIntent serviceIntent = getSiteIntent(context, site);
|
||||
aMgr.setRepeating(AlarmManager.RTC_WAKEUP, nextCheck, site.checkInterval, serviceIntent);
|
||||
final SimpleDateFormat df = 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), next check: %s", site.name, site.url, df.format(new Date(nextCheck))));
|
||||
Log.d("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) {
|
||||
|
|
|
@ -5,24 +5,26 @@ package com.afollestad.nocknock.util;
|
|||
*/
|
||||
public class TimeUtil {
|
||||
|
||||
private static long SECOND = 1000;
|
||||
private static long MINUTE = SECOND * 60;
|
||||
private static long HOUR = MINUTE * 60;
|
||||
private static long DAY = HOUR * 24;
|
||||
private static long WEEK = DAY * 7;
|
||||
private static long MONTH = WEEK * 4;
|
||||
public final static long SECOND = 1000;
|
||||
public final static long MINUTE = SECOND * 60;
|
||||
public final static long HOUR = MINUTE * 60;
|
||||
public final static long DAY = HOUR * 24;
|
||||
public final static long WEEK = DAY * 7;
|
||||
public final static long MONTH = WEEK * 4;
|
||||
|
||||
public static String str(long duration) {
|
||||
if (duration >= MONTH) {
|
||||
return (duration / MONTH) + "mo";
|
||||
if (duration <= 0) {
|
||||
return "";
|
||||
} else if (duration >= MONTH) {
|
||||
return (int) Math.ceil(((float) duration / (float) MONTH)) + "mo";
|
||||
} else if (duration >= WEEK) {
|
||||
return (duration / WEEK) + "w";
|
||||
return (int) Math.ceil(((float) duration / (float) WEEK)) + "w";
|
||||
} else if (duration >= DAY) {
|
||||
return (duration / DAY) + "d";
|
||||
return (int) Math.ceil(((float) duration / (float) DAY)) + "d";
|
||||
} else if (duration >= HOUR) {
|
||||
return (duration / HOUR) + "h";
|
||||
return (int) Math.ceil(((float) duration / (float) HOUR)) + "h";
|
||||
} else if (duration >= MINUTE) {
|
||||
return (duration / MINUTE) + "m";
|
||||
return (int) Math.ceil(((float) duration / (float) MINUTE)) + "m";
|
||||
} else {
|
||||
return "<1m";
|
||||
}
|
||||
|
|
BIN
app/src/main/res/drawable-hdpi-v11/ic_notification.png
Normal file
After Width: | Height: | Size: 998 B |
BIN
app/src/main/res/drawable-mdpi-v11/ic_notification.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
app/src/main/res/drawable-xhdpi-v11/ic_notification.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-xxhdpi-v11/ic_notification.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/drawable-xxxhdpi-v11/ic_notification.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
9
app/src/main/res/drawable/ic_action_delete.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_action_refresh.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||
</vector>
|
|
@ -44,7 +44,8 @@
|
|||
android:hint="@string/site_name"
|
||||
android:inputType="textPersonName|textCapWords|textAutoCorrect"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textColorHint="?android:textColorSecondary" />
|
||||
android:textColorHint="?android:textColorSecondary"
|
||||
android:textSize="@dimen/body_font_size" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
|
@ -63,54 +64,49 @@
|
|||
android:hint="@string/site_url"
|
||||
android:inputType="textUri"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textColorHint="?android:textColorSecondary" />
|
||||
android:textColorHint="?android:textColorSecondary"
|
||||
android:textSize="@dimen/body_font_size" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/checkIntervalLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/list_text_spacing"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/check_interval"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="@dimen/caption_font_size" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/checkIntervalLabel"
|
||||
android:layout_width="wrap_content"
|
||||
<EditText
|
||||
android:id="@+id/checkIntervalInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/list_text_spacing"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/check_interval"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="@dimen/caption_font_size" />
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginEnd="@dimen/content_inset_half"
|
||||
android:layout_marginStart="-4dp"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:hint="0"
|
||||
android:inputType="number"
|
||||
android:textSize="@dimen/body_font_size"
|
||||
tools:ignore="HardcodedText,LabelFor" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<Spinner
|
||||
android:id="@+id/checkIntervalSpinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/checkIntervalInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginEnd="@dimen/content_inset_half"
|
||||
android:layout_marginStart="-4dp"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:hint="@string/never_refresh"
|
||||
android:inputType="number"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/checkIntervalSpinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
179
app/src/main/res/layout/activity_viewsite.xml
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorPrimary"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/ic_action_close"
|
||||
app:title="@string/view_site"
|
||||
app:titleTextColor="?android:textColorPrimary" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/content_inset"
|
||||
android:paddingLeft="@dimen/content_inset"
|
||||
android:paddingRight="@dimen/content_inset"
|
||||
android:paddingTop="@dimen/content_inset_less">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.afollestad.nocknock.views.StatusImageView
|
||||
android:id="@+id/iconStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/content_inset"
|
||||
android:scaleType="centerInside"
|
||||
android:transitionName="status_image_view"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:hint="@string/site_name"
|
||||
android:inputType="textPersonName|textCapWords|textAutoCorrect"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textColorHint="?android:textColorSecondary"
|
||||
android:textSize="@dimen/body_font_size"
|
||||
android:transitionName="site_name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:hint="@string/site_url"
|
||||
android:inputType="textUri"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textColorHint="?android:textColorSecondary"
|
||||
android:textSize="@dimen/body_font_size"
|
||||
android:transitionName="site_url" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:background="#37474F" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/checkIntervalLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/list_text_spacing"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/check_interval"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="@dimen/caption_font_size" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/checkIntervalInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginEnd="@dimen/content_inset_half"
|
||||
android:layout_marginStart="-4dp"
|
||||
android:layout_weight="1"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:hint="0"
|
||||
android:inputType="number"
|
||||
android:textSize="@dimen/body_font_size"
|
||||
tools:ignore="HardcodedText,LabelFor" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/checkIntervalSpinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:text="@string/last_check_result"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="@dimen/caption_font_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textLastCheckResult"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/list_text_spacing"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/medium_text_size"
|
||||
tools:text="Everything checks out!" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/content_inset_more"
|
||||
android:text="@string/next_check"
|
||||
android:textColor="?colorAccent"
|
||||
android:textSize="@dimen/caption_font_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textNextCheck"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/list_text_spacing"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/medium_text_size"
|
||||
tools:text="In 2 hours" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/doneBtn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="-4dp"
|
||||
android:layout_marginRight="-4dp"
|
||||
android:layout_marginTop="@dimen/content_inset_double"
|
||||
android:text="@string/save"
|
||||
android:textColor="#fff" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
|
@ -17,6 +17,7 @@
|
|||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/content_inset"
|
||||
android:scaleType="centerInside"
|
||||
android:transitionName="status_image_view"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<LinearLayout
|
||||
|
@ -41,6 +42,7 @@
|
|||
android:singleLine="true"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/title_font_size"
|
||||
android:transitionName="site_name"
|
||||
tools:text="Website Name" />
|
||||
|
||||
<TextView
|
||||
|
@ -66,6 +68,7 @@
|
|||
android:singleLine="true"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="@dimen/body_font_size"
|
||||
android:transitionName="site_url"
|
||||
tools:text="https://yourwebsitehere.com" />
|
||||
|
||||
<TextView
|
||||
|
|
17
app/src/main/res/menu/menu_viewsite.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/refresh"
|
||||
android:icon="@drawable/ic_action_refresh"
|
||||
android:title="@string/refresh_status"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/remove"
|
||||
android:icon="@drawable/ic_action_delete"
|
||||
android:title="@string/remove_site"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 16 KiB |
|
@ -2,6 +2,7 @@
|
|||
|
||||
<dimen name="headline_font_size">24sp</dimen>
|
||||
<dimen name="title_font_size">20sp</dimen>
|
||||
<dimen name="medium_text_size">16sp</dimen>
|
||||
<dimen name="body_font_size">14sp</dimen>
|
||||
<dimen name="caption_font_size">12sp</dimen>
|
||||
|
||||
|
@ -12,7 +13,7 @@
|
|||
<dimen name="content_inset_double">32dp</dimen>
|
||||
|
||||
<dimen name="list_circle_size">42dp</dimen>
|
||||
<dimen name="list_text_spacing">2dp</dimen>
|
||||
<dimen name="list_text_spacing">4dp</dimen>
|
||||
<dimen name="fab_elevation">4dp</dimen>
|
||||
<dimen name="fab_elevation_pressed">8dp</dimen>
|
||||
<dimen name="button_height">52dp</dimen>
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
<string name="site_url">Site URL</string>
|
||||
<string name="check_interval">Check Interval</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="never_refresh">Never Refresh</string>
|
||||
<string name="please_enter_name">Please enter a name!</string>
|
||||
<string name="please_enter_url">Please enter a URL.</string>
|
||||
<string name="please_enter_valid_url">Please enter a valid URL.</string>
|
||||
|
@ -35,6 +34,15 @@
|
|||
<string name="remove_site">Remove Site</string>
|
||||
<string name="remove_site_prompt"><![CDATA[Remove <b>%1$s</b> from your sites?]]></string>
|
||||
<string name="remove">Remove</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="view_site">View Site</string>
|
||||
<string name="last_check_result">Last Check Result</string>
|
||||
<string name="next_check">Next Check</string>
|
||||
<string name="none_turned_off">None (turned off)</string>
|
||||
<string name="none">None</string>
|
||||
|
||||
<string name="refresh_status">Refresh Status</string>
|
||||
|
||||
<string-array name="interval_options">
|
||||
<item>Minute(s)</item>
|
||||
<item>Hour(s)</item>
|
||||
|
@ -42,9 +50,9 @@
|
|||
<item>Weeks(s)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="site_long_options">
|
||||
<item>Refresh Status</item>
|
||||
<item>Remove Site</item>
|
||||
<string-array name="site_long_options" translatable="false">
|
||||
<item>@string/refresh_status</item>
|
||||
<item>@string/remove_site</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
<item name="android:textColorSecondary">#727272</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Transparent" parent="Theme.AppCompat.NoActionBar">
|
||||
<style name="AppTheme.Ink" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="colorButtonNormal">@color/colorPrimaryDark</item>
|
||||
|
||||
<item name="android:listDivider">@drawable/divider</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Transparent" parent="AppTheme.Ink">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
|
BIN
web_hi_res_512.png
Normal file
After Width: | Height: | Size: 49 KiB |