Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.

Commit 69f7cc9

Browse files
committed
added BackgroundService, notifications, and changed the icon to a carrot. also made the service start on boot
1 parent 9345e6f commit 69f7cc9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+642
-267
lines changed

.idea/misc.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# GAG Stock
2-
This is an app I made to check stock for grow a garden. it uses an [API](https://api.joshlei.com/v2/growagarden/stock) to get the stocks.
2+
This is an app I made to check stock for grow a garden. it uses an ~~[API](https://api.joshlei.com/v2/growagarden/stock)~~ [WebSocket]() to get the stocks.
33
---
44
## Checklist
55
- [x] ~~find a better api~~
6-
- [ ] add notifications
6+
- [x] ~~add notifications~~
77
- [ ] get gears to work
88
- [ ] get eggs to work
9-
- [ ] switch from api to websocket
9+
- [x] ~~switch from api to websocket~~

app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ dependencies {
3939
implementation(libs.constraintlayout)
4040
implementation(libs.volley)
4141
implementation(libs.glide)
42+
implementation(libs.okhttp)
43+
implementation(libs.androidx.datastore.preferences)
44+
implementation(libs.datastore.preferences.rxjava2)
45+
implementation(libs.androidx.datastore.preferences.rxjava3)
4246
annotationProcessor(libs.compiler)
4347
testImplementation(libs.junit)
4448
androidTestImplementation(libs.ext.junit)

app/src/main/AndroidManifest.xml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,48 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
46
<uses-permission android:name="android.permission.INTERNET" />
7+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
58
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
9+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
10+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
611
<application
712
android:allowBackup="true"
813
android:dataExtractionRules="@xml/data_extraction_rules"
914
android:fullBackupContent="@xml/backup_rules"
1015
android:icon="@mipmap/ic_launcher"
1116
android:label="@string/app_name"
12-
android:roundIcon="@mipmap/ic_launcher_round"
1317
android:supportsRtl="true"
1418
android:theme="@style/Theme.GAGStock"
15-
tools:targetApi="31">
19+
tools:targetApi="33">
1620
<activity
1721
android:name=".MainActivity"
18-
android:exported="true">
22+
android:exported="true"
23+
android:enableOnBackInvokedCallback="true">
1924
<intent-filter>
2025
<action android:name="android.intent.action.MAIN" />
2126

2227
<category android:name="android.intent.category.LAUNCHER" />
2328
</intent-filter>
2429
</activity>
30+
<activity
31+
android:name=".SetNotifsActivity"
32+
android:exported="false"
33+
android:enableOnBackInvokedCallback="true" />
34+
<service
35+
android:name=".BackgroundService"
36+
android:exported="false"
37+
android:foregroundServiceType="specialUse" />
38+
<receiver
39+
android:name=".BootReceiver"
40+
android:enabled="true"
41+
android:exported="true">
42+
<intent-filter>
43+
<action android:name="android.intent.action.BOOT_COMPLETED" />
44+
</intent-filter>
45+
</receiver>
2546
</application>
2647
<queries>
2748
<intent>
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.unixity.gagstock;
2+
3+
import android.app.Notification;
4+
import android.app.NotificationChannel;
5+
import android.app.NotificationManager;
6+
import android.app.Service;
7+
import android.content.Context;
8+
import android.content.Intent;
9+
import android.content.SharedPreferences;
10+
import android.net.ConnectivityManager;
11+
import android.net.Network;
12+
import android.os.Build;
13+
import android.os.IBinder;
14+
import android.util.Log;
15+
16+
import androidx.annotation.NonNull;
17+
import androidx.annotation.Nullable;
18+
import androidx.core.app.NotificationCompat;
19+
20+
import org.json.JSONArray;
21+
import org.json.JSONObject;
22+
23+
import okhttp3.OkHttpClient;
24+
import okhttp3.WebSocket;
25+
import okhttp3.WebSocketListener;
26+
27+
public class BackgroundService extends Service {
28+
@Override
29+
public int onStartCommand(Intent intent, int flags, int startId) {
30+
startForeground(1, buildNotification());
31+
32+
ConnectivityManager connectivityManager =
33+
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
34+
35+
if (connectivityManager != null) {
36+
connectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
37+
@Override
38+
public void onAvailable(@NonNull Network network) {
39+
Log.d("Network", "✅ Network is available. Connecting WebSocket...");
40+
connectToEndpointInService();
41+
}
42+
43+
@Override
44+
public void onLost(@NonNull Network network) {
45+
Log.w("Network", "⚠️ Network connection lost. You may want to disconnect the WebSocket here.");
46+
}
47+
});
48+
}
49+
50+
return START_STICKY;
51+
}
52+
53+
private void connectToEndpointInService() {
54+
SharedPreferences prefs = getApplicationContext().getSharedPreferences("notif_prefs", MODE_PRIVATE);
55+
OkHttpClient client = new OkHttpClient();
56+
57+
okhttp3.Request request = new okhttp3.Request.Builder()
58+
.url("wss://websocket.joshlei.com/growagarden/")
59+
.build();
60+
61+
client.newWebSocket(request, new WebSocketListener() {
62+
@Override
63+
public void onMessage(WebSocket webSocket, String text) {
64+
try {
65+
JSONObject json = new JSONObject(text);
66+
if (!json.has("seed_stock")) return;
67+
68+
JSONArray seedStock = json.getJSONArray("seed_stock");
69+
for (int i = 0; i < seedStock.length(); i++) {
70+
JSONObject seed = seedStock.getJSONObject(i);
71+
String rawName = seed.getString("display_name");
72+
String key = rawName.toLowerCase().replaceAll("\\s+", "");
73+
74+
if (prefs.getBoolean(key, false)) {
75+
int quantity = seed.optInt("quantity", 0);
76+
sendSeedNotification(getApplicationContext(), rawName, quantity);
77+
}
78+
}
79+
} catch (Exception e) {
80+
Log.e("WebSocket", "Parsing error", e);
81+
}
82+
}
83+
});
84+
}
85+
private Notification buildNotification() {
86+
NotificationChannel channel = new NotificationChannel(
87+
"channel_id", "Background Channel", NotificationManager.IMPORTANCE_MIN);
88+
NotificationManager manager = getSystemService(NotificationManager.class);
89+
if (manager != null) manager.createNotificationChannel(channel);
90+
// i dont know how to make it non swipeable :sob:
91+
return new NotificationCompat.Builder(this, "channel_id")
92+
.setContentTitle("Service Running")
93+
.setContentText("Running in background")
94+
.setSmallIcon(R.mipmap.ic_launcher_foreground)
95+
.setPriority(NotificationCompat.PRIORITY_MIN)
96+
.setCategory(NotificationCompat.CATEGORY_SERVICE)
97+
.setOngoing(true)
98+
.build();
99+
}
100+
101+
private void sendSeedNotification(Context context, String seedName, int quantity) {
102+
String channelId = "seed_channel";
103+
104+
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
105+
NotificationChannel channel = new NotificationChannel(
106+
channelId, "Seed Stock Alerts", NotificationManager.IMPORTANCE_HIGH);
107+
manager.createNotificationChannel(channel);
108+
109+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
110+
.setSmallIcon(R.mipmap.ic_launcher_foreground)
111+
.setContentTitle("Seed Restock Alert 🌱")
112+
.setContentText(seedName + " is back in stock! (Stock: x" + quantity + ")")
113+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
114+
.setAutoCancel(true);
115+
manager.notify(seedName.hashCode(), builder.build());
116+
}
117+
118+
119+
@Override
120+
public IBinder onBind(Intent intent) {
121+
return null; // Not using binding
122+
}
123+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.unixity.gagstock;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
7+
import androidx.core.content.ContextCompat;
8+
9+
public class BootReceiver extends BroadcastReceiver {
10+
@Override
11+
public void onReceive(Context context, Intent intent) {
12+
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
13+
Intent serviceIntent = new Intent(context, BackgroundService.class);
14+
ContextCompat.startForegroundService(context, serviceIntent);
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)