Skip to content

Commit 077d531

Browse files
committed
Copy, share and read aloud meanings (#45)
* Add support for Google Keep notes as per Firefox Focus method. * Implement copy, share and reading meaning using TextToSpeech API. * Add features to home page.
1 parent a7bf86e commit 077d531

File tree

6 files changed

+256
-42
lines changed

6 files changed

+256
-42
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ android {
2828
applicationId "com.xtreak.notificationdictionary"
2929
minSdk 24
3030
targetSdk 33
31-
versionCode 20
32-
versionName "0.0.20"
31+
versionCode 21
32+
versionName "0.0.21"
3333

3434
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3535
}

app/src/main/AndroidManifest.xml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,52 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
package="com.xtreak.notificationdictionary">
3+
package="com.xtreak.notificationdictionary" >
44

55
<uses-permission android:name="android.permission.INTERNET" />
6-
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
6+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
77

88
<application
99
android:allowBackup="true"
1010
android:icon="@mipmap/ic_launcher"
1111
android:label="@string/app_name"
1212
android:roundIcon="@mipmap/ic_launcher_round"
1313
android:supportsRtl="true"
14-
android:theme="@style/Theme.NotificationDictionary">
14+
android:theme="@style/Theme.NotificationDictionary" >
15+
1516
<activity
1617
android:name=".AboutActivity"
1718
android:exported="true"
1819
android:label="@string/about"
1920
android:parentActivityName=".MainActivity" />
21+
2022
<activity
2123
android:name=".ProcessTextActivity"
2224
android:exported="true"
2325
android:label="@string/process_text_label"
24-
android:parentActivityName=".MainActivity">
26+
android:parentActivityName=".MainActivity" >
2527
<intent-filter>
2628
<action android:name="android.intent.action.PROCESS_TEXT" />
2729
<category android:name="android.intent.category.DEFAULT" />
2830
<data android:mimeType="text/plain" />
2931
</intent-filter>
3032
</activity>
33+
34+
<activity
35+
android:name=".ProcessViewActivity"
36+
android:exported="true"
37+
android:label="@string/process_text_label"
38+
android:parentActivityName=".MainActivity" >
39+
<intent-filter>
40+
<action android:name="android.intent.action.VIEW" />
41+
<category android:name="android.intent.category.BROWSABLE" />
42+
<data android:scheme="http" />
43+
<data android:scheme="https" />
44+
</intent-filter>
45+
</activity>
46+
3147
<activity
3248
android:name=".MainActivity"
33-
android:exported="true">
49+
android:exported="true" >
3450
<intent-filter>
3551
<action android:name="android.intent.action.MAIN" />
3652
<category android:name="android.intent.category.LAUNCHER" />

app/src/main/java/com/xtreak/notificationdictionary/MainActivity.kt

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import android.view.View
2525
import android.view.inputmethod.EditorInfo
2626
import android.widget.*
2727
import android.widget.TextView.OnEditorActionListener
28-
import androidx.activity.result.contract.ActivityResultContracts
2928
import androidx.appcompat.app.AlertDialog
3029
import androidx.appcompat.app.AppCompatActivity
3130
import androidx.core.app.ActivityCompat
@@ -132,22 +131,38 @@ class MainActivity : AppCompatActivity() {
132131
val mListadapter =
133132
RoomAdapter(
134133
listOf(
134+
Word(
135+
1,
136+
"",
137+
"Read meanings aloud as you read",
138+
1,
139+
1,
140+
"""Enable Read switch at the right top to read aloud meaning of the word when the notification is created. There is also read button per notification to read meaning for each word."""
141+
),
142+
Word(
143+
1,
144+
"",
145+
"Copy and share",
146+
1,
147+
1,
148+
"""Click on meaning to copy. Long press to share meaning with others. Notifications also have button for these actions."""
149+
),
135150
Word(
136151
1,
137152
"",
138153
"Thanks for the support",
139154
1,
140155
1,
141156
"""The application is open source and free to use. The development is
142-
done in my free time apart from my day job along with download costs for database files
143-
from CDN. If you find the app useful please leave a review in Play store and share the
144-
app with your friends. It will help and encourage me in maintaining the app and adding more features.
157+
done in my free time apart from my day job along with download costs for database files
158+
from CDN. If you find the app useful please leave a review in Play store and share the
159+
app with your friends. It will help and encourage me in maintaining the app and adding more features.
145160
146161
Please grant notification permission since the app requires notification permission in
147162
Android 13+ to show meanings through notification.
148163
Thanks for your support.
149164
"""
150-
)
165+
),
151166
), this
152167
)
153168
mRecyclerView.adapter = mListadapter
@@ -315,39 +330,35 @@ class MainActivity : AppCompatActivity() {
315330
val inflater = menuInflater
316331
inflater.inflate(R.menu.menu, menu)
317332

318-
val menuItem = menu!!.findItem(R.id.switch_theme)
319-
val view = MenuItemCompat.getActionView(menuItem)
333+
val switchSoundItem = menu!!.findItem(R.id.switch_sound)
334+
val soundView = MenuItemCompat.getActionView(switchSoundItem)
320335
val sharedPref = applicationContext.getSharedPreferences(
321336
getString(R.string.preference_file_key), Context.MODE_PRIVATE
322337
)
323-
val switch = view.findViewById<View>(R.id.theme_switch_button) as Switch
324-
switch.isChecked = sharedPref.getInt(
325-
"selected_theme",
326-
R.style.Theme_NotificationDictionary
327-
) == R.style.Theme_NotificationDictionary_Dark
338+
339+
val switch_sound = soundView.findViewById<View>(R.id.sound_switch_button) as Switch
340+
var switch_sound_value = sharedPref.getBoolean(
341+
"read_definition",
342+
false
343+
)
344+
345+
switch_sound.isChecked = switch_sound_value
346+
328347

329348
// https://stackoverflow.com/questions/32091709/how-to-get-set-action-event-in-android-actionbar-switch
330349
// https://stackoverflow.com/questions/8811594/implementing-user-choice-of-theme
331350
// https://stackoverflow.com/questions/2482848/how-to-change-current-theme-at-runtime-in-android
332351
// recreate needs to be called as per stackoverflow answers after initial theme is set though it's not documented.
333-
switch.setOnCheckedChangeListener { buttonView, isChecked ->
334-
if (isChecked) {
335-
with(sharedPref.edit()) {
336-
putInt("selected_theme", R.style.Theme_NotificationDictionary_Dark)
337-
apply()
338-
commit()
339-
}
340-
setTheme(R.style.Theme_NotificationDictionary_Dark)
341-
recreate()
342-
} else {
343-
with(sharedPref.edit()) {
344-
putInt("selected_theme", R.style.Theme_NotificationDictionary)
345-
apply()
346-
commit()
347-
}
348-
setTheme(R.style.Theme_NotificationDictionary)
349-
recreate()
352+
switch_sound.setOnClickListener { buttonView ->
353+
val sound_button = findViewById<View>(R.id.sound_switch_button) as Switch
354+
switch_sound_value = !switch_sound_value
355+
with(sharedPref.edit()) {
356+
putBoolean("read_definition", switch_sound_value)
357+
apply()
358+
commit()
350359
}
360+
361+
sound_button.isChecked = switch_sound_value
351362
}
352363
return true
353364
}

app/src/main/java/com/xtreak/notificationdictionary/ProcessTextActivity.kt

Lines changed: 170 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,42 @@
1111
package com.xtreak.notificationdictionary
1212

1313
import android.app.PendingIntent
14-
import android.content.Intent
14+
import android.content.*
1515
import android.os.Build
1616
import android.os.Bundle
17+
import android.speech.tts.TextToSpeech
18+
import android.util.Log
1719
import androidx.appcompat.app.AppCompatActivity
1820
import androidx.core.app.NotificationCompat
1921
import androidx.core.app.NotificationManagerCompat
2022
import androidx.core.app.TaskStackBuilder
23+
import java.util.*
2124
import java.util.concurrent.Executors
2225
import java.util.concurrent.TimeUnit
2326

2427

25-
class ProcessTextActivity : AppCompatActivity() {
28+
private class TTSOnInitListener(
29+
private val in_word: String,
30+
private val in_definition: String,
31+
private val context: Context
32+
) : TextToSpeech.OnInitListener {
33+
val tts = TextToSpeech(context, this)
34+
35+
override fun onInit(status: Int) {
36+
if (status == TextToSpeech.SUCCESS) {
37+
tts.language = Locale.getDefault()
38+
Log.d("ndict current locale", Locale.getDefault().language)
39+
tts.speak("$in_word, $in_definition", TextToSpeech.QUEUE_FLUSH, null)
40+
}
41+
}
42+
}
43+
44+
open class ProcessIntentActivity : AppCompatActivity() {
2645

2746
private val CHANNEL_ID = "Dictionary"
2847
private val CHANNEL_NUMBER = 1
48+
private val NOTIFICATION_TIMEOUT = 20000
49+
2950

3051
override fun onCreate(savedInstanceState: Bundle?) {
3152
super.onCreate(savedInstanceState)
@@ -44,7 +65,7 @@ class ProcessTextActivity : AppCompatActivity() {
4465
try {
4566
meaning = dao.getMeaningsByWord(word, 1)
4667
if (meaning != null) {
47-
resolveRedirectMeaning(listOf(meaning) as List<Word>, dao)
68+
resolveRedirectMeaning(listOf(meaning), dao)
4869
}
4970
} catch (e: Exception) {
5071
meaning = Word(
@@ -71,7 +92,13 @@ class ProcessTextActivity : AppCompatActivity() {
7192
.setDefaults(NotificationCompat.DEFAULT_ALL)
7293
.setSound(null) // sound is set null but still the notification importance level seems to trigger sound
7394
.setAutoCancel(true)
74-
.setTimeoutAfter(20000)
95+
.setTimeoutAfter(NOTIFICATION_TIMEOUT.toLong())
96+
97+
if (definition != "No meaning found") {
98+
addCopyButton(word, definition, context, builder)
99+
addShareButton(word, definition, context, builder)
100+
addReadButton(word, definition, context, builder)
101+
}
75102

76103
val intent = Intent(
77104
context, MainActivity::class.java
@@ -95,8 +122,147 @@ class ProcessTextActivity : AppCompatActivity() {
95122
val notificationManager = NotificationManagerCompat.from(context)
96123
notificationManager.notify(CHANNEL_NUMBER, builder.build())
97124

125+
val sharedPref = applicationContext.getSharedPreferences(
126+
getString(R.string.preference_file_key), Context.MODE_PRIVATE
127+
)
128+
val read_definition = sharedPref.getBoolean(
129+
"read_definition",
130+
false
131+
)
132+
133+
if (read_definition) {
134+
TTSOnInitListener(word, definition, context)
135+
}
98136
// Android intent filters should have an activity but we need to raise only a notification, so call finish
99137
// When the app is not open in background or actively running the white screen appears for a second or so.
100138
this.finish()
101139
}
140+
141+
private fun addCopyButton(
142+
word: String,
143+
definition: String,
144+
context: Context,
145+
builder: NotificationCompat.Builder
146+
) {
147+
// Ref : https://stackoverflow.com/questions/14291436/copy-to-clipboard-by-notification-action
148+
val notificationCopy: BroadcastReceiver = object : BroadcastReceiver() {
149+
override fun onReceive(context: Context, intent: Intent?) {
150+
val clipboard: ClipboardManager =
151+
context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
152+
val clip = ClipData.newPlainText("label", "${word} - ${definition}")
153+
clipboard.setPrimaryClip(clip)
154+
155+
// unregister the receiver else they will keep adding themselves to context resulting in duplicate calls
156+
try {
157+
context.unregisterReceiver(this)
158+
} catch (e: IllegalArgumentException) {
159+
Log.e("Notification Dictionary", "Error in unregistering the receiver")
160+
}
161+
}
162+
}
163+
164+
val intentFilter = IntentFilter("com.xtreak.notificationdictionary.ACTION_COPY")
165+
context.registerReceiver(notificationCopy, intentFilter)
166+
167+
val copy = Intent("com.xtreak.notificationdictionary.ACTION_COPY")
168+
val nCopy =
169+
PendingIntent.getBroadcast(
170+
context,
171+
0,
172+
copy,
173+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
174+
)
175+
176+
builder.addAction(NotificationCompat.Action(null, "Copy", nCopy))
177+
}
178+
179+
private fun addShareButton(
180+
word: String,
181+
definition: String,
182+
context: Context,
183+
builder: NotificationCompat.Builder
184+
) {
185+
// Ref : https://stackoverflow.com/questions/14291436/copy-to-clipboard-by-notification-action
186+
val notificationShare: BroadcastReceiver = object : BroadcastReceiver() {
187+
override fun onReceive(context: Context, intent: Intent?) {
188+
val sharingIntent = Intent(Intent.ACTION_SEND)
189+
sharingIntent.type = "text/plain"
190+
val content =
191+
"${word}\n\n${definition}\n\nSent via Notification Dictionary (https://play.google.com/store/apps/details?id=com.xtreak.notificationdictionary)"
192+
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, word)
193+
sharingIntent.putExtra(Intent.EXTRA_TEXT, content)
194+
startActivity(Intent.createChooser(sharingIntent, "Share via"))
195+
196+
// unregister the receiver else they will keep adding themselves to context resulting in duplicate calls
197+
try {
198+
context.unregisterReceiver(this)
199+
} catch (e: IllegalArgumentException) {
200+
Log.e("Notification Dictionary", "Error in unregistering the receiver")
201+
}
202+
}
203+
}
204+
205+
val intentFilter = IntentFilter("com.xtreak.notificationdictionary.ACTION_SHARE")
206+
context.registerReceiver(notificationShare, intentFilter)
207+
208+
val share = Intent("com.xtreak.notificationdictionary.ACTION_SHARE")
209+
val nShare =
210+
PendingIntent.getBroadcast(
211+
context,
212+
0,
213+
share,
214+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
215+
)
216+
217+
builder.addAction(
218+
NotificationCompat.Action(
219+
android.R.drawable.ic_menu_share,
220+
"Share",
221+
nShare
222+
)
223+
)
224+
}
225+
226+
private fun addReadButton(
227+
word: String,
228+
definition: String,
229+
context: Context,
230+
builder: NotificationCompat.Builder,
231+
) {
232+
// Ref : https://stackoverflow.com/questions/14291436/copy-to-clipboard-by-notification-action
233+
val notificationRead: BroadcastReceiver = object : BroadcastReceiver() {
234+
override fun onReceive(context: Context, intent: Intent?) {
235+
TTSOnInitListener(word, definition, context)
236+
237+
// unregister the receiver else they will keep adding themselves to context resulting in duplicate calls
238+
try {
239+
context.unregisterReceiver(this)
240+
} catch (e: IllegalArgumentException) {
241+
Log.e("Notification Dictionary", "Error in unregistering the receiver")
242+
}
243+
}
244+
}
245+
246+
val intentFilter = IntentFilter("com.xtreak.notificationdictionary.ACTION_TTS")
247+
context.registerReceiver(notificationRead, intentFilter)
248+
249+
val read = Intent("com.xtreak.notificationdictionary.ACTION_TTS")
250+
val nRead =
251+
PendingIntent.getBroadcast(
252+
context,
253+
0,
254+
read,
255+
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
256+
)
257+
258+
builder.addAction(NotificationCompat.Action(null, "Read", nRead))
259+
}
260+
102261
}
262+
263+
264+
// Android needs different classes for different intent filters. So add one PROCESS_TEXT and another for VIEW
265+
266+
class ProcessViewActivity : ProcessIntentActivity()
267+
268+
class ProcessTextActivity : ProcessIntentActivity()

0 commit comments

Comments
 (0)