Skip to content

Commit 801a6b6

Browse files
authored
Merge pull request #566 from mubashardev/pr/release-v1
feat: Add call recording, global search, "Delete for me" message recovery, theming improvements, and compatibility fixes
2 parents 7786bc4 + 431ae0f commit 801a6b6

File tree

108 files changed

+9142
-776
lines changed

Some content is hidden

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

108 files changed

+9142
-776
lines changed

.github/workflows/android.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Android CI
22

33
on:
44
push:
5-
branches: [ "master" ]
5+
branches: [ "master", "feature/*" ]
66
jobs:
77
build:
88
permissions: write-all
@@ -22,13 +22,15 @@ jobs:
2222

2323
- name: Write key
2424
if: github.event_name != 'pull_request'
25+
env:
26+
KEY_STORE: ${{ secrets.KEY_STORE }}
2527
run: |
26-
if [ ! -z "${{ secrets.KEY_STORE }}" ]; then
28+
if [ ! -z "$KEY_STORE" ]; then
2729
echo androidStorePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> gradle.properties
2830
echo androidKeyAlias='${{ secrets.ALIAS }}' >> gradle.properties
2931
echo androidKeyPassword='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
3032
echo androidStoreFile='key.jks' >> gradle.properties
31-
echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks
33+
echo "$KEY_STORE" | base64 --decode > key.jks
3234
fi
3335
3436
- name: Grant execute permission for gradlew

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
.cxx
1010
local.properties
1111
key.jks
12+
key_base64.txt

app/build.gradle.kts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import kotlinx.coroutines.delay
22
import kotlinx.coroutines.runBlocking
33
import java.util.Locale
4+
import java.util.Properties
5+
import java.io.FileInputStream
46

57
plugins {
68
alias(libs.plugins.androidApplication)
@@ -50,12 +52,23 @@ android {
5052
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
5153

5254
signingConfigs.create("config") {
55+
val keystorePropertiesFile = rootProject.file("local.properties")
56+
val keystoreProperties = Properties()
57+
if (keystorePropertiesFile.exists()) {
58+
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
59+
}
60+
5361
val androidStoreFile = project.findProperty("androidStoreFile") as String?
62+
?: keystoreProperties.getProperty("androidStoreFile")
63+
5464
if (!androidStoreFile.isNullOrEmpty()) {
5565
storeFile = rootProject.file(androidStoreFile)
56-
storePassword = project.property("androidStorePassword") as String
57-
keyAlias = project.property("androidKeyAlias") as String
58-
keyPassword = project.property("androidKeyPassword") as String
66+
storePassword = project.findProperty("androidStorePassword") as String?
67+
?: keystoreProperties.getProperty("androidStorePassword")
68+
keyAlias = project.findProperty("androidKeyAlias") as String?
69+
?: keystoreProperties.getProperty("androidKeyAlias")
70+
keyPassword = project.findProperty("androidKeyPassword") as String?
71+
?: keystoreProperties.getProperty("androidKeyPassword")
5972
}
6073
}
6174

@@ -166,6 +179,7 @@ dependencies {
166179
implementation(libs.arscblamer)
167180
compileOnly(libs.lombok)
168181
annotationProcessor(libs.lombok)
182+
implementation(libs.markwon.core)
169183
}
170184

171185
configurations.all {

app/src/main/AndroidManifest.xml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
1212
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
1313
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
14+
<uses-permission android:name="android.permission.READ_CONTACTS" />
1415

1516
<uses-permission
1617
android:name="android.permission.READ_EXTERNAL_STORAGE"
@@ -57,6 +58,11 @@
5758
</service>
5859

5960

61+
<provider
62+
android:name=".provider.DeletedMessagesProvider"
63+
android:authorities="${applicationId}.provider"
64+
android:exported="true" />
65+
6066
<activity
6167
android:name=".activities.MainActivity"
6268
android:exported="true"
@@ -82,6 +88,27 @@
8288
android:name=".activities.TextEditorActivity"
8389
android:theme="@style/AppTheme" />
8490

91+
<activity
92+
android:name=".activities.CallRecordingSettingsActivity"
93+
android:theme="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"
94+
android:parentActivityName=".activities.MainActivity" />
95+
96+
<activity
97+
android:name=".activities.SearchActivity"
98+
android:theme="@style/AppTheme"
99+
android:parentActivityName=".activities.MainActivity"
100+
android:windowSoftInputMode="adjustResize" />
101+
102+
<activity
103+
android:name=".activities.DeletedMessagesActivity"
104+
android:theme="@style/AppTheme"
105+
android:parentActivityName=".activities.MainActivity" />
106+
107+
<activity
108+
android:name=".activities.MessageListActivity"
109+
android:theme="@style/AppTheme"
110+
android:parentActivityName=".activities.DeletedMessagesActivity" />
111+
85112
<activity
86113
android:name=".activities.ForceStartActivity"
87114
android:excludeFromRecents="true"
@@ -115,6 +142,16 @@
115142
android:exported="true"
116143
tools:ignore="ExportedService" />
117144

145+
<provider
146+
android:name="androidx.core.content.FileProvider"
147+
android:authorities="${applicationId}.fileprovider"
148+
android:exported="false"
149+
android:grantUriPermissions="true">
150+
<meta-data
151+
android:name="android.support.FILE_PROVIDER_PATHS"
152+
android:resource="@xml/file_paths" />
153+
</provider>
154+
118155
<provider
119156
android:name=".xposed.bridge.providers.HookProvider"
120157
android:authorities="${applicationId}.hookprovider"
Lines changed: 140 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,191 @@
11
package com.wmods.wppenhacer;
22

33
import android.app.Activity;
4+
import android.util.Log;
45

56
import com.wmods.wppenhacer.xposed.core.WppCore;
67
import com.wmods.wppenhacer.xposed.core.components.AlertDialogWpp;
78
import com.wmods.wppenhacer.xposed.utils.Utils;
89

910
import org.json.JSONObject;
1011

12+
import java.text.SimpleDateFormat;
13+
import java.util.Date;
14+
import java.util.Locale;
1115
import java.util.Objects;
16+
import java.util.concurrent.TimeUnit;
1217

1318
import de.robv.android.xposed.XposedBridge;
1419
import okhttp3.OkHttpClient;
1520

21+
import io.noties.markwon.Markwon;
22+
1623
public class UpdateChecker implements Runnable {
1724

25+
private static final String TAG = "WAE_UpdateChecker";
1826
private static final String LATEST_RELEASE_API = "https://api.github.com/repos/Dev4Mod/WaEnhancer/releases/latest";
1927
private static final String RELEASE_TAG_PREFIX = "debug-";
2028
private static final String TELEGRAM_UPDATE_URL = "https://t.me/waenhancher";
2129

30+
// Singleton OkHttpClient - expensive to create, reuse across all checks
31+
private static OkHttpClient httpClient;
32+
2233
private final Activity mActivity;
2334

2435
public UpdateChecker(Activity activity) {
2536
this.mActivity = activity;
2637
}
2738

39+
/**
40+
* Get or create the singleton OkHttpClient with proper timeout configuration
41+
*/
42+
private static synchronized OkHttpClient getHttpClient() {
43+
if (httpClient == null) {
44+
httpClient = new OkHttpClient.Builder()
45+
.connectTimeout(10, TimeUnit.SECONDS)
46+
.readTimeout(10, TimeUnit.SECONDS)
47+
.writeTimeout(10, TimeUnit.SECONDS)
48+
.build();
49+
}
50+
return httpClient;
51+
}
2852

2953
@Override
3054
public void run() {
55+
XposedBridge.log("[" + TAG + "] UpdateChecker.run() started");
3156
try {
32-
var client = new OkHttpClient();
57+
XposedBridge.log("[" + TAG + "] Starting update check...");
58+
3359
var request = new okhttp3.Request.Builder()
3460
.url(LATEST_RELEASE_API)
3561
.build();
62+
3663
String hash;
3764
String changelog;
38-
try (var response = client.newCall(request).execute()) {
39-
if (!response.isSuccessful()) return;
65+
String publishedAt;
66+
67+
try (var response = getHttpClient().newCall(request).execute()) {
68+
if (!response.isSuccessful()) {
69+
XposedBridge.log("[" + TAG + "] Update check failed: HTTP " + response.code());
70+
return;
71+
}
72+
4073
var body = response.body();
74+
if (body == null) {
75+
XposedBridge.log("[" + TAG + "] Update check failed: Empty response body");
76+
return;
77+
}
78+
4179
var content = body.string();
4280
var release = new JSONObject(content);
4381
var tagName = release.optString("tag_name", "");
44-
XposedBridge.log("[UPDATE]" +tagName);
45-
if (tagName.isBlank() || !tagName.startsWith(RELEASE_TAG_PREFIX)) return;
82+
83+
XposedBridge.log("[" + TAG + "] Latest release tag: " + tagName);
84+
85+
if (tagName.isBlank() || !tagName.startsWith(RELEASE_TAG_PREFIX)) {
86+
XposedBridge.log("[" + TAG + "] Invalid or non-debug release tag");
87+
return;
88+
}
89+
4690
hash = tagName.substring(RELEASE_TAG_PREFIX.length()).trim();
4791
changelog = release.optString("body", "No changelog available.").trim();
92+
publishedAt = release.optString("published_at", "");
93+
94+
XposedBridge.log("[" + TAG + "] Release hash: " + hash + ", published: " + publishedAt);
4895
}
4996

50-
if (hash.isBlank()) return;
97+
if (hash.isBlank()) {
98+
XposedBridge.log("[" + TAG + "] Empty hash, skipping");
99+
return;
100+
}
101+
51102
var appInfo = mActivity.getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, 0);
52-
if (!appInfo.versionName.toLowerCase().contains(hash.toLowerCase().trim()) && !Objects.equals(WppCore.getPrivString("ignored_version", ""), hash)) {
103+
boolean isNewVersion = !appInfo.versionName.toLowerCase().contains(hash.toLowerCase().trim());
104+
boolean isIgnored = Objects.equals(WppCore.getPrivString("ignored_version", ""), hash);
105+
106+
if (isNewVersion && !isIgnored) {
107+
XposedBridge.log("[" + TAG + "] New version available, showing dialog");
108+
109+
final String finalHash = hash;
110+
final String finalChangelog = changelog;
111+
final String finalPublishedAt = publishedAt;
112+
53113
mActivity.runOnUiThread(() -> {
54-
var dialog = new AlertDialogWpp(mActivity);
55-
dialog.setTitle("WAE - New version available!");
56-
dialog.setMessage("Changelog:\n\n" + changelog);
57-
dialog.setNegativeButton("Ignore", (dialog1, which) -> {
58-
WppCore.setPrivString("ignored_version", hash);
59-
dialog1.dismiss();
60-
});
61-
dialog.setPositiveButton("Update", (dialog1, which) -> {
62-
Utils.openLink(mActivity, TELEGRAM_UPDATE_URL);
63-
dialog1.dismiss();
64-
});
65-
dialog.show();
114+
showUpdateDialog(finalHash, finalChangelog, finalPublishedAt);
66115
});
116+
} else {
117+
XposedBridge.log("[" + TAG + "] No update needed (isNew=" + isNewVersion + ", isIgnored=" + isIgnored + ")");
118+
}
119+
} catch (java.net.SocketTimeoutException e) {
120+
XposedBridge.log("[" + TAG + "] Update check timeout: " + e.getMessage());
121+
} catch (java.io.IOException e) {
122+
XposedBridge.log("[" + TAG + "] Network error during update check: " + e.getMessage());
123+
} catch (Exception e) {
124+
XposedBridge.log("[" + TAG + "] Unexpected error during update check: " + e.getMessage());
125+
XposedBridge.log(e);
126+
}
127+
}
128+
129+
private void showUpdateDialog(String hash, String changelog, String publishedAt) {
130+
XposedBridge.log("[" + TAG + "] Attempting to show update dialog");
131+
try {
132+
var markwon = Markwon.create(mActivity);
133+
var dialog = new AlertDialogWpp(mActivity);
134+
135+
// Format the published date
136+
String formattedDate = formatPublishedDate(publishedAt);
137+
138+
// Build simple message with version and date
139+
StringBuilder message = new StringBuilder();
140+
message.append("📦 **Version:** `").append(hash).append("`\n");
141+
if (!formattedDate.isEmpty()) {
142+
message.append("📅 **Released:** ").append(formattedDate).append("\n");
143+
}
144+
message.append("\n### What's New\n\n").append(changelog);
145+
146+
dialog.setTitle("🎉 New Update Available!");
147+
dialog.setMessage(markwon.toMarkdown(message.toString()));
148+
dialog.setNegativeButton("Ignore", (dialog1, which) -> {
149+
WppCore.setPrivString("ignored_version", hash);
150+
dialog1.dismiss();
151+
});
152+
dialog.setPositiveButton("Update Now", (dialog1, which) -> {
153+
Utils.openLink(mActivity, TELEGRAM_UPDATE_URL);
154+
dialog1.dismiss();
155+
});
156+
dialog.show();
157+
158+
XposedBridge.log("[" + TAG + "] Update dialog shown successfully");
159+
} catch (Exception e) {
160+
XposedBridge.log("[" + TAG + "] Error showing update dialog: " + e.getMessage());
161+
e.printStackTrace();
162+
}
163+
}
164+
165+
/**
166+
* Format ISO 8601 date to human-readable format
167+
* @param isoDate ISO 8601 date string (e.g., "2024-02-14T12:34:56Z")
168+
* @return Formatted date (e.g., "Feb 14, 2024" or "February 14, 2024")
169+
*/
170+
private String formatPublishedDate(String isoDate) {
171+
if (isoDate == null || isoDate.isEmpty()) {
172+
return "";
173+
}
174+
175+
try {
176+
// Parse ISO 8601 date
177+
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
178+
Date date = isoFormat.parse(isoDate);
179+
180+
if (date != null) {
181+
// Format to readable date
182+
SimpleDateFormat displayFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.US);
183+
return displayFormat.format(date);
67184
}
68-
} catch (Exception ignored) {
185+
} catch (Exception e) {
186+
XposedBridge.log("[" + TAG + "] Error parsing date: " + e.getMessage());
69187
}
188+
189+
return "";
70190
}
71191
}

app/src/main/java/com/wmods/wppenhacer/activities/AboutActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
2727
binding.btnGithub.setOnClickListener(view -> {
2828
Intent intent = new Intent();
2929
intent.setAction(Intent.ACTION_VIEW);
30-
intent.setData(Uri.parse("https://github.com/Dev4Mod/waenhancer"));
30+
intent.setData(Uri.parse("https://github.com/Dev4Mod/WaEnhancer"));
3131
startActivity(intent);
3232
});
3333
binding.btnDonate.setOnClickListener(view -> {

0 commit comments

Comments
 (0)