Skip to content

Commit 9841fa8

Browse files
committed
feat: added logo
1 parent 7d7e881 commit 9841fa8

29 files changed

Lines changed: 323 additions & 16 deletions

File tree

README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,8 @@
33
Native iOS and Android client for [phpMyFAQ](https://www.phpmyfaq.de),
44
built with Kotlin Multiplatform, SwiftUI, and Jetpack Compose.
55

6-
**Status: Phase 0 (foundations)**. The apps display a placeholder
7-
screen that proves the shared module, API client, encrypted database,
8-
and secure storage are wired end-to-end. No user-visible features yet
9-
-- those land in Phase 1.
10-
116
- Website: [myfaq.app](https://myfaq.app)
127
- phpMyFAQ minimum version: **4.2.0**
13-
- Business model: freemium (read + offline free forever; writes behind
14-
Pro unlock in Phase 3)
158

169
---
1710

mobile/LOGO.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Logo / App Icon Drop Instructions
2+
3+
Drop the phpMyFAQ logo PNGs into the locations below. After dropping
4+
the files, rebuild both apps. No code changes required — the asset
5+
catalogs / drawable folders are already wired into the Xcode project,
6+
the Android manifest, and the start screen.
7+
8+
## iOS
9+
10+
`mobile/iosApp/iosApp/Assets.xcassets/`
11+
12+
| File | Size | Purpose |
13+
|------|------|---------|
14+
| `AppIcon.appiconset/icon-1024.png` | 1024×1024 (opaque, no alpha) | App icon — App Store + home screen |
15+
| `Logo.imageset/logo.png` | 120×120 | Start-screen logo (1×) |
16+
| `Logo.imageset/logo@2x.png` | 240×240 | Start-screen logo (2×) |
17+
| `Logo.imageset/logo@3x.png` | 360×360 | Start-screen logo (3×) |
18+
19+
After dropping, in `Info.plist` make sure `CFBundleIcons` references
20+
`AppIcon` (Xcode does this automatically when the asset is present).
21+
22+
## Android
23+
24+
`mobile/androidApp/src/main/res/`
25+
26+
| File | Size | Purpose |
27+
|------|------|---------|
28+
| `mipmap-mdpi/ic_launcher.png` | 48×48 | App icon |
29+
| `mipmap-hdpi/ic_launcher.png` | 72×72 | App icon |
30+
| `mipmap-xhdpi/ic_launcher.png` | 96×96 | App icon |
31+
| `mipmap-xxhdpi/ic_launcher.png` | 144×144 | App icon |
32+
| `mipmap-xxxhdpi/ic_launcher.png` | 192×192 | App icon |
33+
| `drawable/logo.png` | 512×512 (transparent OK) | Start-screen logo |
34+
35+
`AndroidManifest.xml` already references `@mipmap/ic_launcher`. The
36+
start screen looks up `R.drawable.logo` — keep the filename literal
37+
`logo.png`.
38+
39+
## Start screen
40+
41+
The start screen (Workspaces empty state) renders the logo above the
42+
"Add your first instance" CTA. Logo is also reused at the top of the
43+
Add Instance sheet.

mobile/androidApp/src/main/kotlin/app/myfaq/android/Navigation.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import app.myfaq.android.screens.CategoriesScreen
2828
import app.myfaq.android.screens.FaqDetailScreen
2929
import app.myfaq.android.screens.FaqListScreen
3030
import app.myfaq.android.screens.HomeScreen
31+
import app.myfaq.android.screens.LicensesScreen
3132
import app.myfaq.android.screens.NewsDetailScreen
3233
import app.myfaq.android.screens.PaywallScreen
3334
import app.myfaq.android.screens.SearchScreen
@@ -50,6 +51,7 @@ object Routes {
5051
const val NEWS_DETAIL = "news/{newsId}"
5152
const val SEARCH = "search"
5253
const val SETTINGS = "settings"
54+
const val LICENSES = "licenses"
5355
const val PAYWALL = "paywall"
5456

5557
fun faqList(
@@ -80,6 +82,7 @@ enum class BottomTab(
8082

8183
// ── Root scaffold with NavHost ─────────────────────────────────────
8284

85+
@Suppress("LongMethod")
8386
@Composable
8487
fun MyFaqNavHost(aim: ActiveInstanceManager = koinInject()) {
8588
val navController = rememberNavController()
@@ -232,9 +235,14 @@ fun MyFaqNavHost(aim: ActiveInstanceManager = koinInject()) {
232235
}
233236
}
234237
},
238+
onLicenses = { navController.navigate(Routes.LICENSES) },
235239
)
236240
}
237241

242+
composable(Routes.LICENSES) {
243+
LicensesScreen(onBack = { navController.popBackStack() })
244+
}
245+
238246
composable(Routes.PAYWALL) {
239247
PaywallScreen(onBack = { navController.popBackStack() })
240248
}

mobile/androidApp/src/main/kotlin/app/myfaq/android/screens/HomeScreen.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,15 @@ fun HomeScreen(
5858
) {
5959
val vm = remember { HomeViewModel(aim) }
6060
var selectedTab by remember { mutableIntStateOf(0) }
61+
val title by vm.title.collectAsState()
6162

6263
LaunchedEffect(Unit) { vm.loadAll() }
6364

6465
Scaffold(
6566
topBar = {
66-
CenterAlignedTopAppBar(title = { Text("MyFAQ") })
67+
CenterAlignedTopAppBar(
68+
title = { Text(title?.takeIf { it.isNotBlank() } ?: "MyFAQ") },
69+
)
6770
},
6871
) { padding ->
6972
Column(modifier = Modifier.padding(padding)) {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package app.myfaq.android.screens
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.PaddingValues
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.lazy.LazyColumn
9+
import androidx.compose.foundation.lazy.items
10+
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
12+
import androidx.compose.material3.ExperimentalMaterial3Api
13+
import androidx.compose.material3.HorizontalDivider
14+
import androidx.compose.material3.Icon
15+
import androidx.compose.material3.IconButton
16+
import androidx.compose.material3.MaterialTheme
17+
import androidx.compose.material3.Scaffold
18+
import androidx.compose.material3.Text
19+
import androidx.compose.material3.TopAppBar
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.unit.dp
23+
24+
private data class LibraryEntry(
25+
val name: String,
26+
val license: String,
27+
val url: String,
28+
)
29+
30+
private val libraries: List<LibraryEntry> =
31+
listOf(
32+
LibraryEntry("Kotlin", "Apache 2.0", "https://kotlinlang.org"),
33+
LibraryEntry("Kotlin Coroutines", "Apache 2.0", "https://github.com/Kotlin/kotlinx.coroutines"),
34+
LibraryEntry("kotlinx.serialization", "Apache 2.0", "https://github.com/Kotlin/kotlinx.serialization"),
35+
LibraryEntry("Ktor", "Apache 2.0", "https://ktor.io"),
36+
LibraryEntry("SQLDelight", "Apache 2.0", "https://github.com/cashapp/sqldelight"),
37+
LibraryEntry("Koin", "Apache 2.0", "https://insert-koin.io"),
38+
LibraryEntry("Jetpack Compose", "Apache 2.0", "https://developer.android.com/jetpack/compose"),
39+
LibraryEntry("Material Icons", "Apache 2.0", "https://fonts.google.com/icons"),
40+
LibraryEntry("AndroidX Security Crypto", "Apache 2.0", "https://developer.android.com/jetpack/androidx"),
41+
LibraryEntry("SQLCipher", "BSD-Style", "https://www.zetetic.net/sqlcipher/"),
42+
)
43+
44+
@OptIn(ExperimentalMaterial3Api::class)
45+
@Composable
46+
fun LicensesScreen(onBack: () -> Unit) {
47+
Scaffold(
48+
topBar = {
49+
TopAppBar(
50+
title = { Text("Licenses") },
51+
navigationIcon = {
52+
IconButton(onClick = onBack) {
53+
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
54+
}
55+
},
56+
)
57+
},
58+
) { padding ->
59+
LazyColumn(
60+
modifier = Modifier.fillMaxSize().padding(padding),
61+
contentPadding = PaddingValues(vertical = 8.dp),
62+
verticalArrangement = Arrangement.Top,
63+
) {
64+
item {
65+
Column(modifier = Modifier.padding(16.dp)) {
66+
Text(
67+
"MyFAQ.app",
68+
style = MaterialTheme.typography.titleMedium,
69+
)
70+
Text(
71+
"Native client for phpMyFAQ — © phpMyFAQ Team.",
72+
style = MaterialTheme.typography.bodySmall,
73+
color = MaterialTheme.colorScheme.onSurfaceVariant,
74+
)
75+
Text(
76+
"Licensed under MPL 2.0.",
77+
style = MaterialTheme.typography.bodySmall,
78+
color = MaterialTheme.colorScheme.onSurfaceVariant,
79+
)
80+
}
81+
HorizontalDivider()
82+
}
83+
items(libraries, key = { it.name }) { entry ->
84+
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) {
85+
Text(entry.name, style = MaterialTheme.typography.bodyLarge)
86+
Text(
87+
"${entry.license}${entry.url}",
88+
style = MaterialTheme.typography.bodySmall,
89+
color = MaterialTheme.colorScheme.onSurfaceVariant,
90+
)
91+
}
92+
HorizontalDivider()
93+
}
94+
}
95+
}
96+
}

mobile/androidApp/src/main/kotlin/app/myfaq/android/screens/SettingsScreen.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import org.koin.compose.koinInject
3636
@Composable
3737
fun SettingsScreen(
3838
onSwitchInstance: () -> Unit,
39+
onLicenses: () -> Unit = {},
3940
aim: ActiveInstanceManager = koinInject(),
4041
db: MyFaqDatabase = koinInject(),
4142
) {
@@ -176,6 +177,13 @@ fun SettingsScreen(
176177
)
177178
},
178179
)
180+
HorizontalDivider()
181+
182+
// Open source licenses
183+
ListItem(
184+
headlineContent = { Text("Open source licenses") },
185+
modifier = Modifier.clickable(onClick = onLicenses),
186+
)
179187
}
180188
}
181189
}

mobile/androidApp/src/main/kotlin/app/myfaq/android/screens/WorkspacesScreen.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package app.myfaq.android.screens
22

3+
import androidx.compose.foundation.Image
34
import androidx.compose.foundation.clickable
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Box
@@ -38,7 +39,9 @@ import androidx.compose.runtime.remember
3839
import androidx.compose.runtime.setValue
3940
import androidx.compose.ui.Alignment
4041
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.res.painterResource
4143
import androidx.compose.ui.unit.dp
44+
import app.myfaq.android.R
4245
import app.myfaq.shared.data.ActiveInstanceManager
4346
import app.myfaq.shared.domain.Instance
4447
import app.myfaq.shared.ui.WorkspacesViewModel
@@ -206,6 +209,12 @@ private fun EmptyWorkspacesState(
206209
contentAlignment = Alignment.Center,
207210
) {
208211
Column(horizontalAlignment = Alignment.CenterHorizontally) {
212+
Image(
213+
painter = painterResource(id = R.drawable.logo),
214+
contentDescription = "phpMyFAQ logo",
215+
modifier = Modifier.height(120.dp),
216+
)
217+
Spacer(modifier = Modifier.height(24.dp))
209218
Text(
210219
text = "No instances yet",
211220
style = MaterialTheme.typography.headlineSmall,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Placeholder vector drawable. Replace with the phpMyFAQ logo PNG by
4+
dropping `logo.png` into the matching `drawable-*dpi/` folders (or
5+
this same folder with `logo.png` to override). See mobile/LOGO.md.
6+
-->
7+
<vector xmlns:android="http://schemas.android.com/res/android"
8+
android:width="120dp"
9+
android:height="120dp"
10+
android:viewportWidth="120"
11+
android:viewportHeight="120">
12+
<path
13+
android:fillColor="#1A73E8"
14+
android:pathData="M60,10A50,50 0 1,0 60,110A50,50 0 1,0 60,10Z" />
15+
<path
16+
android:fillColor="#FFFFFF"
17+
android:pathData="M60,30 C50,30 42,38 42,48 L52,48 C52,44 56,40 60,40 C64,40 68,44 68,48 C68,52 64,55 60,58 C56,61 54,65 54,72 L66,72 C66,68 68,66 72,63 C76,60 80,55 80,48 C80,38 70,30 60,30 Z M54,80 L66,80 L66,92 L54,92 Z" />
18+
</vector>
4.46 KB
Loading
2.9 KB
Loading

0 commit comments

Comments
 (0)