diff --git a/android/app/build.gradle b/android/app/build.gradle index 539becf..44ac0f4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -21,7 +21,9 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:cardview-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:recyclerview-v7:27.1.1' implementation 'com.google.firebase:firebase-core:16.0.3' implementation 'com.google.firebase:firebase-messaging:17.3.2' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a13d8de..ce0f728 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ Messages; + + private ArrayList> _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", ""); + + Messages.add(new CMessage(time, title, content)); + } + } + + public void add(final long time, final String title, final String content) + { + boolean run = SCNApp.runOnUiThread(new Runnable() { + @Override + public void run() + { + SharedPreferences sharedPref = SCNApp.getContext().getSharedPreferences("CMessageList", Context.MODE_PRIVATE); + int count = sharedPref.getInt("message_count", 0); + + SharedPreferences.Editor e = sharedPref.edit(); + + Messages.add(new CMessage(time, title, content)); + 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.apply(); + + for (WeakReference ref : _listener) + { + MessageAdapter a = ref.get(); + if (a == null) continue; + a.notifyItemInserted(count); + } + CleanUpListener(); + } + }); + + if (!run) + { + Messages.add(new CMessage(time, title, content)); + fullSave(); + } + } + + 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.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); + } + } +} diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/FBMService.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/FBMService.java index dd9c996..f3405a3 100644 --- a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/FBMService.java +++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/FBMService.java @@ -1,17 +1,13 @@ package com.blackforestbytes.simplecloudnotifier; import android.util.Log; +import android.widget.Toast; 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) { @@ -19,36 +15,28 @@ public class FBMService extends FirebaseMessagingService } @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", "From: " + remoteMessage.getFrom()); - - // Check if message contains a data payload. - if (remoteMessage.getData().size() > 0) { + 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()); - // Check if message contains a notification payload. - if (remoteMessage.getNotification() != null) { - Log.i("FB::MessageReceived", "Notify_Title: " + remoteMessage.getNotification().getTitle()); - 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("content"); - // 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. + CMessageList.inst().add(time, title, content); + + + SCNApp.showToast(title, Toast.LENGTH_LONG); + } + catch (Exception e) + { + Log.e("FB:Err", e.toString()); + SCNApp.showToast("Recieved invalid message from server", Toast.LENGTH_LONG); + } } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MainActivity.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MainActivity.java index e66d6aa..4328c58 100644 --- a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MainActivity.java +++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MainActivity.java @@ -2,6 +2,8 @@ package com.blackforestbytes.simplecloudnotifier; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; @@ -9,24 +11,25 @@ import com.google.android.gms.tasks.OnSuccessListener; import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.InstanceIdResult; -public class MainActivity extends AppCompatActivity { - +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() { - @Override - public void onSuccess(InstanceIdResult instanceIdResult) { - Log.d("FB::ID", instanceIdResult.getId()); - Log.d("FB::TOKEN", instanceIdResult.getToken()); - } - }); - } - }); + RecyclerView rvMessages = findViewById(R.id.rvMessages); + rvMessages.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, true)); + rvMessages.setAdapter(new MessageAdapter()); + + SCNApp.register(this); + } + + @Override + protected void onStop() + { + super.onStop(); + + CMessageList.inst().fullSave(); } } diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MessageAdapter.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MessageAdapter.java new file mode 100644 index 0000000..9da1a26 --- /dev/null +++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/MessageAdapter.java @@ -0,0 +1,72 @@ +package com.blackforestbytes.simplecloudnotifier; + +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.TextView; +import android.widget.Toast; + +public class MessageAdapter extends RecyclerView.Adapter +{ + public MessageAdapter() + { + CMessageList.inst().register(this); + } + + @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(); + } + + private class MessagePresenter extends RecyclerView.ViewHolder implements View.OnClickListener + { + private TextView tvTimestamp; + private TextView tvTitle; + private TextView tvMessage; + + 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); + itemView.setOnClickListener(this); + } + + void setMessage(CMessage msg) + { + tvTimestamp.setText(msg.formatTimestamp()); + tvTitle.setText(msg.Title); + tvMessage.setText(msg.Content); + data = msg; + } + + + @Override + public void onClick(View v) + { + SCNApp.showToast(data.Title, Toast.LENGTH_LONG); + } + } +} diff --git a/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java new file mode 100644 index 0000000..de4c214 --- /dev/null +++ b/android/app/src/main/java/com/blackforestbytes/simplecloudnotifier/SCNApp.java @@ -0,0 +1,48 @@ +package com.blackforestbytes.simplecloudnotifier; + +import android.app.Application; +import android.content.Context; +import android.widget.Toast; + +import java.lang.ref.WeakReference; + +public class SCNApp extends Application +{ + private static SCNApp instance; + private static WeakReference mainActivity; + + public SCNApp() + { + instance = this; + } + + public static Context getContext() + { + return instance; + } + + public static void showToast(final String msg, final int duration) + { + final MainActivity a = mainActivity.get(); + if (a != null) + { + a.runOnUiThread(new Runnable() { + public void run() { + 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 register(MainActivity a) + { + mainActivity = new WeakReference<>(a); + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index f83c626..231161d 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -1,29 +1,15 @@ - + tools:showIn="@layout/activity_main"> - + android:clipToPadding="false" + android:scrollbars="vertical" /> -