Skip to content

Commit 1597109

Browse files
committed
feat(player): implement lyrics image sharing with customizable quality and style
- Add comprehensive lyrics sharing feature triggered by long-press on lyric lines. - Implement LyricImageUtils using StaticLayout for professional multi-language text rendering and word wrapping. - Support dynamic card generation with blurred backgrounds, metadata alignment, and separate styling for title/artist/album. - Add "Share Image Quality" settings (1080p, 2K, 4K) with persistent storage in SettingsRepository. - Optimize UI performance with LyricShareItem to prevent full list recomposition in the selection sheet. - Integrate FileProvider and MediaStore for safe image saving and cross-app sharing. - Enhance FullScreenContent layout to prevent text overlapping when metadata is long.
1 parent fe1c100 commit 1597109

15 files changed

Lines changed: 786 additions & 48 deletions

File tree

PLAN.md

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
1-
# Plan: Cache Management Settings
1+
# Plan: Lyrics Image Sharing
2+
3+
## Status
4+
- [ ] Analyze requirement and design UI for lyrics sharing.
5+
- [ ] Add `OpenLyricShare` intent and state to `PlayerContract.kt`.
6+
- [ ] Implement `LyricShareBottomSheet` in `PlayerComponents.kt`.
7+
- [ ] Implement long-press gesture in `LyricContent`.
8+
- [ ] Create `LyricCardGenerator` to render lyrics and metadata to a `Bitmap`.
9+
- [ ] Implement "Save to Gallery" and "Share" functionality using `MediaStore` and `Intent`.
10+
- [ ] Verify build and functionality on Android 16.
11+
12+
## Details
13+
- **Trigger**: Long press on a lyric line in full-screen player.
14+
- **UI**: A `ModalBottomSheet` allowing users to select multiple lines of lyrics.
15+
- **Card Design**:
16+
- Background: Blurred album art or gradient.
17+
- Content: Album art thumbnail, song title, artist, selected lyrics, and a "Kanade" branding.
18+
- **Sharing**:
19+
- Use `Intent.ACTION_SEND` with `FileProvider` (or `MediaStore` URI).
20+
- Save to `Pictures/Kanade/`.
21+
22+
---
23+
24+
# Plan: Cache Management Settings (Completed)
225

326
## Status
427
- [x] Analyze current cache implementation in `CacheManager`.
@@ -9,15 +32,4 @@
932
- [x] Refine `CacheSettingsScreen`: Set limit to 64GB and remove manual clear button.
1033
- [x] Integrate `CacheSettingsScreen` into navigation and main settings.
1134
- [x] Ensure `KanadePlaybackService` respects the user-defined cache limit.
12-
- [x] Verify build stability.
13-
14-
## Details
15-
- **SettingsRepository**: Added `MAX_CACHE_SIZE` key to DataStore.
16-
- **CacheManager**:
17-
- `getCache` now supports dynamic resizing (recreates `SimpleCache` if limit changes).
18-
- Added `getCurrentCacheSize()` and `clearCache()`.
19-
- **UI**:
20-
- New `CacheSettingsScreen` provides a Slider (100MB - 2000MB) for limits.
21-
- Added "Used Space" display with auto-formatting (B, KB, MB, GB).
22-
- Added "Clear Cache" button with error container styling.
23-
- **Service**: `KanadePlaybackService` reads the limit from repository during initialization.
35+
- [x] Verify build stability.

UI.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,60 @@
4444
- **Item Layout**:
4545
- [Hidden] Individual Song Art (Cover).
4646
- Title.
47-
- [Conditional] Artist Name (Hidden if all songs have same artist).
47+
- [Conditional] Artist Name (Hidden if all songs have same artist).
48+
49+
## Lyrics Image Sharing UI
50+
51+
### Trigger
52+
- **Action**: Long press on any lyric line in the full-screen player's lyric view.
53+
54+
### Lyric Selection Page (ModalBottomSheet)
55+
- **Header**: Title "Share Lyrics", "Share" and "Save" buttons.
56+
- **Content**:
57+
- A list of all lyrics with checkboxes for multiple selection.
58+
- Pre-select the long-pressed line.
59+
- Real-time preview toggle (optional) or dynamic image generation.
60+
61+
### Card Design (Generated Image)
62+
- **Background**:
63+
- Material 3 surface color or a blurred version of the current album art.
64+
- Dark mode support (defaulting to a dark, elegant aesthetic).
65+
- **Metadata Section (Top)**:
66+
- **Left Side**:
67+
- Song Title (Large, Bold, White).
68+
- Artist Name (Medium, White with 70% opacity).
69+
- Album Name (Small, White with 50% opacity).
70+
- **Right Side**:
71+
- Album Cover (Small square with rounded corners).
72+
- **Lyrics Section (Middle)**:
73+
- Divider line.
74+
- Selected lyric lines (Left aligned).
75+
- Translation lines (Small, directly below the original text).
76+
- Divider line.
77+
- **Branding Section (Bottom)**:
78+
- **Right Side**: Text `Parallel-SEKAI/Kanade`.
79+
- **Layout Reference (ASCII)**:
80+
```text
81+
+---------------------------------------------------+
82+
| |
83+
| Song Title +----------+ |
84+
| Artist Name | | |
85+
| Album Name | Album | |
86+
| | Cover | |
87+
| | | |
88+
| +----------+ |
89+
| |
90+
| --------------------------------------------- |
91+
| |
92+
| Lyric Line 1 (Original) |
93+
| Lyric Line 1 (Translation) |
94+
| |
95+
| Lyric Line 2 (Original) |
96+
| Lyric Line 2 (Translation) |
97+
| |
98+
| --------------------------------------------- |
99+
| |
100+
| Parallel-SEKAI/Kanade |
101+
| |
102+
+---------------------------------------------------+
103+
```

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
77
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
88
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
9+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
910
<uses-permission android:name="android.permission.WAKE_LOCK" />
1011
<uses-permission android:name="android.permission.INTERNET" />
1112

@@ -43,6 +44,16 @@
4344
<action android:name="android.media.browse.MediaBrowserService" />
4445
</intent-filter>
4546
</service>
47+
48+
<provider
49+
android:name="androidx.core.content.FileProvider"
50+
android:authorities="${applicationId}.fileprovider"
51+
android:exported="false"
52+
android:grantUriPermissions="true">
53+
<meta-data
54+
android:name="android.support.FILE_PROVIDER_PATHS"
55+
android:resource="@xml/file_paths" />
56+
</provider>
4657
</application>
4758

4859
</manifest>

app/src/main/java/org/parallel_sekai/kanade/MainActivity.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,19 @@ class MainActivity : ComponentActivity() {
179179
val currentRoute = navBackStackEntry?.destination?.route
180180
val snackbarHostState = remember { SnackbarHostState() }
181181

182+
LaunchedEffect(Unit) {
183+
playerViewModel.effect.collect { effect ->
184+
when (effect) {
185+
is org.parallel_sekai.kanade.ui.screens.player.PlayerEffect.ShowError -> {
186+
snackbarHostState.showSnackbar(effect.message)
187+
}
188+
is org.parallel_sekai.kanade.ui.screens.player.PlayerEffect.ShowMessage -> {
189+
snackbarHostState.showSnackbar(effect.message)
190+
}
191+
}
192+
}
193+
}
194+
182195
// 记录底部内边距以便播放器定位
183196
var bottomPadding by remember { mutableStateOf<Dp>(0.dp) }
184197

app/src/main/java/org/parallel_sekai/kanade/data/repository/SettingsRepository.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ data class LyricsSettings(
1717
val alignment: Int = 0, // 0 = Left, 1 = Center, 2 = Right
1818
val balanceLines: Boolean = false,
1919
val isSharingEnabled: Boolean = true,
20+
val shareQuality: Float = 1.0f,
2021
)
2122

2223
data class ArtistParsingSettings(
@@ -41,6 +42,7 @@ open class SettingsRepository(private val context: Context) {
4142
private val ALIGNMENT = intPreferencesKey("alignment")
4243
private val BALANCE_LINES = booleanPreferencesKey("balance_lines")
4344
private val LYRIC_SHARING_ENABLED = booleanPreferencesKey("lyric_sharing_enabled")
45+
private val LYRIC_SHARE_QUALITY = floatPreferencesKey("lyric_share_quality")
4446
private val SEARCH_HISTORY = stringSetPreferencesKey("search_history")
4547
private val SEARCH_RESULT_AS_PLAYLIST = booleanPreferencesKey("search_result_as_playlist")
4648
private val EXCLUDED_FOLDERS = stringSetPreferencesKey("excluded_folders")
@@ -92,6 +94,7 @@ open class SettingsRepository(private val context: Context) {
9294
alignment = preferences[ALIGNMENT] ?: 0,
9395
balanceLines = preferences[BALANCE_LINES] ?: false,
9496
isSharingEnabled = preferences[LYRIC_SHARING_ENABLED] ?: true,
97+
shareQuality = preferences[LYRIC_SHARE_QUALITY] ?: 1.0f,
9598
)
9699
}
97100

@@ -236,6 +239,12 @@ open class SettingsRepository(private val context: Context) {
236239
}
237240
}
238241

242+
open suspend fun updateLyricShareQuality(quality: Float) {
243+
context.dataStore.edit { preferences ->
244+
preferences[LYRIC_SHARE_QUALITY] = quality
245+
}
246+
}
247+
239248
open val playbackStateFlow: Flow<PlaybackState> = context.dataStore.data
240249
.map { preferences ->
241250
PlaybackState(

0 commit comments

Comments
 (0)