Skip to content

Commit f62d2cb

Browse files
committed
Add docs api
1 parent 5549e9f commit f62d2cb

7 files changed

Lines changed: 134 additions & 24 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
1717
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
1818
<uses-permission android:name="android.permission.MULTICAST" />
19-
<uses-permission
20-
android:name="android.permission.WRITE_SETTINGS"
21-
tools:ignore="ProtectedPermissions" />
2219
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2320
<uses-permission android:name="android.permission.READ_SMS" />
2421
<uses-permission android:name="android.permission.SEND_SMS" />

app/src/main/java/com/ismartcoding/plain/features/Permissions.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ enum class Permission {
4444
WRITE_CALL_LOG,
4545
CALL_PHONE,
4646
POST_NOTIFICATIONS,
47-
WRITE_SETTINGS,
4847
CAMERA,
4948
SYSTEM_ALERT_WINDOW,
5049
RECORD_AUDIO,
@@ -86,10 +85,6 @@ enum class Permission {
8685
FileHelper.hasStoragePermission(context)
8786
}
8887

89-
this == WRITE_SETTINGS -> {
90-
Settings.System.canWrite(context)
91-
}
92-
9388
this == QUERY_ALL_PACKAGES -> {
9489
true
9590
}
@@ -185,18 +180,6 @@ enum class Permission {
185180
DialogHelper.showMessage("Cannot open app settings to grant storage access.")
186181
}
187182
}
188-
} else if (this == WRITE_SETTINGS) {
189-
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
190-
intent.addCategory(Intent.CATEGORY_DEFAULT)
191-
intent.data = Uri.parse("package:${context.packageName}")
192-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
193-
if (intent.resolveActivity(packageManager) != null) {
194-
intentLauncher?.launch(intent)
195-
} else {
196-
DialogHelper.showMessage(
197-
"ActivityNotFoundException: No Activity found to handle Intent act=android.settings.action.MANAGE_WRITE_SETTINGS",
198-
)
199-
}
200183
} else if (this == NOTIFICATION_LISTENER) {
201184
val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
202185
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -338,7 +321,6 @@ object Permissions {
338321
Permission.CAMERA,
339322
Permission.WRITE_EXTERNAL_STORAGE,
340323
Permission.CALL_PHONE,
341-
Permission.WRITE_SETTINGS,
342324
Permission.READ_CALL_LOG,
343325
Permission.WRITE_CALL_LOG,
344326
Permission.READ_CONTACTS,
@@ -363,7 +345,6 @@ object Permissions {
363345
}
364346

365347
setOf(
366-
Permission.WRITE_SETTINGS,
367348
Permission.WRITE_EXTERNAL_STORAGE,
368349
Permission.SYSTEM_ALERT_WINDOW,
369350
Permission.POST_NOTIFICATIONS,

app/src/main/java/com/ismartcoding/plain/helpers/INDEX.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
| NotificationHelper.kt | Create notifications, channels (Android 8+), remote input, stop action |
5656
| NotificationsHelper.kt | Notification filtering: allowlist/blacklist per app |
5757
| ShareHelper.kt | Android share intent, MIME detection, FileProvider |
58-
| ScreenHelper.kt | Keep screen on toggle via WRITE_SETTINGS |
5958

6059
## Crypto & Security
6160
| File | Purpose |

app/src/main/java/com/ismartcoding/plain/ui/page/OtherFilePage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fun OtherFilePage(
120120
color = MaterialTheme.colorScheme.onSurfaceVariant,
121121
)
122122
VerticalSpace(dp = 64.dp)
123-
PFilledButton(text = stringResource(id = R.string.open_with_other_app), onClick = {
123+
PFilledButton(text = stringResource(id = R.string.open_with_other_app), modifier = Modifier.padding(horizontal = 16.dp), onClick = {
124124
ShareHelper.openPathWith(context, path)
125125
})
126126
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.ismartcoding.plain.web
2+
3+
import android.provider.MediaStore
4+
import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
5+
import com.apurebase.kgraphql.schema.execution.Executor
6+
import com.ismartcoding.lib.helpers.SearchHelper
7+
import com.ismartcoding.plain.MainApp
8+
import com.ismartcoding.plain.enums.FileType
9+
import com.ismartcoding.plain.features.Permission
10+
import com.ismartcoding.plain.features.file.FileSortBy
11+
import com.ismartcoding.plain.features.media.FileMediaStoreHelper
12+
import com.ismartcoding.plain.web.models.DocExtGroup
13+
import com.ismartcoding.plain.web.models.toDocModel
14+
15+
private fun parseDocFileSize(s: String): Long? {
16+
val m = Regex("^([\\d.]+)\\s*(b|kb|mb|gb|tb)?$", RegexOption.IGNORE_CASE).matchEntire(s.trim()) ?: return null
17+
val num = m.groupValues[1].toDoubleOrNull() ?: return null
18+
return when (m.groupValues[2].lowercase()) {
19+
"kb" -> (num * 1024).toLong()
20+
"mb" -> (num * 1024 * 1024).toLong()
21+
"gb" -> (num * 1024 * 1024 * 1024).toLong()
22+
"tb" -> (num * 1024L * 1024 * 1024 * 1024).toLong()
23+
else -> num.toLong()
24+
}
25+
}
26+
27+
private fun matchDocFileSize(fileSize: Long, op: String, filterSize: Long): Boolean {
28+
return when (op) {
29+
">" -> fileSize > filterSize
30+
">=" -> fileSize >= filterSize
31+
"<" -> fileSize < filterSize
32+
"<=" -> fileSize <= filterSize
33+
"!=" -> fileSize != filterSize
34+
else -> fileSize == filterSize
35+
}
36+
}
37+
38+
fun SchemaBuilder.addDocQueries() {
39+
query("docs") {
40+
configure {
41+
executor = Executor.DataLoaderPrepared
42+
}
43+
resolver { offset: Int, limit: Int, query: String, sortBy: FileSortBy ->
44+
val context = MainApp.instance
45+
Permission.WRITE_EXTERNAL_STORAGE.checkAsync(context)
46+
val fields = SearchHelper.parse(query)
47+
val extField = fields.find { it.name == "ext" }
48+
val textField = fields.find { it.name == "text" }
49+
val fileSizeField = fields.find { it.name == "file_size" }
50+
FileMediaStoreHelper.getAllByFileTypeAsync(context, MediaStore.VOLUME_EXTERNAL_PRIMARY, FileType.DOCUMENT, sortBy)
51+
.filter { file ->
52+
val extMatch = extField == null || file.name.substringAfterLast('.', "").lowercase() == extField.value.lowercase()
53+
val textMatch = textField == null || file.name.contains(textField.value, ignoreCase = true)
54+
val sizeMatch = if (fileSizeField == null) true else {
55+
val bytes = parseDocFileSize(fileSizeField.value) ?: return@filter false
56+
matchDocFileSize(file.size, fileSizeField.op, bytes)
57+
}
58+
extMatch && textMatch && sizeMatch
59+
}
60+
.drop(offset).take(limit).map { it.toDocModel() }
61+
}
62+
}
63+
64+
query("docCount") {
65+
resolver { query: String ->
66+
if (Permission.WRITE_EXTERNAL_STORAGE.enabledAndCanAsync(MainApp.instance)) {
67+
val fields = SearchHelper.parse(query)
68+
val extField = fields.find { it.name == "ext" }
69+
val textField = fields.find { it.name == "text" }
70+
val fileSizeField = fields.find { it.name == "file_size" }
71+
FileMediaStoreHelper.getAllByFileTypeAsync(
72+
MainApp.instance, MediaStore.VOLUME_EXTERNAL_PRIMARY, FileType.DOCUMENT, FileSortBy.DATE_DESC
73+
).filter { file ->
74+
val extMatch = extField == null || file.name.substringAfterLast('.', "").lowercase() == extField.value.lowercase()
75+
val textMatch = textField == null || file.name.contains(textField.value, ignoreCase = true)
76+
val sizeMatch = if (fileSizeField == null) true else {
77+
val bytes = parseDocFileSize(fileSizeField.value) ?: return@filter false
78+
matchDocFileSize(file.size, fileSizeField.op, bytes)
79+
}
80+
extMatch && textMatch && sizeMatch
81+
}.size
82+
} else {
83+
0
84+
}
85+
}
86+
}
87+
88+
query("docExtGroups") {
89+
resolver { ->
90+
if (Permission.WRITE_EXTERNAL_STORAGE.enabledAndCanAsync(MainApp.instance)) {
91+
FileMediaStoreHelper.getAllByFileTypeAsync(
92+
MainApp.instance, MediaStore.VOLUME_EXTERNAL_PRIMARY, FileType.DOCUMENT, FileSortBy.NAME_ASC
93+
)
94+
.groupBy { it.name.substringAfterLast('.', "").lowercase() }
95+
.map { DocExtGroup(it.key.uppercase(), it.value.size) }
96+
.sortedBy { it.ext }
97+
} else {
98+
emptyList()
99+
}
100+
}
101+
}
102+
}

app/src/main/java/com/ismartcoding/plain/web/MainGraphQL.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.ismartcoding.plain.web
22

33
import android.os.Build
44
import android.os.Environment
5+
import android.provider.MediaStore
56
import com.apurebase.kgraphql.GraphQLError
67
import com.apurebase.kgraphql.GraphqlRequest
78
import com.apurebase.kgraphql.KGraphQL
@@ -44,6 +45,7 @@ import com.ismartcoding.plain.db.DMessageStatusData
4445
import com.ismartcoding.plain.db.DMessageType
4546
import com.ismartcoding.plain.enums.AppFeatureType
4647
import com.ismartcoding.plain.enums.DataType
48+
import com.ismartcoding.plain.enums.FileType
4749
import com.ismartcoding.plain.enums.MediaPlayMode
4850
import com.ismartcoding.plain.enums.ScreenMirrorControlAction
4951
import com.ismartcoding.plain.enums.ScreenMirrorMode
@@ -122,6 +124,9 @@ import com.ismartcoding.plain.web.models.ActionResult
122124
import com.ismartcoding.plain.web.models.App
123125
import com.ismartcoding.plain.web.models.Audio
124126
import com.ismartcoding.plain.web.models.Call
127+
import com.ismartcoding.plain.web.models.Doc
128+
import com.ismartcoding.plain.web.models.DocExtGroup
129+
import com.ismartcoding.plain.web.models.toDocModel
125130
import com.ismartcoding.plain.chat.PeerChatHelper
126131
import com.ismartcoding.plain.chat.ChannelChatHelper
127132
import com.ismartcoding.plain.chat.ChannelSystemMessageSender
@@ -416,6 +421,7 @@ class MainGraphQL(val schema: Schema) {
416421
}
417422
}
418423
}
424+
addDocQueries()
419425
query("contacts") {
420426
configure {
421427
executor = Executor.DataLoaderPrepared
@@ -1982,6 +1988,7 @@ class MainGraphQL(val schema: Schema) {
19821988
enum<PomodoroState>()
19831989
enum<ScreenMirrorMode>()
19841990
enum<ScreenMirrorControlAction>()
1991+
type<DocExtGroup>()
19851992
stringScalar<kotlin.time.Instant> {
19861993
deserialize = { value: String -> kotlin.time.Instant.parse(value) }
19871994
serialize = kotlin.time.Instant::toString
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.ismartcoding.plain.web.models
2+
3+
import com.ismartcoding.plain.features.file.DFile
4+
import kotlin.time.Instant
5+
6+
data class Doc(
7+
val id: ID,
8+
val name: String,
9+
val path: String,
10+
val extension: String,
11+
val size: Long,
12+
val createdAt: Instant,
13+
val updatedAt: Instant,
14+
)
15+
16+
data class DocExtGroup(
17+
val ext: String,
18+
val count: Int,
19+
)
20+
21+
fun DFile.toDocModel(): Doc {
22+
val ext = name.substringAfterLast('.', "").lowercase()
23+
return Doc(ID(mediaId), name, path, ext, size, createdAt ?: updatedAt, updatedAt)
24+
}

0 commit comments

Comments
 (0)