Skip to content

Commit ef2ad1a

Browse files
authored
Merge pull request #152 from sameerasw/develop
Develop - Watermarks and a lot of fixes
2 parents ce94b49 + 0aafb29 commit ef2ad1a

File tree

110 files changed

+7517
-1201
lines changed

Some content is hidden

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

110 files changed

+7517
-1201
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,4 @@ replay_pid*
3030
*.DS_Store
3131
*app/release/
3232

33-
Build/
34-
build/reports/problems/problems-report.html
33+
Build/

app/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ dependencies {
105105
// SymSpell for word suggestions
106106
implementation("com.darkrockstudios:symspellkt:3.4.0")
107107

108-
// Glance for Widgets
109108
implementation("androidx.glance:glance-appwidget:1.1.0")
110109
implementation("androidx.glance:glance-material3:1.1.0")
110+
111+
// Watermark dependencies
112+
implementation("androidx.exifinterface:exifinterface:1.3.7")
113+
implementation("androidx.compose.material:material-icons-extended:1.7.0") // Compatible with Compose BOM
111114
}

app/src/main/AndroidManifest.xml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,18 @@
205205
android:taskAffinity=""
206206
android:theme="@style/Theme.Essentials.Translucent" />
207207

208+
<activity
209+
android:name=".ui.composables.watermark.WatermarkActivity"
210+
android:exported="true"
211+
android:label="@string/feat_watermark_title"
212+
android:theme="@style/Theme.Essentials">
213+
<intent-filter>
214+
<action android:name="android.intent.action.SEND" />
215+
<category android:name="android.intent.category.DEFAULT" />
216+
<data android:mimeType="image/*" />
217+
</intent-filter>
218+
</activity>
219+
208220
<service
209221
android:name=".services.tiles.ScreenOffAccessibilityService"
210222
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -434,6 +446,16 @@
434446
android:value="Automation Service" />
435447
</service>
436448

449+
<service
450+
android:name=".services.EssentialsConditionProvider"
451+
android:label="Essentials Focus"
452+
android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
453+
android:exported="true">
454+
<intent-filter>
455+
<action android:name="android.service.notification.ConditionProviderService" />
456+
</intent-filter>
457+
</service>
458+
437459
<service
438460
android:name=".services.tiles.AppFreezingTileService"
439461
android:icon="@drawable/rounded_mode_cool_24"
@@ -511,6 +533,28 @@
511533
</intent-filter>
512534
</service>
513535

536+
<service
537+
android:name=".services.tiles.PrivateDnsTileService"
538+
android:exported="true"
539+
android:icon="@drawable/rounded_dns_24"
540+
android:label="@string/tile_private_dns"
541+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
542+
<intent-filter>
543+
<action android:name="android.service.quicksettings.action.QS_TILE" />
544+
</intent-filter>
545+
</service>
546+
547+
<service
548+
android:name=".services.tiles.UsbDebuggingTileService"
549+
android:exported="true"
550+
android:icon="@drawable/rounded_adb_24"
551+
android:label="@string/tile_usb_debugging"
552+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
553+
<intent-filter>
554+
<action android:name="android.service.quicksettings.action.QS_TILE" />
555+
</intent-filter>
556+
</service>
557+
514558
<receiver
515559
android:name=".services.receivers.FlashlightActionReceiver"
516560
android:exported="false">

app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ class SettingsRepository(private val context: Context) {
6161
const val KEY_DYNAMIC_NIGHT_LIGHT_ENABLED = "dynamic_night_light_enabled"
6262
const val KEY_DYNAMIC_NIGHT_LIGHT_SELECTED_APPS = "dynamic_night_light_selected_apps"
6363

64-
const val KEY_SNOOZE_DEBUGGING_ENABLED = "snooze_debugging_enabled"
65-
const val KEY_SNOOZE_FILE_TRANSFER_ENABLED = "snooze_file_transfer_enabled"
66-
const val KEY_SNOOZE_CHARGING_ENABLED = "snooze_charging_enabled"
64+
const val KEY_SNOOZE_DISCOVERED_CHANNELS = "snooze_discovered_channels"
65+
const val KEY_SNOOZE_BLOCKED_CHANNELS = "snooze_blocked_channels"
6766

6867
const val KEY_FLASHLIGHT_ALWAYS_TURN_OFF_ENABLED = "flashlight_always_turn_off_enabled"
6968
const val KEY_FLASHLIGHT_FADE_ENABLED = "flashlight_fade_enabled"
@@ -118,6 +117,8 @@ class SettingsRepository(private val context: Context) {
118117
const val KEY_SHOW_BLUETOOTH_DEVICES = "show_bluetooth_devices"
119118
const val KEY_BATTERY_WIDGET_MAX_DEVICES = "battery_widget_max_devices"
120119
const val KEY_BATTERY_WIDGET_BACKGROUND_ENABLED = "battery_widget_background_enabled"
120+
121+
const val KEY_PINNED_FEATURES = "pinned_features"
121122
}
122123

123124
// Observe changes
@@ -129,6 +130,17 @@ class SettingsRepository(private val context: Context) {
129130
awaitClose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }
130131
}
131132

133+
val isPitchBlackThemeEnabled: Flow<Boolean> = callbackFlow {
134+
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
135+
if (key == KEY_PITCH_BLACK_THEME_ENABLED) {
136+
trySend(getBoolean(KEY_PITCH_BLACK_THEME_ENABLED))
137+
}
138+
}
139+
trySend(getBoolean(KEY_PITCH_BLACK_THEME_ENABLED))
140+
prefs.registerOnSharedPreferenceChangeListener(listener)
141+
awaitClose { prefs.unregisterOnSharedPreferenceChangeListener(listener) }
142+
}
143+
132144
fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
133145
prefs.registerOnSharedPreferenceChangeListener(listener)
134146
}
@@ -299,6 +311,45 @@ class SettingsRepository(private val context: Context) {
299311
saveAppSelection(key, current)
300312
}
301313
}
314+
315+
// Snooze Notifications Helper
316+
fun loadSnoozeDiscoveredChannels(): List<com.sameerasw.essentials.domain.model.SnoozeChannel> {
317+
val json = prefs.getString(KEY_SNOOZE_DISCOVERED_CHANNELS, null)
318+
return if (json != null) {
319+
val type = object : TypeToken<List<com.sameerasw.essentials.domain.model.SnoozeChannel>>() {}.type
320+
try {
321+
gson.fromJson(json, type) ?: emptyList()
322+
} catch (e: Exception) {
323+
emptyList()
324+
}
325+
} else {
326+
emptyList()
327+
}
328+
}
329+
330+
fun saveSnoozeDiscoveredChannels(channels: List<com.sameerasw.essentials.domain.model.SnoozeChannel>) {
331+
val json = gson.toJson(channels)
332+
putString(KEY_SNOOZE_DISCOVERED_CHANNELS, json)
333+
}
334+
335+
fun loadSnoozeBlockedChannels(): Set<String> {
336+
val json = prefs.getString(KEY_SNOOZE_BLOCKED_CHANNELS, null)
337+
return if (json != null) {
338+
val type = object : TypeToken<Set<String>>() {}.type
339+
try {
340+
gson.fromJson(json, type) ?: emptySet()
341+
} catch (e: Exception) {
342+
emptySet()
343+
}
344+
} else {
345+
emptySet()
346+
}
347+
}
348+
349+
fun saveSnoozeBlockedChannels(blockedChannels: Set<String>) {
350+
val json = gson.toJson(blockedChannels)
351+
putString(KEY_SNOOZE_BLOCKED_CHANNELS, json)
352+
}
302353

303354
// Config Export/Import
304355
fun getAllConfigsAsJsonString(): String {
@@ -312,7 +363,9 @@ class SettingsRepository(private val context: Context) {
312363

313364
p.all.forEach { (key, value) ->
314365
// Skip app lists as requested, and stale data
315-
if (key.endsWith("_selected_apps") || key == "freeze_auto_excluded_apps" || key.startsWith("mac_battery_") || key == "airsync_mac_connected") {
366+
if (key.endsWith("_selected_apps") || key == "freeze_auto_excluded_apps" ||
367+
key.startsWith("mac_battery_") || key == "airsync_mac_connected" ||
368+
key == KEY_SNOOZE_DISCOVERED_CHANNELS) {
316369
return@forEach
317370
}
318371

@@ -415,4 +468,18 @@ class SettingsRepository(private val context: Context) {
415468

416469
fun isBatteryWidgetBackgroundEnabled(): Boolean = getBoolean(KEY_BATTERY_WIDGET_BACKGROUND_ENABLED, true)
417470
fun setBatteryWidgetBackgroundEnabled(enabled: Boolean) = putBoolean(KEY_BATTERY_WIDGET_BACKGROUND_ENABLED, enabled)
471+
472+
fun getPinnedFeatures(): List<String> {
473+
val json = prefs.getString(KEY_PINNED_FEATURES, null)
474+
return if (json != null) {
475+
try {
476+
gson.fromJson(json, object : TypeToken<List<String>>() {}.type) ?: emptyList()
477+
} catch (e: Exception) { emptyList() }
478+
} else emptyList()
479+
}
480+
481+
fun savePinnedFeatures(features: List<String>) {
482+
val json = gson.toJson(features)
483+
putString(KEY_PINNED_FEATURES, json)
484+
}
418485
}

app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,19 @@ sealed interface Action {
5050
override val permissions: List<String> = listOf("shizuku", "root")
5151
override val isConfigurable: Boolean = true
5252
}
53+
54+
data class DeviceEffects(
55+
val enabled: Boolean = true,
56+
val grayscale: Boolean = false,
57+
val suppressAmbient: Boolean = false,
58+
val dimWallpaper: Boolean = false,
59+
val nightMode: Boolean = false
60+
) : Action {
61+
override val title: Int get() = R.string.diy_action_device_effects
62+
override val icon: Int get() = R.drawable.rounded_bed_24
63+
override val permissions: List<String> = listOf("notification_policy")
64+
override val isConfigurable: Boolean = true
65+
}
66+
67+
5368
}

app/src/main/java/com/sameerasw/essentials/domain/diy/Automation.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ data class Automation(
88
val actions: List<Action> = emptyList(),
99
val entryAction: Action? = null,
1010
val exitAction: Action? = null,
11-
val isEnabled: Boolean = true
11+
val isEnabled: Boolean = true,
12+
val selectedApps: List<String> = emptyList()
1213
) {
1314
enum class Type {
1415
TRIGGER,
15-
STATE
16+
STATE,
17+
APP
1618
}
1719
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sameerasw.essentials.domain.model
2+
3+
data class SnoozeChannel(
4+
val id: String,
5+
val name: String,
6+
val isBlocked: Boolean = false
7+
)

app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -181,26 +181,7 @@ object FeatureRegistry {
181181
description = R.string.feat_snooze_notifications_desc,
182182
permissionKeys = listOf("NOTIFICATION_LISTENER"),
183183
showToggle = false,
184-
searchableSettings = listOf(
185-
SearchSetting(
186-
R.string.search_snooze_debug_title,
187-
R.string.search_snooze_debug_desc,
188-
"snooze_debugging",
189-
R.array.keywords_adb_debug
190-
),
191-
SearchSetting(
192-
R.string.search_snooze_file_title,
193-
R.string.search_snooze_file_desc,
194-
"snooze_file_transfer",
195-
R.array.keywords_mtp
196-
),
197-
SearchSetting(
198-
R.string.search_snooze_charge_title,
199-
R.string.search_snooze_charge_desc,
200-
"snooze_charging",
201-
R.array.keywords_battery_charge
202-
)
203-
)
184+
searchableSettings = emptyList()
204185
) {
205186
override fun isEnabled(viewModel: MainViewModel) = false
206187
override fun isToggleEnabled(viewModel: MainViewModel, context: Context) = viewModel.isNotificationListenerEnabled.value
@@ -319,6 +300,20 @@ object FeatureRegistry {
319300
"Stay awake",
320301
R.array.keywords_qs_stay_awake,
321302
R.string.feat_qs_tiles_title
303+
),
304+
SearchSetting(
305+
R.string.search_qs_private_dns_title,
306+
R.string.search_qs_private_dns_desc,
307+
"Private DNS",
308+
R.array.keywords_network_visibility,
309+
R.string.feat_qs_tiles_title
310+
),
311+
SearchSetting(
312+
R.string.search_qs_usb_debugging_title,
313+
R.string.search_qs_usb_debugging_desc,
314+
"USB Debugging",
315+
R.array.keywords_adb_debug,
316+
R.string.feat_qs_tiles_title
322317
)
323318
)
324319
) {
@@ -523,6 +518,21 @@ object FeatureRegistry {
523518
) {
524519
override fun isEnabled(viewModel: MainViewModel) = true
525520
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
521+
},
522+
523+
object : Feature(
524+
id = "Watermarks",
525+
title = R.string.feat_watermark_title,
526+
iconRes = R.drawable.rounded_draw_24,
527+
category = R.string.cat_tools,
528+
description = R.string.feat_watermark_desc,
529+
showToggle = false
530+
) {
531+
override fun isEnabled(viewModel: MainViewModel) = true
532+
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
533+
override fun onClick(context: Context, viewModel: MainViewModel) {
534+
context.startActivity(android.content.Intent(context, com.sameerasw.essentials.ui.composables.watermark.WatermarkActivity::class.java))
535+
}
526536
}
527537
)
528538
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.sameerasw.essentials.domain.watermark
2+
3+
import android.content.Context
4+
import android.net.Uri
5+
import androidx.exifinterface.media.ExifInterface
6+
import java.io.InputStream
7+
8+
data class ExifData(
9+
val make: String? = null,
10+
val model: String? = null,
11+
val aperture: String? = null,
12+
val shutterSpeed: String? = null,
13+
val iso: String? = null,
14+
val date: String? = null,
15+
val focalLength: String? = null
16+
)
17+
18+
class MetadataProvider(
19+
private val context: Context
20+
) {
21+
fun extractExif(uri: Uri): ExifData {
22+
var inputStream: InputStream? = null
23+
return try {
24+
inputStream = context.contentResolver.openInputStream(uri)
25+
if (inputStream == null) return ExifData()
26+
27+
val exif = ExifInterface(inputStream)
28+
29+
ExifData(
30+
make = exif.getAttribute(ExifInterface.TAG_MAKE),
31+
model = exif.getAttribute(ExifInterface.TAG_MODEL),
32+
aperture = exif.getAttribute(ExifInterface.TAG_F_NUMBER)?.let { "f/$it" },
33+
shutterSpeed = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME)?.let { "${it}s" },
34+
iso = exif.getAttribute(ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY)?.let { "ISO $it" },
35+
date = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL),
36+
focalLength = exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH)?.let { "${it}mm" }
37+
)
38+
} catch (e: Exception) {
39+
e.printStackTrace()
40+
ExifData()
41+
} finally {
42+
inputStream?.close()
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)