├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── renovate.json5 └── workflows │ └── build-android.yml ├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── com.axiel7.moelist.data.local.MoeListDatabase │ │ └── 1.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── axiel7 │ │ └── moelist │ │ ├── App.kt │ │ ├── data │ │ ├── local │ │ │ ├── DatabaseConverters.kt │ │ │ ├── MoeListDatabase.kt │ │ │ └── searchhistory │ │ │ │ ├── SearchHistoryDao.kt │ │ │ │ ├── SearchHistoryEntity.kt │ │ │ │ └── SearchHistoryMapper.kt │ │ ├── model │ │ │ ├── AccessToken.kt │ │ │ ├── BaseResponse.kt │ │ │ ├── Paging.kt │ │ │ ├── Response.kt │ │ │ ├── SearchHistory.kt │ │ │ ├── User.kt │ │ │ ├── UserStats.kt │ │ │ ├── anime │ │ │ │ ├── AnimeDetails.kt │ │ │ │ ├── AnimeList.kt │ │ │ │ ├── AnimeNode.kt │ │ │ │ ├── AnimeRanking.kt │ │ │ │ ├── AnimeSeasonal.kt │ │ │ │ ├── AnimeSource.kt │ │ │ │ ├── Broadcast.kt │ │ │ │ ├── MyAnimeListStatus.kt │ │ │ │ ├── NodeSeasonal.kt │ │ │ │ ├── Ranking.kt │ │ │ │ ├── Recommendations.kt │ │ │ │ ├── RelatedAnime.kt │ │ │ │ ├── Season.kt │ │ │ │ ├── SeasonType.kt │ │ │ │ ├── StartSeason.kt │ │ │ │ ├── Studio.kt │ │ │ │ ├── Theme.kt │ │ │ │ ├── UserAnimeList.kt │ │ │ │ └── UserAnimeStatistics.kt │ │ │ ├── base │ │ │ │ ├── Colorable.kt │ │ │ │ ├── Localizable.kt │ │ │ │ └── LocalizableAndColorable.kt │ │ │ ├── manga │ │ │ │ ├── Author.kt │ │ │ │ ├── AuthorNode.kt │ │ │ │ ├── MangaDetails.kt │ │ │ │ ├── MangaList.kt │ │ │ │ ├── MangaNode.kt │ │ │ │ ├── MangaRanking.kt │ │ │ │ ├── MyMangaListStatus.kt │ │ │ │ ├── RelatedManga.kt │ │ │ │ ├── SerialNode.kt │ │ │ │ ├── Serialization.kt │ │ │ │ └── UserMangaList.kt │ │ │ └── media │ │ │ │ ├── AlternativeTitles.kt │ │ │ │ ├── BaseMediaDetails.kt │ │ │ │ ├── BaseMediaList.kt │ │ │ │ ├── BaseMediaNode.kt │ │ │ │ ├── BaseMyListStatus.kt │ │ │ │ ├── BaseRanking.kt │ │ │ │ ├── BaseRelated.kt │ │ │ │ ├── BaseUserMediaList.kt │ │ │ │ ├── Character.kt │ │ │ │ ├── Genre.kt │ │ │ │ ├── ListStatus.kt │ │ │ │ ├── ListType.kt │ │ │ │ ├── MainPicture.kt │ │ │ │ ├── MediaFormat.kt │ │ │ │ ├── MediaSort.kt │ │ │ │ ├── MediaStatus.kt │ │ │ │ ├── MediaType.kt │ │ │ │ ├── RankingType.kt │ │ │ │ ├── RelationType.kt │ │ │ │ ├── Stat.kt │ │ │ │ ├── Statistics.kt │ │ │ │ ├── StatisticsStatus.kt │ │ │ │ ├── TitleLanguage.kt │ │ │ │ └── WeekDay.kt │ │ ├── network │ │ │ ├── Api.kt │ │ │ ├── JikanApi.kt │ │ │ └── KtorClient.kt │ │ └── repository │ │ │ ├── AnimeRepository.kt │ │ │ ├── BaseRepository.kt │ │ │ ├── DefaultPreferencesRepository.kt │ │ │ ├── LoginRepository.kt │ │ │ ├── MangaRepository.kt │ │ │ ├── SearchHistoryRepository.kt │ │ │ └── UserRepository.kt │ │ ├── di │ │ ├── DataStoreModule.kt │ │ ├── DatabaseModule.kt │ │ ├── NetworkModule.kt │ │ ├── RepositoryModule.kt │ │ ├── ViewModelModule.kt │ │ └── WorkerModule.kt │ │ ├── ui │ │ ├── base │ │ │ ├── AppLanguage.kt │ │ │ ├── BottomDestination.kt │ │ │ ├── ItemsPerRow.kt │ │ │ ├── ListStyle.kt │ │ │ ├── StartTab.kt │ │ │ ├── TabRowItem.kt │ │ │ ├── ThemeStyle.kt │ │ │ ├── event │ │ │ │ ├── PagedUiEvent.kt │ │ │ │ └── UiEvent.kt │ │ │ ├── navigation │ │ │ │ ├── NavActionManager.kt │ │ │ │ ├── Route.kt │ │ │ │ └── StringArrayNavType.kt │ │ │ ├── state │ │ │ │ ├── MediaUiState.kt │ │ │ │ ├── PagedUiState.kt │ │ │ │ └── UiState.kt │ │ │ └── viewmodel │ │ │ │ └── BaseViewModel.kt │ │ ├── calendar │ │ │ ├── CalendarEvent.kt │ │ │ ├── CalendarUiState.kt │ │ │ ├── CalendarView.kt │ │ │ └── CalendarViewModel.kt │ │ ├── composables │ │ │ ├── ClickableTextField.kt │ │ │ ├── CommonIconButton.kt │ │ │ ├── CommonText.kt │ │ │ ├── DefaultPlaceholder.kt │ │ │ ├── DefaultScaffoldWithTopAppBar.kt │ │ │ ├── DefaultTopAppBar.kt │ │ │ ├── HeaderHorizontalList.kt │ │ │ ├── LazyListState.kt │ │ │ ├── LoadingDialog.kt │ │ │ ├── ModifierCollapsable.kt │ │ │ ├── Rectangle.kt │ │ │ ├── SelectableIconToggleButton.kt │ │ │ ├── TabRowWithPager.kt │ │ │ ├── TextCheckBox.kt │ │ │ ├── TextIcon.kt │ │ │ ├── media │ │ │ │ ├── MediaItemDetailed.kt │ │ │ │ ├── MediaItemVertical.kt │ │ │ │ └── MediaPoster.kt │ │ │ ├── preferences │ │ │ │ ├── ListPreferenceView.kt │ │ │ │ ├── PlainPreferenceView.kt │ │ │ │ └── SwitchPreferenceView.kt │ │ │ ├── score │ │ │ │ ├── ScoreIndicator.kt │ │ │ │ └── ScoreSlider.kt │ │ │ └── stats │ │ │ │ ├── DonutChart.kt │ │ │ │ ├── HorizontalStatsBar.kt │ │ │ │ └── StatChip.kt │ │ ├── details │ │ │ ├── MediaDetailsEvent.kt │ │ │ ├── MediaDetailsUiState.kt │ │ │ ├── MediaDetailsView.kt │ │ │ ├── MediaDetailsViewModel.kt │ │ │ └── composables │ │ │ │ ├── AnimeThemeItem.kt │ │ │ │ ├── MediaDetailsTopAppBar.kt │ │ │ │ └── MediaInfoView.kt │ │ ├── editmedia │ │ │ ├── EditMediaEvent.kt │ │ │ ├── EditMediaSheet.kt │ │ │ ├── EditMediaUiState.kt │ │ │ ├── EditMediaViewModel.kt │ │ │ └── composables │ │ │ │ ├── DeleteMediaEntryDialog.kt │ │ │ │ ├── EditMediaDatePicker.kt │ │ │ │ └── EditMediaProgressRow.kt │ │ ├── fullposter │ │ │ └── FullPosterView.kt │ │ ├── home │ │ │ ├── HomeEvent.kt │ │ │ ├── HomeUiState.kt │ │ │ ├── HomeView.kt │ │ │ ├── HomeViewModel.kt │ │ │ └── composables │ │ │ │ ├── AiringAnimeHorizontalItem.kt │ │ │ │ └── HomeCard.kt │ │ ├── login │ │ │ └── LoginView.kt │ │ ├── main │ │ │ ├── MainActivity.kt │ │ │ ├── MainNavigation.kt │ │ │ ├── MainViewModel.kt │ │ │ └── composables │ │ │ │ ├── MainBottomNavBar.kt │ │ │ │ ├── MainNavigationRail.kt │ │ │ │ └── MainTopAppBar.kt │ │ ├── more │ │ │ ├── MoreEvent.kt │ │ │ ├── MoreView.kt │ │ │ ├── MoreViewModel.kt │ │ │ ├── about │ │ │ │ └── AboutView.kt │ │ │ ├── composables │ │ │ │ ├── FeedbackDialog.kt │ │ │ │ └── MoreItem.kt │ │ │ ├── credits │ │ │ │ └── CreditsView.kt │ │ │ ├── notifications │ │ │ │ ├── NotificationsEvent.kt │ │ │ │ ├── NotificationsUiState.kt │ │ │ │ ├── NotificationsView.kt │ │ │ │ └── NotificationsViewModel.kt │ │ │ └── settings │ │ │ │ ├── SettingsEvent.kt │ │ │ │ ├── SettingsUiState.kt │ │ │ │ ├── SettingsView.kt │ │ │ │ ├── SettingsViewModel.kt │ │ │ │ └── list │ │ │ │ ├── ListStyleSettingsEvent.kt │ │ │ │ ├── ListStyleSettingsView.kt │ │ │ │ └── ListStyleSettingsViewModel.kt │ │ ├── profile │ │ │ ├── ProfileEvent.kt │ │ │ ├── ProfileUiState.kt │ │ │ ├── ProfileView.kt │ │ │ ├── ProfileViewModel.kt │ │ │ └── composables │ │ │ │ └── UserStatsView.kt │ │ ├── ranking │ │ │ ├── MediaRankingEvent.kt │ │ │ ├── MediaRankingUiState.kt │ │ │ ├── MediaRankingView.kt │ │ │ ├── MediaRankingViewModel.kt │ │ │ └── list │ │ │ │ └── MediaRankingListView.kt │ │ ├── recommendations │ │ │ ├── RecommendationsEvent.kt │ │ │ ├── RecommendationsUiState.kt │ │ │ ├── RecommendationsView.kt │ │ │ └── RecommendationsViewModel.kt │ │ ├── search │ │ │ ├── SearchEvent.kt │ │ │ ├── SearchUiState.kt │ │ │ ├── SearchView.kt │ │ │ └── SearchViewModel.kt │ │ ├── season │ │ │ ├── SeasonChartEvent.kt │ │ │ ├── SeasonChartUiState.kt │ │ │ ├── SeasonChartView.kt │ │ │ ├── SeasonChartViewModel.kt │ │ │ └── composables │ │ │ │ └── SeasonChartFilterSheet.kt │ │ ├── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ ├── Type.kt │ │ │ └── WidgetTheme.kt │ │ ├── userlist │ │ │ ├── UserMediaListEvent.kt │ │ │ ├── UserMediaListUiState.kt │ │ │ ├── UserMediaListView.kt │ │ │ ├── UserMediaListViewModel.kt │ │ │ ├── UserMediaListWithFabView.kt │ │ │ ├── UserMediaListWithTabsView.kt │ │ │ └── composables │ │ │ │ ├── CompactUserMediaListItem.kt │ │ │ │ ├── GridUserMediaListItem.kt │ │ │ │ ├── MediaListSortDialog.kt │ │ │ │ ├── MinimalUserMediaListItem.kt │ │ │ │ ├── RandomChip.kt │ │ │ │ ├── SetScoreDialog.kt │ │ │ │ ├── SortChip.kt │ │ │ │ └── StandardUserMediaListItem.kt │ │ └── widget │ │ │ └── AiringWidget.kt │ │ ├── utils │ │ ├── Constants.kt │ │ ├── ContextExtensions.kt │ │ ├── DateUtils.kt │ │ ├── NumExtensions.kt │ │ ├── PkceGenerator.kt │ │ ├── SeasonCalendar.kt │ │ ├── StringExtensions.kt │ │ └── TranslateUtils.kt │ │ └── worker │ │ └── NotificationWorker.kt │ └── res │ ├── drawable-mdpi │ └── ic_discord.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── airing_widget_preview.png │ ├── check_circle_outline_24.xml │ ├── delete_outline_24.xml │ ├── ic_arrow_back.xml │ ├── ic_campaign.xml │ ├── ic_fall_24.xml │ ├── ic_github.xml │ ├── ic_history_24.xml │ ├── ic_info.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_monochrome.xml │ ├── ic_launcher_splash.xml │ ├── ic_moelist_logo.xml │ ├── ic_moelist_logo_white.xml │ ├── ic_more_horizontal.xml │ ├── ic_new_releases.xml │ ├── ic_open_in_browser.xml │ ├── ic_outline_book_24.xml │ ├── ic_outline_home_24.xml │ ├── ic_outline_local_movies_24.xml │ ├── ic_outline_person_24.xml │ ├── ic_outline_translate_24.xml │ ├── ic_round_access_time_24.xml │ ├── ic_round_account_circle_24.xml │ ├── ic_round_add_24.xml │ ├── ic_round_arrow_forward_24.xml │ ├── ic_round_bar_chart_24.xml │ ├── ic_round_book_24.xml │ ├── ic_round_cake_24.xml │ ├── ic_round_casino_24.xml │ ├── ic_round_color_lens_24.xml │ ├── ic_round_details_star_24.xml │ ├── ic_round_edit_24.xml │ ├── ic_round_event_24.xml │ ├── ic_round_feedback_24.xml │ ├── ic_round_filter_list_24.xml │ ├── ic_round_group_24.xml │ ├── ic_round_home_24.xml │ ├── ic_round_keyboard_arrow_down_24.xml │ ├── ic_round_keyboard_arrow_up_24.xml │ ├── ic_round_language_24.xml │ ├── ic_round_local_movies_24.xml │ ├── ic_round_location_on_24.xml │ ├── ic_round_menu_book_24.xml │ ├── ic_round_movie_24.xml │ ├── ic_round_person_24.xml │ ├── ic_round_power_settings_new_24.xml │ ├── ic_round_rss_feed_24.xml │ ├── ic_round_search_24.xml │ ├── ic_round_settings_24.xml │ ├── ic_round_sort_24.xml │ ├── ic_round_star_16.xml │ ├── ic_round_thumbs_up_down_16.xml │ ├── ic_round_thumbs_up_down_24.xml │ ├── ic_round_timer_24.xml │ ├── ic_round_trending_up_24.xml │ ├── ic_spring_24.xml │ ├── ic_summer_24.xml │ ├── ic_winter_24.xml │ ├── no_adult_content_24.xml │ ├── outline_cancel_24.xml │ ├── pause_circle_outline_24.xml │ ├── play_circle_outline_24.xml │ ├── round_bookmark_24.xml │ ├── round_check_24.xml │ ├── round_content_copy_24.xml │ ├── round_delete_sweep_24.xml │ ├── round_drive_file_rename_outline_24.xml │ ├── round_format_list_bulleted_24.xml │ ├── round_grid_view_24.xml │ ├── round_notes_24.xml │ ├── round_notifications_24.xml │ ├── round_notifications_active_24.xml │ ├── round_notifications_off_24.xml │ ├── round_repeat_24.xml │ ├── round_share_24.xml │ ├── round_title_24.xml │ ├── shortcut_anime.xml │ ├── shortcut_home.xml │ ├── shortcut_manga.xml │ └── widget_background.xml │ ├── layout │ ├── airing_widget_preview.xml │ └── widget_loading.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-ar-rSA │ └── strings.xml │ ├── values-bg-rBG │ └── strings.xml │ ├── values-cs-rCZ │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-hi-rIN │ └── strings.xml │ ├── values-in-rID │ └── strings.xml │ ├── values-it-rIT │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-night │ ├── colors.xml │ └── themes.xml │ ├── values-or-rIN │ └── strings.xml │ ├── values-pl-rPL │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-pt-rPT │ └── strings.xml │ ├── values-ru-rRU │ └── strings.xml │ ├── values-sk-rSK │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-uk-rUA │ └── strings.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ ├── xml-v25 │ └── shortcuts.xml │ └── xml │ ├── airing_widget_info.xml │ └── locales_config.xml ├── build.gradle.kts ├── crowdin.yml ├── fastlane ├── Appfile └── Fastfile ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: axiel7 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: axiel7 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Template for reporting bugs 4 | title: '' 5 | labels: bug 6 | assignees: axiel7 7 | 8 | --- 9 | 10 | **Describe the bug (required)** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Enviroment (required)** 27 | - Device model: 28 | - Android version: 29 | - App version: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the app 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | [READ]: # (Please be aware that some feature are not possible due to the official API limitations) 11 | 12 | **Request (required)** 13 | Describe the feature you want to be implemented. 14 | 15 | **Additional context** 16 | Add any other context or app screenshots about the feature request here. 17 | 18 | **Examples** 19 | Show here examples of the feature if exists in other app. 20 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "schedule": ["every sunday"], 7 | "baseBranches": ["develop"], 8 | "packageRules": [ 9 | { 10 | "managers": ["maven"], 11 | "packageNames": ["com.google.guava:guava"], 12 | "versionScheme": "docker" 13 | }, 14 | { 15 | // Compiler plugins are tightly coupled to Kotlin version 16 | "groupName": "Kotlin", 17 | "matchPackagePrefixes": [ 18 | "androidx.compose.compiler", 19 | "org.jetbrains.kotlin", 20 | ], 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /private.properties 5 | /.idea/ 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | /app/src/main/java/com/axiel7/moelist/private/ClientId.kt 12 | /app/release 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /google-services.json 3 | /src/androidTest 4 | /src/test -------------------------------------------------------------------------------- /app/schemas/com.axiel7.moelist.data.local.MoeListDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "bdd220d277ea1849ad977d01755e1c2f", 6 | "entities": [ 7 | { 8 | "tableName": "search_history", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`keyword` TEXT NOT NULL, `updated_at` INTEGER NOT NULL, PRIMARY KEY(`keyword`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "keyword", 13 | "columnName": "keyword", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "updatedAt", 19 | "columnName": "updated_at", 20 | "affinity": "INTEGER", 21 | "notNull": true 22 | } 23 | ], 24 | "primaryKey": { 25 | "autoGenerate": false, 26 | "columnNames": [ 27 | "keyword" 28 | ] 29 | }, 30 | "indices": [], 31 | "foreignKeys": [] 32 | } 33 | ], 34 | "views": [], 35 | "setupQueries": [ 36 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 37 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bdd220d277ea1849ad977d01755e1c2f')" 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/local/DatabaseConverters.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.local 2 | 3 | import androidx.room.TypeConverter 4 | import java.time.Instant 5 | import java.time.LocalDateTime 6 | import java.time.ZoneId 7 | 8 | object DatabaseConverters { 9 | @TypeConverter 10 | fun timestampToLocalDateTime(value: Long?): LocalDateTime? { 11 | return value?.let { 12 | Instant.ofEpochMilli(it) 13 | .atZone(ZoneId.systemDefault()) 14 | .toLocalDateTime() 15 | } 16 | } 17 | 18 | @TypeConverter 19 | fun localDateTimeToTimestamp(value: LocalDateTime?): Long? { 20 | return value 21 | ?.atZone(ZoneId.systemDefault()) 22 | ?.toEpochSecond() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/local/MoeListDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.axiel7.moelist.data.local.searchhistory.SearchHistoryDao 7 | import com.axiel7.moelist.data.local.searchhistory.SearchHistoryEntity 8 | 9 | @Database( 10 | entities = [ 11 | SearchHistoryEntity::class, 12 | ], 13 | version = 1, 14 | ) 15 | @TypeConverters(DatabaseConverters::class) 16 | abstract class MoeListDatabase : RoomDatabase() { 17 | abstract fun searchHistoryDao() : SearchHistoryDao 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/local/searchhistory/SearchHistoryDao.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.local.searchhistory 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Query 6 | import androidx.room.Upsert 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | @Dao 10 | interface SearchHistoryDao { 11 | @Query("select * from search_history order by updated_at DESC limit 10") 12 | fun getItems(): Flow> 13 | 14 | @Upsert 15 | suspend fun addItem(item: SearchHistoryEntity) 16 | 17 | @Delete 18 | suspend fun deleteItem(item: SearchHistoryEntity) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/local/searchhistory/SearchHistoryEntity.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.local.searchhistory 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import java.time.LocalDateTime 7 | 8 | @Entity(tableName = "search_history") 9 | data class SearchHistoryEntity( 10 | @PrimaryKey 11 | @ColumnInfo(name = "keyword") 12 | val keyword: String, 13 | 14 | @ColumnInfo(name = "updated_at") 15 | val updatedAt: LocalDateTime = LocalDateTime.now(), 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/local/searchhistory/SearchHistoryMapper.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.local.searchhistory 2 | 3 | import com.axiel7.moelist.data.model.SearchHistory 4 | 5 | fun SearchHistoryEntity.toSearchHistory(): SearchHistory { 6 | return SearchHistory( 7 | keyword = keyword, 8 | updatedAt = updatedAt, 9 | ) 10 | } 11 | 12 | fun SearchHistory.toSearchEntity(): SearchHistoryEntity { 13 | return SearchHistoryEntity( 14 | keyword = keyword, 15 | updatedAt = updatedAt, 16 | ) 17 | } 18 | 19 | fun List.toSearchHistoryList(): List { 20 | return map(SearchHistoryEntity::toSearchHistory) 21 | } 22 | 23 | fun List.toSearchHistoryEntityList(): List { 24 | return map(SearchHistory::toSearchEntity) 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/AccessToken.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AccessToken( 8 | @SerialName("token_type") 9 | val tokenType: String = "", 10 | @SerialName("expires_in") 11 | val expiresIn: Int = 0, 12 | @SerialName("access_token") 13 | val accessToken: String? = null, 14 | @SerialName("refresh_token") 15 | val refreshToken: String? = null, 16 | 17 | override val error: String? = null, 18 | override val message: String? = null, 19 | ) : BaseResponse -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import kotlinx.serialization.SerialName 4 | 5 | interface BaseResponse { 6 | @SerialName("error") 7 | val error: String? 8 | 9 | @SerialName("message") 10 | val message: String? 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/Paging.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Paging( 8 | @SerialName("next") 9 | val next: String? = null, 10 | @SerialName("previous") 11 | val previous: String? = null 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/Response.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Response( 8 | @SerialName("data") 9 | val data: T? = null, 10 | @SerialName("paging") 11 | val paging: Paging? = null, 12 | override val error: String? = null, 13 | override val message: String? = null, 14 | ) : BaseResponse { 15 | val wasError = data == null || error != null || message != null 16 | val isSuccess = !wasError 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/SearchHistory.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import java.time.LocalDateTime 4 | 5 | data class SearchHistory( 6 | val keyword: String, 7 | val updatedAt: LocalDateTime, 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model 2 | 3 | import com.axiel7.moelist.data.model.anime.UserAnimeStatistics 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class User( 9 | @SerialName("id") 10 | val id: Int = 0, 11 | @SerialName("name") 12 | val name: String? = null, 13 | @SerialName("gender") 14 | val gender: String? = null, 15 | @SerialName("birthday") 16 | val birthday: String? = null, 17 | @SerialName("location") 18 | val location: String? = null, 19 | @SerialName("joined_at") 20 | val joinedAt: String? = null, 21 | @SerialName("picture") 22 | val picture: String? = null, 23 | @SerialName("anime_statistics") 24 | val animeStatistics: UserAnimeStatistics? = null, 25 | 26 | override val message: String? = null, 27 | override val error: String? = null, 28 | ) : BaseResponse -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/AnimeList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaList 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AnimeList( 8 | override val node: AnimeNode 9 | ) : BaseMediaList 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/AnimeNode.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.AlternativeTitles 4 | import com.axiel7.moelist.data.model.media.BaseMediaNode 5 | import com.axiel7.moelist.data.model.media.MainPicture 6 | import com.axiel7.moelist.data.model.media.MediaFormat 7 | import com.axiel7.moelist.data.model.media.MediaStatus 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | data class AnimeNode( 13 | @SerialName("id") 14 | override val id: Int, 15 | @SerialName("title") 16 | override val title: String, 17 | @SerialName("alternative_titles") 18 | override val alternativeTitles: AlternativeTitles? = null, 19 | @SerialName("main_picture") 20 | override val mainPicture: MainPicture? = null, 21 | @SerialName("start_season") 22 | val startSeason: StartSeason? = null, 23 | @SerialName("broadcast") 24 | val broadcast: Broadcast? = null, 25 | @SerialName("num_episodes") 26 | val numEpisodes: Int? = null, 27 | @SerialName("num_list_users") 28 | override val numListUsers: Int? = null, 29 | @SerialName("media_type") 30 | override val mediaFormat: MediaFormat? = null, 31 | @SerialName("status") 32 | override val status: MediaStatus? = null, 33 | @SerialName("mean") 34 | override val mean: Float? = null, 35 | ) : BaseMediaNode() -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/AnimeRanking.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.BaseRanking 4 | import com.axiel7.moelist.data.model.media.RankingType 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class AnimeRanking( 10 | @SerialName("node") 11 | override val node: AnimeNode, 12 | @SerialName("ranking") 13 | override val ranking: Ranking? = null, 14 | @SerialName("ranking_type") 15 | override val rankingType: RankingType? = null, 16 | ) : BaseRanking 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/AnimeSeasonal.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AnimeSeasonal( 8 | @SerialName("node") 9 | val node: NodeSeasonal 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/MyAnimeListStatus.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import androidx.annotation.IntRange 4 | import androidx.compose.runtime.Immutable 5 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 6 | import com.axiel7.moelist.data.model.media.ListStatus 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Immutable 11 | @Serializable 12 | data class MyAnimeListStatus( 13 | 14 | @SerialName("status") 15 | override val status: ListStatus, 16 | 17 | @SerialName("score") 18 | @IntRange(0, 10) 19 | override val score: Int = 0, 20 | 21 | @SerialName("updated_at") 22 | override val updatedAt: String? = null, 23 | 24 | @SerialName("start_date") 25 | override val startDate: String? = null, 26 | 27 | @SerialName("finish_date") 28 | override val finishDate: String? = null, 29 | 30 | @SerialName("num_episodes_watched") 31 | override val progress: Int? = 0, 32 | 33 | @SerialName("is_rewatching") 34 | override val isRepeating: Boolean = false, 35 | 36 | @SerialName("num_times_rewatched") 37 | override val repeatCount: Int? = 0, 38 | 39 | @SerialName("rewatch_value") 40 | @IntRange(0, 5) 41 | override val repeatValue: Int? = 0, 42 | 43 | @SerialName("priority") 44 | @IntRange(0, 2) 45 | override val priority: Int = 0, 46 | 47 | @SerialName("tags") 48 | override val tags: List? = null, 49 | 50 | @SerialName("comments") 51 | override val comments: String? = null 52 | 53 | ) : BaseMyListStatus() -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/NodeSeasonal.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.AlternativeTitles 4 | import com.axiel7.moelist.data.model.media.BaseMediaNode 5 | import com.axiel7.moelist.data.model.media.MainPicture 6 | import com.axiel7.moelist.data.model.media.MediaFormat 7 | import com.axiel7.moelist.data.model.media.MediaStatus 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | data class NodeSeasonal( 13 | @SerialName("id") 14 | override val id: Int, 15 | @SerialName("title") 16 | override val title: String, 17 | @SerialName("alternative_titles") 18 | override val alternativeTitles: AlternativeTitles? = null, 19 | @SerialName("broadcast") 20 | val broadcast: Broadcast? = null, 21 | @SerialName("main_picture") 22 | override val mainPicture: MainPicture? = null, 23 | @SerialName("num_episodes") 24 | val numEpisodes: Int? = null, 25 | @SerialName("num_list_users") 26 | override val numListUsers: Int? = null, 27 | @SerialName("media_type") 28 | override val mediaFormat: MediaFormat? = null, 29 | @SerialName("start_season") 30 | val startSeason: StartSeason? = null, 31 | @SerialName("status") 32 | override val status: MediaStatus? = null, 33 | @SerialName("mean") 34 | override val mean: Float? = null, 35 | ) : BaseMediaNode() -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/Ranking.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Ranking( 8 | @SerialName("rank") 9 | val rank: Int 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/Recommendations.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaNode 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class Recommendations( 9 | @SerialName("node") 10 | val node: T, 11 | @SerialName("num_recommendations") 12 | val numRecommendations: Int 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/RelatedAnime.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.BaseRelated 4 | import com.axiel7.moelist.data.model.media.RelationType 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class RelatedAnime( 10 | @SerialName("node") 11 | override val node: AnimeNode, 12 | @SerialName("relation_type") 13 | override val relationType: RelationType, 14 | ) : BaseRelated 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/Season.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import com.axiel7.moelist.R 7 | import com.axiel7.moelist.data.model.base.Localizable 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | enum class Season( 13 | val value: String, 14 | @DrawableRes val icon: Int 15 | ) : Localizable { 16 | @SerialName("winter") 17 | WINTER( 18 | value = "winter", 19 | icon = R.drawable.ic_winter_24 20 | ), 21 | 22 | @SerialName("spring") 23 | SPRING( 24 | value = "spring", 25 | icon = R.drawable.ic_spring_24 26 | ), 27 | 28 | @SerialName("summer") 29 | SUMMER( 30 | value = "summer", 31 | icon = R.drawable.ic_summer_24 32 | ), 33 | 34 | @SerialName("fall") 35 | FALL( 36 | value = "fall", 37 | icon = R.drawable.ic_fall_24 38 | ); 39 | 40 | @Composable 41 | override fun localized() = when (this) { 42 | WINTER -> stringResource(R.string.winter) 43 | SPRING -> stringResource(R.string.spring) 44 | SUMMER -> stringResource(R.string.summer) 45 | FALL -> stringResource(R.string.fall) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/SeasonType.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | import com.axiel7.moelist.utils.SeasonCalendar 8 | 9 | enum class SeasonType : Localizable { 10 | PREVIOUS, 11 | CURRENT, 12 | NEXT; 13 | 14 | @Composable 15 | override fun localized() = when (this) { 16 | PREVIOUS -> stringResource(R.string.previous_season) 17 | CURRENT -> stringResource(R.string.current_season) 18 | NEXT -> stringResource(R.string.next_season) 19 | } 20 | 21 | val season 22 | get() = when (this) { 23 | PREVIOUS -> SeasonCalendar.prevStartSeason 24 | CURRENT -> SeasonCalendar.currentStartSeason 25 | NEXT -> SeasonCalendar.nextStartSeason 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/StartSeason.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import androidx.compose.runtime.Composable 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class StartSeason( 9 | @SerialName("year") 10 | val year: Int, 11 | @SerialName("season") 12 | val season: Season 13 | ) { 14 | 15 | @Composable 16 | fun seasonYearText() = "${season.localized()} $year" 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/Studio.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Studio( 8 | @SerialName("id") 9 | val id: Int, 10 | @SerialName("name") 11 | val name: String 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Theme( 8 | @SerialName("id") 9 | val id: Int, 10 | @SerialName("anime_id") 11 | val animeId: Int, 12 | @SerialName("text") 13 | val text: String 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/UserAnimeList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import com.axiel7.moelist.data.model.media.BaseUserMediaList 4 | import com.axiel7.moelist.data.model.media.ListStatus 5 | import com.axiel7.moelist.data.model.media.MediaFormat 6 | import com.axiel7.moelist.data.model.media.MediaStatus 7 | import com.axiel7.moelist.data.model.media.WeekDay 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | data class UserAnimeList( 13 | @SerialName("node") 14 | override val node: AnimeNode, 15 | @SerialName("list_status") 16 | override val listStatus: MyAnimeListStatus? = null, 17 | ) : BaseUserMediaList() 18 | 19 | val exampleUserAnimeList = UserAnimeList( 20 | node = AnimeNode( 21 | id = 0, 22 | title = "This is a large anime or manga title", 23 | broadcast = Broadcast(WeekDay.SUNDAY, "12:00"), 24 | numEpisodes = 12, 25 | mediaFormat = MediaFormat.TV, 26 | status = MediaStatus.AIRING, 27 | mean = 8f 28 | ), 29 | listStatus = MyAnimeListStatus( 30 | status = ListStatus.WATCHING, 31 | score = 6, 32 | progress = 6, 33 | repeatCount = 2, 34 | tags = listOf("good", "wtf"), 35 | comments = "this is a comment" 36 | ) 37 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/anime/UserAnimeStatistics.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.anime 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class UserAnimeStatistics( 8 | @SerialName("num_items_watching") 9 | val numItemsWatching: Int?, 10 | @SerialName("num_items_completed") 11 | val numItemsCompleted: Int?, 12 | @SerialName("num_items_on_hold") 13 | val numItemsOnHold: Int?, 14 | @SerialName("num_items_dropped") 15 | val numItemsDropped: Int?, 16 | @SerialName("num_items_plan_to_watch") 17 | val numItemsPlanToWatch: Int?, 18 | @SerialName("num_items") 19 | val numItems: Int?, 20 | @SerialName("num_days_watched") 21 | val numDaysWatched: Float?, 22 | @SerialName("num_days_watching") 23 | val numDaysWatching: Float?, 24 | @SerialName("num_days_completed") 25 | val numDaysCompleted: Float?, 26 | @SerialName("num_days_on_hold") 27 | val numDaysOnHold: Float?, 28 | @SerialName("num_days_dropped") 29 | val numDaysDropped: Float?, 30 | @SerialName("num_days") 31 | val numDays: Float?, 32 | @SerialName("num_episodes") 33 | val numEpisodes: Int?, 34 | @SerialName("num_times_rewatched") 35 | val numTimesRewatched: Int?, 36 | @SerialName("mean_score") 37 | val meanScore: Float?, 38 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/base/Colorable.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.base 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Color 5 | 6 | interface Colorable { 7 | @Composable 8 | fun primaryColor(): Color 9 | @Composable 10 | fun onPrimaryColor(): Color 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/base/Localizable.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.base 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | fun interface Localizable { 6 | @Composable 7 | fun localized(): String 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/base/LocalizableAndColorable.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.base 2 | 3 | interface LocalizableAndColorable : Localizable, Colorable -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/Author.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Author( 8 | @SerialName("node") 9 | val node: AuthorNode, 10 | @SerialName("role") 11 | val role: String 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/AuthorNode.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AuthorNode( 8 | @SerialName("id") 9 | val id: Int, 10 | @SerialName("first_name") 11 | val firstName: String, 12 | @SerialName("last_name") 13 | val lastName: String 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/MangaList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaList 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class MangaList( 8 | override val node: MangaNode 9 | ) : BaseMediaList 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/MangaNode.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import com.axiel7.moelist.data.model.media.AlternativeTitles 4 | import com.axiel7.moelist.data.model.media.BaseMediaNode 5 | import com.axiel7.moelist.data.model.media.MainPicture 6 | import com.axiel7.moelist.data.model.media.MediaFormat 7 | import com.axiel7.moelist.data.model.media.MediaStatus 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | data class MangaNode( 13 | @SerialName("id") 14 | override val id: Int, 15 | @SerialName("title") 16 | override val title: String, 17 | @SerialName("alternative_titles") 18 | override val alternativeTitles: AlternativeTitles? = null, 19 | @SerialName("main_picture") 20 | override val mainPicture: MainPicture? = null, 21 | @SerialName("start_date") 22 | val startDate: String? = null, 23 | @SerialName("num_volumes") 24 | val numVolumes: Int? = null, 25 | @SerialName("num_chapters") 26 | val numChapters: Int? = null, 27 | @SerialName("num_list_users") 28 | override val numListUsers: Int? = null, 29 | @SerialName("media_type") 30 | override val mediaFormat: MediaFormat? = null, 31 | @SerialName("status") 32 | override val status: MediaStatus? = null, 33 | @SerialName("mean") 34 | override val mean: Float? = null 35 | ) : BaseMediaNode() -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/MangaRanking.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import com.axiel7.moelist.data.model.anime.Ranking 4 | import com.axiel7.moelist.data.model.media.BaseRanking 5 | import com.axiel7.moelist.data.model.media.RankingType 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | @Serializable 10 | data class MangaRanking( 11 | @SerialName("node") 12 | override val node: MangaNode, 13 | @SerialName("ranking") 14 | override val ranking: Ranking? = null, 15 | @SerialName("ranking_type") 16 | override val rankingType: RankingType? = null, 17 | ) : BaseRanking 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/MyMangaListStatus.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import androidx.annotation.IntRange 4 | import androidx.compose.runtime.Immutable 5 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 6 | import com.axiel7.moelist.data.model.media.ListStatus 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Immutable 11 | @Serializable 12 | data class MyMangaListStatus( 13 | 14 | @SerialName("status") 15 | override val status: ListStatus, 16 | 17 | @SerialName("score") 18 | @IntRange(0, 10) 19 | override val score: Int = 0, 20 | 21 | @SerialName("updated_at") 22 | override val updatedAt: String? = null, 23 | 24 | @SerialName("start_date") 25 | override val startDate: String? = null, 26 | 27 | @SerialName("finish_date") 28 | override val finishDate: String? = null, 29 | 30 | @SerialName("num_chapters_read") 31 | override val progress: Int? = 0, 32 | 33 | @SerialName("num_volumes_read") 34 | val numVolumesRead: Int = 0, 35 | 36 | @SerialName("is_rereading") 37 | override val isRepeating: Boolean = false, 38 | 39 | @SerialName("num_times_reread") 40 | override val repeatCount: Int? = 0, 41 | 42 | @SerialName("reread_value") 43 | @IntRange(0, 5) 44 | override val repeatValue: Int? = 0, 45 | 46 | @SerialName("priority") 47 | @IntRange(0, 2) 48 | override val priority: Int = 0, 49 | 50 | @SerialName("tags") 51 | override val tags: List? = null, 52 | 53 | @SerialName("comments") 54 | override val comments: String? = null 55 | 56 | ) : BaseMyListStatus() { 57 | 58 | fun isUsingVolumeProgress() = numVolumesRead > 0 && (progress == null || progress == 0) 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/RelatedManga.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import com.axiel7.moelist.data.model.media.BaseRelated 4 | import com.axiel7.moelist.data.model.media.RelationType 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class RelatedManga( 10 | @SerialName("node") 11 | override val node: MangaNode, 12 | @SerialName("relation_type") 13 | override val relationType: RelationType, 14 | ) : BaseRelated 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/SerialNode.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SerialNode( 8 | @SerialName("id") 9 | val id: Int, 10 | @SerialName("name") 11 | val name: String 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/Serialization.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Serialization( 8 | @SerialName("node") 9 | val node: SerialNode 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/manga/UserMangaList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.manga 2 | 3 | import com.axiel7.moelist.data.model.media.BaseUserMediaList 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class UserMangaList( 9 | @SerialName("node") 10 | override val node: MangaNode, 11 | @SerialName("list_status") 12 | override val listStatus: MyMangaListStatus? = null, 13 | ) : BaseUserMediaList() -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/AlternativeTitles.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import androidx.compose.runtime.Immutable 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Immutable 8 | @Serializable 9 | data class AlternativeTitles( 10 | @SerialName("synonyms") 11 | val synonyms: List?, 12 | @SerialName("en") 13 | val en: String, 14 | @SerialName("ja") 15 | val ja: String 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/BaseMediaList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | interface BaseMediaList { 4 | val node: BaseMediaNode 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/BaseRanking.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import com.axiel7.moelist.data.model.anime.Ranking 4 | 5 | interface BaseRanking { 6 | val node: BaseMediaNode 7 | val ranking: Ranking? 8 | val rankingType: RankingType? 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/BaseRelated.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | interface BaseRelated { 4 | val node: BaseMediaNode 5 | val relationType: RelationType 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/BaseUserMediaList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import com.axiel7.moelist.data.model.anime.AnimeNode 4 | import com.axiel7.moelist.data.model.anime.UserAnimeList 5 | import com.axiel7.moelist.data.model.manga.MyMangaListStatus 6 | import com.axiel7.moelist.data.model.manga.UserMangaList 7 | 8 | abstract class BaseUserMediaList { 9 | abstract val node: T 10 | abstract val listStatus: BaseMyListStatus? 11 | 12 | fun userProgress() = when (this) { 13 | is UserAnimeList -> listStatus?.progress 14 | is UserMangaList -> { 15 | val status = listStatus as? MyMangaListStatus 16 | if (status?.isUsingVolumeProgress() == true) { 17 | status.numVolumesRead 18 | } else { 19 | status?.progress 20 | } 21 | } 22 | 23 | else -> null 24 | } 25 | 26 | fun totalProgress() = when (this) { 27 | is UserAnimeList -> node.numEpisodes 28 | is UserMangaList -> { 29 | if (listStatus?.isUsingVolumeProgress() == true) { 30 | node.numVolumes 31 | } else { 32 | node.numChapters 33 | } 34 | } 35 | 36 | else -> null 37 | } 38 | 39 | fun calculateProgressBarValue(): Float { 40 | val total = totalProgress() ?: 0 41 | return if (total == 0) 0f 42 | else (userProgress() ?: 0).div(total.toFloat()) 43 | } 44 | 45 | val isAiring 46 | get() = node.status == MediaStatus.AIRING 47 | || ((node as? AnimeNode)?.broadcast != null && node.status == MediaStatus.AIRING) 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/ListType.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import com.axiel7.moelist.data.repository.DefaultPreferencesRepository 4 | 5 | data class ListType( 6 | val status: ListStatus, 7 | val mediaType: MediaType, 8 | ) { 9 | fun stylePreference( 10 | defaultPreferencesRepository: DefaultPreferencesRepository 11 | ) = when (status) { 12 | ListStatus.WATCHING -> defaultPreferencesRepository.animeCurrentListStyle 13 | ListStatus.READING -> defaultPreferencesRepository.mangaCurrentListStyle 14 | ListStatus.PLAN_TO_WATCH -> defaultPreferencesRepository.animePlannedListStyle 15 | ListStatus.PLAN_TO_READ -> defaultPreferencesRepository.mangaPlannedListStyle 16 | ListStatus.COMPLETED -> 17 | if (mediaType == MediaType.ANIME) defaultPreferencesRepository.animeCompletedListStyle 18 | else defaultPreferencesRepository.mangaCompletedListStyle 19 | 20 | ListStatus.ON_HOLD -> 21 | if (mediaType == MediaType.ANIME) defaultPreferencesRepository.animePausedListStyle 22 | else defaultPreferencesRepository.mangaPausedListStyle 23 | 24 | ListStatus.DROPPED -> 25 | if (mediaType == MediaType.ANIME) defaultPreferencesRepository.animeDroppedListStyle 26 | else defaultPreferencesRepository.mangaDroppedListStyle 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/MainPicture.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class MainPicture( 8 | @SerialName("medium") 9 | val medium: String? = null, 10 | @SerialName("large") 11 | val large: String? = null 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/MediaSort.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | 8 | enum class MediaSort(val value: String) : Localizable { 9 | ANIME_TITLE("anime_title"), 10 | ANIME_SCORE("anime_score"), 11 | ANIME_NUM_USERS("anime_num_list_users"), 12 | ANIME_START_DATE("anime_start_date"), 13 | SCORE("list_score"), 14 | UPDATED("list_updated_at"), 15 | MANGA_TITLE("manga_title"), 16 | MANGA_START_DATE("manga_start_date"); 17 | 18 | override fun toString() = value 19 | 20 | @Composable 21 | override fun localized() = when (this) { 22 | ANIME_TITLE -> stringResource(R.string.sort_title) 23 | ANIME_SCORE -> stringResource(R.string.sort_score) 24 | ANIME_NUM_USERS -> stringResource(R.string.members) 25 | ANIME_START_DATE -> stringResource(R.string.start_date) 26 | SCORE -> stringResource(R.string.sort_score) 27 | UPDATED -> stringResource(R.string.sort_last_updated) 28 | MANGA_TITLE -> stringResource(R.string.sort_title) 29 | MANGA_START_DATE -> stringResource(R.string.start_date) 30 | } 31 | 32 | companion object { 33 | fun valueOf(malValue: String) = entries.firstOrNull { it.value == malValue } 34 | 35 | val animeListSortItems = arrayOf(ANIME_TITLE, SCORE, UPDATED, ANIME_START_DATE) 36 | 37 | val mangaListSortItems = arrayOf(MANGA_TITLE, SCORE, UPDATED, MANGA_START_DATE) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/MediaStatus.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Serializable 11 | enum class MediaStatus : Localizable { 12 | @SerialName("currently_airing") 13 | AIRING, 14 | 15 | @SerialName("finished_airing") 16 | FINISHED_AIRING, 17 | 18 | @SerialName("not_yet_aired") 19 | NOT_AIRED, 20 | 21 | @SerialName("currently_publishing") 22 | PUBLISHING, 23 | 24 | @SerialName("finished") 25 | FINISHED, 26 | 27 | @SerialName("on_hiatus") 28 | HIATUS, 29 | 30 | @SerialName("discontinued") 31 | DISCONTINUED; 32 | 33 | @Composable 34 | override fun localized() = when (this) { 35 | AIRING -> stringResource(R.string.airing) 36 | FINISHED_AIRING -> stringResource(R.string.finished) 37 | NOT_AIRED -> stringResource(R.string.not_yet_aired) 38 | PUBLISHING -> stringResource(R.string.publishing) 39 | FINISHED -> stringResource(R.string.finished) 40 | HIATUS -> stringResource(R.string.on_hiatus) 41 | DISCONTINUED -> stringResource(R.string.discontinued) 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/MediaType.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import android.os.Bundle 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.res.stringResource 6 | import androidx.navigation.NavType 7 | import com.axiel7.moelist.R 8 | import com.axiel7.moelist.data.model.base.Localizable 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | enum class MediaType : Localizable { 13 | ANIME, MANGA; 14 | 15 | @Composable 16 | override fun localized() = when (this) { 17 | ANIME -> stringResource(R.string.anime) 18 | MANGA -> stringResource(R.string.manga) 19 | } 20 | 21 | companion object { 22 | val navType = object : NavType(isNullableAllowed = true) { 23 | override fun get(bundle: Bundle, key: String): MediaType? { 24 | return try { 25 | bundle.getString(key)?.let { 26 | MediaType.valueOf(it) 27 | } 28 | } catch (_: IllegalArgumentException) { 29 | null 30 | } 31 | } 32 | 33 | override fun parseValue(value: String): MediaType { 34 | return try { 35 | MediaType.valueOf(value) 36 | } catch (_: IllegalArgumentException) { 37 | ANIME 38 | } 39 | } 40 | 41 | override fun put(bundle: Bundle, key: String, value: MediaType) { 42 | bundle.putString(key, value.name) 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/RankingType.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Serializable 11 | enum class RankingType : Localizable { 12 | @SerialName("all") 13 | SCORE, 14 | 15 | @SerialName("bypopularity") 16 | POPULARITY, 17 | 18 | @SerialName("favorite") 19 | FAVORITE, 20 | 21 | @SerialName("upcoming") 22 | UPCOMING, 23 | 24 | @SerialName("airing") 25 | AIRING; 26 | 27 | @Composable 28 | override fun localized() = stringResource(stringRes) 29 | 30 | val stringRes 31 | get() = when (this) { 32 | SCORE -> R.string.sort_score 33 | POPULARITY -> R.string.popularity 34 | FAVORITE -> R.string.favorite 35 | UPCOMING -> R.string.upcoming 36 | AIRING -> R.string.airing 37 | } 38 | 39 | val serialName 40 | get() = when (this) { 41 | SCORE -> "all" 42 | POPULARITY -> "bypopularity" 43 | FAVORITE -> "favorite" 44 | UPCOMING -> "upcoming" 45 | AIRING -> "airing" 46 | } 47 | 48 | companion object { 49 | 50 | val rankingAnimeValues = arrayOf(SCORE, POPULARITY, FAVORITE, UPCOMING) 51 | 52 | val rankingMangaValues = arrayOf(SCORE, POPULARITY, FAVORITE) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/Stat.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import com.axiel7.moelist.data.model.base.LocalizableAndColorable 4 | 5 | data class Stat( 6 | val type: T, 7 | val value: Float, 8 | ) { 9 | companion object { 10 | val exampleStats = listOf( 11 | Stat( 12 | type = ListStatus.WATCHING, 13 | value = 12f, 14 | ), 15 | Stat( 16 | type = ListStatus.COMPLETED, 17 | value = 120f, 18 | ), 19 | Stat( 20 | type = ListStatus.ON_HOLD, 21 | value = 5f, 22 | ), 23 | Stat( 24 | type = ListStatus.DROPPED, 25 | value = 3f, 26 | ), 27 | Stat( 28 | type = ListStatus.PLAN_TO_WATCH, 29 | value = 30f, 30 | ), 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/Statistics.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class Statistics( 8 | @SerialName("status") 9 | val status: StatisticsStatus, 10 | @SerialName("num_list_users") 11 | val numListUsers: Int 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/StatisticsStatus.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class StatisticsStatus( 8 | @SerialName("watching") 9 | val watching: String, 10 | @SerialName("completed") 11 | val completed: String, 12 | @SerialName("on_hold") 13 | val onHold: String, 14 | @SerialName("dropped") 15 | val dropped: String, 16 | @SerialName("plan_to_watch") 17 | val planToWatch: String 18 | ) { 19 | fun toStats() = listOf( 20 | Stat( 21 | type = ListStatus.WATCHING, 22 | value = watching.toFloatOrNull() ?: 0f 23 | ), 24 | Stat( 25 | type = ListStatus.COMPLETED, 26 | value = completed.toFloatOrNull() ?: 0f 27 | ), 28 | Stat( 29 | type = ListStatus.ON_HOLD, 30 | value = onHold.toFloatOrNull() ?: 0f 31 | ), 32 | Stat( 33 | type = ListStatus.DROPPED, 34 | value = dropped.toFloatOrNull() ?: 0f 35 | ), 36 | Stat( 37 | type = ListStatus.PLAN_TO_WATCH, 38 | value = planToWatch.toFloatOrNull() ?: 0f 39 | ) 40 | ) 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/TitleLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import com.axiel7.moelist.R 4 | 5 | enum class TitleLanguage { 6 | ROMAJI, ENGLISH, JAPANESE; 7 | 8 | val stringRes 9 | get() = when (this) { 10 | ROMAJI -> R.string.romaji 11 | ENGLISH -> R.string.english 12 | JAPANESE -> R.string.japanese 13 | } 14 | 15 | companion object { 16 | val entriesLocalized = entries.associateWith { it.stringRes } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/model/media/WeekDay.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.model.media 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | import com.axiel7.moelist.ui.base.TabRowItem 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | enum class WeekDay : Localizable { 13 | @SerialName("monday") 14 | MONDAY, 15 | 16 | @SerialName("tuesday") 17 | TUESDAY, 18 | 19 | @SerialName("wednesday") 20 | WEDNESDAY, 21 | 22 | @SerialName("thursday") 23 | THURSDAY, 24 | 25 | @SerialName("friday") 26 | FRIDAY, 27 | 28 | @SerialName("saturday") 29 | SATURDAY, 30 | 31 | @SerialName("sunday") 32 | SUNDAY; 33 | 34 | @Composable 35 | override fun localized() = stringResource(stringRes) 36 | 37 | val stringRes 38 | get() = when (this) { 39 | MONDAY -> R.string.monday 40 | TUESDAY -> R.string.tuesday 41 | WEDNESDAY -> R.string.wednesday 42 | THURSDAY -> R.string.thursday 43 | FRIDAY -> R.string.friday 44 | SATURDAY -> R.string.saturday 45 | SUNDAY -> R.string.sunday 46 | } 47 | 48 | companion object { 49 | val tabRowItems = 50 | entries.map { TabRowItem(value = it, title = it.stringRes) }.toTypedArray() 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/network/JikanApi.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.network 2 | 3 | import com.axiel7.moelist.data.model.Response 4 | import com.axiel7.moelist.data.model.UserStats 5 | import com.axiel7.moelist.utils.JIKAN_API_URL 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.call.body 8 | import io.ktor.client.request.HttpRequestBuilder 9 | import io.ktor.client.request.get 10 | import io.ktor.http.HttpHeaders 11 | 12 | class JikanApi(private val client: HttpClient) { 13 | 14 | private fun HttpRequestBuilder.removeOfficialApiHeaders() = headers.apply { 15 | remove(HttpHeaders.Authorization) 16 | remove("X-MAL-CLIENT-ID") 17 | } 18 | 19 | suspend fun getUserStats( 20 | username: String 21 | ): Response = client.get("${JIKAN_API_URL}users/$username/statistics") { 22 | removeOfficialApiHeaders() 23 | }.body() 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/network/KtorClient.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.network 2 | 3 | import com.axiel7.moelist.App 4 | import com.axiel7.moelist.BuildConfig 5 | import io.ktor.client.HttpClient 6 | import io.ktor.client.engine.okhttp.OkHttp 7 | import io.ktor.client.plugins.DefaultRequest 8 | import io.ktor.client.plugins.cache.HttpCache 9 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 10 | import io.ktor.client.plugins.logging.ANDROID 11 | import io.ktor.client.plugins.logging.LogLevel 12 | import io.ktor.client.plugins.logging.Logger 13 | import io.ktor.client.plugins.logging.Logging 14 | import io.ktor.client.request.header 15 | import io.ktor.http.HttpHeaders 16 | import io.ktor.serialization.kotlinx.json.json 17 | import kotlinx.serialization.json.Json 18 | 19 | val ktorHttpClient = HttpClient(OkHttp) { 20 | 21 | expectSuccess = false 22 | 23 | install(ContentNegotiation) { 24 | json( 25 | Json { 26 | coerceInputValues = true 27 | isLenient = true 28 | ignoreUnknownKeys = true 29 | } 30 | ) 31 | } 32 | 33 | install(HttpCache) 34 | 35 | if (BuildConfig.DEBUG) { 36 | install(Logging) { 37 | logger = Logger.ANDROID 38 | level = LogLevel.ALL 39 | } 40 | } 41 | 42 | install(DefaultRequest) { 43 | header("X-MAL-CLIENT-ID", BuildConfig.CLIENT_ID) 44 | App.accessToken?.let { header(HttpHeaders.Authorization, "Bearer $it") } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/repository/BaseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.repository 2 | 3 | import com.axiel7.moelist.App 4 | import com.axiel7.moelist.BuildConfig 5 | import com.axiel7.moelist.data.network.Api 6 | import kotlinx.coroutines.flow.first 7 | 8 | abstract class BaseRepository( 9 | private val api: Api, 10 | private val defaultPreferencesRepository: DefaultPreferencesRepository 11 | ) { 12 | suspend fun handleResponseError(error: String) { 13 | when (error) { 14 | "invalid_token" -> { 15 | val refreshToken = defaultPreferencesRepository.refreshToken.first() 16 | if (refreshToken != null) { 17 | try { 18 | val newToken = api.getAccessToken( 19 | clientId = BuildConfig.CLIENT_ID, 20 | refreshToken = refreshToken 21 | ) 22 | defaultPreferencesRepository.saveTokens(newToken) 23 | App.accessToken = newToken.accessToken 24 | } catch (e: Exception) { 25 | defaultPreferencesRepository.removeTokens() 26 | } 27 | } else { 28 | defaultPreferencesRepository.removeTokens() 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/repository/SearchHistoryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.repository 2 | 3 | import com.axiel7.moelist.data.local.searchhistory.SearchHistoryDao 4 | import com.axiel7.moelist.data.local.searchhistory.SearchHistoryEntity 5 | import com.axiel7.moelist.data.local.searchhistory.toSearchEntity 6 | import com.axiel7.moelist.data.local.searchhistory.toSearchHistoryList 7 | import com.axiel7.moelist.data.model.SearchHistory 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.map 10 | 11 | class SearchHistoryRepository( 12 | private val dao: SearchHistoryDao, 13 | ) { 14 | fun getItems(): Flow> { 15 | return dao.getItems().map(List::toSearchHistoryList) 16 | } 17 | 18 | suspend fun addItem(query: String) { 19 | val trimmedQuery = query.trim() 20 | 21 | if (trimmedQuery.isNotBlank()) { 22 | dao.addItem(SearchHistoryEntity(keyword = trimmedQuery)) 23 | } 24 | } 25 | 26 | suspend fun deleteItem(item: SearchHistory) { 27 | dao.deleteItem(item.toSearchEntity()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/data/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.data.repository 2 | 3 | import com.axiel7.moelist.data.model.Response 4 | import com.axiel7.moelist.data.model.User 5 | import com.axiel7.moelist.data.model.UserStats 6 | import com.axiel7.moelist.data.network.Api 7 | import com.axiel7.moelist.data.network.JikanApi 8 | 9 | class UserRepository( 10 | private val api: Api, 11 | private val jikanApi: JikanApi, 12 | defaultPreferencesRepository: DefaultPreferencesRepository 13 | ) : BaseRepository(api, defaultPreferencesRepository) { 14 | 15 | companion object { 16 | private const val USER_FIELDS = "id,name,gender,location,joined_at,anime_statistics" 17 | } 18 | 19 | suspend fun getMyUser(): User? { 20 | return try { 21 | val result = api.getUser(USER_FIELDS) 22 | result.error?.let { handleResponseError(it) } 23 | return result 24 | } catch (e: Exception) { 25 | null 26 | } 27 | } 28 | 29 | suspend fun getUserStats( 30 | username: String 31 | ): Response { 32 | return try { 33 | jikanApi.getUserStats(username) 34 | } catch (e: Exception) { 35 | Response(message = e.message) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/di/DataStoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.di 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory 6 | import androidx.datastore.preferences.core.Preferences 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.preferencesDataStoreFile 9 | import kotlinx.coroutines.flow.first 10 | import kotlinx.coroutines.flow.map 11 | import kotlinx.coroutines.runBlocking 12 | import org.koin.android.ext.koin.androidApplication 13 | import org.koin.core.qualifier.named 14 | import org.koin.dsl.module 15 | 16 | const val DEFAULT_DATA_STORE = "default" 17 | const val NOTIFICATIONS_DATA_STORE = "notifications" 18 | 19 | val dataStoreModule = module { 20 | single(named(DEFAULT_DATA_STORE)) { provideDataStore(androidApplication(), DEFAULT_DATA_STORE) } 21 | single(named(NOTIFICATIONS_DATA_STORE)) { provideDataStore(androidApplication(), NOTIFICATIONS_DATA_STORE) } 22 | } 23 | 24 | private fun provideDataStore(context: Context, name: String) = 25 | PreferenceDataStoreFactory.create { 26 | context.preferencesDataStoreFile(name) 27 | } 28 | 29 | fun DataStore.getValue(key: Preferences.Key) = data.map { it[key] } 30 | 31 | fun DataStore.getValue( 32 | key: Preferences.Key, 33 | default: T, 34 | ) = data.map { it[key] ?: default } 35 | 36 | suspend fun DataStore.setValue( 37 | key: Preferences.Key, 38 | value: T? 39 | ) = edit { 40 | if (value != null) it[key] = value 41 | else it.remove(key) 42 | } 43 | 44 | fun DataStore.getValueBlocking(key: Preferences.Key) = 45 | runBlocking { data.first() }[key] -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.axiel7.moelist.data.local.MoeListDatabase 6 | import com.axiel7.moelist.data.local.searchhistory.SearchHistoryDao 7 | import com.axiel7.moelist.data.repository.SearchHistoryRepository 8 | import org.koin.android.ext.koin.androidContext 9 | import org.koin.core.module.dsl.singleOf 10 | import org.koin.dsl.module 11 | 12 | val databaseModule = module { 13 | single { provideDatabase(androidContext()) } 14 | single { get().searchHistoryDao() } 15 | 16 | singleOf(::SearchHistoryRepository) 17 | } 18 | 19 | private fun provideDatabase(context: Context): MoeListDatabase { 20 | return Room 21 | .databaseBuilder( 22 | context = context, 23 | klass = MoeListDatabase::class.java, 24 | name = "moelist-database", 25 | ) 26 | .build() 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.di 2 | 3 | import com.axiel7.moelist.data.network.Api 4 | import com.axiel7.moelist.data.network.JikanApi 5 | import com.axiel7.moelist.data.network.ktorHttpClient 6 | import org.koin.core.module.dsl.singleOf 7 | import org.koin.dsl.module 8 | 9 | val networkModule = module { 10 | single { ktorHttpClient } 11 | singleOf(::Api) 12 | singleOf(::JikanApi) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.di 2 | 3 | import com.axiel7.moelist.data.repository.AnimeRepository 4 | import com.axiel7.moelist.data.repository.DefaultPreferencesRepository 5 | import com.axiel7.moelist.data.repository.LoginRepository 6 | import com.axiel7.moelist.data.repository.MangaRepository 7 | import com.axiel7.moelist.data.repository.UserRepository 8 | import org.koin.core.module.dsl.singleOf 9 | import org.koin.core.qualifier.named 10 | import org.koin.dsl.module 11 | 12 | val repositoryModule = module { 13 | single { DefaultPreferencesRepository(get(named(DEFAULT_DATA_STORE))) } 14 | singleOf(::AnimeRepository) 15 | singleOf(::MangaRepository) 16 | singleOf(::LoginRepository) 17 | singleOf(::UserRepository) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/di/WorkerModule.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.di 2 | 3 | import com.axiel7.moelist.worker.NotificationWorker 4 | import com.axiel7.moelist.worker.NotificationWorkerManager 5 | import org.koin.android.ext.koin.androidApplication 6 | import org.koin.androidx.workmanager.dsl.workerOf 7 | import org.koin.core.qualifier.named 8 | import org.koin.dsl.module 9 | 10 | val workerModule = module { 11 | single { 12 | NotificationWorkerManager( 13 | context = androidApplication(), 14 | dataStore = get(named(NOTIFICATIONS_DATA_STORE))) 15 | } 16 | workerOf(::NotificationWorker) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/ItemsPerRow.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base 2 | 3 | import com.axiel7.moelist.R 4 | 5 | enum class ItemsPerRow(val value: Int) { 6 | DEFAULT(0), 7 | ONE(1), 8 | TWO(2), 9 | THREE(3), 10 | FOUR(4), 11 | FIVE(5), 12 | SIX(6), 13 | SEVEN(7), 14 | EIGHT(8), 15 | NINE(9), 16 | TEN(10); 17 | 18 | val stringRes 19 | get() = when (this) { 20 | DEFAULT -> R.string.default_setting 21 | ONE -> R.string.one 22 | TWO -> R.string.two 23 | THREE -> R.string.three 24 | FOUR -> R.string.four 25 | FIVE -> R.string.five 26 | SIX -> R.string.six 27 | SEVEN -> R.string.seven 28 | EIGHT -> R.string.eight 29 | NINE -> R.string.nine 30 | TEN -> R.string.ten 31 | } 32 | 33 | companion object { 34 | fun valueOf(value: Int) = entries.find { it.value == value } 35 | 36 | val entriesLocalized = entries.associateWith { it.stringRes } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/ListStyle.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | 8 | enum class ListStyle : Localizable { 9 | STANDARD, 10 | COMPACT, 11 | MINIMAL, 12 | GRID; 13 | 14 | val stringRes 15 | get() = when (this) { 16 | STANDARD -> R.string.list_mode_standard 17 | COMPACT -> R.string.list_mode_compact 18 | MINIMAL -> R.string.list_mode_minimal 19 | GRID -> R.string.list_mode_grid 20 | } 21 | 22 | @Composable 23 | override fun localized() = stringResource(stringRes) 24 | 25 | companion object { 26 | fun valueOfOrNull(value: String) = try { 27 | valueOf(value) 28 | } catch (e: IllegalArgumentException) { 29 | null 30 | } 31 | 32 | val entriesLocalized = entries.associateWith { it.stringRes } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/StartTab.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import com.axiel7.moelist.R 6 | import com.axiel7.moelist.data.model.base.Localizable 7 | 8 | enum class StartTab( 9 | val value: String 10 | ) : Localizable { 11 | LAST_USED("last_used"), 12 | HOME("home"), 13 | ANIME("anime"), 14 | MANGA("manga"), 15 | MORE("more"); 16 | 17 | val stringRes 18 | get() = when (this) { 19 | LAST_USED -> R.string.last_used 20 | HOME -> R.string.title_home 21 | ANIME -> R.string.title_anime_list 22 | MANGA -> R.string.title_manga_list 23 | MORE -> R.string.more 24 | } 25 | 26 | @Composable 27 | override fun localized() = stringResource(stringRes) 28 | 29 | companion object { 30 | fun valueOf(tabName: String) = entries.find { it.value == tabName } 31 | 32 | val entriesLocalized = entries.associateWith { it.stringRes } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/TabRowItem.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | 6 | data class TabRowItem( 7 | val value: T, 8 | @StringRes val title: Int?, 9 | @DrawableRes val icon: Int? = null, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/ThemeStyle.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base 2 | 3 | import com.axiel7.moelist.R 4 | 5 | enum class ThemeStyle { 6 | FOLLOW_SYSTEM, LIGHT, DARK; 7 | 8 | val stringRes 9 | get() = when (this) { 10 | FOLLOW_SYSTEM -> R.string.theme_system 11 | LIGHT -> R.string.theme_light 12 | DARK -> R.string.theme_dark 13 | } 14 | 15 | companion object { 16 | fun valueOfOrNull(value: String) = try { 17 | valueOf(value) 18 | } catch (e: IllegalArgumentException) { 19 | null 20 | } 21 | 22 | val entriesLocalized = entries.associateWith { it.stringRes } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/event/PagedUiEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.event 2 | 3 | interface PagedUiEvent : UiEvent { 4 | fun loadMore() 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/event/UiEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.event 2 | 3 | interface UiEvent { 4 | fun showMessage(message: String?) 5 | fun onMessageDisplayed() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/navigation/Route.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.navigation 2 | 3 | import com.axiel7.moelist.data.model.media.MediaType 4 | import kotlinx.serialization.Serializable 5 | 6 | sealed interface Route { 7 | sealed interface Tab : Route { 8 | @Serializable 9 | data object Home : Tab 10 | 11 | @Serializable 12 | data class Anime(val mediaType: MediaType) : Tab 13 | 14 | @Serializable 15 | data class Manga(val mediaType: MediaType) : Tab 16 | 17 | @Serializable 18 | data object More : Tab 19 | } 20 | 21 | @Serializable 22 | data class MediaRanking(val mediaType: MediaType) : Route 23 | 24 | @Serializable 25 | data class MediaDetails( 26 | val mediaType: MediaType, 27 | val mediaId: Int, 28 | ) : Route 29 | 30 | @Serializable 31 | data object Calendar : Route 32 | 33 | @Serializable 34 | data object SeasonChart : Route 35 | 36 | @Serializable 37 | data object Recommendations : Route 38 | 39 | @Serializable 40 | data object Profile : Route 41 | 42 | @Serializable 43 | data class Search( 44 | val mediaType: MediaType = MediaType.ANIME 45 | ) : Route 46 | 47 | @Serializable 48 | data class FullPoster(val pictures: List) : Route 49 | 50 | @Serializable 51 | data object Settings : Route 52 | 53 | @Serializable 54 | data object ListStyleSettings : Route 55 | 56 | @Serializable 57 | data object Notifications : Route 58 | 59 | @Serializable 60 | data object About : Route 61 | 62 | @Serializable 63 | data object Credits : Route 64 | } 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/navigation/StringArrayNavType.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.navigation 2 | 3 | import android.os.Bundle 4 | import androidx.navigation.NavType 5 | import kotlinx.serialization.json.Json 6 | 7 | object StringArrayNavType : NavType>(isNullableAllowed = false) { 8 | override fun get(bundle: Bundle, key: String): Array? { 9 | return bundle.getStringArray(key) 10 | } 11 | 12 | override fun parseValue(value: String): Array { 13 | return Json.decodeFromString(value) 14 | } 15 | 16 | override fun put(bundle: Bundle, key: String, value: Array) { 17 | bundle.putStringArray(key, value) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/state/MediaUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.state 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaNode 4 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 5 | 6 | interface MediaForEdit { 7 | val mediaInfo: BaseMediaNode? 8 | val myListStatus: BaseMyListStatus? 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/state/PagedUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.state 2 | 3 | abstract class PagedUiState : UiState() { 4 | 5 | abstract val nextPage: String? 6 | 7 | /** 8 | * Trigger variable to load more items, be careful to set it to false after loading more 9 | */ 10 | abstract val loadMore: Boolean 11 | 12 | val canLoadMore get() = nextPage != null && !isLoading 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/state/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.state 2 | 3 | abstract class UiState { 4 | abstract val isLoading: Boolean 5 | abstract val message: String? 6 | 7 | // These methods are required because we can't have an abstract data class 8 | // so we need to manually implement the copy() method 9 | 10 | /** 11 | * copy(isLoading = value) 12 | */ 13 | abstract fun setLoading(value: Boolean): UiState 14 | 15 | /** 16 | * copy(message = value) 17 | */ 18 | abstract fun setMessage(value: String?): UiState 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/base/viewmodel/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.base.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.axiel7.moelist.ui.base.event.UiEvent 5 | import com.axiel7.moelist.ui.base.state.UiState 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.coroutines.flow.asStateFlow 9 | import kotlinx.coroutines.flow.update 10 | 11 | abstract class BaseViewModel : ViewModel(), UiEvent { 12 | 13 | protected abstract val mutableUiState: MutableStateFlow 14 | val uiState: StateFlow by lazy { mutableUiState.asStateFlow() } 15 | 16 | @Suppress("UNCHECKED_CAST") 17 | fun setLoading(value: Boolean) { 18 | mutableUiState.update { it.setLoading(value) as S } 19 | } 20 | 21 | @Suppress("UNCHECKED_CAST") 22 | override fun showMessage(message: String?) { 23 | mutableUiState.update { it.setMessage(message ?: GENERIC_ERROR) as S } 24 | } 25 | 26 | @Suppress("UNCHECKED_CAST") 27 | override fun onMessageDisplayed() { 28 | mutableUiState.update { it.setMessage(null) as S } 29 | } 30 | 31 | companion object { 32 | private const val GENERIC_ERROR = "Generic Error" 33 | const val FLOW_TIMEOUT = 5_000L 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/calendar/CalendarEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.calendar 2 | 3 | import com.axiel7.moelist.ui.base.event.UiEvent 4 | 5 | interface CalendarEvent : UiEvent { 6 | 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/calendar/CalendarUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.calendar 2 | 3 | import androidx.compose.runtime.Immutable 4 | import com.axiel7.moelist.data.model.anime.AnimeRanking 5 | import com.axiel7.moelist.ui.base.state.UiState 6 | 7 | @Immutable 8 | data class CalendarUiState( 9 | val mondayAnime: List = emptyList(), 10 | val tuesdayAnime: List = emptyList(), 11 | val wednesdayAnime: List = emptyList(), 12 | val thursdayAnime: List = emptyList(), 13 | val fridayAnime: List = emptyList(), 14 | val saturdayAnime: List = emptyList(), 15 | val sundayAnime: List = emptyList(), 16 | override val isLoading: Boolean = false, 17 | override val message: String? = null, 18 | ) : UiState() { 19 | override fun setLoading(value: Boolean) = copy(isLoading = value) 20 | override fun setMessage(value: String?) = copy(message = value) 21 | 22 | fun weekAnime(weekday: Int) = when (weekday) { 23 | 1 -> mondayAnime 24 | 2 -> tuesdayAnime 25 | 3 -> wednesdayAnime 26 | 4 -> thursdayAnime 27 | 5 -> fridayAnime 28 | 6 -> saturdayAnime 29 | 7 -> sundayAnime 30 | else -> emptyList() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/calendar/CalendarViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.calendar 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import com.axiel7.moelist.data.repository.AnimeRepository 5 | import com.axiel7.moelist.ui.base.viewmodel.BaseViewModel 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.update 9 | import kotlinx.coroutines.launch 10 | 11 | class CalendarViewModel( 12 | private val animeRepository: AnimeRepository 13 | ) : BaseViewModel(), CalendarEvent { 14 | 15 | override val mutableUiState = MutableStateFlow(CalendarUiState(isLoading = true)) 16 | 17 | init { 18 | viewModelScope.launch(Dispatchers.IO) { 19 | val result = animeRepository.getWeeklyAnime() 20 | 21 | if (result.wasError) { 22 | showMessage(result.message) 23 | } else if (result.data != null) { 24 | mutableUiState.update { 25 | it.copy( 26 | mondayAnime = result.data[0], 27 | tuesdayAnime = result.data[1], 28 | wednesdayAnime = result.data[2], 29 | thursdayAnime = result.data[3], 30 | fridayAnime = result.data[4], 31 | saturdayAnime = result.data[5], 32 | sundayAnime = result.data[6] 33 | ) 34 | } 35 | } 36 | setLoading(false) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/CommonIconButton.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.material3.Icon 4 | import androidx.compose.material3.IconButton 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.platform.LocalContext 7 | import androidx.compose.ui.res.painterResource 8 | import androidx.compose.ui.res.stringResource 9 | import androidx.lifecycle.compose.dropUnlessResumed 10 | import com.axiel7.moelist.R 11 | import com.axiel7.moelist.utils.ContextExtensions.openShareSheet 12 | 13 | @Composable 14 | fun BackIconButton( 15 | onClick: () -> Unit 16 | ) { 17 | IconButton(onClick = dropUnlessResumed { onClick() }) { 18 | Icon(painter = painterResource(R.drawable.ic_arrow_back), contentDescription = "arrow_back") 19 | } 20 | } 21 | 22 | @Composable 23 | fun ViewInBrowserButton( 24 | onClick: () -> Unit 25 | ) { 26 | IconButton(onClick = dropUnlessResumed { onClick() }) { 27 | Icon( 28 | painter = painterResource(R.drawable.ic_open_in_browser), 29 | contentDescription = stringResource(R.string.view_on_mal) 30 | ) 31 | } 32 | } 33 | 34 | @Composable 35 | fun ShareButton( 36 | url: String 37 | ) { 38 | val context = LocalContext.current 39 | IconButton(onClick = { context.openShareSheet(url) }) { 40 | Icon( 41 | painter = painterResource(R.drawable.round_share_24), 42 | contentDescription = stringResource(R.string.share) 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/CommonText.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.Text 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.dp 9 | import androidx.compose.ui.unit.sp 10 | 11 | @Composable 12 | fun InfoTitle(text: String) { 13 | Text( 14 | text = text, 15 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), 16 | fontSize = 18.sp, 17 | fontWeight = FontWeight.Bold 18 | ) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/DefaultPlaceholder.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.composed 6 | import io.github.fornewid.placeholder.foundation.PlaceholderHighlight 7 | import io.github.fornewid.placeholder.material3.fade 8 | import io.github.fornewid.placeholder.material3.placeholder 9 | 10 | fun Modifier.defaultPlaceholder( 11 | visible: Boolean 12 | ) = composed { 13 | placeholder( 14 | visible = visible, 15 | color = MaterialTheme.colorScheme.outline, 16 | highlight = PlaceholderHighlight.fade() 17 | ) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/DefaultScaffoldWithTopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.WindowInsets 5 | import androidx.compose.foundation.layout.systemBars 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.Scaffold 8 | import androidx.compose.material3.TopAppBarDefaults 9 | import androidx.compose.material3.rememberTopAppBarState 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.input.nestedscroll.nestedScroll 13 | 14 | @OptIn(ExperimentalMaterial3Api::class) 15 | @Composable 16 | fun DefaultScaffoldWithTopAppBar( 17 | title: String, 18 | navigateBack: () -> Unit, 19 | floatingActionButton: @Composable (() -> Unit) = {}, 20 | contentWindowInsets: WindowInsets = WindowInsets.systemBars, 21 | content: @Composable (PaddingValues) -> Unit 22 | ) { 23 | val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior( 24 | rememberTopAppBarState() 25 | ) 26 | Scaffold( 27 | modifier = Modifier.nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), 28 | topBar = { 29 | DefaultTopAppBar( 30 | title = title, 31 | scrollBehavior = topAppBarScrollBehavior, 32 | navigateBack = navigateBack 33 | ) 34 | }, 35 | floatingActionButton = floatingActionButton, 36 | contentWindowInsets = contentWindowInsets, 37 | content = content 38 | ) 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/DefaultTopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.material3.ExperimentalMaterial3Api 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TopAppBar 6 | import androidx.compose.material3.TopAppBarScrollBehavior 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.tooling.preview.Preview 9 | import com.axiel7.moelist.ui.theme.MoeListTheme 10 | 11 | @OptIn(ExperimentalMaterial3Api::class) 12 | @Composable 13 | fun DefaultTopAppBar( 14 | title: String, 15 | scrollBehavior: TopAppBarScrollBehavior? = null, 16 | navigateBack: () -> Unit, 17 | ) { 18 | TopAppBar( 19 | title = { Text(text = title) }, 20 | navigationIcon = { 21 | BackIconButton(onClick = navigateBack) 22 | }, 23 | scrollBehavior = scrollBehavior 24 | ) 25 | } 26 | 27 | @OptIn(ExperimentalMaterial3Api::class) 28 | @Preview 29 | @Composable 30 | fun DefaultTopAppBarPreview() { 31 | MoeListTheme { 32 | DefaultTopAppBar( 33 | title = "MoeList", 34 | navigateBack = {} 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/HeaderHorizontalList.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.vector.ImageVector 15 | import androidx.compose.ui.res.vectorResource 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.unit.dp 18 | import androidx.compose.ui.unit.sp 19 | import com.axiel7.moelist.R 20 | 21 | @Composable 22 | fun HeaderHorizontalList(text: String, onClick: () -> Unit) { 23 | Box( 24 | modifier = Modifier.clickable(onClick = onClick) 25 | ) { 26 | Row( 27 | modifier = Modifier 28 | .padding(horizontal = 20.dp, vertical = 16.dp) 29 | .fillMaxWidth(), 30 | horizontalArrangement = Arrangement.SpaceBetween, 31 | verticalAlignment = Alignment.CenterVertically 32 | ) { 33 | Text(text, fontSize = 18.sp, fontWeight = FontWeight.SemiBold) 34 | Icon( 35 | imageVector = ImageVector.vectorResource(R.drawable.ic_round_arrow_forward_24), 36 | contentDescription = text 37 | ) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/LoadingDialog.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.material3.AlertDialog 6 | import androidx.compose.material3.CircularProgressIndicator 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.res.stringResource 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | import com.axiel7.moelist.R 14 | import com.axiel7.moelist.ui.theme.MoeListTheme 15 | 16 | @Composable 17 | fun LoadingDialog( 18 | text: String = stringResource(R.string.loading), 19 | onDismiss: () -> Unit = {}, 20 | ) { 21 | AlertDialog( 22 | onDismissRequest = onDismiss, 23 | confirmButton = {}, 24 | title = { 25 | Row( 26 | horizontalArrangement = Arrangement.spacedBy(16.dp), 27 | verticalAlignment = Alignment.CenterVertically 28 | ) { 29 | CircularProgressIndicator(progress = { 0.7f }) 30 | Text(text = text) 31 | } 32 | } 33 | ) 34 | } 35 | 36 | @Preview 37 | @Composable 38 | fun LoadingDialogPreview() { 39 | MoeListTheme { 40 | LoadingDialog() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/Rectangle.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.Canvas 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import androidx.compose.ui.unit.Dp 11 | import androidx.compose.ui.unit.dp 12 | import com.axiel7.moelist.ui.theme.MoeListTheme 13 | 14 | @Composable 15 | fun Rectangle( 16 | width: Dp, 17 | height: Dp, 18 | color: Color 19 | ) { 20 | Canvas(modifier = Modifier.size(width, height)) { 21 | drawIntoCanvas { 22 | drawRect( 23 | color = color, 24 | size = size 25 | ) 26 | } 27 | } 28 | } 29 | 30 | @Preview 31 | @Composable 32 | fun RectanglePreview() { 33 | MoeListTheme { 34 | Rectangle(width = 50.dp, height = 12.dp, color = Color.Blue) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/TextCheckBox.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.material3.Checkbox 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import com.axiel7.moelist.ui.theme.MoeListTheme 12 | 13 | @Composable 14 | fun TextCheckBox( 15 | text: String, 16 | checked: Boolean, 17 | onCheckedChange: (Boolean) -> Unit, 18 | modifier: Modifier = Modifier 19 | ) { 20 | Row( 21 | modifier = modifier.clickable { onCheckedChange(!checked) }, 22 | verticalAlignment = Alignment.CenterVertically 23 | ) { 24 | Checkbox( 25 | checked = checked, 26 | onCheckedChange = onCheckedChange 27 | ) 28 | Text(text = text) 29 | } 30 | } 31 | 32 | @Preview(showBackground = true) 33 | @Composable 34 | fun TextCheckBoxPreview() { 35 | MoeListTheme { 36 | TextCheckBox( 37 | text = "This is a CheckBox", 38 | checked = false, 39 | onCheckedChange = {} 40 | ) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/composables/score/ScoreSlider.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.composables.score 2 | 3 | import androidx.compose.material3.Slider 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType 7 | import androidx.compose.ui.platform.LocalHapticFeedback 8 | import kotlin.math.roundToInt 9 | 10 | @Composable 11 | fun ScoreSlider( 12 | score: Int, 13 | onValueChange: (Int) -> Unit, 14 | modifier: Modifier = Modifier 15 | ) { 16 | val haptic = LocalHapticFeedback.current 17 | Slider( 18 | value = score.toFloat(), 19 | onValueChange = { 20 | haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) 21 | onValueChange(it.roundToInt()) 22 | }, 23 | modifier = modifier, 24 | valueRange = 0f..10f, 25 | steps = 9, 26 | ) 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/details/MediaDetailsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.details 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 4 | import com.axiel7.moelist.data.model.media.WeekDay 5 | import com.axiel7.moelist.ui.base.event.UiEvent 6 | import java.time.LocalDate 7 | import java.time.LocalTime 8 | 9 | interface MediaDetailsEvent : UiEvent { 10 | 11 | fun onChangedMyListStatus(value: BaseMyListStatus?, removed: Boolean = false) 12 | 13 | fun getCharacters() 14 | 15 | fun scheduleAiringAnimeNotification( 16 | title: String, 17 | animeId: Int, 18 | weekDay: WeekDay, 19 | jpHour: LocalTime 20 | ) 21 | 22 | fun scheduleAnimeStartNotification( 23 | title: String, 24 | animeId: Int, 25 | startDate: LocalDate, 26 | ) 27 | 28 | fun removeAiringAnimeNotification(animeId: Int) 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/details/composables/AnimeThemeItem.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.details.composables 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.text.style.TextOverflow 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import androidx.compose.ui.unit.dp 12 | import com.axiel7.moelist.ui.theme.MoeListTheme 13 | 14 | @Composable 15 | fun AnimeThemeItem(text: String, onClick: () -> Unit) { 16 | Text( 17 | text = text, 18 | modifier = Modifier 19 | .padding(horizontal = 16.dp, vertical = 4.dp) 20 | .clickable(onClick = onClick), 21 | color = MaterialTheme.colorScheme.primary, 22 | overflow = TextOverflow.Ellipsis, 23 | maxLines = 1 24 | ) 25 | } 26 | 27 | @Preview(showBackground = true) 28 | @Composable 29 | fun AnimeThemeItemPreview() { 30 | MoeListTheme { 31 | AnimeThemeItem(text = "Opening 1: A large title to test the preview", onClick = {}) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/details/composables/MediaInfoView.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.details.composables 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.text.selection.SelectionContainer 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | import com.axiel7.moelist.R 15 | import com.axiel7.moelist.ui.theme.MoeListTheme 16 | 17 | @Composable 18 | fun MediaInfoView( 19 | title: String, 20 | info: String?, 21 | modifier: Modifier = Modifier 22 | ) { 23 | Row( 24 | modifier = Modifier 25 | .padding(horizontal = 16.dp, vertical = 4.dp) 26 | .then(modifier) 27 | ) { 28 | Text( 29 | text = title, 30 | modifier = Modifier.weight(1f), 31 | color = MaterialTheme.colorScheme.onSurfaceVariant 32 | ) 33 | Column(modifier = Modifier.weight(1.4f)) { 34 | SelectionContainer { 35 | Text(text = info ?: stringResource(R.string.unknown)) 36 | } 37 | } 38 | } 39 | } 40 | 41 | @Preview(showBackground = true) 42 | @Composable 43 | fun MediaInfoPreview() { 44 | MoeListTheme { 45 | MediaInfoView( 46 | title = "Title", 47 | info = "Hello this is a large anime title do you like it?" 48 | ) 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/editmedia/EditMediaEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.editmedia 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaNode 4 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 5 | import com.axiel7.moelist.data.model.media.ListStatus 6 | import com.axiel7.moelist.ui.base.event.UiEvent 7 | import java.time.LocalDate 8 | 9 | interface EditMediaEvent : UiEvent { 10 | 11 | fun setMediaInfo(value: BaseMediaNode) 12 | 13 | fun setEditVariables(myListStatus: BaseMyListStatus) 14 | 15 | fun onChangeStatus(value: ListStatus) 16 | 17 | fun onChangeProgress(value: Int?) 18 | 19 | fun onChangeVolumeProgress(value: Int?) 20 | 21 | fun onChangeScore(value: Int) 22 | 23 | fun onChangeStartDate(value: LocalDate?) 24 | 25 | fun openStartDatePicker() 26 | 27 | fun onChangeFinishDate(value: LocalDate?) 28 | 29 | fun openFinishDatePicker() 30 | 31 | fun closeDatePickers() 32 | 33 | fun onChangeTags(value: String) 34 | 35 | fun onChangePriority(value: Int) 36 | 37 | fun onChangeIsRepeating(value: Boolean) 38 | 39 | fun onChangeRepeatCount(value: Int?) 40 | 41 | fun onChangeRepeatValue(value: Int) 42 | 43 | fun onChangeComments(value: String) 44 | 45 | fun updateListItem() 46 | 47 | fun toggleDeleteDialog(open: Boolean) 48 | 49 | fun deleteEntry() 50 | 51 | fun onDismiss() 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/editmedia/EditMediaUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.editmedia 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaNode 4 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 5 | import com.axiel7.moelist.data.model.media.ListStatus 6 | import com.axiel7.moelist.data.model.media.MediaType 7 | import com.axiel7.moelist.ui.base.state.UiState 8 | import java.time.LocalDate 9 | 10 | data class EditMediaUiState( 11 | val mediaType: MediaType, 12 | val status: ListStatus = if (mediaType == MediaType.ANIME) ListStatus.PLAN_TO_WATCH else ListStatus.PLAN_TO_READ, 13 | val progress: Int? = null, 14 | val volumeProgress: Int? = null, 15 | val score: Int = 0, 16 | val startDate: LocalDate? = null, 17 | val finishDate: LocalDate? = null, 18 | val isRepeating: Boolean = false, 19 | val repeatCount: Int? = null, 20 | val repeatValue: Int = 0, 21 | val priority: Int = 0, 22 | val tags: String? = null, 23 | val comments: String? = null, 24 | val openStartDatePicker: Boolean = false, 25 | val openFinishDatePicker: Boolean = false, 26 | val openDeleteDialog: Boolean = false, 27 | val updateSuccess: Boolean? = null, 28 | val removed: Boolean = false, 29 | val mediaInfo: BaseMediaNode? = null, 30 | val myListStatus: BaseMyListStatus? = null, 31 | override val isLoading: Boolean = false, 32 | override val message: String? = null 33 | ) : UiState() { 34 | override fun setLoading(value: Boolean) = copy(isLoading = value) 35 | override fun setMessage(value: String?) = copy(message = value) 36 | 37 | val isNewEntry 38 | get() = myListStatus == null 39 | 40 | val isPlanned 41 | get() = isNewEntry || status.isPlanning() 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/editmedia/composables/DeleteMediaEntryDialog.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.editmedia.composables 2 | 3 | import androidx.compose.material3.AlertDialog 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.res.stringResource 8 | import com.axiel7.moelist.R 9 | 10 | @Composable 11 | fun DeleteMediaEntryDialog( 12 | onConfirm: () -> Unit, 13 | onDismiss: () -> Unit 14 | ) { 15 | AlertDialog( 16 | onDismissRequest = onDismiss, 17 | confirmButton = { 18 | TextButton(onClick = onConfirm) { 19 | Text(text = stringResource(R.string.ok)) 20 | } 21 | }, 22 | dismissButton = { 23 | TextButton(onClick = onDismiss) { 24 | Text(text = stringResource(R.string.cancel)) 25 | } 26 | }, 27 | title = { Text(text = stringResource(R.string.delete)) }, 28 | text = { Text(text = stringResource(R.string.delete_confirmation)) } 29 | ) 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/editmedia/composables/EditMediaDatePicker.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.editmedia.composables 2 | 3 | import androidx.compose.material3.DatePicker 4 | import androidx.compose.material3.DatePickerDialog 5 | import androidx.compose.material3.DatePickerState 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.Text 8 | import androidx.compose.material3.TextButton 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.derivedStateOf 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.ui.res.stringResource 14 | import com.axiel7.moelist.R 15 | 16 | 17 | @OptIn(ExperimentalMaterial3Api::class) 18 | @Composable 19 | fun EditMediaDatePicker( 20 | datePickerState: DatePickerState, 21 | onDateSelected: (Long) -> Unit, 22 | onDismiss: () -> Unit, 23 | ) { 24 | val dateConfirmEnabled by remember { 25 | derivedStateOf { datePickerState.selectedDateMillis != null } 26 | } 27 | 28 | DatePickerDialog( 29 | onDismissRequest = onDismiss, 30 | confirmButton = { 31 | TextButton( 32 | onClick = { 33 | onDateSelected(datePickerState.selectedDateMillis!!) 34 | }, 35 | enabled = dateConfirmEnabled 36 | ) { 37 | Text(text = stringResource(R.string.ok)) 38 | } 39 | }, 40 | dismissButton = { 41 | TextButton(onClick = onDismiss) { 42 | Text(text = stringResource(R.string.cancel)) 43 | } 44 | } 45 | ) { 46 | DatePicker(state = datePickerState) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/home/HomeEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.home 2 | 3 | import com.axiel7.moelist.ui.base.event.UiEvent 4 | 5 | interface HomeEvent : UiEvent { 6 | fun initRequestChain(isLoggedIn: Boolean) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/home/HomeUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.home 2 | 3 | import androidx.compose.runtime.Immutable 4 | import com.axiel7.moelist.data.model.anime.AnimeList 5 | import com.axiel7.moelist.data.model.anime.AnimeRanking 6 | import com.axiel7.moelist.data.model.anime.AnimeSeasonal 7 | import com.axiel7.moelist.ui.base.state.UiState 8 | 9 | @Immutable 10 | data class HomeUiState( 11 | val todayAnimes: List = emptyList(), 12 | val seasonAnimes: List = emptyList(), 13 | val recommendedAnimes: List = emptyList(), 14 | override val isLoading: Boolean = true, 15 | override val message: String? = null 16 | ) : UiState() { 17 | override fun setLoading(value: Boolean) = copy(isLoading = value) 18 | override fun setMessage(value: String?) = copy(message = value) 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/MoreEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more 2 | 3 | interface MoreEvent { 4 | fun logOut() 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/MoreViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.axiel7.moelist.data.repository.LoginRepository 6 | import kotlinx.coroutines.launch 7 | 8 | class MoreViewModel( 9 | private val loginRepository: LoginRepository 10 | ) : ViewModel(), MoreEvent { 11 | override fun logOut() { 12 | viewModelScope.launch { 13 | loginRepository.logOut() 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/composables/FeedbackDialog.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.composables 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material3.AlertDialog 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TextButton 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.platform.LocalContext 9 | import androidx.compose.ui.res.stringResource 10 | import com.axiel7.moelist.R 11 | import com.axiel7.moelist.utils.ContextExtensions.openAction 12 | import com.axiel7.moelist.utils.DISCORD_SERVER_URL 13 | import com.axiel7.moelist.utils.GITHUB_ISSUES_URL 14 | 15 | @Composable 16 | fun FeedbackDialog( 17 | onDismiss: () -> Unit 18 | ) { 19 | val context = LocalContext.current 20 | AlertDialog( 21 | onDismissRequest = onDismiss, 22 | confirmButton = { 23 | TextButton(onClick = onDismiss) { 24 | Text(text = stringResource(R.string.cancel)) 25 | } 26 | }, 27 | text = { 28 | Column { 29 | MoreItem( 30 | title = stringResource(R.string.github), 31 | icon = R.drawable.ic_github, 32 | onClick = { 33 | context.openAction(GITHUB_ISSUES_URL) 34 | } 35 | ) 36 | MoreItem( 37 | title = stringResource(R.string.discord), 38 | icon = R.drawable.ic_discord, 39 | onClick = { 40 | context.openAction(DISCORD_SERVER_URL) 41 | } 42 | ) 43 | } 44 | } 45 | ) 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/notifications/NotificationsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.notifications 2 | 3 | import com.axiel7.moelist.ui.base.event.UiEvent 4 | 5 | interface NotificationsEvent : UiEvent { 6 | fun removeNotification(animeId: Int) 7 | fun removeAllNotifications() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/notifications/NotificationsUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.notifications 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.datastore.preferences.core.Preferences 5 | import com.axiel7.moelist.ui.base.state.UiState 6 | 7 | @Immutable 8 | data class NotificationsUiState( 9 | val notifications: Preferences? = null, //TODO: migrate to room 10 | override val isLoading: Boolean = false, 11 | override val message: String? = null 12 | ) : UiState() { 13 | override fun setLoading(value: Boolean) = copy(isLoading = value) 14 | override fun setMessage(value: String?) = copy(message = value) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/notifications/NotificationsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.notifications 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.lifecycle.viewModelScope 6 | import com.axiel7.moelist.ui.base.viewmodel.BaseViewModel 7 | import com.axiel7.moelist.worker.NotificationWorkerManager 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.launchIn 10 | import kotlinx.coroutines.flow.onEach 11 | import kotlinx.coroutines.flow.update 12 | import kotlinx.coroutines.launch 13 | 14 | class NotificationsViewModel( 15 | dataStore: DataStore, 16 | private val notificationWorkerManager: NotificationWorkerManager, 17 | ) : BaseViewModel(), NotificationsEvent { 18 | 19 | override val mutableUiState = MutableStateFlow(NotificationsUiState()) 20 | 21 | override fun removeNotification(animeId: Int) { 22 | viewModelScope.launch { 23 | notificationWorkerManager.removeAiringAnimeNotification(animeId) 24 | } 25 | } 26 | 27 | override fun removeAllNotifications() { 28 | viewModelScope.launch { 29 | notificationWorkerManager.removeAllNotifications() 30 | } 31 | } 32 | 33 | init { 34 | dataStore.data 35 | .onEach { notifications -> 36 | mutableUiState.update { it.copy(notifications = notifications) } 37 | } 38 | .launchIn(viewModelScope) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/settings/SettingsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.settings 2 | 3 | import com.axiel7.moelist.data.model.media.TitleLanguage 4 | import com.axiel7.moelist.ui.base.AppLanguage 5 | import com.axiel7.moelist.ui.base.ItemsPerRow 6 | import com.axiel7.moelist.ui.base.ListStyle 7 | import com.axiel7.moelist.ui.base.StartTab 8 | import com.axiel7.moelist.ui.base.ThemeStyle 9 | import com.axiel7.moelist.ui.base.event.UiEvent 10 | 11 | interface SettingsEvent : UiEvent { 12 | fun setLanguage(value: AppLanguage) 13 | fun setTheme(value: ThemeStyle) 14 | fun setUseBlackColors(value: Boolean) 15 | fun setShowNsfw(value: Boolean) 16 | fun setUseGeneralListStyle(value: Boolean) 17 | fun setGeneralListStyle(value: ListStyle) 18 | fun setItemsPerRow(value: ItemsPerRow) 19 | fun setStartTab(value: StartTab) 20 | fun setTitleLanguage(value: TitleLanguage) 21 | fun setUseListTabs(value: Boolean) 22 | fun setLoadCharacters(value: Boolean) 23 | fun setRandomListEntryEnabled(value: Boolean) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/settings/SettingsUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.settings 2 | 3 | import com.axiel7.moelist.data.model.media.TitleLanguage 4 | import com.axiel7.moelist.ui.base.AppLanguage 5 | import com.axiel7.moelist.ui.base.ItemsPerRow 6 | import com.axiel7.moelist.ui.base.ListStyle 7 | import com.axiel7.moelist.ui.base.StartTab 8 | import com.axiel7.moelist.ui.base.ThemeStyle 9 | import com.axiel7.moelist.ui.base.state.UiState 10 | 11 | data class SettingsUiState( 12 | val language: AppLanguage = AppLanguage.FOLLOW_SYSTEM, 13 | val theme: ThemeStyle = ThemeStyle.FOLLOW_SYSTEM, 14 | val useBlackColors: Boolean = false, 15 | val showNsfw: Boolean = false, 16 | val useGeneralListStyle: Boolean = true, 17 | val generalListStyle: ListStyle = ListStyle.STANDARD, 18 | val itemsPerRow: ItemsPerRow = ItemsPerRow.DEFAULT, 19 | val startTab: StartTab = StartTab.LAST_USED, 20 | val titleLanguage: TitleLanguage = TitleLanguage.ROMAJI, 21 | val useListTabs: Boolean = false, 22 | val loadCharacters: Boolean = false, 23 | val randomListEntryEnabled: Boolean = false, 24 | override val isLoading: Boolean = false, 25 | override val message: String? = null 26 | ) : UiState() { 27 | override fun setLoading(value: Boolean) = copy(isLoading = value) 28 | override fun setMessage(value: String?) = copy(message = value) 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/more/settings/list/ListStyleSettingsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.more.settings.list 2 | 3 | import com.axiel7.moelist.data.model.media.ListStatus 4 | import com.axiel7.moelist.data.model.media.MediaType 5 | import com.axiel7.moelist.ui.base.ListStyle 6 | import com.axiel7.moelist.ui.base.event.UiEvent 7 | import kotlinx.coroutines.flow.StateFlow 8 | 9 | interface ListStyleSettingsEvent { 10 | fun getListStyle(mediaType: MediaType, status: ListStatus): StateFlow 11 | fun setListStyle(mediaType: MediaType, status: ListStatus, value: ListStyle) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/profile/ProfileEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.profile 2 | 3 | import com.axiel7.moelist.ui.base.event.UiEvent 4 | 5 | interface ProfileEvent : UiEvent { 6 | 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/profile/ProfileUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.profile 2 | 3 | import androidx.compose.runtime.Immutable 4 | import com.axiel7.moelist.data.model.User 5 | import com.axiel7.moelist.data.model.UserStats 6 | import com.axiel7.moelist.data.model.media.ListStatus 7 | import com.axiel7.moelist.data.model.media.Stat 8 | import com.axiel7.moelist.ui.base.state.UiState 9 | 10 | @Immutable 11 | data class ProfileUiState( 12 | val user: User? = null, 13 | val profilePictureUrl: String? = null, 14 | val animeStats: List> = emptyList(), 15 | val mangaStats: List> = emptyList(), 16 | val userMangaStats: UserStats.MangaStats? = null, 17 | val isLoadingManga: Boolean = true, 18 | override val isLoading: Boolean = true, 19 | override val message: String? = null 20 | ) : UiState() { 21 | override fun setLoading(value: Boolean) = copy(isLoading = value) 22 | override fun setMessage(value: String?) = copy(message = value) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/ranking/MediaRankingEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.ranking 2 | 3 | import com.axiel7.moelist.ui.base.event.PagedUiEvent 4 | 5 | interface MediaRankingEvent : PagedUiEvent { 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/ranking/MediaRankingUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.ranking 2 | 3 | import androidx.compose.runtime.Stable 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import com.axiel7.moelist.data.model.media.BaseRanking 7 | import com.axiel7.moelist.data.model.media.RankingType 8 | import com.axiel7.moelist.ui.base.state.PagedUiState 9 | 10 | @Stable 11 | data class MediaRankingUiState( 12 | val rankingType: RankingType, 13 | val mediaList: SnapshotStateList = mutableStateListOf(), 14 | override val nextPage: String? = null, 15 | override val loadMore: Boolean = true, 16 | override val isLoading: Boolean = true, 17 | override val message: String? = null 18 | ) : PagedUiState() { 19 | override fun setLoading(value: Boolean) = copy(isLoading = value) 20 | override fun setMessage(value: String?) = copy(message = value) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/recommendations/RecommendationsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.recommendations 2 | 3 | import com.axiel7.moelist.ui.base.event.PagedUiEvent 4 | 5 | interface RecommendationsEvent : PagedUiEvent { 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/recommendations/RecommendationsUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.recommendations 2 | 3 | import androidx.compose.runtime.Stable 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import com.axiel7.moelist.data.model.anime.AnimeList 7 | import com.axiel7.moelist.ui.base.state.PagedUiState 8 | import com.axiel7.moelist.ui.base.state.UiState 9 | 10 | @Stable 11 | data class RecommendationsUiState( 12 | val animes: SnapshotStateList = mutableStateListOf(), 13 | override val nextPage: String? = null, 14 | override val loadMore: Boolean = true, 15 | override val isLoading: Boolean = true, 16 | override val message: String? = null 17 | ) : PagedUiState() { 18 | override fun setLoading(value: Boolean) = copy(isLoading = value) 19 | override fun setMessage(value: String?) = copy(message = value) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/search/SearchEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.search 2 | 3 | import com.axiel7.moelist.data.model.SearchHistory 4 | import com.axiel7.moelist.data.model.media.MediaType 5 | import com.axiel7.moelist.ui.base.event.PagedUiEvent 6 | 7 | interface SearchEvent : PagedUiEvent { 8 | fun search(query: String) 9 | fun onChangeMediaType(value: MediaType) 10 | fun onSaveSearchHistory(query: String) 11 | fun onRemoveSearchHistory(item: SearchHistory) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/search/SearchUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.search 2 | 3 | import androidx.compose.runtime.Stable 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import com.axiel7.moelist.data.model.SearchHistory 7 | import com.axiel7.moelist.data.model.media.BaseMediaList 8 | import com.axiel7.moelist.data.model.media.MediaType 9 | import com.axiel7.moelist.ui.base.state.PagedUiState 10 | 11 | @Stable 12 | data class SearchUiState( 13 | val query: String = "", 14 | val mediaType: MediaType = MediaType.ANIME, 15 | val mediaList: SnapshotStateList = mutableStateListOf(), 16 | val searchHistoryList: List = emptyList(), 17 | val performSearch: Boolean = false, 18 | val noResults: Boolean = false, 19 | override val nextPage: String? = null, 20 | override val loadMore: Boolean = false, 21 | override val isLoading: Boolean = false, 22 | override val message: String? = null 23 | ) : PagedUiState() { 24 | override fun setLoading(value: Boolean) = copy(isLoading = value) 25 | override fun setMessage(value: String?) = copy(message = value) 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/season/SeasonChartEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.season 2 | 3 | import com.axiel7.moelist.data.model.anime.Season 4 | import com.axiel7.moelist.data.model.anime.SeasonType 5 | import com.axiel7.moelist.data.model.media.MediaSort 6 | import com.axiel7.moelist.ui.base.event.PagedUiEvent 7 | 8 | interface SeasonChartEvent : PagedUiEvent { 9 | fun setSeason(season: Season? = null, year: Int? = null) 10 | fun setSeason(type: SeasonType) 11 | fun onChangeSort(value: MediaSort) 12 | fun onChangeIsNew(value: Boolean) 13 | fun onApplyFilters() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/season/SeasonChartUiState.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.season 2 | 3 | import androidx.compose.runtime.Stable 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.snapshots.SnapshotStateList 6 | import com.axiel7.moelist.data.model.anime.AnimeSeasonal 7 | import com.axiel7.moelist.data.model.anime.SeasonType 8 | import com.axiel7.moelist.data.model.anime.StartSeason 9 | import com.axiel7.moelist.data.model.media.MediaSort 10 | import com.axiel7.moelist.ui.base.state.PagedUiState 11 | import com.axiel7.moelist.utils.SeasonCalendar 12 | 13 | @Stable 14 | data class SeasonChartUiState( 15 | val season: StartSeason = SeasonCalendar.currentStartSeason, 16 | val sort: MediaSort = MediaSort.ANIME_NUM_USERS, 17 | val isNew: Boolean = true, 18 | val seasonType: SeasonType? = SeasonType.CURRENT, 19 | val animes: SnapshotStateList = mutableStateListOf(), 20 | override val nextPage: String? = null, 21 | override val loadMore: Boolean = true, 22 | override val isLoading: Boolean = true, 23 | override val message: String? = null 24 | ) : PagedUiState() { 25 | override fun setLoading(value: Boolean) = copy(isLoading = value) 26 | override fun setMessage(value: String?) = copy(message = value) 27 | 28 | companion object { 29 | private const val BASE_YEAR = 1917 30 | val years = ((SeasonCalendar.currentYear + 1) downTo BASE_YEAR).toList() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/userlist/UserMediaListEvent.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.userlist 2 | 3 | import com.axiel7.moelist.data.model.media.BaseMediaNode 4 | import com.axiel7.moelist.data.model.media.BaseMyListStatus 5 | import com.axiel7.moelist.data.model.media.BaseUserMediaList 6 | import com.axiel7.moelist.data.model.media.ListStatus 7 | import com.axiel7.moelist.data.model.media.MediaSort 8 | import com.axiel7.moelist.ui.base.event.PagedUiEvent 9 | 10 | interface UserMediaListEvent : PagedUiEvent { 11 | fun onChangeStatus(value: ListStatus) 12 | fun onChangeSort(value: MediaSort) 13 | fun onUpdateProgress(item: BaseUserMediaList) 14 | fun onItemSelected(item: BaseUserMediaList<*>) 15 | fun onChangeItemMyListStatus(value: BaseMyListStatus?, removed: Boolean = false) 16 | fun setScore(score: Int) 17 | fun refreshList() 18 | fun toggleSortDialog(open: Boolean) 19 | fun toggleSetScoreDialog(open: Boolean) 20 | fun getRandomIdOfList() 21 | fun onRandomIdOpen() 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/userlist/composables/RandomChip.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.userlist.composables 2 | 3 | import androidx.compose.material3.AssistChip 4 | import androidx.compose.material3.Icon 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.res.painterResource 9 | import androidx.compose.ui.res.stringResource 10 | import com.axiel7.moelist.R 11 | 12 | @Composable 13 | fun RandomChip( 14 | onClick: () -> Unit, 15 | modifier: Modifier = Modifier 16 | ) { 17 | AssistChip( 18 | onClick = onClick, 19 | label = { Text(text = stringResource(R.string.random)) }, 20 | modifier = modifier, 21 | leadingIcon = { 22 | Icon( 23 | painter = painterResource(R.drawable.ic_round_casino_24), 24 | contentDescription = stringResource(R.string.random) 25 | ) 26 | } 27 | ) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/userlist/composables/SetScoreDialog.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.userlist.composables 2 | 3 | import androidx.compose.material3.AlertDialog 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.runtime.mutableIntStateOf 9 | import androidx.compose.runtime.remember 10 | import androidx.compose.runtime.setValue 11 | import androidx.compose.ui.res.stringResource 12 | import com.axiel7.moelist.R 13 | import com.axiel7.moelist.data.model.media.scoreText 14 | import com.axiel7.moelist.ui.composables.score.ScoreSlider 15 | 16 | @Composable 17 | fun SetScoreDialog( 18 | onDismiss: () -> Unit, 19 | onConfirm: (Int) -> Unit, 20 | ) { 21 | var score by remember { mutableIntStateOf(0) } 22 | AlertDialog( 23 | onDismissRequest = onDismiss, 24 | confirmButton = { 25 | TextButton(onClick = { onConfirm(score) }) { 26 | Text(text = stringResource(R.string.ok)) 27 | } 28 | }, 29 | dismissButton = { 30 | TextButton(onClick = onDismiss) { 31 | Text(text = stringResource(R.string.cancel)) 32 | } 33 | }, 34 | title = { 35 | Text(text = stringResource(id = R.string.score_value, score.scoreText())) 36 | }, 37 | text = { 38 | ScoreSlider( 39 | score = score, 40 | onValueChange = { score = it } 41 | ) 42 | } 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/ui/userlist/composables/SortChip.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.ui.userlist.composables 2 | 3 | import androidx.compose.material3.AssistChip 4 | import androidx.compose.material3.Icon 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.res.painterResource 9 | import androidx.compose.ui.res.stringResource 10 | import com.axiel7.moelist.R 11 | import com.axiel7.moelist.ui.userlist.UserMediaListEvent 12 | import com.axiel7.moelist.ui.userlist.UserMediaListUiState 13 | 14 | @Composable 15 | fun SortChip( 16 | text: String, 17 | onClick: () -> Unit, 18 | modifier: Modifier = Modifier 19 | ) { 20 | AssistChip( 21 | onClick = onClick, 22 | label = { Text(text = text) }, 23 | modifier = modifier, 24 | leadingIcon = { 25 | Icon( 26 | painter = painterResource(R.drawable.ic_round_sort_24), 27 | contentDescription = stringResource(R.string.sort_by) 28 | ) 29 | } 30 | ) 31 | } 32 | 33 | @Composable 34 | fun SortChip( 35 | uiState: UserMediaListUiState, 36 | event: UserMediaListEvent?, 37 | modifier: Modifier = Modifier 38 | ) { 39 | SortChip( 40 | text = uiState.listSort?.localized() ?: stringResource(R.string.sort_by), 41 | onClick = { event?.toggleSortDialog(true) }, 42 | modifier = modifier, 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.utils 2 | 3 | //Urls 4 | const val MAL_OAUTH2_URL = "https://myanimelist.net/v1/oauth2/" 5 | const val MAL_API_URL = "https://api.myanimelist.net/v2/" 6 | const val MOELIST_PAGELINK = "moelist://moelist.page.link/" 7 | const val ANIME_URL = "https://myanimelist.net/anime/" 8 | const val MANGA_URL = "https://myanimelist.net/manga/" 9 | const val YOUTUBE_QUERY_URL = "https://www.youtube.com/results?search_query=" 10 | const val MAL_NEWS_URL = "https://myanimelist.net/news" 11 | const val MAL_ANNOUNCEMENTS_URL = "https://myanimelist.net/forum/?board=5" 12 | const val MAL_PROFILE_URL = "https://myanimelist.net/profile/" 13 | const val CHARACTER_URL = "https://myanimelist.net/character/" 14 | 15 | const val JIKAN_API_URL = "https://api.jikan.moe/v4/" 16 | 17 | const val DISCORD_SERVER_URL = "https://discord.gg/CTv3WdfxHh" 18 | const val GITHUB_REPO_URL = "https://github.com/axiel7/MoeList" 19 | const val GITHUB_ISSUES_URL = "$GITHUB_REPO_URL/issues" 20 | const val LOGO_CREDIT_URL = "https://instagram.com/danielvd_art" 21 | const val GENERAL_HELP_CREDIT_URL = "https://github.com/Jeluchu" 22 | 23 | const val UNKNOWN_CHAR = "─" -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/utils/PkceGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.utils 2 | 3 | object PkceGenerator { 4 | private val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + '-' + '.' + '_' + '~' 5 | 6 | fun generateVerifier(length: Int): String { 7 | return (1..length) 8 | .map { allowedChars.random() } 9 | .joinToString("") 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/axiel7/moelist/utils/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.axiel7.moelist.utils 2 | 3 | object StringExtensions { 4 | /** 5 | * Returns a string representation of the object. 6 | * Can be called with a null receiver, in which case it returns `null`. 7 | */ 8 | fun Any?.toStringOrNull() = this.toString().let { if (it == "null") null else it } 9 | 10 | /** 11 | * Returns a string representation of the object. 12 | * Can be called with a null receiver, in which case it returns an empty String. 13 | */ 14 | fun Any?.toStringOrEmpty() = this.toString().let { if (it == "null") "" else it } 15 | 16 | /** 17 | * Format the opening/ending text from MAL to use it on YouTube search 18 | */ 19 | fun String.buildQueryFromThemeText() = this 20 | .replace(" ", "+") 21 | .replace("\"", "") 22 | .replaceFirst(Regex("#?\\w+:"), "") // theme number 23 | .replace(Regex("\\(ep.*\\)"), "") // episodes 24 | .trim() 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_discord.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/airing_widget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/drawable/airing_widget_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_campaign.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fall_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_history_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_moelist_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_moelist_logo_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_horizontal.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new_releases.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_in_browser.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_book_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_local_movies_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_person_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_translate_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_access_time_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_add_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_arrow_forward_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_bar_chart_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_book_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_cake_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_casino_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_color_lens_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_details_star_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_edit_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_event_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_feedback_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_filter_list_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_group_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_keyboard_arrow_down_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_keyboard_arrow_up_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_language_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_local_movies_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_location_on_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_movie_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_person_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_power_settings_new_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_rss_feed_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_search_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_settings_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_sort_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_star_16.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_thumbs_up_down_16.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_thumbs_up_down_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_timer_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_trending_up_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_spring_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_summer_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_winter_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/no_adult_content_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_cancel_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pause_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/play_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_bookmark_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_check_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_content_copy_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_delete_sweep_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_drive_file_rename_outline_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_format_list_bulleted_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_grid_view_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_notes_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_notifications_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_notifications_active_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_notifications_off_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_repeat_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_share_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_title_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_anime.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_manga.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/widget_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/widget_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-hi-rIN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #BAC3FF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-or-rIN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-v29/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/xml-v25/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 12 | 13 | 18 | 22 | 23 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/xml/airing_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | val kotlinVersion = "2.0.10" 4 | id("com.android.application") version "8.5.2" apply false 5 | id("org.jetbrains.kotlin.android") version kotlinVersion apply false 6 | id("org.jetbrains.kotlin.plugin.compose") version kotlinVersion apply false 7 | id("org.jetbrains.kotlin.plugin.serialization") version kotlinVersion apply false 8 | id("com.google.devtools.ksp") version "$kotlinVersion-1.0.24" apply false 9 | } 10 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /app/src/main/res/values/strings.xml 3 | translation: /app/src/main/res/values-%android_code%/%original_file_name% 4 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | package_name("com.axiel7.moelist") 3 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:android) 17 | 18 | platform :android do 19 | desc "Build and sign APK" 20 | lane :release do 21 | gradle( 22 | task: "assemble", 23 | build_type: "release", 24 | print_command: false, 25 | properties: { 26 | "android.injected.signing.store.file" => ENV["KEYSTORE_FILE"], 27 | "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"], 28 | "android.injected.signing.key.alias" => ENV["KEY_ALIAS"], 29 | "android.injected.signing.key.password" => ENV["KEY_PASSWORD"], 30 | } 31 | ) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | android.nonTransitiveRClass=true 21 | android.nonFinalResIds=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiel7/MoeList/2e116cab489fe4f82b2f9f15bbdde1635f184a73/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { setUrl("https://www.jitpack.io") } 14 | } 15 | } 16 | rootProject.name = "MoeList" 17 | include(":app") 18 | --------------------------------------------------------------------------------