From fca080bb7f29014c34b5ea61f8234e945d38f393 Mon Sep 17 00:00:00 2001 From: Aidan Follestad Date: Sat, 30 Jul 2016 02:42:04 -0500 Subject: [PATCH] Initial commit --- .gitignore | 183 ++++++++++++++++++ .idea/.name | 1 + .idea/compiler.xml | 22 +++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/misc.xml | 46 +++++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 ++ app/.gitignore | 1 + app/build.gradle | 39 ++++ app/proguard-rules.pro | 17 ++ app/src/main/AndroidManifest.xml | 27 +++ .../nocknock/adapter/ServerAdapter.java | 133 +++++++++++++ .../afollestad/nocknock/api/ServerModel.java | 27 +++ .../afollestad/nocknock/api/ServerStatus.java | 12 ++ .../nocknock/dialogs/AboutDialog.java | 33 ++++ .../nocknock/ui/AddSiteActivity.java | 144 ++++++++++++++ .../afollestad/nocknock/ui/MainActivity.java | 126 ++++++++++++ .../afollestad/nocknock/util/MathUtil.java | 32 +++ .../afollestad/nocknock/util/TimeUtil.java | 30 +++ app/src/main/res/anim/fade_out.xml | 11 ++ app/src/main/res/drawable/green_circle.xml | 12 ++ app/src/main/res/drawable/ic_action_close.xml | 9 + app/src/main/res/drawable/ic_add.xml | 9 + app/src/main/res/drawable/red_circle.xml | 12 ++ app/src/main/res/drawable/yellow_circle.xml | 12 ++ app/src/main/res/layout/activity_addsite.xml | 129 ++++++++++++ app/src/main/res/layout/activity_main.xml | 46 +++++ app/src/main/res/layout/list_item_server.xml | 90 +++++++++ app/src/main/res/layout/list_item_spinner.xml | 10 + app/src/main/res/menu/menu_main.xml | 8 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes app/src/main/res/values/colors.xml | 14 ++ app/src/main/res/values/dimens.xml | 20 ++ app/src/main/res/values/strings.xml | 36 ++++ app/src/main/res/values/styles.xml | 24 +++ build.gradle | 23 +++ gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++++++++++++ gradlew.bat | 90 +++++++++ settings.gradle | 1 + 47 files changed, 1643 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/afollestad/nocknock/adapter/ServerAdapter.java create mode 100644 app/src/main/java/com/afollestad/nocknock/api/ServerModel.java create mode 100644 app/src/main/java/com/afollestad/nocknock/api/ServerStatus.java create mode 100644 app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.java create mode 100644 app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.java create mode 100644 app/src/main/java/com/afollestad/nocknock/ui/MainActivity.java create mode 100644 app/src/main/java/com/afollestad/nocknock/util/MathUtil.java create mode 100644 app/src/main/java/com/afollestad/nocknock/util/TimeUtil.java create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/drawable/green_circle.xml create mode 100644 app/src/main/res/drawable/ic_action_close.xml create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/red_circle.xml create mode 100644 app/src/main/res/drawable/yellow_circle.xml create mode 100644 app/src/main/res/layout/activity_addsite.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/list_item_server.xml create mode 100644 app/src/main/res/layout/list_item_spinner.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..161128f --- /dev/null +++ b/.gitignore @@ -0,0 +1,183 @@ + +# Created by https://www.gitignore.io/api/android,intellij,osx,windows,gradle,java + +### Android ### +# Built application files +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea/workspace.xml +.idea/libraries + +# Keystore files +*.jks + +### Android Patch ### +gen-external-apklibs + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..fb8fe6b --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Nock Nock \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fbb6828 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bcb2a12 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..32524be --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.0" + + defaultConfig { + applicationId "com.afollestad.nocknock" + minSdkVersion 21 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + + jackOptions { + enabled true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:24.1.1' + compile 'com.afollestad.material-dialogs:core:0.8.6.2' + compile 'com.afollestad.material-dialogs:commons:0.8.6.2' + compile 'com.afollestad:bridge:3.2.5' + compile 'com.afollestad:inquiry:2.0.2' + compile 'com.android.support:design:24.1.1' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..666743b --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\drumm\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..43e442d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/adapter/ServerAdapter.java b/app/src/main/java/com/afollestad/nocknock/adapter/ServerAdapter.java new file mode 100644 index 0000000..cf473a8 --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/adapter/ServerAdapter.java @@ -0,0 +1,133 @@ +package com.afollestad.nocknock.adapter; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +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.util.TimeUtil; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * @author Aidan Follestad (afollestad) + */ +public class ServerAdapter extends RecyclerView.Adapter { + + private final Object LOCK = new Object(); + private ArrayList mServers; + + public ServerAdapter() { + 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 void remove(int index) { + mServers.remove(index); + notifyItemRemoved(index); + } + + public void remove(ServerModel model) { + synchronized (LOCK) { + for (int i = 0; i < mServers.size(); i++) { + if (mServers.get(i).id == model.id) { + remove(i); + break; + } + } + } + } + + public void set(ServerModel[] models) { + mServers = new ArrayList<>(models.length); + Collections.addAll(mServers, models); + 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); + } + + @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); + + switch (model.status) { + case ServerStatus.OK: + holder.textStatus.setText(R.string.everything_checks_out); + holder.iconStatus.setImageResource(R.drawable.green_circle); + break; + case ServerStatus.WAITING: + holder.textStatus.setText(R.string.waiting); + holder.iconStatus.setImageResource(R.drawable.yellow_circle); + break; + case ServerStatus.CHECKING: + holder.textStatus.setText(R.string.checking_status); + holder.iconStatus.setImageResource(R.drawable.yellow_circle); + break; + case ServerStatus.ERROR: + holder.textStatus.setText(R.string.something_wrong); + holder.iconStatus.setImageResource(R.drawable.red_circle); + break; + } + + 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 { + + final ImageView iconStatus; + final TextView textName; + final TextView textInterval; + final TextView textUrl; + final TextView textStatus; + + public ServerVH(View itemView) { + super(itemView); + iconStatus = (ImageView) 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); + } + } +} diff --git a/app/src/main/java/com/afollestad/nocknock/api/ServerModel.java b/app/src/main/java/com/afollestad/nocknock/api/ServerModel.java new file mode 100644 index 0000000..02a78f2 --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/api/ServerModel.java @@ -0,0 +1,27 @@ +package com.afollestad.nocknock.api; + +import com.afollestad.inquiry.annotations.Column; + +import java.io.Serializable; + +/** + * @author Aidan Follestad (afollestad) + */ +public class ServerModel implements Serializable { + + public ServerModel() { + } + + @Column(name = "_id", primaryKey = true, notNull = true, autoIncrement = true) + public long id; + @Column + public String name; + @Column + public String url; + @Column + public int status; + @Column + public long checkInterval; + @Column + public long lastCheck; +} \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/api/ServerStatus.java b/app/src/main/java/com/afollestad/nocknock/api/ServerStatus.java new file mode 100644 index 0000000..0a61f6f --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/api/ServerStatus.java @@ -0,0 +1,12 @@ +package com.afollestad.nocknock.api; + +/** + * @author Aidan Follestad (afollestad) + */ +public final class ServerStatus { + + public final static int OK = 1; + public final static int WAITING = 2; + public final static int CHECKING = 3; + public final static int ERROR = 4; +} diff --git a/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.java b/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.java new file mode 100644 index 0000000..3eeed80 --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/dialogs/AboutDialog.java @@ -0,0 +1,33 @@ +package com.afollestad.nocknock.dialogs; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AppCompatActivity; +import android.text.Html; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.afollestad.nocknock.R; + +/** + * @author Aidan Follestad (afollestad) + */ +public class AboutDialog extends DialogFragment { + + public static void show(AppCompatActivity context) { + AboutDialog dialog = new AboutDialog(); + dialog.show(context.getSupportFragmentManager(), "[ABOUT_DIALOG]"); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new MaterialDialog.Builder(getActivity()) + .title(R.string.about) + .positiveText(R.string.dismiss) + .content(Html.fromHtml(getString(R.string.about_body))) + .contentLineSpacing(1.6f) + .build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.java b/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.java new file mode 100644 index 0000000..9b844b6 --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/ui/AddSiteActivity.java @@ -0,0 +1,144 @@ +package com.afollestad.nocknock.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; + +import com.afollestad.nocknock.R; +import com.afollestad.nocknock.api.ServerModel; +import com.afollestad.nocknock.api.ServerStatus; + +/** + * @author Aidan Follestad (afollestad) + */ +public class AddSiteActivity extends AppCompatActivity implements View.OnClickListener { + + private View rootLayout; + private Toolbar toolbar; + + private EditText inputName; + private EditText inputUrl; + private EditText inputInterval; + private Spinner spinnerInterval; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_addsite); + + rootLayout = findViewById(R.id.rootView); + inputName = (EditText) findViewById(R.id.inputName); + inputUrl = (EditText) findViewById(R.id.inputUrl); + inputInterval = (EditText) findViewById(R.id.checkIntervalInput); + spinnerInterval = (Spinner) findViewById(R.id.checkIntervalSpinner); + + toolbar = (Toolbar) findViewById(R.id.toolbar); + toolbar.setNavigationOnClickListener(view -> { + closeFromNavWithReveal(); + }); + + if (savedInstanceState == null) { + rootLayout.setVisibility(View.INVISIBLE); + ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver(); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + circularRevealActivity(); + rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + }); + } + } + + ArrayAdapter intervalOptionsAdapter = new ArrayAdapter<>(this, R.layout.list_item_spinner, + getResources().getStringArray(R.array.interval_options)); + spinnerInterval.setAdapter(intervalOptionsAdapter); + + findViewById(R.id.doneBtn).setOnClickListener(this); + } + + private void closeFromNavWithReveal() { + final int offset = (int) getResources().getDimension(R.dimen.content_inset); + final int cx = rootLayout.getMeasuredWidth(); + final int cy = rootLayout.getMeasuredHeight(); + float initialRadius = Math.max(cx, cy); + + final Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, offset, offset, initialRadius, 0); + circularReveal.setDuration(300); + circularReveal.setInterpolator(new AccelerateInterpolator()); + circularReveal.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + finish(); + overridePendingTransition(0, 0); + } + }); + + circularReveal.start(); + } + + private void circularRevealActivity() { + final int cx = rootLayout.getMeasuredWidth() / 2; + final int cy = rootLayout.getMeasuredHeight() / 2; + final int animDuration = 300; + final float finalRadius = Math.max(cx, cy); + final Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, 0, finalRadius); + + circularReveal.setDuration(animDuration); + circularReveal.setInterpolator(new DecelerateInterpolator()); + + toolbar.setAlpha(0f); + rootLayout.setVisibility(View.VISIBLE); + + toolbar.animate().alpha(1f).setDuration(animDuration).start(); + circularReveal.start(); + } + + // Done button + @Override + public void onClick(View view) { + ServerModel model = new ServerModel(); + model.name = inputName.getText().toString().trim(); + model.url = inputUrl.getText().toString().trim(); + model.lastCheck = -1; + model.status = ServerStatus.WAITING; + + 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; + } + + setResult(RESULT_OK, new Intent() + .putExtra("model", model)); + finish(); + overridePendingTransition(R.anim.fade_out, R.anim.fade_out); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.java b/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.java new file mode 100644 index 0000000..9266758 --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/ui/MainActivity.java @@ -0,0 +1,126 @@ +package com.afollestad.nocknock.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Intent; +import android.graphics.Path; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.PathInterpolator; +import android.widget.TextView; + +import com.afollestad.nocknock.R; +import com.afollestad.nocknock.adapter.ServerAdapter; +import com.afollestad.nocknock.api.ServerModel; +import com.afollestad.nocknock.api.ServerStatus; +import com.afollestad.nocknock.dialogs.AboutDialog; +import com.afollestad.nocknock.util.MathUtil; + +public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener { + + private FloatingActionButton mFab; + private RecyclerView mList; + private ServerAdapter mAdapter; + private TextView mEmptyText; + + private ObjectAnimator mFabAnimator; + private float mOrigFabX; + private float mOrigFabY; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mAdapter = new ServerAdapter(); + mEmptyText = (TextView) findViewById(R.id.emptyText); + + mList = (RecyclerView) findViewById(R.id.list); + mList.setLayoutManager(new LinearLayoutManager(this)); + mList.setAdapter(mAdapter); + + SwipeRefreshLayout sr = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); + sr.setOnRefreshListener(this); + sr.setColorSchemeColors(ContextCompat.getColor(this, R.color.md_green), + ContextCompat.getColor(this, R.color.md_yellow), + ContextCompat.getColor(this, R.color.md_red)); + + mFab = (FloatingActionButton) findViewById(R.id.fab); + mFab.setOnClickListener(this); + + ServerModel model = new ServerModel(); + model.name = "Personal Site"; + model.url = "https://aidanfollestad.com"; + model.id = 1; + model.status = ServerStatus.OK; + model.checkInterval = 1000 * 60; + model.lastCheck = System.currentTimeMillis(); + mAdapter.add(model); + + model = new ServerModel(); + model.name = "Polar Request Manager"; + model.url = "https://polar.aidanfollestad.com"; + model.id = 2; + model.status = ServerStatus.CHECKING; + model.checkInterval = 1000 * 60 * 2; + model.lastCheck = System.currentTimeMillis(); + mAdapter.add(model); + + mEmptyText.setVisibility(View.GONE); + } + + @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() { + // TODO check all servers in order + } + + // 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(0.4f, 0.4f, 1, 1)); + mFabAnimator.setDuration(300); + mFabAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + startActivity(new Intent(MainActivity.this, AddSiteActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)); + mFab.postDelayed(() -> { + mFab.setX(mOrigFabX); + mFab.setY(mOrigFabY); + }, 1000); + } + }); + mFabAnimator.start(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/util/MathUtil.java b/app/src/main/java/com/afollestad/nocknock/util/MathUtil.java new file mode 100644 index 0000000..6cb885b --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/util/MathUtil.java @@ -0,0 +1,32 @@ +package com.afollestad.nocknock.util; + +import android.graphics.Path; +import android.support.design.widget.FloatingActionButton; +import android.view.View; + +/** + * @author Aidan Follestad (afollestad) + */ +public final class MathUtil { + + public static Path bezierCurve(FloatingActionButton fab, View rootView) { + final int fabCenterX = (int) (fab.getX() + fab.getMeasuredWidth() / 2); + final int fabCenterY = (int) (fab.getY() + fab.getMeasuredHeight() / 2); + + final int endCenterX = (rootView.getMeasuredWidth() / 2) - (fab.getMeasuredWidth() / 2); + final int endCenterY = (rootView.getMeasuredHeight() / 2) - (fab.getMeasuredHeight() / 2); + + final int halfX = (fabCenterX - endCenterX) / 2; + final int halfY = (fabCenterY - endCenterY) / 2; + int mControlX = endCenterX + halfX; + int mControlY = endCenterY + halfY; + mControlY -= halfY; + mControlX += halfX; + + Path path = new Path(); + path.moveTo(fab.getX(), fab.getY()); + path.quadTo(mControlX, mControlY, endCenterX, endCenterY); + + return path; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/afollestad/nocknock/util/TimeUtil.java b/app/src/main/java/com/afollestad/nocknock/util/TimeUtil.java new file mode 100644 index 0000000..398d1ee --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/util/TimeUtil.java @@ -0,0 +1,30 @@ +package com.afollestad.nocknock.util; + +/** + * @author Aidan Follestad (afollestad) + */ +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 static String str(long duration) { + if (duration >= MONTH) { + return (duration / MONTH) + "mo"; + } else if (duration >= WEEK) { + return (duration / WEEK) + "w"; + } else if (duration >= DAY) { + return (duration / DAY) + "d"; + } else if (duration >= HOUR) { + return (duration / HOUR) + "h"; + } else if (duration >= MINUTE) { + return (duration / MINUTE) + "m"; + } else { + return "<1m"; + } + } +} diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..4ef8fd1 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/green_circle.xml b/app/src/main/res/drawable/green_circle.xml new file mode 100644 index 0000000..31bc563 --- /dev/null +++ b/app/src/main/res/drawable/green_circle.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_close.xml b/app/src/main/res/drawable/ic_action_close.xml new file mode 100644 index 0000000..f4977c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..7b18128 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/red_circle.xml b/app/src/main/res/drawable/red_circle.xml new file mode 100644 index 0000000..d6bdd26 --- /dev/null +++ b/app/src/main/res/drawable/red_circle.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/yellow_circle.xml b/app/src/main/res/drawable/yellow_circle.xml new file mode 100644 index 0000000..1201e65 --- /dev/null +++ b/app/src/main/res/drawable/yellow_circle.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml new file mode 100644 index 0000000..ae5b544 --- /dev/null +++ b/app/src/main/res/layout/activity_addsite.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +