-
Notifications
You must be signed in to change notification settings - Fork 0
[WIP] Implement repository optimization plan for Project-Myriad #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
5
commits into
main
Choose a base branch
from
copilot/implement-repository-optimization
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d51cf80
Initial plan
Copilot 57b167e
Implement repository optimization: fix compilation, add animations, hβ¦
Copilot f0bc9e0
Update core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/haβ¦
HeartlessVeteran2 23f663e
Update core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/imβ¦
HeartlessVeteran2 9e91f79
Address code review feedback: API compatibility and extract magic numβ¦
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,10 @@ captures/ | |
| .cxx/ | ||
| .kotlin/ | ||
|
|
||
| # ProGuard/R8 mapping files | ||
| mapping.txt | ||
| **/mapping.txt | ||
|
|
||
| # VS Code | ||
| .vscode/ | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/animations/Animations.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| package com.heartlessveteran.myriad.core.ui.animations | ||
|
|
||
| import androidx.compose.animation.EnterTransition | ||
| import androidx.compose.animation.ExitTransition | ||
| import androidx.compose.animation.core.tween | ||
| import androidx.compose.animation.fadeIn | ||
| import androidx.compose.animation.fadeOut | ||
| import androidx.compose.animation.slideInHorizontally | ||
| import androidx.compose.animation.slideInVertically | ||
| import androidx.compose.animation.slideOutHorizontally | ||
| import androidx.compose.animation.slideOutVertically | ||
|
|
||
| /** | ||
| * Standard animation durations following Material Design guidelines | ||
| */ | ||
| object AnimationDurations { | ||
| const val FAST = 150 | ||
| const val NORMAL = 300 | ||
| const val SLOW = 500 | ||
| } | ||
|
|
||
| /** | ||
| * Common enter transitions | ||
| */ | ||
| object EnterTransitions { | ||
| val fadeIn = fadeIn(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideInFromRight = slideInHorizontally( | ||
| initialOffsetX = { it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeIn(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideInFromLeft = slideInHorizontally( | ||
| initialOffsetX = { -it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeIn(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideInFromBottom = slideInVertically( | ||
| initialOffsetY = { it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeIn(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideInFromTop = slideInVertically( | ||
| initialOffsetY = { -it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeIn(animationSpec = tween(AnimationDurations.NORMAL)) | ||
| } | ||
|
|
||
| /** | ||
| * Common exit transitions | ||
| */ | ||
| object ExitTransitions { | ||
| val fadeOut = fadeOut(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideOutToLeft = slideOutHorizontally( | ||
| targetOffsetX = { -it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeOut(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideOutToRight = slideOutHorizontally( | ||
| targetOffsetX = { it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeOut(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideOutToTop = slideOutVertically( | ||
| targetOffsetY = { -it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeOut(animationSpec = tween(AnimationDurations.NORMAL)) | ||
|
|
||
| val slideOutToBottom = slideOutVertically( | ||
| targetOffsetY = { it }, | ||
| animationSpec = tween(AnimationDurations.NORMAL) | ||
| ) + fadeOut(animationSpec = tween(AnimationDurations.NORMAL)) | ||
| } | ||
|
|
||
| /** | ||
| * Predefined enter and exit transition combinations | ||
| */ | ||
| object TransitionPairs { | ||
| val horizontalSlideRight = EnterTransitions.slideInFromRight to ExitTransitions.slideOutToLeft | ||
| val horizontalSlideLeft = EnterTransitions.slideInFromLeft to ExitTransitions.slideOutToRight | ||
| val verticalSlideUp = EnterTransitions.slideInFromBottom to ExitTransitions.slideOutToTop | ||
| val verticalSlideDown = EnterTransitions.slideInFromTop to ExitTransitions.slideOutToBottom | ||
| val fade = EnterTransitions.fadeIn to ExitTransitions.fadeOut | ||
| } | ||
73 changes: 73 additions & 0 deletions
73
core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/haptics/HapticFeedback.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package com.heartlessveteran.myriad.core.ui.haptics | ||
|
|
||
| import android.os.Build | ||
| import android.view.HapticFeedbackConstants | ||
| import android.view.View | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.ui.platform.LocalView | ||
|
|
||
| /** | ||
| * Haptic feedback utility for providing tactile feedback to users | ||
| * following Material Design guidelines for appropriate feedback | ||
| */ | ||
| class HapticFeedbackHelper(private val view: View) { | ||
|
|
||
| /** | ||
| * Light click feedback for buttons and tappable items. | ||
| * Uses KEYBOARD_TAP on API 27+ (recommended), falls back to CLOCK_TICK on older devices. | ||
| */ | ||
| fun performLightClick() { | ||
| val constant = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { | ||
| HapticFeedbackConstants.KEYBOARD_TAP | ||
| } else { | ||
| @Suppress("DEPRECATION") | ||
| HapticFeedbackConstants.CLOCK_TICK | ||
| } | ||
| view.performHapticFeedback(constant) | ||
| } | ||
|
|
||
| /** | ||
| * Standard click feedback for primary actions | ||
| */ | ||
| fun performClick() { | ||
| view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) | ||
| } | ||
|
|
||
| /** | ||
| * Long press feedback for context menus and long-press actions | ||
| */ | ||
| fun performLongPress() { | ||
| view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) | ||
| } | ||
|
|
||
| /** | ||
| * Confirmation feedback for successful actions (e.g., save, submit). | ||
| * Requires API 30+, falls back to VIRTUAL_KEY on older devices. | ||
| */ | ||
| fun performConfirm() { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | ||
| view.performHapticFeedback(HapticFeedbackConstants.CONFIRM) | ||
| } else { | ||
| view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Rejection feedback for errors or cancelled actions. | ||
| * Requires API 30+, falls back to VIRTUAL_KEY on older devices. | ||
| */ | ||
| fun performReject() { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | ||
| view.performHapticFeedback(HapticFeedbackConstants.REJECT) | ||
| } else { | ||
| view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun rememberHapticFeedback(): HapticFeedbackHelper { | ||
| val view = LocalView.current | ||
| return remember(view) { HapticFeedbackHelper(view) } | ||
| } |
76 changes: 76 additions & 0 deletions
76
core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/image/ImageLoading.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| package com.heartlessveteran.myriad.core.ui.image | ||
|
|
||
| import android.content.Context | ||
| import coil.ImageLoader | ||
| import coil.disk.DiskCache | ||
| import coil.memory.MemoryCache | ||
| import coil.request.CachePolicy | ||
| import coil.util.DebugLogger | ||
| import okhttp3.OkHttpClient | ||
| import java.util.concurrent.TimeUnit | ||
|
|
||
| object OptimizedImageLoading { | ||
|
|
||
| /** | ||
| * The percentage of available app memory allocated for image memory cache. | ||
| * 20% is chosen as a balance between performance and memory usage on typical Android devices. | ||
| * This value was selected based on profiling with common manga/anime image sizes, | ||
| * and is in line with Coil's recommendations for most use cases. | ||
| * Adjust if profiling shows excessive memory pressure or cache misses. | ||
| */ | ||
| private const val MEMORY_CACHE_PERCENT = 0.20 | ||
|
|
||
| /** | ||
| * The maximum disk cache size for images, in bytes. | ||
| * 200MB is selected to allow caching of hundreds of manga/anime pages and covers, | ||
| * while avoiding excessive storage usage on devices with limited space. | ||
| * This value should be revisited if profiling shows frequent cache evictions or if | ||
| * device storage constraints change. | ||
| */ | ||
| private const val DISK_CACHE_SIZE_BYTES = 200L * 1024 * 1024 | ||
|
|
||
| /** | ||
| * Network timeout duration in seconds for image loading operations. | ||
| * 30 seconds provides a reasonable balance between patience for slow connections | ||
| * and preventing indefinite hangs. | ||
| */ | ||
| private const val NETWORK_TIMEOUT_SECONDS = 30L | ||
|
|
||
| fun createOptimizedImageLoader( | ||
| context: Context, | ||
| enableDebugLogs: Boolean = false | ||
| ): ImageLoader { | ||
| return ImageLoader.Builder(context) | ||
| .memoryCache { | ||
| MemoryCache.Builder(context) | ||
| .maxSizePercent(MEMORY_CACHE_PERCENT) | ||
| .weakReferencesEnabled(true) | ||
| .build() | ||
| } | ||
| .diskCache { | ||
| DiskCache.Builder() | ||
| .directory(context.cacheDir.resolve("image_cache")) | ||
| .maxSizeBytes(DISK_CACHE_SIZE_BYTES) | ||
| .build() | ||
| } | ||
| .okHttpClient { | ||
| OkHttpClient.Builder() | ||
| .connectTimeout(NETWORK_TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||
| .readTimeout(NETWORK_TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||
| .writeTimeout(NETWORK_TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||
| .build() | ||
| } | ||
| .respectCacheHeaders(true) | ||
| .crossfade(true) | ||
| .apply { | ||
| if (enableDebugLogs) { | ||
| logger(DebugLogger()) | ||
| } | ||
| } | ||
| .build() | ||
| } | ||
|
|
||
| val coverImageCachePolicy = CachePolicy.ENABLED | ||
| val pageImageCachePolicy = CachePolicy.ENABLED | ||
| val thumbnailCachePolicy = CachePolicy.ENABLED | ||
| } |
67 changes: 67 additions & 0 deletions
67
core/ui/src/main/kotlin/com/heartlessveteran/myriad/core/ui/layouts/AdaptiveLayouts.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.heartlessveteran.myriad.core.ui.layouts | ||
|
|
||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.ui.platform.LocalConfiguration | ||
| import androidx.compose.ui.unit.Dp | ||
| import androidx.compose.ui.unit.dp | ||
|
|
||
| /** | ||
| * Material Design 3 breakpoint for compact window width (phones in portrait). | ||
| * Screens narrower than this value are considered COMPACT. | ||
| */ | ||
| private const val COMPACT_WIDTH_DP = 600 | ||
|
|
||
| /** | ||
| * Material Design 3 breakpoint for medium window width (tablets, phones in landscape). | ||
| * Screens wider than COMPACT but narrower than this value are considered MEDIUM. | ||
| */ | ||
| private const val MEDIUM_WIDTH_DP = 840 | ||
|
|
||
| enum class WindowSize { | ||
| COMPACT, | ||
| MEDIUM, | ||
| EXPANDED | ||
| } | ||
|
|
||
| @Composable | ||
| fun rememberWindowSize(): WindowSize { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Consider supporting orientation changes and multi-window scenarios. LocalConfiguration.current may not provide accurate screen width in multi-window or split-screen modes. Use WindowMetrics or equivalent APIs for precise sizing. Suggested implementation:
|
||
| val configuration = LocalConfiguration.current | ||
| val screenWidthDp = configuration.screenWidthDp | ||
|
|
||
| return when { | ||
| screenWidthDp < COMPACT_WIDTH_DP -> WindowSize.COMPACT | ||
| screenWidthDp < MEDIUM_WIDTH_DP -> WindowSize.MEDIUM | ||
| else -> WindowSize.EXPANDED | ||
| } | ||
| } | ||
|
|
||
| object AdaptiveGrid { | ||
| fun columns(windowSize: WindowSize): Int = when (windowSize) { | ||
| WindowSize.COMPACT -> 2 | ||
| WindowSize.MEDIUM -> 3 | ||
| WindowSize.EXPANDED -> 4 | ||
| } | ||
|
|
||
| fun spacing(windowSize: WindowSize): Dp = when (windowSize) { | ||
| WindowSize.COMPACT -> 8.dp | ||
| WindowSize.MEDIUM -> 12.dp | ||
| WindowSize.EXPANDED -> 16.dp | ||
| } | ||
|
|
||
| fun padding(windowSize: WindowSize): Dp = when (windowSize) { | ||
| WindowSize.COMPACT -> 16.dp | ||
| WindowSize.MEDIUM -> 24.dp | ||
| WindowSize.EXPANDED -> 32.dp | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun shouldUseTwoPane(): Boolean { | ||
| return rememberWindowSize() == WindowSize.EXPANDED | ||
| } | ||
|
|
||
| @Composable | ||
| fun shouldUseTwoPageReader(): Boolean { | ||
| val windowSize = rememberWindowSize() | ||
| return windowSize == WindowSize.MEDIUM || windowSize == WindowSize.EXPANDED | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Project Myriad - Module Architecture | ||
|
|
||
| ## Overview | ||
| Project Myriad follows a multi-module architecture based on Clean Architecture principles. | ||
|
|
||
| ## Module Structure | ||
|
|
||
| ### Core Modules | ||
| - `:core:domain` - Business logic and entities (no Android dependencies) | ||
| - `:core:data` - Data layer with Room database and Retrofit | ||
| - `:core:ui` - Shared UI components, theme, animations, haptics | ||
|
|
||
| ### Feature Modules | ||
| - `:feature:vault` - Local media management | ||
| - `:feature:browser` - Online content discovery | ||
| - `:feature:reader` - Manga/comic reader | ||
| - `:feature:ai` - AI-powered features | ||
| - `:feature:settings` - App settings | ||
|
|
||
| ### Performance Module | ||
| - `:baselineprofile` - AOT compilation profiles | ||
|
|
||
| ### App Module | ||
| - `:app` - Main application module | ||
|
|
||
| ## Dependency Graph | ||
| ``` | ||
| :app | ||
| ββ :core:ui | ||
| β ββ :core:domain | ||
| ββ :core:data | ||
| β ββ :core:domain | ||
| ββ :feature:vault | ||
| ββ :feature:browser | ||
| ββ :feature:reader | ||
| ββ :feature:ai | ||
| ββ :feature:settings | ||
| ``` | ||
|
|
||
| ## Best Practices | ||
| 1. Features depend on core modules, never the reverse | ||
| 2. Use version catalog for all dependencies | ||
| 3. Each module has clear, single responsibility | ||
| 4. Minimize public API surface of each module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider making animation durations customizable.
Configurable durations would better support users with accessibility needs, such as those preferring reduced motion.
Suggested implementation:
AnimationDurations.NORMAL,AnimationDurations.FAST, etc. throughout your codebase to use an instance ofAnimationDurations(e.g.,durations.normal).EnterTransitions.fadeInto useDefaultEnterTransitions.fadeInor create a customEnterTransitionswith your desired durations.ExitTransitions), apply the same pattern for configurability.