Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/main/java/com/mccal/folio/MarketInstallSheet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Shield
Expand Down Expand Up @@ -78,6 +79,16 @@ internal fun MarketInstallSheet(
stringResource(R.string.text_1_s_version_2_s, manifest.author.name.english, manifest.version.toString()),
color = Color.White.copy(alpha = .55f), fontSize = 14.sp, modifier = Modifier.padding(bottom = 12.dp),
)
// Said before Get, because it's part of deciding: the author declared AI helped make this.
manifest.aiAssisted?.let { ai ->

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show the disclosure in the external-app chooser

For an AI-assisted package whose kind is externalApp, onExternalOrConfirm routes Get to MarketExternalSheet rather than this component, and Get can be invoked directly from package or source lists. The user can therefore choose Play, F-Droid, Obtainium, or Folio's APK installer without seeing this disclosure unless they separately opened the package page; render the aiAssisted declaration in the external chooser as well.

Useful? React with 👍 / 👎.

Row(Modifier.padding(bottom = 12.dp).testTag("install-ai-assisted"), verticalAlignment = Alignment.Top) {
Icon(Icons.Rounded.AutoAwesome, contentDescription = null, tint = Color.White.copy(alpha = .55f), modifier = Modifier.size(16.dp).padding(top = 2.dp))
Column(Modifier.padding(start = 8.dp)) {
Text(stringResource(R.string.made_with_help_from_ai_1_s, ai.tools.joinToString()), color = Color.White.copy(alpha = .85f), fontSize = 14.sp)
ai.note?.let { Text(it.english, color = Color.White.copy(alpha = .55f), fontSize = 13.sp) }
}
}
}

Row(Modifier.fillMaxWidth().clip(RoundedCornerShape(14.dp)).background(Color(0xFF2C2C2E)).padding(14.dp)) {
Icon(Icons.Rounded.Shield, contentDescription = null, tint = Color(0xFF30D158), modifier = Modifier.size(20.dp))
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/mccal/folio/MarketScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.border
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -1044,6 +1045,9 @@ private fun MarketPackagePage(
Column {
Text(name, color = Color.White, fontSize = 26.sp, fontWeight = FontWeight.Bold)
entry.manifest?.author?.name?.english?.let { Text(it, color = Color.White.copy(alpha = .55f), fontSize = 14.sp) }
// The author said AI helped make it (the manifest's aiAssisted). A plain tag, not a warning: it's
// how the package was made, the way the author line says who made it.
if (entry.manifest?.aiAssisted != null) AiAssistedTag(Modifier.padding(top = 4.dp))
}
}

Expand Down Expand Up @@ -1179,6 +1183,13 @@ private fun MarketPackagePage(
color = Color.White.copy(alpha = .55f), fontSize = 13.sp,
)
installed?.let { Text(stringResource(R.string.installed_1_s, it.version), color = Color.White.copy(alpha = .55f), fontSize = 13.sp) }
entry.manifest?.aiAssisted?.let { ai ->
Text(
stringResource(R.string.made_with_ai_1_s, ai.tools.joinToString()),
color = Color.White.copy(alpha = .85f), fontSize = 14.sp, modifier = Modifier.padding(top = 4.dp),
)
ai.note?.let { Text(it.english, color = Color.White.copy(alpha = .55f), fontSize = 13.sp) }
}
if (external) {
// Where it comes from, and whether Android has it - the two things the store can honestly say
// about an app it doesn't install.
Expand Down Expand Up @@ -1219,6 +1230,17 @@ private fun MarketPackagePage(
}
}

/** "AI-assisted", beside the developer on a package page. */
@Composable
internal fun AiAssistedTag(modifier: Modifier = Modifier) {
Text(
stringResource(R.string.ai_assisted),
color = Color.White.copy(alpha = .7f), fontSize = 11.sp,
modifier = modifier.border(0.5.dp, Color.White.copy(alpha = .3f), RoundedCornerShape(50))
.padding(horizontal = 7.dp, vertical = 1.dp).testTag("package-ai-assisted"),
)
}

/** The "… is on · Undo" line, the same shape as Folio's other undo messages. */
@Composable
private fun MarketMessage(text: String, undo: (() -> Unit)?, onDismiss: () -> Unit) {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values-b+zh+Hans/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,9 @@
<string name="information">信息</string>
<string name="source_1_s">源:%1$s</string>
<string name="built_into_folio">内置于 Folio</string>
<string name="ai_assisted">AI 辅助</string>
<string name="made_with_ai_1_s">借助 AI 制作:%1$s</string>
<string name="made_with_help_from_ai_1_s">在 AI 帮助下制作:%1$s</string>
<string name="inside_the_app">在 App 内</string>
<string name="folio_couldn_t_reach_this_source">Folio 无法连接到这个源</string>
<string name="showing_the_list_it_had_before">显示的是之前的列表。</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@
<string name="information">Information</string>
<string name="source_1_s">Source: %1$s</string>
<string name="built_into_folio">Built into Folio</string>
<!-- A package's manifest declares aiAssisted: the tag beside its developer, then who helped. %1$s is a list of tool names. -->
<string name="ai_assisted">AI-assisted</string>
<string name="made_with_ai_1_s">Made with AI: %1$s</string>
<string name="made_with_help_from_ai_1_s">Made with help from AI: %1$s</string>
<string name="inside_the_app">Inside the app</string>
<string name="folio_couldn_t_reach_this_source">Folio couldn\'t reach this source</string>
<string name="showing_the_list_it_had_before">Showing the list it had before.</string>
Expand Down
16 changes: 16 additions & 0 deletions app/src/test/java/com/mccal/folio/MarketScreenRenderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,22 @@ class MarketScreenRenderTest {
assertEquals(listOf("com.mccal.folio.cabinet"), session.installed().map { it.id })
}

@Test fun `the install sheet says when AI helped make a package, and says nothing when it didn't`() {
val root = generateSequence(java.io.File("").absoluteFile) { it.parentFile }.first { java.io.File(it, "CHANGELOG.md").exists() }
val cabinet = org.json.JSONObject(java.io.File(root, "docs/sdk/source/packages/cabinet/manifest.json").readText())
fun manifest(json: org.json.JSONObject) =
(com.mccal.folio.market.PackageManifest.parse(json.toString()) as com.mccal.folio.market.ParseResult.Ok).value
val helped = manifest(org.json.JSONObject(cabinet.toString()).put("aiAssisted",
org.json.JSONObject().put("tools", org.json.JSONArray().put("Claude").put("Example tool")).put("note", "Drafted the page.")))
val shown = androidx.compose.runtime.mutableStateOf(helped)
compose.setContent { MarketInstallSheet(shown.value, InstallOrigin("From a source"), onGet = {}, onCancel = {}) }
compose.onNodeWithText("Made with help from AI: Claude, Example tool").assertExists()
compose.onNodeWithText("Drafted the page.").assertExists()
shown.value = manifest(cabinet)
compose.waitForIdle()
compose.onNodeWithTag("install-ai-assisted", useUnmergedTree = true).assertDoesNotExist()
}

@Test fun `a package from a source shows up in the list, with where it came from`() {
val session = session()
session.prefs.introductionSeen = true
Expand Down
6 changes: 3 additions & 3 deletions docs/standards/ai-contributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ The short version:
an icon pack, a wallpaper, a layout preset) as **AI-assisted**, naming the tool:
- a Market package sets `aiAssisted` in its manifest: `{ "tools": ["Claude"], "note": "Drafted the colours." }`
([format](../sdk/format-v1.md));
- and, until the Market shows that field, also starts `description` with "AI-assisted (tool name)." so every Folio
version shows it (see Gaps);
- and also starts `description` with "AI-assisted (tool name).", because Folio 0.6.6 doesn't show the field
(later versions show it on the package page and the install sheet; see Gaps);
- a theme in `themes/` starts its description the same way;
- the credits or README that travels with it says so too.
- **AI-7 MUST NOT** remove or reword an AI-assisted label when republishing, forking or updating someone else's
Expand Down Expand Up @@ -73,6 +73,6 @@ The short version:

| # | Work | Size |
|---|---|---|
| 1 | Show `aiAssisted` on the Market's package page and at install; then the `description` prefix is no longer needed (the field itself is in schema v1 and the parser) | S |
| 1 | Drop the `description` prefix rule once the release that shows `aiAssisted` (package page and install sheet) has replaced 0.6.6 on most phones | S |
| 2 | The same field in the community theme format (`themes/README.md`, `CommunityThemesTest`) | S |
| 3 | A "made with AI" filter in the Market | S |
Loading