Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
e33243e589
|
|||
26f04dec9e
|
|||
8b44df8636
|
|||
3bb7386d72
|
|||
5d4ea0e057
|
|||
0b030406bb
|
|||
28ef5cb2f5
|
|||
7ea0572d79
|
|||
63633de256
|
|||
a4cc8752ff
|
|||
543f359acd
|
|||
fcdb5217ee
|
|||
b6252a1c1a
|
|||
5fcd33e294
|
7
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="PROJECT_PROFILE" value="Default" />
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
0
.gitignore → android/.gitignore
vendored
32
android/.idea/assetWizardSettings.xml
generated
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WizardSettings">
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="vectorWizard">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="vectorAssetStep">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="assetSourceType" value="FILE" />
|
||||||
|
<entry key="outputName" value="priority_low" />
|
||||||
|
<entry key="sourceFile" value="C:\Users\Mike\Downloads\Low Priority-595b40b75ba036ed117d9842.svg" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
10
android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
0
app/.gitignore → android/app/.gitignore
vendored
110
android/app/build.gradle
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
def versionPropsFile = file('version.properties')
|
||||||
|
def vNumber
|
||||||
|
def vName
|
||||||
|
if (versionPropsFile.canRead()) {
|
||||||
|
Properties versionProps = new Properties()
|
||||||
|
new FileInputStream(versionPropsFile).withCloseable { stream -> versionProps.load(stream) }
|
||||||
|
vNumber = versionProps['VERSION_CODE'].toInteger()
|
||||||
|
vName = versionProps['VERSION_NAME'].toString()
|
||||||
|
} else throw new FileNotFoundException("Could not read version.properties!")
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.blackforestbytes.simplecloudnotifier"
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode vNumber
|
||||||
|
versionName vName
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility 1.8
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
implementation 'com.android.support:support-v4:28.0.0'
|
||||||
|
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||||
|
implementation 'com.android.support:cardview-v7:28.0.0'
|
||||||
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
|
implementation 'com.android.support:recyclerview-v7:28.0.0'
|
||||||
|
implementation 'com.android.support:design:28.0.0'
|
||||||
|
|
||||||
|
implementation 'com.takisoft.fix:preference-v7:28.0.0.0'
|
||||||
|
implementation 'com.takisoft.fix:preference-v7-extras:28.0.0.0'
|
||||||
|
|
||||||
|
implementation 'com.google.firebase:firebase-core:16.0.4'
|
||||||
|
implementation 'com.google.firebase:firebase-messaging:17.3.3'
|
||||||
|
|
||||||
|
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||||
|
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
|
implementation 'com.github.kenglxn.QRGen:android:2.5.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
|
||||||
|
task updateVersion << {
|
||||||
|
def lastTag = ['git', 'describe', "--abbrev=0", "--tags"].execute().text.trim()
|
||||||
|
|
||||||
|
def versionPropsFile = file('version.properties')
|
||||||
|
if (!versionPropsFile.canRead()) throw new FileNotFoundException("Could not read version.properties!")
|
||||||
|
Properties versionProps = new Properties()
|
||||||
|
new FileInputStream(versionPropsFile).withCloseable { fis -> versionProps.load(fis) }
|
||||||
|
|
||||||
|
def matcher = lastTag =~ /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/
|
||||||
|
|
||||||
|
if (!matcher.matches()) throw new Exception("Last Tag ('" + lastTag + "') has invalid format :(")
|
||||||
|
|
||||||
|
def vName = (matcher[0][1] as Integer) + "." + (matcher[0][2] as Integer) + "." + (matcher[0][3] as Integer)
|
||||||
|
def vCode = versionProps['VERSION_CODE'] as Integer
|
||||||
|
|
||||||
|
if (new File(".do_publish_beta_release").exists()) new File(".do_publish_beta_release").delete()
|
||||||
|
if (new File(".do_publish_prod_release").exists()) new File(".do_publish_prod_release").delete()
|
||||||
|
|
||||||
|
if (vName == versionProps['VERSION_NAME'].toString()) {
|
||||||
|
println "This version was already built - skip deployment"
|
||||||
|
} else if (vName.endsWith(".0")) {
|
||||||
|
println ""
|
||||||
|
println "====================================================================="
|
||||||
|
println "====================================================================="
|
||||||
|
println "(!) This is a new PRODUCTION release - create deployment trigger file"
|
||||||
|
println "====================================================================="
|
||||||
|
println "====================================================================="
|
||||||
|
println ""
|
||||||
|
|
||||||
|
vCode++
|
||||||
|
new File(".do_publish_prod_release").createNewFile()
|
||||||
|
|
||||||
|
versionProps['VERSION_NAME'] = vName.toString()
|
||||||
|
versionProps['VERSION_CODE'] = vCode.toString()
|
||||||
|
|
||||||
|
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
|
||||||
|
} else {
|
||||||
|
println ""
|
||||||
|
println "==============================================================="
|
||||||
|
println "(!) This is a new beta release - create deployment trigger file"
|
||||||
|
println "==============================================================="
|
||||||
|
println ""
|
||||||
|
|
||||||
|
vCode++
|
||||||
|
new File(".do_publish_beta_release").createNewFile()
|
||||||
|
|
||||||
|
versionProps['VERSION_NAME'] = vName.toString()
|
||||||
|
versionProps['VERSION_CODE'] = vCode.toString()
|
||||||
|
|
||||||
|
versionPropsFile.newWriter().withCloseable { w -> versionProps.store(w, null) }
|
||||||
|
}
|
||||||
|
}
|
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:name="SCNApp"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".view.MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/icon" />
|
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/icon" />
|
||||||
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
|
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
|
||||||
|
|
||||||
<service android:name=".FBMService">
|
<service android:name=".service.FBMService">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
@@ -0,0 +1,94 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.Lifecycle;
|
||||||
|
import android.arch.lifecycle.LifecycleObserver;
|
||||||
|
import android.arch.lifecycle.OnLifecycleEvent;
|
||||||
|
import android.arch.lifecycle.ProcessLifecycleOwner;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.AccountFragment;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.TabAdapter;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class SCNApp extends Application implements LifecycleObserver
|
||||||
|
{
|
||||||
|
private static SCNApp instance;
|
||||||
|
private static WeakReference<MainActivity> mainActivity;
|
||||||
|
|
||||||
|
public static final boolean DEBUG = BuildConfig.DEBUG || !BuildConfig.VERSION_NAME.endsWith(".0");
|
||||||
|
public static final boolean RELEASE = !DEBUG;
|
||||||
|
|
||||||
|
private static boolean isBackground = true;
|
||||||
|
|
||||||
|
public SCNApp()
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getContext()
|
||||||
|
{
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBackground()
|
||||||
|
{
|
||||||
|
return isBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showToast(final String msg, final int duration)
|
||||||
|
{
|
||||||
|
final MainActivity a = mainActivity.get();
|
||||||
|
if (a != null)
|
||||||
|
{
|
||||||
|
a.runOnUiThread(() -> Toast.makeText(a, msg, duration).show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean runOnUiThread(Runnable r)
|
||||||
|
{
|
||||||
|
final MainActivity a = mainActivity.get();
|
||||||
|
if (a != null) {a.runOnUiThread(r); return true;}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void refreshAccountTab()
|
||||||
|
{
|
||||||
|
runOnUiThread(() ->
|
||||||
|
{
|
||||||
|
MainActivity a = mainActivity.get();
|
||||||
|
if (a == null) return;
|
||||||
|
|
||||||
|
TabAdapter ta = a.adpTabs;
|
||||||
|
if (ta == null) return;
|
||||||
|
|
||||||
|
AccountFragment tf = ta.tab2;
|
||||||
|
if (tf == null) return;
|
||||||
|
|
||||||
|
tf.updateUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(MainActivity a)
|
||||||
|
{
|
||||||
|
mainActivity = new WeakReference<>(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
public void onAppBackgrounded()
|
||||||
|
{
|
||||||
|
isBackground = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
public void onAppForegrounded()
|
||||||
|
{
|
||||||
|
isBackground = false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class CMessage
|
||||||
|
{
|
||||||
|
public final long Timestamp ;
|
||||||
|
public final String Title;
|
||||||
|
public final String Content;
|
||||||
|
public final PriorityEnum Priority;
|
||||||
|
|
||||||
|
private static final SimpleDateFormat _format;
|
||||||
|
static
|
||||||
|
{
|
||||||
|
_format = new SimpleDateFormat("yyyy'-'MM'-'dd HH':'mm':'ss", Locale.getDefault());
|
||||||
|
_format.setTimeZone(TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage(long t, String mt, String mc, PriorityEnum p)
|
||||||
|
{
|
||||||
|
Timestamp = t;
|
||||||
|
Title = mt;
|
||||||
|
Content = mc;
|
||||||
|
Priority = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
public String formatTimestamp()
|
||||||
|
{
|
||||||
|
return _format.format(new Date(Timestamp*1000));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,142 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MessageAdapter;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class CMessageList
|
||||||
|
{
|
||||||
|
public ArrayList<CMessage> Messages;
|
||||||
|
|
||||||
|
private ArrayList<WeakReference<MessageAdapter>> _listener = new ArrayList<>();
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static CMessageList _inst = null;
|
||||||
|
public static CMessageList inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
return _inst = new CMessageList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CMessageList()
|
||||||
|
{
|
||||||
|
Messages = new ArrayList<>();
|
||||||
|
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("message_count", 0);
|
||||||
|
for (int i=0; i < count; i++)
|
||||||
|
{
|
||||||
|
long time = sharedPref.getLong("message["+i+"].timestamp", 0);
|
||||||
|
String title = sharedPref.getString("message["+i+"].title", "");
|
||||||
|
String content = sharedPref.getString("message["+i+"].content", "");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(sharedPref.getInt("message["+i+"].priority", 1));
|
||||||
|
|
||||||
|
Messages.add(new CMessage(time, title, content, prio));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage add(final long time, final String title, final String content, final PriorityEnum pe)
|
||||||
|
{
|
||||||
|
CMessage msg = new CMessage(time, title, content, pe);
|
||||||
|
|
||||||
|
boolean run = SCNApp.runOnUiThread(() ->
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
int count = sharedPref.getInt("message_count", 0);
|
||||||
|
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
Messages.add(msg);
|
||||||
|
e.putInt("message_count", count+1);
|
||||||
|
e.putLong("message["+count+"].timestamp", time);
|
||||||
|
e.putString("message["+count+"].title", title);
|
||||||
|
e.putString("message["+count+"].content", content);
|
||||||
|
e.putInt("message["+count+"].priority", pe.ID);
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
|
||||||
|
for (WeakReference<MessageAdapter> ref : _listener)
|
||||||
|
{
|
||||||
|
MessageAdapter a = ref.get();
|
||||||
|
if (a == null) continue;
|
||||||
|
a.customNotifyItemInserted(count);
|
||||||
|
}
|
||||||
|
CleanUpListener();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!run)
|
||||||
|
{
|
||||||
|
Messages.add(new CMessage(time, title, content, pe));
|
||||||
|
fullSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
Messages.clear();
|
||||||
|
fullSave();
|
||||||
|
|
||||||
|
for (WeakReference<MessageAdapter> ref : _listener)
|
||||||
|
{
|
||||||
|
MessageAdapter a = ref.get();
|
||||||
|
if (a == null) continue;
|
||||||
|
a.customNotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
CleanUpListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fullSave()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.clear();
|
||||||
|
|
||||||
|
e.putInt("message_count", Messages.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < Messages.size(); i++)
|
||||||
|
{
|
||||||
|
e.putLong("message["+i+"].timestamp", Messages.get(i).Timestamp);
|
||||||
|
e.putString("message["+i+"].title", Messages.get(i).Title);
|
||||||
|
e.putString("message["+i+"].content", Messages.get(i).Content);
|
||||||
|
e.putInt("message["+i+"].priority", Messages.get(i).Priority.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMessage tryGet(int pos)
|
||||||
|
{
|
||||||
|
if (pos < 0 || pos >= Messages.size()) return null;
|
||||||
|
return Messages.get(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size()
|
||||||
|
{
|
||||||
|
return Messages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(MessageAdapter adp)
|
||||||
|
{
|
||||||
|
_listener.add(new WeakReference<>(adp));
|
||||||
|
CleanUpListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanUpListener()
|
||||||
|
{
|
||||||
|
for (int i=_listener.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (_listener.get(i).get() == null) _listener.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
public enum PriorityEnum
|
||||||
|
{
|
||||||
|
LOW(0),
|
||||||
|
NORMAL(1),
|
||||||
|
HIGH(2);
|
||||||
|
|
||||||
|
public final int ID;
|
||||||
|
|
||||||
|
PriorityEnum(int id) { ID = id; }
|
||||||
|
|
||||||
|
public static PriorityEnum parseAPI(String v) throws Exception
|
||||||
|
{
|
||||||
|
for (PriorityEnum p : values())
|
||||||
|
{
|
||||||
|
if (String.valueOf(p.ID).equals(v.trim())) return p;
|
||||||
|
}
|
||||||
|
throw new Exception("Invalid value for <PriorityEnum> : '"+v+"'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PriorityEnum parseAPI(int v)
|
||||||
|
{
|
||||||
|
for (PriorityEnum p : values())
|
||||||
|
{
|
||||||
|
if (p.ID == v) return p;
|
||||||
|
}
|
||||||
|
return PriorityEnum.NORMAL;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,126 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
|
||||||
|
public class SCNSettings
|
||||||
|
{
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static SCNSettings _inst = null;
|
||||||
|
public static SCNSettings inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
return _inst = new SCNSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int quota_curr;
|
||||||
|
public int quota_max;
|
||||||
|
public int user_id;
|
||||||
|
public String user_key;
|
||||||
|
|
||||||
|
public String fcm_token_local;
|
||||||
|
public String fcm_token_server;
|
||||||
|
|
||||||
|
public SCNSettings()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
quota_curr = sharedPref.getInt("quota_curr", 0);
|
||||||
|
quota_max = sharedPref.getInt("quota_max", 0);
|
||||||
|
user_id = sharedPref.getInt("user_id", -1);
|
||||||
|
user_key = sharedPref.getString("user_key", "");
|
||||||
|
fcm_token_local = sharedPref.getString("fcm_token_local", "");
|
||||||
|
fcm_token_server = sharedPref.getString("fcm_token_server", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("Config", Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor e = sharedPref.edit();
|
||||||
|
|
||||||
|
e.putInt("quota_curr", quota_curr);
|
||||||
|
e.putInt("quota_max", quota_max);
|
||||||
|
e.putInt("user_id", user_id);
|
||||||
|
e.putString("user_key", user_key);
|
||||||
|
e.putString("fcm_token_local", fcm_token_local);
|
||||||
|
e.putString("fcm_token_server", fcm_token_server);
|
||||||
|
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return user_id>=0 && user_key != null && !user_key.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createOnlineURL()
|
||||||
|
{
|
||||||
|
if (!isConnected()) return ServerCommunication.BASE_URL + "index.php";
|
||||||
|
return ServerCommunication.BASE_URL + "index.php?preset_user_id="+user_id+"&preset_user_key="+user_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerToken(String token, View loader)
|
||||||
|
{
|
||||||
|
if (isConnected())
|
||||||
|
{
|
||||||
|
fcm_token_local = token;
|
||||||
|
save();
|
||||||
|
if (!fcm_token_local.equals(fcm_token_server)) ServerCommunication.update(user_id, user_key, fcm_token_local, loader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fcm_token_local = token;
|
||||||
|
save();
|
||||||
|
ServerCommunication.register(fcm_token_local, loader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void work(Activity a)
|
||||||
|
{
|
||||||
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
|
{
|
||||||
|
String newToken = instanceIdResult.getToken();
|
||||||
|
Log.e("FB::GetInstanceId", newToken);
|
||||||
|
SCNSettings.inst().setServerToken(newToken, null);
|
||||||
|
}).addOnCompleteListener(r ->
|
||||||
|
{
|
||||||
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(View loader)
|
||||||
|
{
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
ServerCommunication.update(user_id, user_key, loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh(View loader, Activity a)
|
||||||
|
{
|
||||||
|
if (isConnected())
|
||||||
|
{
|
||||||
|
ServerCommunication.info(user_id, user_key, loader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(a, instanceIdResult ->
|
||||||
|
{
|
||||||
|
String newToken = instanceIdResult.getToken();
|
||||||
|
Log.e("FB::GetInstanceId", newToken);
|
||||||
|
SCNSettings.inst().setServerToken(newToken, loader);
|
||||||
|
}).addOnCompleteListener(r ->
|
||||||
|
{
|
||||||
|
if (isConnected()) ServerCommunication.info(user_id, user_key, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,273 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.model;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public class ServerCommunication
|
||||||
|
{
|
||||||
|
public static final String BASE_URL = "https://scn.blackforestbytes.com/";
|
||||||
|
|
||||||
|
private static final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
private ServerCommunication(){ throw new Error("no."); }
|
||||||
|
|
||||||
|
public static void register(String token, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "register.php?fcm_token="+token)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(int id, String key, String token, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "update.php?user_id="+id+"&user_key="+key+"&fcm_token="+token)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response)
|
||||||
|
{
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success"))
|
||||||
|
{
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
|
SCNSettings.inst().fcm_token_server = token;
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SCNApp.runOnUiThread(() -> { if (loader!=null)loader.setVisibility(View.GONE); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(int id, String key, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "update.php?user_id=" + id + "&user_key=" + key)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success")) {
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().user_key = json.getString("user_key");
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
} finally {
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(int id, String key, View loader)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(BASE_URL + "info.php?user_id=" + id + "&user_key=" + key)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
if (responseBody == null) throw new IOException("No response");
|
||||||
|
|
||||||
|
String r = responseBody.string();
|
||||||
|
Log.d("Server::Response", r);
|
||||||
|
|
||||||
|
JSONObject json = (JSONObject) new JSONTokener(r).nextValue();
|
||||||
|
|
||||||
|
if (!json.getBoolean("success")) {
|
||||||
|
SCNApp.showToast(json.getString("message"), 4000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SCNSettings.inst().user_id = json.getInt("user_id");
|
||||||
|
SCNSettings.inst().quota_curr = json.getInt("quota");
|
||||||
|
SCNSettings.inst().quota_max = json.getInt("quota_max");
|
||||||
|
SCNSettings.inst().save();
|
||||||
|
|
||||||
|
SCNApp.refreshAccountTab();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
} finally {
|
||||||
|
SCNApp.runOnUiThread(() -> {
|
||||||
|
if (loader != null) loader.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
SCNApp.showToast("Communication with server failed", 4000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.PriorityEnum;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||||
|
import com.google.firebase.messaging.RemoteMessage;
|
||||||
|
|
||||||
|
public class FBMService extends FirebaseMessagingService
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onNewToken(String token)
|
||||||
|
{
|
||||||
|
Log.i("Firebase::NewToken", token);
|
||||||
|
SCNSettings.inst().setServerToken(token, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(RemoteMessage remoteMessage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.i("FB::MessageReceived", "From: " + remoteMessage.getFrom());
|
||||||
|
Log.i("FB::MessageReceived", "Payload: " + remoteMessage.getData());
|
||||||
|
if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Title: " + remoteMessage.getNotification().getTitle());
|
||||||
|
if (remoteMessage.getNotification() != null) Log.i("FB::MessageReceived", "Notify_Body: " + remoteMessage.getNotification().getBody());
|
||||||
|
|
||||||
|
long time = Long.parseLong(remoteMessage.getData().get("timestamp"));
|
||||||
|
String title = remoteMessage.getData().get("title");
|
||||||
|
String content = remoteMessage.getData().get("body");
|
||||||
|
PriorityEnum prio = PriorityEnum.parseAPI(remoteMessage.getData().get("priority"));
|
||||||
|
|
||||||
|
CMessage msg = CMessageList.inst().add(time, title, content, prio);
|
||||||
|
|
||||||
|
|
||||||
|
if (SCNApp.isBackground())
|
||||||
|
{
|
||||||
|
NotificationService.inst().show(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SCNApp.showToast("Message recieved: " + title, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e("FB:Err", e.toString());
|
||||||
|
SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.service;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.view.MainActivity;
|
||||||
|
|
||||||
|
public class NotificationService
|
||||||
|
{
|
||||||
|
private final static String CHANNEL_ID = "CHAN_BFB_SCN_MESSAGES";
|
||||||
|
|
||||||
|
private final static Object _lock = new Object();
|
||||||
|
private static NotificationService _inst = null;
|
||||||
|
public static NotificationService inst()
|
||||||
|
{
|
||||||
|
synchronized (_lock)
|
||||||
|
{
|
||||||
|
if (_inst != null) return _inst;
|
||||||
|
return _inst = new NotificationService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationService()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||||
|
{
|
||||||
|
Context ctxt = SCNApp.getContext();
|
||||||
|
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Push notifications", NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
channel.setDescription("Messages from the API");
|
||||||
|
channel.setLightColor(Color.rgb(255, 0, 0));
|
||||||
|
channel.setVibrationPattern(new long[]{200});
|
||||||
|
channel.enableLights(true);
|
||||||
|
channel.enableVibration(true);
|
||||||
|
|
||||||
|
NotificationManager notificationManager = ctxt.getSystemService(NotificationManager.class);
|
||||||
|
if (notificationManager != null) notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(CMessage msg)
|
||||||
|
{
|
||||||
|
Context ctxt = SCNApp.getContext();
|
||||||
|
|
||||||
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ctxt, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_bfb)
|
||||||
|
.setContentTitle(msg.Title)
|
||||||
|
.setContentText(msg.Content)
|
||||||
|
.setShowWhen(true)
|
||||||
|
.setWhen(msg.Timestamp)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
Intent intent = new Intent(ctxt, MainActivity.class);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(ctxt, 0, intent, 0);
|
||||||
|
mBuilder.setContentIntent(pi);
|
||||||
|
NotificationManager mNotificationManager = (NotificationManager) ctxt.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (mNotificationManager != null) mNotificationManager.notify(0, mBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,115 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
|
||||||
|
import net.glxn.qrgen.android.QRCode;
|
||||||
|
import net.glxn.qrgen.core.image.ImageType;
|
||||||
|
|
||||||
|
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||||
|
|
||||||
|
public class AccountFragment extends Fragment
|
||||||
|
{
|
||||||
|
public AccountFragment()
|
||||||
|
{
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.fragment_account, container, false);
|
||||||
|
|
||||||
|
updateUI(v);
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnCopyUserID).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) cv.getContext().getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText("UserID", String.valueOf(SCNSettings.inst().user_id)));
|
||||||
|
SCNApp.showToast("Copied userID to clipboard", 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnCopyUserKey).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) cv.getContext().getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText("UserKey", String.valueOf(SCNSettings.inst().user_key)));
|
||||||
|
SCNApp.showToast("Copied key to clipboard", 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnAccountReset).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
View lpnl = v.findViewById(R.id.loadingPanel);
|
||||||
|
lpnl.setVisibility(View.VISIBLE);
|
||||||
|
SCNSettings.inst().reset(lpnl);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnClearLocalStorage).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
CMessageList.inst().clear();
|
||||||
|
SCNApp.showToast("Notifications cleared", 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnQR).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(SCNSettings.inst().createOnlineURL()));
|
||||||
|
startActivity(browserIntent);
|
||||||
|
});
|
||||||
|
|
||||||
|
v.findViewById(R.id.btnRefresh).setOnClickListener(cv ->
|
||||||
|
{
|
||||||
|
View lpnl = v.findViewById(R.id.loadingPanel);
|
||||||
|
lpnl.setVisibility(View.VISIBLE);
|
||||||
|
SCNSettings.inst().refresh(lpnl, getActivity());
|
||||||
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUI()
|
||||||
|
{
|
||||||
|
updateUI(getView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
public void updateUI(View v)
|
||||||
|
{
|
||||||
|
if (v == null) return;
|
||||||
|
TextView tvUserID = v.findViewById(R.id.tvUserID);
|
||||||
|
TextView tvUserKey = v.findViewById(R.id.tvUserKey);
|
||||||
|
TextView tvQuota = v.findViewById(R.id.tvQuota);
|
||||||
|
ImageButton btnQR = v.findViewById(R.id.btnQR);
|
||||||
|
|
||||||
|
SCNSettings s = SCNSettings.inst();
|
||||||
|
|
||||||
|
if (s.isConnected())
|
||||||
|
{
|
||||||
|
tvUserID.setText(String.valueOf(s.user_id));
|
||||||
|
tvUserKey.setText(s.user_key);
|
||||||
|
tvQuota.setText(String.format("%d / %d", s.quota_curr, s.quota_max));
|
||||||
|
btnQR.setImageBitmap(QRCode.from(s.createOnlineURL()).to(ImageType.PNG).withSize(512, 512).bitmap());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tvUserID.setText(R.string.str_not_connected);
|
||||||
|
tvUserKey.setText(R.string.str_not_connected);
|
||||||
|
tvQuota.setText(R.string.str_not_connected);
|
||||||
|
btnQR.setImageResource(R.drawable.qr_default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.view.PagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.SCNSettings;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.ServerCommunication;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.service.NotificationService;
|
||||||
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity
|
||||||
|
{
|
||||||
|
public TabAdapter adpTabs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
NotificationService.inst();
|
||||||
|
CMessageList.inst();
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
ViewPager viewPager = findViewById(R.id.pager);
|
||||||
|
PagerAdapter adapter = adpTabs = new TabAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
TabLayout tabLayout = findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
SCNApp.register(this);
|
||||||
|
|
||||||
|
SCNSettings.inst().work(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop()
|
||||||
|
{
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
CMessageList.inst().fullSave();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,112 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
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 android.widget.Toast;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.SCNApp;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessage;
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.model.CMessageList;
|
||||||
|
|
||||||
|
public class MessageAdapter extends RecyclerView.Adapter
|
||||||
|
{
|
||||||
|
private final View vNoElements;
|
||||||
|
|
||||||
|
public MessageAdapter(View noElementsView)
|
||||||
|
{
|
||||||
|
vNoElements = noElementsView;
|
||||||
|
CMessageList.inst().register(this);
|
||||||
|
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
|
||||||
|
{
|
||||||
|
View myView = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_card, parent, false);
|
||||||
|
return new MessagePresenter(myView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
|
||||||
|
{
|
||||||
|
CMessage msg = CMessageList.inst().tryGet(position);
|
||||||
|
MessagePresenter view = (MessagePresenter) holder;
|
||||||
|
view.setMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount()
|
||||||
|
{
|
||||||
|
return CMessageList.inst().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void customNotifyItemInserted(int idx)
|
||||||
|
{
|
||||||
|
notifyItemInserted(idx);
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void customNotifyDataSetChanged()
|
||||||
|
{
|
||||||
|
notifyDataSetChanged();
|
||||||
|
vNoElements.setVisibility(getItemCount()>0 ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener
|
||||||
|
{
|
||||||
|
private TextView tvTimestamp;
|
||||||
|
private TextView tvTitle;
|
||||||
|
private TextView tvMessage;
|
||||||
|
private ImageView ivPriority;
|
||||||
|
|
||||||
|
private CMessage data;
|
||||||
|
|
||||||
|
MessagePresenter(View itemView)
|
||||||
|
{
|
||||||
|
super(itemView);
|
||||||
|
tvTimestamp = itemView.findViewById(R.id.tvTimestamp);
|
||||||
|
tvTitle = itemView.findViewById(R.id.tvTitle);
|
||||||
|
tvMessage = itemView.findViewById(R.id.tvMessage);
|
||||||
|
ivPriority = itemView.findViewById(R.id.ivPriority);
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMessage(CMessage msg)
|
||||||
|
{
|
||||||
|
tvTimestamp.setText(msg.formatTimestamp());
|
||||||
|
tvTitle.setText(msg.Title);
|
||||||
|
tvMessage.setText(msg.Content);
|
||||||
|
|
||||||
|
switch (msg.Priority)
|
||||||
|
{
|
||||||
|
case LOW:
|
||||||
|
ivPriority.setVisibility(View.VISIBLE);
|
||||||
|
ivPriority.setImageResource(R.drawable.priority_low);
|
||||||
|
break;
|
||||||
|
case NORMAL:
|
||||||
|
ivPriority.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
case HIGH:
|
||||||
|
ivPriority.setVisibility(View.VISIBLE);
|
||||||
|
ivPriority.setImageResource(R.drawable.priority_high);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v)
|
||||||
|
{
|
||||||
|
//SCNApp.showToast(data.Title, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
|
||||||
|
public class NotificationsFragment extends Fragment
|
||||||
|
{
|
||||||
|
public NotificationsFragment()
|
||||||
|
{
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||||
|
|
||||||
|
RecyclerView rvMessages = v.findViewById(R.id.rvMessages);
|
||||||
|
rvMessages.setLayoutManager(new LinearLayoutManager(this.getContext(), RecyclerView.VERTICAL, true));
|
||||||
|
rvMessages.setAdapter(new MessageAdapter(v.findViewById(R.id.tvNoElements)));
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import com.blackforestbytes.simplecloudnotifier.R;
|
||||||
|
|
||||||
|
public class SettingsFragment extends PreferenceFragmentCompat
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
|
||||||
|
{
|
||||||
|
setPreferencesFromResource(R.xml.preferences, rootKey);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package com.blackforestbytes.simplecloudnotifier.view;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
|
||||||
|
public class TabAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
|
public NotificationsFragment tab1 = new NotificationsFragment();
|
||||||
|
public AccountFragment tab2 = new AccountFragment();
|
||||||
|
public SettingsFragment tab3 = new SettingsFragment();
|
||||||
|
|
||||||
|
public TabAdapter(FragmentManager fm)
|
||||||
|
{
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return tab1;
|
||||||
|
case 1:
|
||||||
|
return tab2;
|
||||||
|
case 2:
|
||||||
|
return tab3;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position)
|
||||||
|
{
|
||||||
|
switch (position)
|
||||||
|
{
|
||||||
|
case 0: return "Notifications";
|
||||||
|
case 1: return "Account";
|
||||||
|
case 2: return "Settings";
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
15
android/app/src/main/res/drawable/ic_bfb.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="1000"
|
||||||
|
android:viewportWidth="1000" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000"
|
||||||
|
android:pathData="M500,500m-500,0a500,500 0,1 1,1000 0a500,500 0,1 1,-1000 0"
|
||||||
|
android:strokeColor="#000000" android:strokeWidth="1"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M300,694L700,694L500,136L300,694"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M473,559L527,559L527,774L473,774"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M376,640L624,640L500,295L376,640"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M100,730L500,730L300,172L100,730"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,676L424,676L300,331L176,676"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M273,595L327,595L327,810L273,810"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M500,730L900,730L700,172L500,730"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M576,676L824,676L700,331L576,676"/>
|
||||||
|
<path android:fillColor="#ffffff" android:pathData="M673,595L727,595L727,810L673,810"/>
|
||||||
|
</vector>
|
6
android/app/src/main/res/drawable/ic_copy.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="22"
|
||||||
|
android:viewportWidth="21" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#000000" android:fillType="evenOdd"
|
||||||
|
android:pathData="M14.5,0L2.5,0C1.4,0 0.5,0.9 0.5,2L0.5,16L2.5,16L2.5,2L14.5,2L14.5,0L14.5,0ZM17.5,4L6.5,4C5.4,4 4.5,4.9 4.5,6L4.5,20C4.5,21.1 5.4,22 6.5,22L17.5,22C18.6,22 19.5,21.1 19.5,20L19.5,6C19.5,4.9 18.6,4 17.5,4L17.5,4ZM17.5,20L6.5,20L6.5,6L17.5,6L17.5,20L17.5,20Z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||||
|
</vector>
|
7
android/app/src/main/res/drawable/ic_fuel.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="100"
|
||||||
|
android:viewportWidth="100" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#000000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="m22.917,12.5c-2.308,0 -4.167,1.858 -4.167,4.167l0,66.667c0,2.083 2.083,4.167 4.167,4.167l37.5,0c2.083,0 4.167,-2.083 4.167,-4.167l0,-20.833 2.083,0c2.083,0 2.083,2.083 2.083,2.083l0,8.333c0,4.167 2.083,6.25 6.25,6.25 4.167,0 6.25,-2.083 6.25,-6.25 0,-4.861 0,-18.75 0,-22.917 0,-4.167 -8.333,-8.333 -8.333,-12.5l0,-12.5 -4.167,0 -4.167,-4.167 0,-4.167c0,-2.308 -1.858,-4.167 -4.167,-4.167zM27.083,20.833 L56.25,20.833 56.25,37.5 27.083,37.5zM64.583,33.333 L68.75,33.333c0,0 0,3.472 0,6.25 0,4.167 8.333,8.333 8.333,12.5l0,20.833C77.083,75 75,75 75,75c0,0 -2.083,0 -2.083,-2.083 0,0 0,-8.333 0,-10.417 0,-2.083 -2.083,-4.167 -4.167,-4.167 -1.389,0 -4.167,0 -4.167,0z"
|
||||||
|
android:strokeColor="#00000000" android:strokeWidth="2"/>
|
||||||
|
</vector>
|
4
android/app/src/main/res/drawable/ic_key.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="48"
|
||||||
|
android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M25.3,20c-1.65,-4.66 -6.08,-8 -11.3,-8 -6.63,0 -12,5.37 -12,12s5.37,12 12,12c5.22,0 9.65,-3.34 11.3,-8h8.7v8h8v-8h4v-8h-20.7zM14,28c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/>
|
||||||
|
</vector>
|
6
android/app/src/main/res/drawable/ic_refresh.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<vector
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="16" android:viewportHeight="16"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#231F20" android:pathData="M14,8c-0.609,0-0.898,0.43-1,0.883C12.635,10.516,11.084,13,8,13c-0.757,0-1.473-0.172-2.114-0.474L6.414,12 C6.773,11.656,7,11.445,7,11c0-0.523-0.438-1-1-1H3c-0.609,0-1,0.492-1,1v3c0,0.541,0.428,1,1,1c0.484,0,0.688-0.273,1-0.594 l0.408-0.407C5.458,14.632,6.685,15,8,15c4.99,0,7-4.75,7-5.938C15,8.336,14.469,8,14,8z M3,7.117C3.365,5.485,4.916,3,8,3 c0.757,0,1.473,0.171,2.114,0.473L9.586,4C9.227,4.344,9,4.555,9,5c0,0.523,0.438,1,1,1h3c0.609,0,1-0.492,1-1V2 c0-0.541-0.428-1-1-1c-0.484,0-0.688,0.273-1,0.594l-0.408,0.407C10.542,1.368,9.315,1,8,1C3.01,1,1,5.75,1,6.938 C1,7.664,1.531,8,2,8C2.609,8,2.898,7.57,3,7.117z"/>
|
||||||
|
</vector>
|
12
android/app/src/main/res/drawable/ic_user.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7,8a5,6 0,1 0,10 0a5,6 0,1 0,-10 0z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.8,19.1c-0.9,-1.8 -2.6,-3.3 -4.8,-4.2c-0.6,-0.2 -1.3,-0.2 -1.8,0.1c-1,0.6 -2,0.9 -3.2,0.9s-2.2,-0.3 -3.2,-0.9C8.3,14.8 7.6,14.7 7,15c-2.2,0.9 -3.9,2.4 -4.8,4.2C1.5,20.5 2.6,22 4.1,22h15.8C21.4,22 22.5,20.5 21.8,19.1z"/>
|
||||||
|
</vector>
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
9
android/app/src/main/res/drawable/priority_high.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.4,10.6l-8,-8c-0.8,-0.8 -2,-0.8 -2.8,0l-8,8c-0.8,0.8 -0.8,2 0,2.8l8,8c0.8,0.8 2,0.8 2.8,0l8,-8C22.2,12.6 22.2,11.4 21.4,10.6zM13,17h-2v-2h2V17zM13,13h-2V7h2V13z"/>
|
||||||
|
</vector>
|
9
android/app/src/main/res/drawable/priority_low.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.4,10.6l-8,-8c-0.8,-0.8 -2,-0.8 -2.8,0l-8,8c-0.8,0.8 -0.8,2 0,2.8l8,8c0.8,0.8 2,0.8 2.8,0l8,-8C22.2,12.6 22.2,11.4 21.4,10.6zM12,17l-3,-3h2V7h2v7h2L12,17z"/>
|
||||||
|
</vector>
|
422
android/app/src/main/res/drawable/qr_default.xml
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
<vector
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="296" android:viewportHeight="296"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M32,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M40,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M48,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M56,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M64,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M72,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M80,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M88,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M96,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M104,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M112,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M120,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M128,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M136,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M144,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M152,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M160,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M168,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M176,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M184,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,88h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M192,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,184h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M208,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M216,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,112h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,216h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M224,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,96h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,120h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,200h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M232,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,144h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,160h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,208h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,248h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M240,256h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,104h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M248,240h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,32h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,40h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,48h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,56h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,64h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,72h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,80h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,128h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,136h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,152h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,168h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,176h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,192h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,224h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,232h8v8h-8z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M256,248h8v8h-8z"/>
|
||||||
|
</vector>
|
35
android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:showIn="@layout/activity_main">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="6dp"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<android.support.design.widget.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/toolbar"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="6dp"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:id="@+id/pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_below="@id/tab_layout"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
239
android/app/src/main/res/layout/fragment_account.xml
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<RelativeLayout 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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".view.AccountFragment">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_img_user"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="92dp"
|
||||||
|
android:contentDescription="@string/ic_img_user_desc"
|
||||||
|
android:src="@drawable/ic_user"
|
||||||
|
android:tint="#888"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lblUserID"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="@string/str_userid"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnRefresh"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_user"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/ic_img_user" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/svUserID"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnCopyUserID"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_user"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/lblUserID">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUserID"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout_editor_absoluteX="8dp" />
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnCopyUserID"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:contentDescription="@string/str_copy"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="?android:attr/actionModeCopyDrawable"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/svUserID"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnRefresh"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/svUserID" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnRefresh"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:contentDescription="@string/str_reload"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:src="@drawable/ic_refresh"
|
||||||
|
android:tint="#666"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/btnCopyUserID"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/lblUserID" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_img_key"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="92dp"
|
||||||
|
android:contentDescription="@string/ic_img_key_desc"
|
||||||
|
android:src="@drawable/ic_key"
|
||||||
|
android:tint="#888"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ic_img_user" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lblUserKey"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="@string/str_userkey"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_key"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/ic_img_key" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/svUserKey"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btnCopyUserKey"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_key"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/lblUserKey">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUserKey"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout_editor_absoluteX="8dp" />
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnCopyUserKey"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:contentDescription="@string/str_copy"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="?android:attr/actionModeCopyDrawable"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/svUserKey"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/svUserKey" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ic_img_quota"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="92dp"
|
||||||
|
android:contentDescription="@string/ic_img_fuel_desc"
|
||||||
|
android:src="@drawable/ic_fuel"
|
||||||
|
android:tint="#888"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ic_img_key" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lblQuota"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="@string/str_quota"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_quota"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/ic_img_quota" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/svQuota"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ic_img_quota"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/lblQuota">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQuota"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout_editor_absoluteX="8dp" />
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnQR"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:contentDescription="@string/str_qr_code"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/qr_default"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btnAccountReset"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ic_img_quota" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnAccountReset"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/str_reset_account"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btnClearLocalStorage" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnClearLocalStorage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Clear Messages"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:layout_editor_absoluteY="352dp" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/loadingPanel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#DD000000"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:indeterminateTint="#FFF"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
22
android/app/src/main/res/layout/fragment_notifications.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".view.NotificationsFragment">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/rvMessages"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvNoElements"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/no_notifications"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</FrameLayout>
|
80
android/app/src/main/res/layout/message_card.xml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/card_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="@dimen/card_margin"
|
||||||
|
android:elevation="3dp"
|
||||||
|
card_view:cardCornerRadius="@dimen/card_album_radius">
|
||||||
|
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:background="#FFFFFFFF"
|
||||||
|
android:layout_margin="3dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTimestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
|
||||||
|
android:text="2018-09-11 20:22:32" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/tvTimestamp"
|
||||||
|
android:layout_marginEnd="4sp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/colorBlack"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:maxLines="6"
|
||||||
|
|
||||||
|
android:text="Message from me"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvMessage"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/ivPriority"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_margin="4sp"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:maxLines="32"
|
||||||
|
android:scrollHorizontally="false"
|
||||||
|
|
||||||
|
android:text="asdasd asdasd asdasd a" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivPriority"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvTimestamp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_margin="4sp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:contentDescription="@string/desc_priority_icon" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -3,4 +3,6 @@
|
|||||||
<color name="colorPrimary">#3F51B5</color>
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
<color name="colorAccent">#FF4081</color>
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
|
||||||
|
<color name="colorBlack">#000</color>
|
||||||
</resources>
|
</resources>
|
5
android/app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="card_margin">5dp</dimen>
|
||||||
|
<dimen name="card_album_radius">0dp</dimen>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Simple Cloud Notifier</string>
|
||||||
|
|
||||||
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
|
<string name="ic_img_user_desc">Icon User</string>
|
||||||
|
<string name="str_userid">User ID</string>
|
||||||
|
<string name="str_userkey">Auth Key</string>
|
||||||
|
<string name="str_quota">Quota</string>
|
||||||
|
<string name="str_copy">Copy to clipboard</string>
|
||||||
|
<string name="ic_img_key_desc">Icon Key</string>
|
||||||
|
<string name="ic_img_fuel_desc">Icon Fuel</string>
|
||||||
|
<string name="str_qr_code">QR Code</string>
|
||||||
|
<string name="str_reset_account">Reset Account</string>
|
||||||
|
<string name="no_notifications">No notifications</string>
|
||||||
|
<string name="str_not_connected">not connected</string>
|
||||||
|
<string name="str_reload">reload</string>
|
||||||
|
<string name="desc_priority_icon">Priority icon</string>
|
||||||
|
</resources>
|
13
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="@style/PreferenceFixTheme.Light.NoActionBar">
|
||||||
|
<!-- your app branding color for the app bar -->
|
||||||
|
<item name="colorPrimary">#3F51B5</item>
|
||||||
|
<!-- darker variant for the status bar and contextual app bars -->
|
||||||
|
<item name="colorPrimaryDark">#303F9F</item>
|
||||||
|
<!-- theme UI controls like checkboxes and text fields -->
|
||||||
|
<item name="colorAccent">#FF4081</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
33
android/app/src/main/res/xml/preferences.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="message_count"
|
||||||
|
android:title="Keep x notifications"
|
||||||
|
android:summary="Remember tha last x notifications locally"
|
||||||
|
android:defaultValue="200" />
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="Notifications"
|
||||||
|
android:key="pref_key_notifications">
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="notification_enable_sound"
|
||||||
|
android:title="Notification sound"
|
||||||
|
android:summary="Play a sound when a notification is recieved"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="notification_enable_light"
|
||||||
|
android:title="Notification light"
|
||||||
|
android:summary="Turn the notification LED on when a notification is recieved"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="notification_enable_vibrate"
|
||||||
|
android:title="Notification vibration"
|
||||||
|
android:summary="Vibrate when a notification is recieved"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
3
android/app/version.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#Sat Oct 20 02:47:36 CEST 2018
|
||||||
|
VERSION_NAME=0.0.2
|
||||||
|
VERSION_CODE=2
|
@@ -7,10 +7,8 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||||
classpath 'com.google.gms:google-services:4.0.1'
|
classpath 'com.google.gms:google-services:4.0.1'
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
@@ -20,6 +18,8 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
maven { url "https://dl.bintray.com/gericop/maven" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -1,6 +1,6 @@
|
|||||||
#Fri Sep 21 22:14:10 CEST 2018
|
#Wed Sep 26 22:10:14 CEST 2018
|
||||||
distributionBase=GRADLE_USER_HOME
|
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-4.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
0
gradlew → android/gradlew
vendored
0
gradlew.bat → android/gradlew.bat
vendored
@@ -1,30 +0,0 @@
|
|||||||
apply plugin: 'com.android.application'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 27
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.blackforestbytes.simplecloudnotifier"
|
|
||||||
minSdkVersion 15
|
|
||||||
targetSdkVersion 27
|
|
||||||
versionCode 2
|
|
||||||
versionName "0.2"
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
|
|
||||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
|
||||||
|
|
||||||
implementation 'com.google.firebase:firebase-core:16.0.3'
|
|
||||||
implementation 'com.google.firebase:firebase-messaging:17.3.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
|
@@ -1,53 +0,0 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
||||||
import com.google.firebase.messaging.RemoteMessage;
|
|
||||||
|
|
||||||
public class FBMService extends FirebaseMessagingService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Called if InstanceID token is updated. This may occur if the security of
|
|
||||||
* the previous token had been compromised. Note that this is called when the InstanceID token
|
|
||||||
* is initially generated so this is where you would retrieve the token.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onNewToken(String token)
|
|
||||||
{
|
|
||||||
Log.i("Firebase::NewToken", token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
|
||||||
// [START_EXCLUDE]
|
|
||||||
// There are two types of messages data messages and notification messages. Data messages are handled
|
|
||||||
// here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
|
|
||||||
// traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
|
|
||||||
// is in the foreground. When the app is in the background an automatically generated notification is displayed.
|
|
||||||
// When the user taps on the notification they are returned to the app. Messages containing both notification
|
|
||||||
// and data payloads are treated as notification messages. The Firebase console always sends notification
|
|
||||||
// messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
|
|
||||||
// [END_EXCLUDE]
|
|
||||||
|
|
||||||
Log.i("FB::MessageReceived<0>", "");
|
|
||||||
|
|
||||||
|
|
||||||
// TODO(developer): Handle FCM messages here.
|
|
||||||
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
|
|
||||||
Log.i("FB::MessageReceived<1>", "From: " + remoteMessage.getFrom());
|
|
||||||
|
|
||||||
// Check if message contains a data payload.
|
|
||||||
if (remoteMessage.getData().size() > 0) {
|
|
||||||
Log.i("FB::MessageReceived<3>", "Payload: " + remoteMessage.getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if message contains a notification payload.
|
|
||||||
if (remoteMessage.getNotification() != null) {
|
|
||||||
Log.i("FB::MessageReceived<2>", "Payload: " + remoteMessage.getNotification().getBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also if you intend on generating your own notifications as a result of a received FCM
|
|
||||||
// message, here is where that should be initiated. See sendNotification method below.
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
package com.blackforestbytes.simplecloudnotifier;
|
|
||||||
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.google.android.gms.tasks.OnSuccessListener;
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
|
||||||
import com.google.firebase.iid.InstanceIdResult;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(InstanceIdResult instanceIdResult) {
|
|
||||||
Log.d("FB::ID", instanceIdResult.getId());
|
|
||||||
Log.d("FB::TOKEN", instanceIdResult.getToken());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Hello World!"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="Get ID"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
@@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">SimpleCloudNotifier</string>
|
|
||||||
</resources>
|
|
@@ -1,11 +0,0 @@
|
|||||||
<resources>
|
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
BIN
data/function_graphic.pdn
Normal file
BIN
data/function_graphic.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
data/icon.pdn
Normal file
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 83 KiB |
BIN
data/roguehero.ttf
Normal file
16
store/description.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
SimpleCloudNotifier is a app to display messages that you can send to your phone with a simple POST request to the right URL.
|
||||||
|
|
||||||
|
After you start the app it generates a userID and a private key.
|
||||||
|
Now you can send your message to https://simplecloudnotifier.blackforestbytes.com/send.php and a notification will be pushed to your phone.
|
||||||
|
(see https://simplecloudnotifier.blackforestbytes.com/ for an example with curl)
|
||||||
|
|
||||||
|
|
||||||
|
Use it to
|
||||||
|
- send yourself automated messages from cron jobs
|
||||||
|
- notify youreself when long-running scripts finish
|
||||||
|
- send server error messages directly to your phone
|
||||||
|
- integrate with other online services
|
||||||
|
|
||||||
|
The possibilities are endless*
|
||||||
|
|
||||||
|
<i>*Disclaimer: Developer does not actually guarantee endless possibilities</i>
|
183
web/.gitignore
vendored
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/git,windows,intellij,phpstorm+all
|
||||||
|
|
||||||
|
### Git ###
|
||||||
|
# Created by git for backups. To disable backups in Git:
|
||||||
|
# $ git config --global mergetool.keepBackup false
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Created by git when using merge tools for conflicts
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
*.REMOTE.*
|
||||||
|
*_BACKUP_*.txt
|
||||||
|
*_BASE_*.txt
|
||||||
|
*_LOCAL_*.txt
|
||||||
|
*_REMOTE_*.txt
|
||||||
|
|
||||||
|
### 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/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Intellij Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
### PhpStorm+all ###
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
|
||||||
|
### PhpStorm+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/git,windows,intellij,phpstorm+all
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
|
||||||
|
config.php
|
1
web/css/mini-dark.min.css
vendored
Normal file
1
web/css/mini-default.min.css
vendored
Normal file
1
web/css/mini-nord.min.css
vendored
Normal file
195
web/css/style.css
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
|
||||||
|
html
|
||||||
|
{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink-shadow {
|
||||||
|
0% { box-shadow: 0 0 32px #DDD; }
|
||||||
|
50% { box-shadow: none; }
|
||||||
|
100% { box-shadow: 0 0 32px #DDD; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainpnl
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 32px #DDD;
|
||||||
|
animation:blink-shadow ease-in-out 4s infinite;
|
||||||
|
width: 87%;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 900px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 485px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainpnl input,
|
||||||
|
#mainpnl textarea
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-label {
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.responsive-label .col-md-3 {
|
||||||
|
text-align:right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainpnl h1
|
||||||
|
{
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FFF;
|
||||||
|
text-shadow: #000 0 0 2px, #888 0 0 8px;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
#mainpnl h1 {
|
||||||
|
font-size: calc(0.85rem * var(--heading-ratio) * var(--heading-ratio) * var(--heading-ratio) * var(--heading-ratio));
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainpnl button
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copyinfo
|
||||||
|
{
|
||||||
|
margin: 4px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copyinfo a:hover
|
||||||
|
{
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
color: #00F;
|
||||||
|
}
|
||||||
|
|
||||||
|
#copyinfo a,
|
||||||
|
#copyinfo a:visited,
|
||||||
|
#copyinfo a:active
|
||||||
|
{
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
color: #AAA;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tr_link
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: -1px -1px 0 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tl_link
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: -1px 0 0 -1px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
padding: 4px 4px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icn-google-play {
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IgogICAgIHdpZHRoPSI1MCIgaGVpZ2h0PSI1MCIKICAgICB2aWV3Qm94PSIwIDAgNDggNDgiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDsiPjxnIGlkPSJzdXJmYWNlMSI+PHBhdGggc3R5bGU9IiBmaWxsOiM0REI2QUM7IiBkPSJNIDcuNzAzMTI1IDQuMDQyOTY5IEMgNy4yOTI5NjkgNC4xNDg0MzggNyA0LjUwNzgxMyA3IDUuMTIxMDk0IEMgNyA2LjkyMTg3NSA3IDIzLjkxNDA2MyA3IDIzLjkxNDA2MyBDIDcgMjMuOTE0MDYzIDcgNDIuMjgxMjUgNyA0My4wODk4NDQgQyA3IDQzLjUzNTE1NiA3LjE5NTMxMyA0My44MzU5MzggNy41IDQzLjk0NTMxMyBMIDI3LjY3OTY4OCAyMy44ODI4MTMgWiAiPjwvcGF0aD48cGF0aCBzdHlsZT0iIGZpbGw6I0RDRTc3NTsiIGQ9Ik0gMzMuMjM4MjgxIDE4LjM1OTM3NSBMIDI0LjkyOTY4OCAxMy41NjI1IEMgMjQuOTI5Njg4IDEzLjU2MjUgOS42ODM1OTQgNC43NjE3MTkgOC43ODkwNjMgNC4yNDIxODggQyA4LjQwMjM0NCA0LjAxOTUzMSA4LjAxOTUzMSAzLjk2MDkzOCA3LjcwMzEyNSA0LjA0Mjk2OSBMIDI3LjY4MzU5NCAyMy44ODI4MTMgWiAiPjwvcGF0aD48cGF0aCBzdHlsZT0iIGZpbGw6I0QzMkYyRjsiIGQ9Ik0gOC40MTc5NjkgNDMuODAwNzgxIEMgOC45NDkyMTkgNDMuNDkyMTg4IDIzLjY5OTIxOSAzNC45NzY1NjMgMzMuMjgxMjUgMjkuNDQ1MzEzIEwgMjcuNjc5Njg4IDIzLjg4MjgxMyBMIDcuNSA0My45NDUzMTMgQyA3Ljc0NjA5NCA0NC4wMzkwNjMgOC4wNjY0MDYgNDQuMDAzOTA2IDguNDE3OTY5IDQzLjgwMDc4MSBaICI+PC9wYXRoPjxwYXRoIHN0eWxlPSIgZmlsbDojRkJDMDJEOyIgZD0iTSA0MS4zOTg0MzggMjMuMDcwMzEzIEMgNDAuNjAxNTYzIDIyLjY0MDYyNSAzMy4yOTY4NzUgMTguMzk0NTMxIDMzLjI5Njg3NSAxOC4zOTQ1MzEgTCAzMy4yMzgyODEgMTguMzU5Mzc1IEwgMjcuNjc5Njg4IDIzLjg4MjgxMyBMIDMzLjI4MTI1IDI5LjQ0NTMxMyBDIDM3LjcxNDg0NCAyNi44ODY3MTkgNDEuMDQyOTY5IDI0Ljk2NDg0NCA0MS4zMzk4NDQgMjQuNzkyOTY5IEMgNDIuMjg1MTU2IDI0LjI0NjA5NCA0Mi4xOTUzMTMgMjMuNSA0MS4zOTg0MzggMjMuMDcwMzEzIFogIj48L3BhdGg+PC9nPjwvc3ZnPg==') 50% 50% no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSend
|
||||||
|
{
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSend .spinnerbox .spinner
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSend .spinnerbox
|
||||||
|
{
|
||||||
|
margin: -8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='number'] {
|
||||||
|
-moz-appearance:textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-invalid,
|
||||||
|
.input-invalid:hover,
|
||||||
|
.input-invalid:active
|
||||||
|
{
|
||||||
|
border-color: var(--input-invalid-color) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.success {
|
||||||
|
--card-back-color: rgb(48, 135, 50);
|
||||||
|
--card-border-color: rgba(0, 0, 0, 0.3);;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullcenterflex
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.card,
|
||||||
|
a.card:active,
|
||||||
|
a.card:visited,
|
||||||
|
a.card:hover
|
||||||
|
{
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.card:hover
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 16px #AAA;
|
||||||
|
}
|
15
web/css/toastify.min.css
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Minified by jsDelivr using clean-css v4.2.0.
|
||||||
|
* Original file: /npm/toastify.js@1.3.0/src/toastify.css
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* Toastify js 1.2.2
|
||||||
|
* https://github.com/apvarun/toastify-js
|
||||||
|
* @license MIT licensed
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Varun A P
|
||||||
|
*/
|
||||||
|
.toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215,.61,.355,1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px)}.toastify.on{opacity:1}.toast-close{opacity:.4;padding:0 5px}.right{right:15px}.left{left:15px}.top{top:-150px}.bottom{bottom:-150px}.rounded{border-radius:25px}.avatar{width:1.5em;height:1.5em;margin:0 5px;border-radius:2px}@media only screen and (max-width:360px){.left,.right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}}
|
||||||
|
/*# sourceMappingURL=/sm/734ed69e2fe87a4469526acc0a10708fa8e0211c7d4359f9e034ceb89bb5d540.map */
|
65
web/index.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Simple Cloud Notifications</title>
|
||||||
|
<link rel="stylesheet" href="/css/toastify.min.css"/>
|
||||||
|
<link rel="stylesheet" href="/css/mini-default.min.css"> <!-- https://minicss.org/ -->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form id="mainpnl">
|
||||||
|
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
|
<a href="/index_api.php" class="button bordered" id="tr_link">API</a>
|
||||||
|
|
||||||
|
<h1>Simple Cloud Notifier</h1>
|
||||||
|
|
||||||
|
<div class="row responsive-label">
|
||||||
|
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
|
||||||
|
<div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" <?php echo (isset($_GET['preset_user_id']) ? (' value="'.$_GET['preset_user_id'].'" '):(''));?> type="number"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row responsive-label">
|
||||||
|
<div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div>
|
||||||
|
<div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" <?php echo (isset($_GET['preset_user_key']) ? (' value="'.$_GET['preset_user_key'].'" '):(''));?> type="text" maxlength="64"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row responsive-label">
|
||||||
|
<div class="col-sm-12 col-md-3"><label for="prio" class="doc">Priority</label></div>
|
||||||
|
<div class="col-sm-12 col-md">
|
||||||
|
<select id="prio" class="doc" type="text" style="width:100%;">
|
||||||
|
<option value="0" <?php echo (( isset($_GET['preset_priority'])&&$_GET['preset_priority']==='0') ? 'selected':'');?>>Low</option>
|
||||||
|
<option value="1" <?php echo ((!isset($_GET['preset_priority'])||$_GET['preset_priority']==='1') ? 'selected':'');?>>Normal</option>
|
||||||
|
<option value="2" <?php echo (( isset($_GET['preset_priority'])&&$_GET['preset_priority']==='2') ? 'selected':'');?>>High</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row responsive-label">
|
||||||
|
<div class="col-sm-12 col-md-3"><label for="msg" class="doc">Message Title</label></div>
|
||||||
|
<div class="col-sm-12 col-md"><input placeholder="Message" id="msg" class="doc" <?php echo (isset($_GET['preset_title']) ? (' value="'.$_GET['preset_title'].'" '):(''));?> type="text" maxlength="80"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row responsive-label">
|
||||||
|
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
|
||||||
|
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="5"></textarea></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-3"></div>
|
||||||
|
<div class="col-sm-12 col-md"><button type="submit" class="primary bordered" id="btnSend">Send</button></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/logic.js" type="text/javascript" ></script>
|
||||||
|
<script src="/js/toastify.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
web/index_api.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/css/mini-default.min.css">
|
||||||
|
<title>Simple Cloud Notifications - API</title>
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form id="mainpnl">
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
|
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||||
|
|
||||||
|
<h1>Simple Cloud Notifier</h1>
|
||||||
|
|
||||||
|
<p>Get your user-id and user-key from the app and send notifications to your phone by performing a POST request against <code>https://simplecloudnotifier.blackforestbytes.com/send.php</code></p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "priority={0|1|2}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
--data "content={message_content}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
<p>The <code>content</code> and <code>priority</code> parameters are optional, you can also send message with only a title and the default priority</p>
|
||||||
|
<pre>curl \
|
||||||
|
--data "user_id={userid}" \
|
||||||
|
--data "user_key={userkey}" \
|
||||||
|
--data "title={message_title}" \
|
||||||
|
https://scn.blackforestbytes.com/send.php</pre>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
54
web/index_sent.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Simple Cloud Notifications</title>
|
||||||
|
<link rel="stylesheet" href="/css/mini-default.min.css">
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-nord.min.css">-->
|
||||||
|
<!--<link rel="stylesheet" href="/css/mini-dark.min.css">-->
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<form id="mainpnl">
|
||||||
|
|
||||||
|
<div class="fullcenterflex">
|
||||||
|
|
||||||
|
<?php if (isset($_GET['ok']) && $_GET['ok'] === "1" ): ?>
|
||||||
|
|
||||||
|
<a class="card success" href="/index.php?preset_user_id=<?php echo isset($_GET['preset_user_id'])?$_GET['preset_user_id']:'ERR';?>&preset_user_key=<?php echo isset($_GET['preset_user_key'])?$_GET['preset_user_key']:'ERR';?>">
|
||||||
|
<div class="section">
|
||||||
|
<h3 class="doc">Message sent</h3>
|
||||||
|
<p class="doc">Message succesfully sent<br>
|
||||||
|
<?php echo isset($_GET['quota_remain'])?$_GET['quota_remain']:'ERR';?>/<?php echo isset($_GET['quota_max'])?$_GET['quota_max']:'ERR';?> remaining</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<a class="card error" href="/index.php">
|
||||||
|
<div class="section">
|
||||||
|
<h3 class="doc">Failure</h3>
|
||||||
|
<p class="doc">Unknown error</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||||
|
<a href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||||
|
|
||||||
|
<h1>Simple Cloud Notifier</h1>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="copyinfo">
|
||||||
|
<a href="https://www.blackforestbytes.com">© blackforestbytes</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
42
web/info.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errid'=>101, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errid'=>102, 'message' => 'Missing parameter [[user_key]]']));
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (count($datas)<=0) die(json_encode(['success' => false, 'errid'=>201, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) die(json_encode(['success' => false, 'errid'=>202, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errid'=>203, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errid'=>204, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
$quota = $data['quota_today'];
|
||||||
|
$quota_max = $data['quota_max'];
|
||||||
|
|
||||||
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $quota=0;
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'quota' => $quota,
|
||||||
|
'quota_max'=> $quota_max,
|
||||||
|
'message' => 'ok'
|
||||||
|
]);
|
||||||
|
return 0;
|
90
web/js/logic.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
function send()
|
||||||
|
{
|
||||||
|
let me = document.getElementById("btnSend");
|
||||||
|
if (me.classList.contains("btn-disabled")) return;
|
||||||
|
|
||||||
|
me.innerHTML = "<div class=\"spinnerbox\"><div class=\"spinner primary\"></div></div>";
|
||||||
|
|
||||||
|
me.classList.add("btn-disabled");
|
||||||
|
|
||||||
|
let uid = document.getElementById("uid");
|
||||||
|
let key = document.getElementById("ukey");
|
||||||
|
let msg = document.getElementById("msg");
|
||||||
|
let txt = document.getElementById("txt");
|
||||||
|
let pio = document.getElementById("prio");
|
||||||
|
|
||||||
|
uid.classList.remove('input-invalid');
|
||||||
|
key.classList.remove('input-invalid');
|
||||||
|
msg.classList.remove('input-invalid');
|
||||||
|
txt.classList.remove('input-invalid');
|
||||||
|
pio.classList.remove('input-invalid');
|
||||||
|
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('user_id', uid.value);
|
||||||
|
data.append('user_key', key.value);
|
||||||
|
data.append('title', msg.value);
|
||||||
|
data.append('content', txt.value);
|
||||||
|
data.append('priority', pio.value);
|
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/send.php', true);
|
||||||
|
xhr.onreadystatechange = function ()
|
||||||
|
{
|
||||||
|
if (xhr.readyState !== 4) return;
|
||||||
|
|
||||||
|
console.log('Status: ' + xhr.status);
|
||||||
|
if (xhr.status === 200)
|
||||||
|
{
|
||||||
|
let resp = JSON.parse(xhr.responseText);
|
||||||
|
if (!resp.success)
|
||||||
|
{
|
||||||
|
if (resp.errhighlight === 101) uid.classList.add('input-invalid');
|
||||||
|
if (resp.errhighlight === 102) key.classList.add('input-invalid');
|
||||||
|
if (resp.errhighlight === 103) msg.classList.add('input-invalid');
|
||||||
|
if (resp.errhighlight === 104) txt.classList.add('input-invalid');
|
||||||
|
if (resp.errhighlight === 105) pio.classList.add('input-invalid');
|
||||||
|
|
||||||
|
Toastify({
|
||||||
|
text: resp.message,
|
||||||
|
gravity: "top",
|
||||||
|
positionLeft: false,
|
||||||
|
backgroundColor: "#D32F2F",
|
||||||
|
}).showToast();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location.href =
|
||||||
|
'/index_sent.php' +
|
||||||
|
'?ok=' + 1 +
|
||||||
|
'&message_count=' + resp.messagecount +
|
||||||
|
'"a=' + resp.quota +
|
||||||
|
'"a_remain=' + (resp.quota_max-resp.quota) +
|
||||||
|
'"a_max=' + resp.quota_max +
|
||||||
|
'&preset_user_id=' + uid.value +
|
||||||
|
'&preset_user_key=' + key.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Toastify({
|
||||||
|
text: 'Request failed: Statuscode=' + xhr.status,
|
||||||
|
gravity: "top",
|
||||||
|
positionLeft: false,
|
||||||
|
backgroundColor: "#D32F2F",
|
||||||
|
}).showToast();
|
||||||
|
}
|
||||||
|
|
||||||
|
me.classList.remove("btn-disabled");
|
||||||
|
me.innerHTML = "Send";
|
||||||
|
};
|
||||||
|
xhr.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load",function ()
|
||||||
|
{
|
||||||
|
let btnSend = document.getElementById("btnSend");
|
||||||
|
|
||||||
|
if (btnSend !== undefined) btnSend.onclick = function () { send(); return false; };
|
||||||
|
|
||||||
|
},false);
|
8
web/js/toastify.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Minified by jsDelivr using UglifyJS v3.4.3.
|
||||||
|
* Original file: /npm/toastify-js@1.3.0/src/toastify.js
|
||||||
|
*
|
||||||
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
*/
|
||||||
|
!function(t,o){"object"==typeof module&&module.exports?(require("./toastify.css"),module.exports=o()):t.Toastify=o()}(this,function(t){var i=function(t){return new i.lib.init(t)};function r(t,o){return!(!t||"string"!=typeof o)&&!!(t.className&&-1<t.className.trim().split(/\s+/gi).indexOf(o))}return i.lib=i.prototype={toastify:"1.2.2",constructor:i,init:function(t){return t||(t={}),this.options={},this.options.text=t.text||"Hi there!",this.options.duration=t.duration||3e3,this.options.selector=t.selector,this.options.callback=t.callback||function(){},this.options.destination=t.destination,this.options.newWindow=t.newWindow||!1,this.options.close=t.close||!1,this.options.gravity="bottom"==t.gravity?"bottom":"top",this.options.positionLeft=t.positionLeft||!1,this.options.backgroundColor=t.backgroundColor,this.options.avatar=t.avatar||"",this.options.className=t.className||"",this},buildToast:function(){if(!this.options)throw"Toastify is not initialized";var t=document.createElement("div");if(t.className="toastify on "+this.options.className,!0===this.options.positionLeft?t.className+=" left":t.className+=" right",t.className+=" "+this.options.gravity,this.options.backgroundColor&&(t.style.background=this.options.backgroundColor),t.innerHTML=this.options.text,""!==this.options.avatar){var o=document.createElement("img");o.src=this.options.avatar,o.className="avatar",!0===this.options.positionLeft?t.appendChild(o):t.insertAdjacentElement("beforeend",o)}if(!0===this.options.close){var i=document.createElement("span");i.innerHTML="✖",i.className="toast-close",i.addEventListener("click",function(t){t.stopPropagation(),this.removeElement(t.target.parentElement),window.clearTimeout(t.target.parentElement.timeOutValue)}.bind(this));var n=0<window.innerWidth?window.innerWidth:screen.width;!0===this.options.positionLeft&&360<n?t.insertAdjacentElement("afterbegin",i):t.appendChild(i)}return void 0!==this.options.destination&&t.addEventListener("click",function(t){t.stopPropagation(),!0===this.options.newWindow?window.open(this.options.destination,"_blank"):window.location=this.options.destination}.bind(this)),t},showToast:function(){var t,o=this.buildToast();if(!(t=void 0===this.options.selector?document.body:document.getElementById(this.options.selector)))throw"Root element is not defined";return t.insertBefore(o,t.firstChild),i.reposition(),o.timeOutValue=window.setTimeout(function(){this.removeElement(o)}.bind(this),this.options.duration),this},removeElement:function(t){t.className=t.className.replace(" on",""),window.setTimeout(function(){t.parentNode.removeChild(t),this.options.callback.call(t),i.reposition()}.bind(this),400)}},i.reposition=function(){for(var t,o={top:15,bottom:15},i={top:15,bottom:15},n={top:15,bottom:15},e=document.getElementsByClassName("toastify"),s=0;s<e.length;s++){t=!0===r(e[s],"top")?"top":"bottom";var a=e[s].offsetHeight;(0<window.innerWidth?window.innerWidth:screen.width)<=360?(e[s].style[t]=n[t]+"px",n[t]+=a+15):!0===r(e[s],"left")?(e[s].style[t]=o[t]+"px",o[t]+=a+15):(e[s].style[t]=i[t]+"px",i[t]+=a+15)}return this},i.lib.init.prototype=i.lib,i});
|
||||||
|
//# sourceMappingURL=/sm/3f68e387be4f7a323a891120e4e01e3bee54a927113a386cf5e598b3cd442fcc.map
|
BIN
web/lib/httpful.phar
Normal file
73
web/model.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include('lib/httpful.phar');
|
||||||
|
|
||||||
|
class Statics
|
||||||
|
{
|
||||||
|
public static $DB = NULL;
|
||||||
|
public static $CFG = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig()
|
||||||
|
{
|
||||||
|
if (Statics::$CFG !== NULL) return Statics::$CFG;
|
||||||
|
|
||||||
|
return Statics::$CFG = require "config.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatabase()
|
||||||
|
{
|
||||||
|
if (Statics::$DB !== NULL) return Statics::$DB;
|
||||||
|
|
||||||
|
$_config = getConfig()['database'];
|
||||||
|
|
||||||
|
$dsn = "mysql:host=" . $_config['host'] . ";dbname=" . $_config['database'] . ";charset=utf8";
|
||||||
|
$opt = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
return Statics::$DB = new PDO($dsn, $_config['user'], $_config['password'], $opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRandomAuthKey()
|
||||||
|
{
|
||||||
|
$random = '';
|
||||||
|
for ($i = 0; $i < 64; $i++)
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (random_int(1, 3)) {
|
||||||
|
case 1:
|
||||||
|
$random .= chr(random_int(ord('0'), ord('9')));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
$random .= chr(random_int(ord('A'), ord('Z')));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
$random .= chr(random_int(ord('a'), ord('z')));
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
die(json_encode(['success' => false, 'message' => 'Internal error - no randomness']));
|
||||||
|
}
|
||||||
|
return $random;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPOST($url, $body, $header)
|
||||||
|
{
|
||||||
|
$builder = \Httpful\Request::post($url);
|
||||||
|
|
||||||
|
$builder->body($body);
|
||||||
|
|
||||||
|
foreach ($header as $k => $v) $builder->addHeader($k, $v);
|
||||||
|
|
||||||
|
$response = $builder->send();
|
||||||
|
|
||||||
|
if ($response->code != 200) throw new Exception("Repsponse code: " . $response->code);
|
||||||
|
|
||||||
|
return $response->body;
|
||||||
|
}
|
28
web/register.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
if (!isset($INPUT['fcm_token'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[fcm_token]]']));
|
||||||
|
|
||||||
|
$fcmtoken = $INPUT['fcm_token'];
|
||||||
|
$user_key = generateRandomAuthKey();
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO users (user_key, fcm_token, timestamp_accessed) VALUES (:key, :token, NOW())');
|
||||||
|
$stmt->execute(['key' => $user_key, 'token' => $fcmtoken]);
|
||||||
|
$user_id = $pdo->lastInsertId('user_id');
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $user_key,
|
||||||
|
'quota' => 0,
|
||||||
|
'quota_max'=> 100,
|
||||||
|
'message' => 'New user registered'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return 0;
|
15
web/schema.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE `users`
|
||||||
|
(
|
||||||
|
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_key` VARCHAR(64) NOT NULL,
|
||||||
|
`fcm_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||||
|
`messages_sent` INT(11) NOT NULL DEFAULT '0',
|
||||||
|
`timestamp_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`timestamp_accessed` DATETIME NULL DEFAULT NULL,
|
||||||
|
|
||||||
|
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
||||||
|
`quota_day` DATE NULL DEFAULT NULL,
|
||||||
|
`quota_max` INT(11) NOT NULL DEFAULT '100',
|
||||||
|
|
||||||
|
PRIMARY KEY (`user_id`)
|
||||||
|
);
|
100
web/send.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
sleep(1);
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'errhighlight' => 102, 'message' => 'Missing parameter [[user_token]]']));
|
||||||
|
if (!isset($INPUT['title'])) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'Missing parameter [[title]]']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
$message = $INPUT['title'];
|
||||||
|
$content = isset($INPUT['content']) ? $INPUT['content'] : '';
|
||||||
|
$priority = isset($INPUT['priority']) ? $INPUT['priority'] : '1';
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
if ($priority !== '0' && $priority !== '1' && $priority !== '2') die(json_encode(['success' => false, 'errhighlight' => 105, 'message' => 'Invalid priority']));
|
||||||
|
|
||||||
|
if (strlen(trim($message)) == 0) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'No title specified']));
|
||||||
|
if (strlen($message) > 120) die(json_encode(['success' => false, 'errhighlight' => 103, 'message' => 'Title too long (120 characters)']));
|
||||||
|
if (strlen($content) > 10000) die(json_encode(['success' => false, 'errhighlight' => 104, 'message' => 'Content too long (10000 characters)']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, fcm_token, messages_sent, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (count($datas)<=0) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'errhighlight' => 101, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'errhighlight' => 102, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
$fcm = $data['fcm_token'];
|
||||||
|
|
||||||
|
$new_quota = $data['quota_today'] + 1;
|
||||||
|
if ($data['quota_day'] === null || $data['quota_day'] !== date("Y-m-d")) $new_quota=1;
|
||||||
|
if ($new_quota > $data['quota_max']) die(json_encode(['success' => false, 'errhighlight' => -1, 'message' => 'Daily quota reached ('.$data['quota_max'].')']));
|
||||||
|
|
||||||
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
|
$url = "https://fcm.googleapis.com/fcm/send";
|
||||||
|
$payload = json_encode(
|
||||||
|
[
|
||||||
|
'to' => $fcm,
|
||||||
|
//'dry_run' => true,
|
||||||
|
'android' => [ 'priority' => 'high' ],
|
||||||
|
//'notification' =>
|
||||||
|
//[
|
||||||
|
// 'title' => $message,
|
||||||
|
// 'body' => $content,
|
||||||
|
//],
|
||||||
|
'data' =>
|
||||||
|
[
|
||||||
|
'title' => $message,
|
||||||
|
'body' => $content,
|
||||||
|
'priority' => $priority,
|
||||||
|
'timestamp' => time(),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
$header=
|
||||||
|
[
|
||||||
|
'Authorization' => 'key=' . getConfig()['firebase']['server_key'],
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$httpresult = sendPOST($url, $payload, $header);
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
die(json_encode(['success' => false, 'message' => 'Exception: ' . $e->getMessage()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), messages_sent=messages_sent+1, quota_today=:q, quota_day=NOW() WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'q' => $new_quota]);
|
||||||
|
|
||||||
|
echo (json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Message sent',
|
||||||
|
'response' => $httpresult,
|
||||||
|
'messagecount' => $data['messages_sent']+1,
|
||||||
|
'quota'=>$new_quota,
|
||||||
|
'quota_max'=>$data['quota_max'],
|
||||||
|
]));
|
||||||
|
return 0;
|
66
web/update.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
include_once 'model.php';
|
||||||
|
|
||||||
|
$INPUT = array_merge($_GET, $_POST);
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($INPUT['user_id'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_id]]']));
|
||||||
|
if (!isset($INPUT['user_key'])) die(json_encode(['success' => false, 'message' => 'Missing parameter [[user_key]]']));
|
||||||
|
|
||||||
|
$user_id = $INPUT['user_id'];
|
||||||
|
$user_key = $INPUT['user_key'];
|
||||||
|
$fcm_token = isset($INPUT['fcm_token']) ? $INPUT['fcm_token'] : null;
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
|
||||||
|
$pdo = getDatabase();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('SELECT user_id, user_key, quota_today, quota_max, quota_day FROM users WHERE user_id = :uid LIMIT 1');
|
||||||
|
$stmt->execute(['uid' => $user_id]);
|
||||||
|
|
||||||
|
$datas = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
if (count($datas)<=0) die(json_encode(['success' => false, 'message' => 'User not found']));
|
||||||
|
$data = $datas[0];
|
||||||
|
|
||||||
|
if ($data === null) die(json_encode(['success' => false, 'message' => 'User not found']));
|
||||||
|
if ($data['user_id'] !== (int)$user_id) die(json_encode(['success' => false, 'message' => 'UserID not found']));
|
||||||
|
if ($data['user_key'] !== $user_key) die(json_encode(['success' => false, 'message' => 'Authentification failed']));
|
||||||
|
|
||||||
|
$quota = $data['quota_today'];
|
||||||
|
$quota_max = $data['quota_max'];
|
||||||
|
|
||||||
|
$new_userkey = generateRandomAuthKey();
|
||||||
|
|
||||||
|
if ($fcm_token === null)
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), user_key=:at WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'at' => $new_userkey]);
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $quota,
|
||||||
|
'quota_max'=> $quota_max,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare('UPDATE users SET timestamp_accessed=NOW(), fcm_token=:ft, user_key=:at WHERE user_id = :uid');
|
||||||
|
$stmt->execute(['uid' => $user_id, 'ft' => $fcm_token, 'at' => $new_userkey]);
|
||||||
|
|
||||||
|
echo json_encode(
|
||||||
|
[
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'user_key' => $new_userkey,
|
||||||
|
'quota' => $quota,
|
||||||
|
'quota_max'=> $quota_max,
|
||||||
|
'message' => 'user updated'
|
||||||
|
]);
|
||||||
|
return 0;
|
||||||
|
}
|