├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ └── feature_request.yaml └── workflows │ └── android.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── debug.keystore ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── saulhdev │ │ └── feeder │ │ └── data │ │ └── entity │ │ ├── FeedCategory.aidl │ │ ├── FeedItem.aidl │ │ ├── IFeedInterface.aidl │ │ └── IFeedInterfaceCallback.aidl │ ├── assets │ ├── changelog.htm │ ├── dark.css │ ├── license.htm │ └── light.css │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ ├── google │ │ └── android │ │ │ ├── a │ │ │ ├── LauncherOverlayBinder.java │ │ │ ├── a.java │ │ │ └── c.java │ │ │ ├── apps │ │ │ └── gsa │ │ │ │ └── nowoverlayservice │ │ │ │ └── ConfigurationOverlayController.kt │ │ │ └── libraries │ │ │ ├── gsa │ │ │ └── d │ │ │ │ └── a │ │ │ │ ├── BaseCallback.java │ │ │ │ ├── ByteBundleHolder.java │ │ │ │ ├── DialogListeners.java │ │ │ │ ├── DialogOverlayController.java │ │ │ │ ├── MinusOneOverlayCallback.java │ │ │ │ ├── OverlayController.java │ │ │ │ ├── OverlayControllerBinder.java │ │ │ │ ├── OverlayControllerCallback.java │ │ │ │ ├── OverlayControllerLayoutChangeListener.java │ │ │ │ ├── OverlayControllerSlidingPanelLayout.java │ │ │ │ ├── OverlayControllerStateChanger.java │ │ │ │ ├── OverlaysController.java │ │ │ │ ├── PanelState.java │ │ │ │ ├── SlidingPanelLayout.java │ │ │ │ ├── SlidingPanelLayoutInterpolator.java │ │ │ │ ├── SlidingPanelLayoutProperty.java │ │ │ │ ├── TransparentOverlayController.java │ │ │ │ ├── t.java │ │ │ │ └── v.java │ │ │ └── i │ │ │ ├── LauncherOverlayInterfaceBinder.java │ │ │ ├── a.java │ │ │ ├── d.java │ │ │ └── f.java │ │ └── saulhdev │ │ └── feeder │ │ ├── MainActivity.kt │ │ ├── NeoApp.kt │ │ ├── data │ │ ├── content │ │ │ ├── BasePreferences.kt │ │ │ └── FeedPreferences.kt │ │ ├── db │ │ │ ├── Converters.kt │ │ │ ├── NeoFeedDb.kt │ │ │ ├── dao │ │ │ │ ├── FeedArticleDao.kt │ │ │ │ └── FeedSourceDao.kt │ │ │ └── models │ │ │ │ ├── Feed.kt │ │ │ │ ├── FeedArticle.kt │ │ │ │ └── FeedItemIdWithLink.kt │ │ ├── entity │ │ │ ├── ActionStyle.kt │ │ │ ├── DividerStyle.kt │ │ │ ├── FeedCategory.kt │ │ │ ├── FeedItem.kt │ │ │ ├── FeedItemType.kt │ │ │ ├── MenuItem.kt │ │ │ └── SortFilterModel.kt │ │ └── repository │ │ │ ├── ArticleRepository.kt │ │ │ └── FeedRepository.kt │ │ ├── manager │ │ ├── launcherapi │ │ │ ├── LauncherAPI.kt │ │ │ └── OverlayThemeHolder.kt │ │ ├── models │ │ │ ├── EditFeedViewState.kt │ │ │ ├── FeedParser.kt │ │ │ ├── FullTextParser.kt │ │ │ ├── OPMLActions.kt │ │ │ ├── OPMLParser.kt │ │ │ ├── OPMLParserToDatabase.kt │ │ │ ├── OPMLToRoom.kt │ │ │ ├── StoryCardContent.kt │ │ │ └── writeFile.kt │ │ ├── plugin │ │ │ └── PluginFetcher.kt │ │ ├── service │ │ │ ├── DrawerOverlayService.kt │ │ │ └── HFPluginService.kt │ │ └── sync │ │ │ ├── FeedSyncer.kt │ │ │ ├── RssLocalSync.kt │ │ │ └── SyncRestClient.kt │ │ ├── ui │ │ ├── components │ │ │ ├── ActionPreference.kt │ │ │ ├── AnnotatedString.kt │ │ │ ├── ArticleButton.kt │ │ │ ├── ArticleItem.kt │ │ │ ├── BaseDialog.kt │ │ │ ├── BasePreference.kt │ │ │ ├── Bidi.kt │ │ │ ├── Block.kt │ │ │ ├── BookmarkItem.kt │ │ │ ├── Button.kt │ │ │ ├── Chip.kt │ │ │ ├── ComposeSwitchView.kt │ │ │ ├── ComposeWebView.kt │ │ │ ├── ContributorRow.kt │ │ │ ├── FeedItem.kt │ │ │ ├── LinkItem.kt │ │ │ ├── OverflowMenu.kt │ │ │ ├── Pager.kt │ │ │ ├── PreferenceBuilder.kt │ │ │ ├── PreferenceGroup.kt │ │ │ ├── PullToRefreshLazyColumn.kt │ │ │ ├── SeekBarPreference.kt │ │ │ ├── StringSelectionPreference.kt │ │ │ ├── StringSetPreference.kt │ │ │ ├── SwitchPreference.kt │ │ │ ├── Table.kt │ │ │ ├── TextComposer.kt │ │ │ ├── ViewWithActionBar.kt │ │ │ └── dialog │ │ │ │ ├── ActionsDialogUI.kt │ │ │ │ ├── DialogButton.kt │ │ │ │ └── StringSelectionPrefDialogUI.kt │ │ ├── compose │ │ │ ├── icon │ │ │ │ ├── __Phosphor.kt │ │ │ │ └── phosphor │ │ │ │ │ ├── ArrowCounterClockwise.kt │ │ │ │ │ ├── ArrowLeft.kt │ │ │ │ │ ├── ArrowSquareOut.kt │ │ │ │ │ ├── ArrowUUpLeft.kt │ │ │ │ │ ├── Asterisk.kt │ │ │ │ │ ├── BookBookmark.kt │ │ │ │ │ ├── Bookmarks.kt │ │ │ │ │ ├── BracketsSquare.kt │ │ │ │ │ ├── Browser.kt │ │ │ │ │ ├── Bug.kt │ │ │ │ │ ├── CaretDown.kt │ │ │ │ │ ├── CaretUp.kt │ │ │ │ │ ├── Check.kt │ │ │ │ │ ├── CheckCircle.kt │ │ │ │ │ ├── Circle.kt │ │ │ │ │ ├── Clock.kt │ │ │ │ │ ├── CloudArrowDown.kt │ │ │ │ │ ├── CloudArrowUp.kt │ │ │ │ │ ├── Copyleft.kt │ │ │ │ │ ├── DotsThreeVertical.kt │ │ │ │ │ ├── EyedropperSample.kt │ │ │ │ │ ├── FunnelSimple.kt │ │ │ │ │ ├── GearSix.kt │ │ │ │ │ ├── GithubLogo.kt │ │ │ │ │ ├── Graph.kt │ │ │ │ │ ├── Hash.kt │ │ │ │ │ ├── HeartStraight.kt │ │ │ │ │ ├── HeartStraightFill.kt │ │ │ │ │ ├── Info.kt │ │ │ │ │ ├── ListDashes.kt │ │ │ │ │ ├── Megaphone.kt │ │ │ │ │ ├── Nut.kt │ │ │ │ │ ├── PaintRoller.kt │ │ │ │ │ ├── Play.kt │ │ │ │ │ ├── Plus.kt │ │ │ │ │ ├── Power.kt │ │ │ │ │ ├── SelectionBackground.kt │ │ │ │ │ ├── ShareNetwork.kt │ │ │ │ │ ├── SortAscending.kt │ │ │ │ │ ├── SortDescending.kt │ │ │ │ │ ├── SubtractSquare.kt │ │ │ │ │ ├── Swatches.kt │ │ │ │ │ ├── TelegramLogo.kt │ │ │ │ │ ├── TrashSimple.kt │ │ │ │ │ └── WifiHigh.kt │ │ │ ├── theme │ │ │ │ ├── AppTheme.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── Dimensions.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theming.kt │ │ │ │ └── Typography.kt │ │ │ └── util │ │ │ │ ├── KeyEvents.kt │ │ │ │ ├── Modifiers.kt │ │ │ │ └── PaddingValues.kt │ │ ├── feed │ │ │ ├── FeedAdapter.kt │ │ │ └── binders │ │ │ │ ├── FeedBinder.kt │ │ │ │ └── StoryCardBinder.kt │ │ ├── navigation │ │ │ ├── NavigationManager.kt │ │ │ ├── NavigationSuite.kt │ │ │ └── PageItem.kt │ │ ├── overlay │ │ │ ├── ComposeOverlayView.kt │ │ │ └── OverlayView.kt │ │ ├── pages │ │ │ ├── AboutPage.kt │ │ │ ├── AddFeedPage.kt │ │ │ ├── ArticlePage.kt │ │ │ ├── EditFeedPage.kt │ │ │ ├── MainPage.kt │ │ │ ├── OverlayPage.kt │ │ │ ├── PreferencesPage.kt │ │ │ ├── SortFilterSheet.kt │ │ │ └── SourcesPage.kt │ │ └── views │ │ │ ├── DialogMenu.kt │ │ │ ├── HtmlToPlainTextConverter.kt │ │ │ ├── MenuAdapter.kt │ │ │ ├── MenuDividerItem.kt │ │ │ └── MenuListView.kt │ │ ├── utils │ │ ├── ApplicationCoroutineScope.kt │ │ ├── Blob.kt │ │ ├── FeederUtils.kt │ │ ├── HtmlToComposable.kt │ │ ├── JsonFeedParser.kt │ │ ├── LinearLayoutManagerWrapper.kt │ │ ├── OkHttpBuilderExtensions.kt │ │ ├── OverlayBridge.kt │ │ ├── PopupContentAnimator.kt │ │ ├── PopupWindowImpl.kt │ │ ├── RelativeTimeHelper.kt │ │ ├── RomeExtensionsKt.kt │ │ ├── VideoTagHunter.kt │ │ ├── extensions │ │ │ ├── Context.kt │ │ │ ├── Koin.kt │ │ │ └── KtExtensions.kt │ │ └── openLinkInCustomTab.kt │ │ └── viewmodels │ │ ├── ArticleViewModel.kt │ │ ├── ArticlesViewModel.kt │ │ ├── EditFeedViewModel.kt │ │ ├── FeedsViewModel.kt │ │ └── SearchFeedViewModel.kt │ └── res │ ├── drawable-hdpi │ └── vkim_bg_overlay.9.png │ ├── drawable-mdpi │ └── vkim_bg_overlay.9.png │ ├── drawable-xhdpi │ └── vkim_bg_overlay.9.png │ ├── drawable-xxhdpi │ └── vkim_bg_overlay.9.png │ ├── drawable-xxxhdpi │ └── vkim_bg_overlay.9.png │ ├── drawable │ ├── ic_arrow_clockwise.xml │ ├── ic_caret_up.xml │ ├── ic_cloud_arrow_down.xml │ ├── ic_cloud_arrow_up.xml │ ├── ic_gear.xml │ ├── ic_heart.xml │ ├── ic_heart_fill.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_monochrome.xml │ ├── ic_notification.xml │ ├── ic_power.xml │ ├── ic_settings.xml │ ├── ic_share.xml │ ├── ic_trash_simple.xml │ ├── ic_youtube.xml │ └── placeholder_image_article_day.xml │ ├── font │ └── kingthings_printingkit.ttf │ ├── layout │ ├── compose_overlay.xml │ ├── feed_card_story_large.xml │ ├── menu_item.xml │ ├── menu_list.xml │ ├── overlay_header.xml │ └── overlay_layout.xml │ ├── mipmap-anydpi │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-ar │ └── strings.xml │ ├── values-cs │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fa │ └── strings.xml │ ├── values-fil │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-hi │ └── strings.xml │ ├── values-ia │ └── strings.xml │ ├── values-in │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-lv │ └── strings.xml │ ├── values-nb-rNO │ └── strings.xml │ ├── values-night │ ├── bools.xml │ ├── colors.xml │ └── styles.xml │ ├── values-nn │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-ro │ └── strings.xml │ ├── values-ru │ ├── arrays.xml │ ├── plurals.xml │ └── strings.xml │ ├── values-ta │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-v27 │ └── styles.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── bools.xml │ ├── colors.xml │ ├── plurals.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml ├── badge_github.png ├── badge_izzy.png ├── build.gradle.kts ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 1700.txt │ ├── 1701.txt │ ├── 1703.txt │ ├── 1800.txt │ ├── 50.txt │ ├── 55.txt │ └── 57.txt │ ├── full_description.pl │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ └── 04.png │ └── short_description.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── neo_banner.png └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: [ "https://www.paypal.com/paypalme/saulhdev", "https://strike.me/saulhdev" ] 4 | github: saulhdev # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 5 | patreon: saulhdev # Replace with a single Patreon username 6 | open_collective: # Replace with a single Open Collective username 7 | ko_fi: # Replace with a single Ko-fi username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | liberapay: # Replace with a single Liberapay username 11 | issuehunt: # Replace with a single IssueHunt username 12 | otechie: # Replace with a single Otechie username 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Translate Neo Feed 4 | url: https://hosted.weblate.org/engage/neo-feed/ 5 | about: Help translate Neo Feed to your language on Weblate 6 | - name: Matrix Community 7 | url: https://matrix.to/#/#neo-launcher:matrix.org 8 | about: 'Join our Matrix community group!' 9 | - name: Telegram Community 10 | url: https://t.me/neo_launcher 11 | about: 'Join our Telegram community group!' 12 | - name: Discussions 13 | url: https://github.com/NeoApplications/Neo-Feed/discussions 14 | about: View discussions or start one yourself. But be aware that our Telegram & Matrix groups are the preferred way for discussions. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for Neo Feed 3 | title: "[Feature Request] " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | **README: Before You Submit Your Issue** 9 | - Issues are not a place to go ask support questions or start discussions. 10 | - Please ask support questions in our [Telegram](https://t.me/neo_launcher) or [Matrix](https://matrix.to/#/#neo-launcher:matrix.org) groups or start discussions on the [discussions page](https://github.com/NeoApplications/Neo-Feed/discussions). 11 | - type: checkboxes 12 | attributes: 13 | label: Guidelines 14 | description: Please ensure you've completed all of the following. 15 | options: 16 | - label: I have searched the issue tracker for [open](https://github.com/NeoApplications/Neo-Feed/issues) and [closed](https://github.com/NeoApplications/Neo-Feed/issues?q=is%3Aissue+is%3Aclosed) issues that are similar to the feature request I want to file, without success. 17 | required: true 18 | - label: I'm on the latest version. 19 | required: true 20 | - label: I'm not using a test build (alpha/beta/release-candidate). 21 | required: true 22 | - label: This issue contains only one feature request. 23 | required: true 24 | - type: textarea 25 | attributes: 26 | label: Problem Description 27 | description: Please add a clear and concise description of the problem you are seeking to solve with this feature request. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Proposed Solution 33 | description: Describe the solution you'd like. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Alternatives Considered 39 | description: Describe alternatives you've considered. 40 | validations: 41 | required: false 42 | - type: textarea 43 | attributes: 44 | label: Relevant information 45 | description: | 46 | Feel free to add any other context or media about the feature request here. 47 | value: | 48 | - App's Version: 49 | - Device: 50 | - Android Version: 51 | - ROM: (AOSP, CalyxOS, MIUI, GOS...) 52 | - Launcher: (Neo Launcher, Lawnchair...) 53 | - type: markdown 54 | attributes: 55 | value: | 56 | **Thanks for contributing ideas!** 57 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Neo Feeder CI 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v3 15 | with: 16 | java-version: 17 17 | distribution: 'temurin' 18 | cache: gradle 19 | - uses: actions/cache@v3 20 | with: 21 | path: | 22 | ~/.gradle/caches 23 | ~/.gradle/wrapper 24 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 25 | restore-keys: | 26 | ${{ runner.os }}-gradle- 27 | - name: Grant execute permission for gradlew 28 | run: chmod +x gradlew 29 | - name: Compile with Gradle 30 | run: ./gradlew assembleDebug 31 | 32 | - name: Save name of our Artifact 33 | id: set-result-artifact 34 | run: | 35 | ARTIFACT_PATHNAME_APK=$(ls app/build/outputs/apk/debug/*.apk | head -n 1) 36 | ARTIFACT_NAME_APK=$(basename $ARTIFACT_PATHNAME_APK) 37 | echo "ARTIFACT_NAME_APK is " ${ARTIFACT_NAME_APK} 38 | echo "ARTIFACT_PATHNAME_APK=${ARTIFACT_PATHNAME_APK}" >> $GITHUB_ENV 39 | echo "ARTIFACT_NAME_APK=${ARTIFACT_NAME_APK}" >> $GITHUB_ENV 40 | 41 | - uses: actions/upload-artifact@v3 42 | with: 43 | name: ${{ env.ARTIFACT_NAME_APK }} 44 | path: ${{ env.ARTIFACT_PATHNAME_APK }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Java class files 2 | *.class 3 | 4 | # Generated files 5 | .project 6 | .classpath 7 | .project.properties 8 | bin/ 9 | gen/ 10 | out/ 11 | .idea/ 12 | 13 | # Gradle files 14 | .gradle/ 15 | build/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Built application files 21 | *.apk 22 | *.ap_ 23 | *.aab 24 | release/ 25 | neo/ 26 | 27 | # IntelliJ 28 | .kotlin/ 29 | *.iml 30 | .idea 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/debug.keystore -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | -keep class com.google.android.libraries.launcherclient.** {*;} 9 | -keep class ua.itaysonlab.hfsdk.** { *; } 10 | 11 | # For Okio 12 | # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. 13 | -dontwarn org.codehaus.mojo.animal_sniffer.* 14 | 15 | # For TagSoup 16 | -keep class org.ccil.cowan.tagsoup.** { *; } 17 | 18 | # For Jsoup 19 | -keep class org.jsoup.** { *; } 20 | 21 | # For Rome 22 | -keep class com.rometools.** { *; } 23 | -keep class com.rometools.rome.** { *; } 24 | -keep class com.rometools.rome.feed.synd.impl.ConverterForAtom10 { *; } 25 | -keep class com.rometools.rome.feed.synd.SyndFeedImpl { *; } 26 | 27 | ## Autogenerated in missing_rules.txt deep in build folder 28 | -dontwarn com.android.org.conscrypt.SSLParametersImpl 29 | -dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl 30 | -dontwarn org.bouncycastle.jsse.BCSSLParameters 31 | -dontwarn org.bouncycastle.jsse.BCSSLSocket 32 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider 33 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters 34 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket 35 | -dontwarn org.openjsse.net.ssl.OpenJSSE 36 | -dontwarn org.slf4j.impl.StaticLoggerBinder -------------------------------------------------------------------------------- /app/src/main/aidl/com/saulhdev/feeder/data/entity/FeedCategory.aidl: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity; 2 | 3 | parcelable FeedCategory; -------------------------------------------------------------------------------- /app/src/main/aidl/com/saulhdev/feeder/data/entity/FeedItem.aidl: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity; 2 | 3 | parcelable FeedItem; -------------------------------------------------------------------------------- /app/src/main/aidl/com/saulhdev/feeder/data/entity/IFeedInterface.aidl: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity; 2 | 3 | /** 4 | * Main feed service 5 | */ 6 | import com.saulhdev.feeder.data.entity.FeedItem; 7 | import com.saulhdev.feeder.data.entity.IFeedInterfaceCallback; 8 | 9 | interface IFeedInterface { 10 | // Get main feed by page with specific parameters (can be null) 11 | // Page contains variable item count 12 | // default category - "default", this can be unified feed or other... 13 | void getFeed(in IFeedInterfaceCallback callback, in int page, in String category_id, in Bundle parameters); 14 | 15 | // Get feed categories 16 | void getCategories(in IFeedInterfaceCallback callback); 17 | } -------------------------------------------------------------------------------- /app/src/main/aidl/com/saulhdev/feeder/data/entity/IFeedInterfaceCallback.aidl: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity; 2 | 3 | /** 4 | * Main feed service [callback] 5 | */ 6 | import com.saulhdev.feeder.data.entity.FeedItem; 7 | import com.saulhdev.feeder.data.entity.FeedCategory; 8 | 9 | interface IFeedInterfaceCallback { 10 | // When service receives feed data 11 | void onFeedReceive(in List feed); 12 | 13 | void onCategoriesReceive(in List categories); 14 | } -------------------------------------------------------------------------------- /app/src/main/assets/dark.css: -------------------------------------------------------------------------------- 1 | html,body{ 2 | color:#fafafa; 3 | background:#303030;} 4 | 5 | a:link { 6 | color: #ff7d00; 7 | text-decoration: none; 8 | } 9 | a:visited { 10 | color: #ffca7d; 11 | text-decoration: none; 12 | } 13 | 14 | hr{ 15 | border: none; 16 | height: 1px; 17 | color: #FFFFFF; 18 | background-color: #FFFFFF; 19 | } 20 | .license .name{ 21 | font-weight:bold; 22 | color:#FFFFFF; 23 | font-size: 20px; 24 | margin-bottom: 0px; 25 | padding-bottom: 0px; 26 | } 27 | 28 | .license .developer{ 29 | margin-top: 1px; 30 | margin-bottom: 0px; 31 | padding-bottom: 0px; 32 | color: #FFFFFF; 33 | } 34 | .license .source{ 35 | margin-top: 1px; 36 | color: #FFFFFF; 37 | } 38 | 39 | .topButton { 40 | box-shadow:inset 0px -3px 7px 0px #29bbff; 41 | background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%); 42 | background-color:#2dabf9; 43 | border-radius:3px; 44 | border:1px solid #0b0e07; 45 | display:inline-block; 46 | cursor:pointer; 47 | color:#ffffff; 48 | font-family:Arial; 49 | font-size:15px; 50 | padding:9px 23px; 51 | text-decoration:none; 52 | text-shadow:0px 1px 0px #263666; 53 | } 54 | 55 | .topButton:link {color: #ffffff;} 56 | .topButton:visited {color: #ffffff;} 57 | 58 | .topButton:hover { 59 | background:linear-gradient(to bottom, #0688fa 5%, #2dabf9 100%); 60 | background-color:#0688fa; 61 | } 62 | .topButton:active { 63 | position:relative; 64 | top:1px; 65 | } -------------------------------------------------------------------------------- /app/src/main/assets/light.css: -------------------------------------------------------------------------------- 1 | html,body{color:#212121;background:#fafafa;} 2 | a:link {color: #ff7d00;} 3 | a:visited {color: #ff610a;} 4 | 5 | .license .name{ 6 | font-weight:bold; 7 | color:#1e1e1e; 8 | font-size: 16px; 9 | margin-bottom: 0px; 10 | padding-bottom: 0px; 11 | } 12 | 13 | .license .developer{ 14 | margin-top: 1px; 15 | margin-bottom: 0px; 16 | padding-bottom: 0px; 17 | color: #4f4f4f; 18 | } 19 | .license .source{ 20 | margin-top: 1px; 21 | color: #4f4f4f; 22 | } 23 | 24 | .topButton { 25 | box-shadow:inset 0px -3px 7px 0px #29bbff; 26 | background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%); 27 | background-color:#2dabf9; 28 | border-radius:3px; 29 | border:1px solid #0b0e07; 30 | display:inline-block; 31 | cursor:pointer; 32 | color:#ffffff; 33 | font-family:Arial; 34 | font-size:15px; 35 | padding:9px 23px; 36 | text-decoration:none; 37 | text-shadow:0px 1px 0px #263666; 38 | } 39 | 40 | .topButton:link {color: #ffffff;} 41 | .topButton:visited {color: #ffffff;} 42 | 43 | .topButton:hover { 44 | background:linear-gradient(to bottom, #0688fa 5%, #2dabf9 100%); 45 | background-color:#0688fa; 46 | } 47 | .topButton:active { 48 | position:relative; 49 | top:1px; 50 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/a/LauncherOverlayBinder.java: -------------------------------------------------------------------------------- 1 | package com.google.android.a; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.Parcel; 7 | import android.os.RemoteException; 8 | 9 | public class LauncherOverlayBinder extends Binder implements IInterface { 10 | public IBinder asBinder() { 11 | return this; 12 | } 13 | 14 | protected final boolean a(int i, Parcel parcel, Parcel parcel2, int i2) throws RemoteException {//Todo: throws is new 15 | if (i > 16777215) { 16 | return super.onTransact(i, parcel, parcel2, i2); 17 | } 18 | parcel.enforceInterface(getInterfaceDescriptor()); 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/a/a.java: -------------------------------------------------------------------------------- 1 | package com.google.android.a; 2 | 3 | import android.os.IBinder; 4 | import android.os.IInterface; 5 | import android.os.Parcel; 6 | import android.os.RemoteException; 7 | 8 | public class a implements IInterface { 9 | private final String bHh; 10 | private final IBinder binder; 11 | 12 | protected a(IBinder iBinder, String str) { 13 | this.binder = iBinder; 14 | this.bHh = str; 15 | } 16 | 17 | public IBinder asBinder() { 18 | return this.binder; 19 | } 20 | 21 | protected final Parcel pg() { 22 | Parcel obtain = Parcel.obtain(); 23 | obtain.writeInterfaceToken(this.bHh); 24 | return obtain; 25 | } 26 | 27 | //Todo: different from source 28 | public final Parcel a(int i, Parcel parcel) { 29 | IBinder iBinder; 30 | Parcel obtain = Parcel.obtain(); 31 | try { 32 | iBinder = this.binder; 33 | iBinder.transact(i, parcel, obtain, 0); 34 | obtain.readException(); 35 | } catch (RuntimeException | RemoteException e) { 36 | parcel = null; 37 | //throw e; 38 | } finally { 39 | obtain.recycle(); 40 | } 41 | return parcel; 42 | } 43 | 44 | //Todo: different from source 45 | protected final void c(int i, Parcel parcel) { 46 | try { 47 | this.binder.transact(i, parcel, null, 1); 48 | } catch (RemoteException e) { 49 | e.printStackTrace(); 50 | } finally { 51 | parcel.recycle(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/a/c.java: -------------------------------------------------------------------------------- 1 | package com.google.android.a; 2 | 3 | import android.os.IInterface; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | import android.os.Parcelable.Creator; 7 | 8 | public class c { 9 | private c() { 10 | } 11 | 12 | public static boolean a(Parcel parcel) { 13 | return parcel.readInt() == 1; 14 | } 15 | 16 | public static void a(Parcel parcel, boolean z) { 17 | parcel.writeInt(z ? 1 : 0); 18 | } 19 | 20 | public static Parcelable a(Parcel parcel, Creator creator) { 21 | if (parcel.readInt() == 0) { 22 | return null; 23 | } 24 | return (Parcelable) creator.createFromParcel(parcel); 25 | } 26 | 27 | public static void a(Parcel parcel, Parcelable parcelable) { 28 | if (parcelable == null) { 29 | parcel.writeInt(0); 30 | return; 31 | } 32 | parcel.writeInt(1); 33 | parcelable.writeToParcel(parcel, 0); 34 | } 35 | 36 | public static void a(Parcel parcel, IInterface iInterface) { 37 | if (iInterface == null) { 38 | parcel.writeStrongBinder(null); 39 | } else { 40 | parcel.writeStrongBinder(iInterface.asBinder()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/apps/gsa/nowoverlayservice/ConfigurationOverlayController.kt: -------------------------------------------------------------------------------- 1 | package com.google.android.apps.gsa.nowoverlayservice 2 | 3 | import android.app.Service 4 | import android.content.res.Configuration 5 | import com.google.android.libraries.gsa.d.a.OverlayController 6 | import com.google.android.libraries.gsa.d.a.OverlaysController 7 | import com.google.android.libraries.gsa.d.a.v 8 | import com.saulhdev.feeder.ui.overlay.OverlayView 9 | 10 | class ConfigurationOverlayController(private val service: Service) : OverlaysController(service) { 11 | override fun Hx() = 24 12 | 13 | override fun createController( 14 | configuration: Configuration?, 15 | i: Int, 16 | i2: Int 17 | ): OverlayController = OverlayView( 18 | if (configuration != null) service.createConfigurationContext(configuration) else service 19 | ) 20 | 21 | override fun HA() = v() 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/BaseCallback.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.os.Handler.Callback; 4 | import android.os.Message; 5 | 6 | import java.io.PrintWriter; 7 | 8 | class BaseCallback implements Callback { 9 | 10 | BaseCallback() { 11 | } 12 | 13 | public boolean handleMessage(Message message) { 14 | return true; 15 | } 16 | 17 | public void dump(PrintWriter printWriter, String str) { 18 | printWriter.println(String.valueOf(str).concat("BaseCallback: nothing to dump")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/ByteBundleHolder.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.os.Bundle; 4 | 5 | final class ByteBundleHolder { 6 | 7 | private final Bundle extras; 8 | private final byte[] bytes; 9 | 10 | public ByteBundleHolder(byte[] bArr, Bundle bundle) { 11 | this.bytes = bArr; 12 | this.extras = bundle; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/DialogListeners.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.content.DialogInterface.OnDismissListener; 4 | import android.content.DialogInterface.OnShowListener; 5 | 6 | interface DialogListeners extends OnDismissListener, OnShowListener { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/OverlayControllerLayoutChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | import android.view.View.OnLayoutChangeListener; 6 | import android.view.WindowManager.LayoutParams; 7 | 8 | final class OverlayControllerLayoutChangeListener implements OnLayoutChangeListener { 9 | 10 | private final OverlayController overlayController; 11 | 12 | OverlayControllerLayoutChangeListener(OverlayController overlayControllerVar) { 13 | this.overlayController = overlayControllerVar; 14 | } 15 | 16 | public final void onLayoutChange(View view, int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8) { 17 | this.overlayController.window.getDecorView().removeOnLayoutChangeListener(this); 18 | if (this.overlayController.panelState == PanelState.CLOSED) {//Todo: PanelState.uoe was default 19 | OverlayController overlayControllerVar = this.overlayController; 20 | LayoutParams attributes = overlayControllerVar.window.getAttributes(); 21 | float f = attributes.alpha; 22 | attributes.alpha = 0.0f; 23 | if (f != attributes.alpha) { 24 | overlayControllerVar.window.setAttributes(attributes); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/PanelState.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | public enum PanelState { 4 | CLOSED,//Todo: PanelState.uoe was default 5 | DRAGGING,//Todo: PanelState.uof was default 6 | OPEN_AS_DRAWER,//Todo: PanelState.uog was default 7 | OPEN_AS_LAYER//Todo: PanelState.uoh was default 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/SlidingPanelLayoutInterpolator.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.view.animation.Interpolator; 7 | 8 | final class SlidingPanelLayoutInterpolator extends AnimatorListenerAdapter implements Interpolator { 9 | 10 | private ObjectAnimator mAnimator; 11 | int mFinalX; 12 | private final SlidingPanelLayout slidingPanelLayout; 13 | 14 | public SlidingPanelLayoutInterpolator(SlidingPanelLayout slidingPanelLayoutVar) { 15 | this.slidingPanelLayout = slidingPanelLayoutVar; 16 | } 17 | 18 | public final void cnP() { 19 | if (this.mAnimator != null) { 20 | this.mAnimator.removeAllListeners(); 21 | this.mAnimator.cancel(); 22 | } 23 | } 24 | 25 | public final void dt(int i, int i2) { 26 | cnP(); 27 | this.mFinalX = i; 28 | if (i2 > 0) { 29 | this.mAnimator = ObjectAnimator.ofInt(this.slidingPanelLayout, SlidingPanelLayout.PANEL_X, i).setDuration((long) i2); 30 | this.mAnimator.setInterpolator(this); 31 | this.mAnimator.addListener(this); 32 | this.mAnimator.start(); 33 | return; 34 | } 35 | onAnimationEnd(null); 36 | } 37 | 38 | public final boolean isFinished() { 39 | return this.mAnimator == null; 40 | } 41 | 42 | public final void onAnimationEnd(Animator animator) { 43 | this.mAnimator = null; 44 | this.slidingPanelLayout.BM(this.mFinalX); 45 | SlidingPanelLayout slidingPanelLayoutVar = this.slidingPanelLayout; 46 | if (slidingPanelLayoutVar.mSettling) { 47 | slidingPanelLayoutVar.mSettling = false; 48 | if (slidingPanelLayoutVar.uoC == 0) { 49 | slidingPanelLayoutVar.cnO(); 50 | slidingPanelLayoutVar.mIsPanelOpen = false; 51 | slidingPanelLayoutVar.mIsPageMoving = false; 52 | if (slidingPanelLayoutVar.uoH != null) { 53 | slidingPanelLayoutVar.uoH.close(); 54 | } 55 | } else if (slidingPanelLayoutVar.uoC == slidingPanelLayoutVar.getMeasuredWidth()) { 56 | slidingPanelLayoutVar.cnG(); 57 | } 58 | } 59 | } 60 | 61 | public final float getInterpolation(float f) { 62 | float f2 = f - 1.0f; 63 | return (f2 * (((f2 * f2) * f2) * f2)) + 1.0f; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/SlidingPanelLayoutProperty.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.util.Property; 4 | 5 | final class SlidingPanelLayoutProperty extends Property { 6 | 7 | SlidingPanelLayoutProperty(Class cls, String str) { 8 | super(cls, str); 9 | } 10 | 11 | public Object get(Object obj) { 12 | return ((SlidingPanelLayout) obj).uoC; 13 | } 14 | 15 | public void set(Object obj, Object obj2) { 16 | ((SlidingPanelLayout) obj).BM((Integer) obj2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/TransparentOverlayController.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | import android.os.Build; 4 | import android.view.WindowManager.LayoutParams; 5 | 6 | final class TransparentOverlayController implements t { 7 | 8 | private final OverlayController overlayController; 9 | 10 | TransparentOverlayController(OverlayController overlayControllerVar) { 11 | this.overlayController = overlayControllerVar; 12 | } 13 | 14 | public final void drag() { 15 | 16 | } 17 | 18 | public final void cnF() { 19 | } 20 | 21 | public final void oc(boolean z) { 22 | } 23 | 24 | public final void open() { 25 | this.overlayController.setVisible(true); 26 | OverlayController overlayControllerVar = this.overlayController; 27 | LayoutParams attributes = overlayControllerVar.window.getAttributes(); 28 | float f = attributes.alpha; 29 | attributes.alpha = 1.0f; 30 | if (f != attributes.alpha) { 31 | overlayControllerVar.window.setAttributes(attributes); 32 | } 33 | overlayControllerVar = this.overlayController; 34 | PanelState panelStateVar = PanelState.OPEN_AS_LAYER;//Todo: PanelState.uoh was default 35 | if (overlayControllerVar.panelState != panelStateVar) { 36 | overlayControllerVar.panelState = panelStateVar; 37 | overlayControllerVar.setState(overlayControllerVar.panelState); 38 | } 39 | } 40 | 41 | public final void close() { 42 | OverlayController overlayControllerVar = this.overlayController; 43 | LayoutParams attributes = overlayControllerVar.window.getAttributes(); 44 | float f = attributes.alpha; 45 | attributes.alpha = 0.0f; 46 | if (f != attributes.alpha) { 47 | overlayControllerVar.window.setAttributes(attributes); 48 | } 49 | this.overlayController.setVisible(false); 50 | overlayControllerVar = this.overlayController; 51 | PanelState panelStateVar = PanelState.CLOSED;//Todo: PanelState.uoe was default 52 | if (overlayControllerVar.panelState != panelStateVar) { 53 | overlayControllerVar.panelState = panelStateVar; 54 | overlayControllerVar.setState(overlayControllerVar.panelState); 55 | } 56 | this.overlayController.slidingPanelLayout.uoH = this.overlayController.overlayControllerStateChanger; 57 | } 58 | 59 | public final void D(float f) { 60 | } 61 | 62 | public final boolean cnI() { 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/t.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | interface t { 4 | void D(float f); 5 | 6 | void drag(); 7 | 8 | void cnF(); 9 | 10 | void open(); 11 | 12 | void close(); 13 | 14 | boolean cnI(); 15 | 16 | void oc(boolean z); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/gsa/d/a/v.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.gsa.d.a; 2 | 3 | public class v { 4 | public String HB() { 5 | return null; 6 | } 7 | 8 | public boolean HC() { 9 | return false; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/i/a.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.i; 2 | 3 | import android.os.Bundle; 4 | import android.os.IInterface; 5 | import android.view.WindowManager.LayoutParams; 6 | 7 | public interface a extends IInterface { 8 | void BJ(int i); 9 | 10 | void BK(int i); 11 | 12 | String HB(); 13 | 14 | boolean HC(); 15 | 16 | void a(Bundle bundle, d dVar); 17 | 18 | void a(LayoutParams layoutParams, d dVar, int i); 19 | 20 | boolean a(byte[] bArr, Bundle bundle); 21 | 22 | void aL(float f); 23 | 24 | void cnK(); 25 | 26 | void cnL(); 27 | 28 | void fI(int i); 29 | 30 | void od(boolean z); 31 | 32 | void oe(boolean z); 33 | 34 | void onPause(); 35 | 36 | void onResume(); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/i/d.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.i; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface d extends IInterface { 6 | void BI(int i); 7 | 8 | void aK(float f); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/google/android/libraries/i/f.java: -------------------------------------------------------------------------------- 1 | package com.google.android.libraries.i; 2 | 3 | import android.os.IBinder; 4 | import android.os.Parcel; 5 | 6 | import com.google.android.a.a; 7 | 8 | public final class f extends a implements d { 9 | 10 | f(IBinder iBinder) { 11 | super(iBinder, "com.google.android.libraries.launcherclient.ILauncherOverlayCallback"); 12 | } 13 | 14 | public final void aK(float f) { 15 | Parcel pg = pg(); 16 | pg.writeFloat(f); 17 | c(1, pg); 18 | } 19 | 20 | public final void BI(int i) { 21 | Parcel pg = pg(); 22 | pg.writeInt(i); 23 | c(2, pg); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/db/Converters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.data.db 20 | 21 | import androidx.room.TypeConverter 22 | import com.google.gson.Gson 23 | 24 | import com.google.gson.reflect.TypeToken 25 | import com.saulhdev.feeder.utils.sloppyLinkToStrictURLNoThrows 26 | import org.threeten.bp.Instant 27 | import org.threeten.bp.ZonedDateTime 28 | import java.lang.reflect.Type 29 | import java.net.URL 30 | 31 | class Converters { 32 | @TypeConverter 33 | fun fromString(value: String): ArrayList { 34 | val listType: Type = object : TypeToken?>() {}.type 35 | return Gson().fromJson(value, listType) 36 | } 37 | 38 | @TypeConverter 39 | fun fromArrayList(list: ArrayList): String { 40 | val gson = Gson() 41 | return gson.toJson(list) 42 | } 43 | 44 | @TypeConverter 45 | fun dateTimeFromString(value: String?): ZonedDateTime? { 46 | var dt: ZonedDateTime? = null 47 | if (value != null) { 48 | try { 49 | dt = ZonedDateTime.parse(value) 50 | } catch (_: Throwable) { 51 | } 52 | } 53 | return dt 54 | } 55 | 56 | @TypeConverter 57 | fun stringFromDateTime(value: ZonedDateTime?): String? = 58 | value?.toString() 59 | 60 | @TypeConverter 61 | fun stringFromURL(value: URL?): String? = 62 | value?.toString() 63 | 64 | @TypeConverter 65 | fun urlFromString(value: String?): URL? = 66 | value?.let { sloppyLinkToStrictURLNoThrows(it) } 67 | 68 | @TypeConverter 69 | fun instantFromLong(value: Long?): Instant? = 70 | try { 71 | value?.let { Instant.ofEpochMilli(it) } 72 | } catch (t: Throwable) { 73 | null 74 | } 75 | 76 | @TypeConverter 77 | fun longFromInstant(value: Instant?): Long? = 78 | value?.toEpochMilli() 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/db/models/Feed.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.data.db.models 20 | 21 | import androidx.room.ColumnInfo 22 | import androidx.room.Entity 23 | import androidx.room.Index 24 | import androidx.room.PrimaryKey 25 | import com.saulhdev.feeder.data.db.ID_UNSET 26 | import com.saulhdev.feeder.utils.sloppyLinkToStrictURL 27 | import org.threeten.bp.Instant 28 | import java.net.URL 29 | 30 | @Entity( 31 | tableName = "Feeds", 32 | indices = [ 33 | Index(value = ["url"], unique = true), 34 | Index(value = ["id", "url", "title"], unique = true) 35 | ] 36 | ) 37 | data class Feed( 38 | @PrimaryKey(autoGenerate = true) 39 | val id: Long = ID_UNSET, 40 | val title: String = "", 41 | val description: String = "", 42 | val url: URL = sloppyLinkToStrictURL(""), 43 | val feedImage: URL = sloppyLinkToStrictURL(""), 44 | @ColumnInfo(typeAffinity = ColumnInfo.INTEGER) var lastSync: Instant = Instant.EPOCH, 45 | val alternateId: Boolean = false, 46 | val fullTextByDefault: Boolean = false, 47 | val tag: String = "", 48 | val currentlySyncing: Boolean = false, 49 | val isEnabled: Boolean = true, 50 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/db/models/FeedItemIdWithLink.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.db.models 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Ignore 5 | import com.saulhdev.feeder.data.db.ID_UNSET 6 | 7 | data class FeedItemIdWithLink @Ignore constructor( 8 | @ColumnInfo(name = "id") override var id: Long = ID_UNSET, 9 | @ColumnInfo(name = "link") override var link: String? = null, 10 | ) : FeedItemForFetching { 11 | constructor() : this(id = ID_UNSET) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/ActionStyle.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | import android.graphics.drawable.Drawable 4 | import androidx.annotation.UiThread 5 | 6 | @UiThread 7 | data class ActionStyle( 8 | var optionBackground: Drawable?, 9 | var paddingStart: Int, 10 | var paddingEnd: Int, 11 | var iconSpace: Int, 12 | var iconTint: Int?, 13 | var textSize: Int, 14 | var textColor: Int 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/DividerStyle.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | import androidx.annotation.UiThread 4 | 5 | @UiThread 6 | data class DividerStyle( 7 | var dividerHeight: Int, 8 | var dividerSize: Int, 9 | var dividerColor: Int 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/FeedCategory.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.ColorInt 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class FeedCategory( 9 | val id: String, 10 | val title: String, 11 | @ColorInt val categoryColor: Int, 12 | val serverIcon: String? 13 | ) : Parcelable { 14 | override fun describeContents(): Int { 15 | return 0 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/FeedItem.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | import android.graphics.Color 4 | import android.os.Parcelable 5 | import com.saulhdev.feeder.data.db.models.Feed 6 | import com.saulhdev.feeder.data.db.models.FeedArticle 7 | import com.saulhdev.feeder.manager.models.StoryCardContent 8 | import kotlinx.parcelize.Parcelize 9 | import java.time.ZonedDateTime 10 | import java.time.format.DateTimeFormatter 11 | import java.util.Date 12 | 13 | @Parcelize 14 | data class FeedItem( 15 | val id: Long, 16 | val title: String, 17 | val type: FeedItemType, 18 | val content: StoryCardContent, 19 | val bookmarked: Boolean, 20 | val time: Long 21 | ) : Parcelable { 22 | constructor( 23 | article: FeedArticle, 24 | feed: Feed, 25 | ) : this( 26 | id = article.id, 27 | title = "${feed.title} [RSS]", 28 | type = FeedItemType.STORY_CARD, 29 | content = StoryCardContent( 30 | title = article.title, 31 | text = article.description, 32 | background_url = article.imageUrl ?: "", 33 | link = article.link ?: "", 34 | source = FeedCategory( 35 | feed.id.toString(), 36 | feed.title, 37 | Color.GREEN, 38 | feed.feedImage.toString() 39 | ) 40 | ), 41 | bookmarked = article.bookmarked, 42 | time = Date.from( 43 | ZonedDateTime.parse( 44 | article.pubDate.toString(), 45 | DateTimeFormatter.ISO_ZONED_DATE_TIME 46 | ).toInstant() 47 | ).time 48 | ) 49 | 50 | override fun describeContents(): Int { 51 | return 0 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/FeedItemType.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | // Feed types which are available in HomeFeeder listing 4 | enum class FeedItemType { 5 | // Text card (like the old notification card). Contains a title, subtitle and a description. 6 | // Added in API 1. 7 | TEXT_CARD, 8 | 9 | // TEXT_CARD, but with larger header and actions to click. 10 | // Added in API 1. 11 | TEXT_CARD_ACTIONS, 12 | 13 | // Story card - a large card adapted for news. 14 | // Added in API 2. 15 | STORY_CARD 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/MenuItem.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2024 NeoApplications Team 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.data.entity 20 | 21 | import androidx.annotation.DrawableRes 22 | import androidx.annotation.StringRes 23 | 24 | data class MenuItem( 25 | @DrawableRes val icon: Int, 26 | @StringRes val title: Int, 27 | val group: Int, 28 | val id: String 29 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/data/entity/SortFilterModel.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.data.entity 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class SortFilterModel( 8 | val sort: String = SORT_CHRONOLOGICAL, 9 | val sortAsc: Boolean = false, 10 | val sourcesFilter: Set = emptySet(), 11 | ) : Parcelable 12 | 13 | const val SORT_CHRONOLOGICAL = "chronological" 14 | const val SORT_TITLE = "title" 15 | const val SORT_SOURCE = "source" -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/launcherapi/LauncherAPI.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.launcherapi 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import androidx.annotation.ColorInt 6 | 7 | /** 8 | * A class which parses data from launcher's options [Bundle]. 9 | * */ 10 | class LauncherAPI(bundle: Bundle = Bundle()) { 11 | companion object { 12 | const val LOG_TAG = "LauncherAPI" 13 | 14 | const val DARK_THEME_KEY = "is_background_dark" 15 | const val BG_HINT_KEY = "background_color_hint" 16 | const val BG_SECONDARY_HINT_KEY = "background_secondary_color_hint" 17 | const val BG_TERTIARY_HINT_KEY = "background_tertiary_color_hint" 18 | } 19 | 20 | /** 21 | * If the wallpaper is dark enough. 22 | * Available on almost every launcher 23 | */ 24 | var darkTheme = false 25 | 26 | /** 27 | * Primary wallpaper color. 28 | * Seems like it's available only on Lawnchair. 29 | * On Shade, it returns applied theme color. 30 | */ 31 | @ColorInt 32 | var backgroundColorHint = Color.BLACK 33 | 34 | /** 35 | * Secondary wallpaper color. 36 | * Only on Lawnchair (and Librechair too). 37 | */ 38 | @ColorInt 39 | var backgroundColorHintSecondary = Color.BLACK 40 | 41 | /** 42 | * Tertiary wallpaper color. 43 | * Available only on Librechair. 44 | */ 45 | @ColorInt 46 | var backgroundColorHintTertiary = Color.BLACK 47 | 48 | init { 49 | darkTheme = bundle.getBoolean(DARK_THEME_KEY, true) 50 | backgroundColorHint = bundle.getInt(BG_HINT_KEY, Color.BLACK) 51 | backgroundColorHintSecondary = bundle.getInt(BG_SECONDARY_HINT_KEY, Color.BLACK) 52 | backgroundColorHintTertiary = bundle.getInt(BG_TERTIARY_HINT_KEY, Color.BLACK) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/launcherapi/OverlayThemeHolder.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.launcherapi 2 | 3 | import android.util.SparseIntArray 4 | import com.saulhdev.feeder.data.content.FeedPreferences 5 | import com.saulhdev.feeder.ui.compose.theme.Theming 6 | import com.saulhdev.feeder.ui.overlay.OverlayView 7 | import com.saulhdev.feeder.utils.extensions.clearLightFlags 8 | import com.saulhdev.feeder.utils.extensions.setLightFlags 9 | import org.koin.java.KoinJavaComponent.inject 10 | 11 | /** 12 | * A class which manages overlay styling. 13 | * */ 14 | class OverlayThemeHolder(private val overlay: OverlayView) { 15 | 16 | val prefs: FeedPreferences by inject(FeedPreferences::class.java) 17 | 18 | /** 19 | * Current theme colors mapping 20 | */ 21 | var currentTheme = Theming.defaultDarkThemeColors 22 | 23 | /** 24 | * If we should apply light statusbar/navbar 25 | */ 26 | var shouldUseSN = true 27 | 28 | /** 29 | * If we are applied light statusbar/navbar 30 | */ 31 | var isSNApplied = false 32 | 33 | /** 34 | * If we are using system colors instead of [LauncherAPI] 35 | */ 36 | var systemColors = false 37 | 38 | /** 39 | * Replaces the color mapping ([currentTheme]) with config-specified values 40 | */ 41 | fun setTheme(theme: SparseIntArray) { 42 | currentTheme = theme 43 | 44 | if (shouldUseSN && !isSNApplied) { 45 | isSNApplied = true 46 | overlay.window.setLightFlags() 47 | } else if (shouldUseSN && isSNApplied) { 48 | isSNApplied = false 49 | overlay.window.clearLightFlags() 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/models/EditFeedViewState.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.models 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | data class EditFeedViewState( 7 | val title: String = "", 8 | val url: String = "", 9 | val fullTextByDefault: Boolean = true, 10 | val isEnabled: Boolean = true, 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/models/OPMLActions.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.models 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import android.util.Log 6 | import com.saulhdev.feeder.R 7 | import com.saulhdev.feeder.data.db.NeoFeedDb 8 | import com.saulhdev.feeder.data.repository.FeedRepository 9 | import com.saulhdev.feeder.manager.sync.requestFeedSync 10 | import com.saulhdev.feeder.utils.extensions.ToastMaker 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.withContext 13 | import org.koin.java.KoinJavaComponent.inject 14 | import kotlin.system.measureTimeMillis 15 | 16 | /** 17 | * Exports OPML on a background thread 18 | */ 19 | suspend fun exportOpml(uri: Uri) = withContext(Dispatchers.IO) { 20 | try { 21 | val time = measureTimeMillis { 22 | val contentResolver: ContentResolver by inject(ContentResolver::class.java) 23 | val sourceRepository: FeedRepository by inject(FeedRepository::class.java) 24 | contentResolver.openOutputStream(uri)?.let { 25 | writeOutputStream( 26 | it, 27 | sourceRepository.loadTags() 28 | ) { tag -> 29 | sourceRepository.loadFeedsByTag(tag = tag) 30 | } 31 | } 32 | } 33 | Log.d("OPML", "Exported OPML in $time ms on ${Thread.currentThread().name}") 34 | } catch (e: Throwable) { 35 | Log.e("OMPL", "Failed to export OPML", e) 36 | val toastMaker: ToastMaker by inject(ToastMaker::class.java) 37 | toastMaker.makeToast(R.string.failed_to_export_OPML) 38 | (e.localizedMessage ?: e.message)?.let { message -> 39 | toastMaker.makeToast(message) 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Imports OPML on a background thread 46 | */ 47 | suspend fun importOpml(uri: Uri) = withContext(Dispatchers.IO) { 48 | val db: NeoFeedDb by inject(NeoFeedDb::class.java) 49 | try { 50 | val time = measureTimeMillis { 51 | val parser = OPMLParser(OPMLToRoom(db)) 52 | val contentResolver: ContentResolver by inject(ContentResolver::class.java) 53 | contentResolver.openInputStream(uri).use { 54 | it?.let { stream -> 55 | parser.parseInputStream(stream) 56 | } 57 | } 58 | requestFeedSync() 59 | } 60 | Log.d("OPML", "Imported OPML in $time ms on ${Thread.currentThread().name}") 61 | } catch (e: Throwable) { 62 | Log.e("OMPL", "Failed to import OPML", e) 63 | val toastMaker: ToastMaker by inject(ToastMaker::class.java) 64 | toastMaker.makeToast(R.string.failed_to_import_OPML) 65 | (e.localizedMessage ?: e.message)?.let { message -> 66 | toastMaker.makeToast(message) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/models/OPMLParserToDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.models 2 | 3 | import com.saulhdev.feeder.data.db.models.Feed 4 | 5 | interface OPMLParserToDatabase { 6 | suspend fun getFeed(url: String): Feed? 7 | 8 | suspend fun saveFeed(feed: Feed) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/models/OPMLToRoom.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.models 2 | 3 | import com.saulhdev.feeder.data.db.NeoFeedDb 4 | import com.saulhdev.feeder.data.db.models.Feed 5 | import com.saulhdev.feeder.utils.sloppyLinkToStrictURLNoThrows 6 | 7 | class OPMLToRoom(db: NeoFeedDb) : OPMLParserToDatabase { 8 | 9 | private val dao = db.feedSourceDao() 10 | 11 | override suspend fun getFeed(url: String): Feed? = 12 | dao.getFeedByURL(sloppyLinkToStrictURLNoThrows(url)) 13 | 14 | override suspend fun saveFeed(feed: Feed) { 15 | val existing = dao.getFeedByURL(feed.url) 16 | 17 | // Don't want to remove existing feed on OPML imports 18 | if (existing != null) { 19 | dao.update(feed) 20 | } else { 21 | dao.insert(feed) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/models/StoryCardContent.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.models 2 | 3 | import android.os.Parcelable 4 | import com.saulhdev.feeder.data.entity.FeedCategory 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class StoryCardContent( 9 | val title: String, 10 | val text: String, 11 | val background_url: String, 12 | val link: String, 13 | val source: FeedCategory 14 | ) : Parcelable { 15 | override fun describeContents(): Int { 16 | return 0 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/plugin/PluginFetcher.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.plugin 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.util.Log 7 | import com.saulhdev.feeder.data.content.FeedPreferences 8 | import org.koin.core.component.KoinComponent 9 | import org.koin.core.component.inject 10 | 11 | object PluginFetcher : KoinComponent { 12 | // List of available packages. 13 | private val availablePlugins = hashMapOf() 14 | val prefs: FeedPreferences by inject() 15 | 16 | // Required part for AIDL connection. 17 | const val INTENT_ACTION_SERVICE = "ua.itaysonlab.hfsdk.HOMEFEEDER_PLUGIN_SERVICE" 18 | 19 | // Metadata value acting for SDK version. 20 | private const val METADATA_SDK_VERSION = "HF_PluginSDK_Version" 21 | 22 | private const val METADATA_NAME = "HF_Plugin_Name" 23 | private const val METADATA_DESCRIPTION = "HF_Plugin_Description" 24 | private const val METADATA_AUTHOR = "HF_Plugin_Author" 25 | private const val METADATA_HAS_SETTINGS = "HF_Plugin_HasSettingsActivity" 26 | 27 | fun init(ctx: Context) { 28 | fillListBy(ctx.packageManager) 29 | } 30 | 31 | // Fill list of suitable packages. 32 | private fun fillListBy(packageManager: PackageManager) { 33 | availablePlugins.clear() 34 | 35 | val hasService = packageManager.queryIntentServices( 36 | Intent(INTENT_ACTION_SERVICE), 37 | PackageManager.GET_META_DATA 38 | ).map { 39 | Pair(it.serviceInfo.packageName, it.serviceInfo.metaData) 40 | } 41 | 42 | if (prefs.debugging.getValue()) { 43 | Log.d("PluginFetcher", "Packages that has service: $hasService") 44 | } 45 | 46 | hasService.forEach { 47 | //Logger.log("PluginFetcher", "$it") 48 | availablePlugins[it.first] = SlimPluginInfo( 49 | it.first, 50 | hasPluginSettings = it.second.getBoolean(METADATA_HAS_SETTINGS), 51 | sdkVersion = it.second.getInt(METADATA_SDK_VERSION), 52 | title = it.second.getString(METADATA_NAME, ""), 53 | description = it.second.getString(METADATA_DESCRIPTION, ""), 54 | author = it.second.getString(METADATA_AUTHOR, "") 55 | ) 56 | } 57 | } 58 | 59 | data class SlimPluginInfo( 60 | val pkg: String, 61 | val hasPluginSettings: Boolean, 62 | val sdkVersion: Int, 63 | val title: String, 64 | val description: String, 65 | val author: String 66 | ) 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/service/DrawerOverlayService.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.service 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.IBinder 6 | import com.google.android.apps.gsa.nowoverlayservice.ConfigurationOverlayController 7 | import com.google.android.libraries.gsa.d.a.OverlaysController 8 | 9 | class DrawerOverlayService : Service() { 10 | private lateinit var overlaysController: OverlaysController 11 | override fun onCreate() { 12 | super.onCreate() 13 | this.overlaysController = ConfigurationOverlayController(this) 14 | } 15 | 16 | override fun onDestroy() { 17 | this.overlaysController.onDestroy() 18 | super.onDestroy() 19 | } 20 | 21 | override fun onBind(intent: Intent): IBinder? = this.overlaysController.onBind(intent) 22 | 23 | override fun onUnbind(intent: Intent): Boolean { 24 | this.overlaysController.onUnbind(intent) 25 | return false 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/service/HFPluginService.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.manager.service 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.os.IBinder 8 | import com.saulhdev.feeder.data.db.models.Feed 9 | import com.saulhdev.feeder.data.entity.FeedCategory 10 | import com.saulhdev.feeder.data.entity.FeedItem 11 | import com.saulhdev.feeder.data.entity.IFeedInterface 12 | import com.saulhdev.feeder.data.entity.IFeedInterfaceCallback 13 | import com.saulhdev.feeder.data.repository.ArticleRepository 14 | import com.saulhdev.feeder.data.repository.FeedRepository 15 | import kotlinx.coroutines.CoroutineScope 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.MainScope 18 | import kotlinx.coroutines.launch 19 | import kotlinx.coroutines.withContext 20 | import org.koin.java.KoinJavaComponent 21 | 22 | class HFPluginService : Service(), CoroutineScope by MainScope() { 23 | private val feedsRepo: FeedRepository by KoinJavaComponent.inject(FeedRepository::class.java) 24 | private val articlesRepo: ArticleRepository by KoinJavaComponent.inject(ArticleRepository::class.java) 25 | 26 | private val mBinder: IBinder = object : IFeedInterface.Stub() { 27 | override fun getFeed( 28 | callback: IFeedInterfaceCallback?, 29 | page: Int, 30 | category_id: String, 31 | parameters: Bundle? 32 | ) { 33 | callback ?: return 34 | launch { 35 | val list = mutableListOf() 36 | 37 | //Load feed articles from database 38 | withContext(Dispatchers.IO) { 39 | val feedList: List = feedsRepo.loadFeeds() 40 | 41 | feedList.forEach { feed -> 42 | articlesRepo.getFeedArticles(feed).forEach { article -> 43 | list.add(FeedItem(article, feed)) 44 | } 45 | } 46 | } 47 | 48 | callback.onFeedReceive(list) // TODO Simplify 49 | } 50 | } 51 | 52 | override fun getCategories(callback: IFeedInterfaceCallback) { 53 | val feedList: List = feedsRepo.loadFeeds() 54 | 55 | callback.onCategoriesReceive(feedList.map { 56 | FeedCategory(it.id.toString(), it.title, Color.GREEN, it.feedImage.toString()) 57 | }) 58 | } 59 | } 60 | 61 | override fun onBind(intent: Intent?): IBinder { 62 | return mBinder 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/manager/sync/SyncRestClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.manager.sync 20 | 21 | import com.saulhdev.feeder.data.db.ID_ALL 22 | import kotlinx.coroutines.Dispatchers 23 | import kotlinx.coroutines.withContext 24 | 25 | class SyncRestClient() { 26 | suspend fun syncAllFeeds() = withContext(Dispatchers.IO) { 27 | requestFeedSync(ID_ALL) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.MutableState 5 | import androidx.compose.ui.window.Dialog 6 | import androidx.compose.ui.window.DialogProperties 7 | 8 | @Composable 9 | fun BaseDialog( 10 | openDialogCustom: MutableState, 11 | dialogUI: @Composable (() -> Unit) 12 | ) { 13 | Dialog( 14 | onDismissRequest = { openDialogCustom.value = false }, 15 | properties = DialogProperties(usePlatformDefaultWidth = false) 16 | ) { 17 | dialogUI() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/Bidi.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import androidx.compose.ui.platform.LocalLayoutDirection 6 | import androidx.compose.ui.unit.LayoutDirection 7 | import java.text.Bidi 8 | 9 | @Composable 10 | inline fun WithBidiDeterminedLayoutDirection( 11 | paragraph: String, 12 | crossinline content: @Composable () -> Unit, 13 | ) { 14 | val bidi = Bidi(paragraph, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT) 15 | 16 | if (bidi.baseIsLeftToRight()) { 17 | CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { 18 | content() 19 | } 20 | } else { 21 | CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { 22 | content() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/Button.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.material3.ButtonDefaults 6 | import androidx.compose.material3.ElevatedButton 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.vector.ImageVector 14 | import androidx.compose.ui.unit.dp 15 | 16 | @Composable 17 | fun ActionButton( 18 | modifier: Modifier = Modifier, 19 | text: String, 20 | positive: Boolean = true, 21 | icon: ImageVector? = null, 22 | enabled: Boolean = true, 23 | onClick: () -> Unit, 24 | ) { 25 | ElevatedButton( 26 | modifier = modifier, 27 | colors = ButtonDefaults.elevatedButtonColors( 28 | contentColor = when { 29 | positive -> MaterialTheme.colorScheme.onPrimaryContainer 30 | else -> MaterialTheme.colorScheme.onTertiaryContainer 31 | }, 32 | containerColor = when { 33 | positive -> MaterialTheme.colorScheme.primaryContainer 34 | else -> MaterialTheme.colorScheme.tertiaryContainer 35 | } 36 | ), 37 | enabled = enabled, 38 | onClick = onClick 39 | ) { 40 | Row( 41 | horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), 42 | verticalAlignment = Alignment.CenterVertically 43 | ) { 44 | if (icon != null) Icon(imageVector = icon, contentDescription = null) 45 | Text(text = text) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/ComposeSwitchView.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Switch 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.vector.ImageVector 13 | import androidx.compose.ui.res.stringResource 14 | import androidx.compose.ui.unit.dp 15 | 16 | @Composable 17 | fun ComposeSwitchView( 18 | @StringRes titleId: Int, 19 | modifier: Modifier = Modifier, 20 | @StringRes summaryId: Int = -1, 21 | icon: ImageVector? = null, 22 | onCheckedChange: ((Boolean) -> Unit), 23 | isChecked: Boolean = false, 24 | isEnabled: Boolean = true, 25 | index: Int = -1, 26 | groupSize: Int = -1, 27 | ) { 28 | val (checked, check) = remember(isChecked) { mutableStateOf(isChecked) } 29 | BasePreference( 30 | modifier = modifier, 31 | titleId = titleId, 32 | summaryId = summaryId, 33 | index = index, 34 | groupSize = groupSize, 35 | startWidget = icon?.let { 36 | { 37 | Icon( 38 | imageVector = icon, 39 | contentDescription = stringResource(id = titleId), 40 | tint = MaterialTheme.colorScheme.onSurface, 41 | ) 42 | } 43 | }, 44 | isEnabled = isEnabled, 45 | onClick = { 46 | check(!checked) 47 | onCheckedChange(!checked) 48 | }, 49 | endWidget = { 50 | Switch( 51 | modifier = Modifier 52 | .height(24.dp), 53 | checked = checked, 54 | onCheckedChange = { 55 | check(it) 56 | onCheckedChange(it) 57 | }, 58 | enabled = isEnabled, 59 | ) 60 | } 61 | ) 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/ContributorRow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Omega Launcher 3 | * Copyright (c) 2022 Omega Launcher Team 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | package com.saulhdev.feeder.ui.components 19 | 20 | import androidx.annotation.StringRes 21 | import androidx.compose.foundation.Image 22 | import androidx.compose.foundation.background 23 | import androidx.compose.foundation.layout.size 24 | import androidx.compose.foundation.shape.CircleShape 25 | import androidx.compose.material3.MaterialTheme 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.draw.clip 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.unit.dp 32 | import coil.annotation.ExperimentalCoilApi 33 | import coil.compose.rememberAsyncImagePainter 34 | import coil.request.ImageRequest 35 | import com.saulhdev.feeder.utils.extensions.launchView 36 | 37 | @ExperimentalCoilApi 38 | @Composable 39 | fun ContributorRow( 40 | @StringRes nameId: Int, 41 | @StringRes roleId: Int, 42 | photoUrl: String, 43 | url: String, 44 | index: Int = 0, 45 | groupSize: Int = 1 46 | ) { 47 | val context = LocalContext.current 48 | 49 | BasePreference( 50 | titleId = nameId, 51 | onClick = { context.launchView(url) }, 52 | summaryId = roleId, 53 | index = index, 54 | groupSize = groupSize, 55 | startWidget = { 56 | Image( 57 | painter = rememberAsyncImagePainter( 58 | ImageRequest.Builder(LocalContext.current).data(data = photoUrl) 59 | .apply(block = fun ImageRequest.Builder.() { 60 | crossfade(true) 61 | }).build() 62 | ), 63 | contentDescription = stringResource(id = nameId), 64 | modifier = Modifier 65 | .clip(CircleShape) 66 | .size(30.dp) 67 | .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.12F)) 68 | ) 69 | } 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/LinkItem.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Omega Launcher 3 | * Copyright (c) 2022 Omega Launcher Team 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | package com.saulhdev.feeder.ui.components 19 | 20 | import androidx.compose.material3.AssistChip 21 | import androidx.compose.material3.AssistChipDefaults 22 | import androidx.compose.material3.Icon 23 | import androidx.compose.material3.MaterialTheme 24 | import androidx.compose.material3.Text 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.graphics.vector.ImageVector 28 | import androidx.compose.ui.platform.LocalContext 29 | import androidx.compose.ui.text.style.TextOverflow 30 | import com.saulhdev.feeder.utils.extensions.launchView 31 | 32 | @Composable 33 | fun LinkItem( 34 | modifier: Modifier = Modifier, 35 | icon: ImageVector, 36 | label: String, 37 | url: String, 38 | ) { 39 | val context = LocalContext.current 40 | 41 | AssistChip( 42 | modifier = modifier, 43 | border = null, 44 | shape = MaterialTheme.shapes.medium, 45 | colors = AssistChipDefaults.assistChipColors( 46 | containerColor = MaterialTheme.colorScheme.surfaceContainer, 47 | ), 48 | leadingIcon = { 49 | Icon( 50 | imageVector = icon, 51 | contentDescription = label, 52 | tint = MaterialTheme.colorScheme.primary, 53 | ) 54 | }, 55 | label = { 56 | Text( 57 | text = label, 58 | style = MaterialTheme.typography.bodyMedium, 59 | color = MaterialTheme.colorScheme.primary, 60 | maxLines = 1, 61 | overflow = TextOverflow.Ellipsis 62 | ) 63 | }, 64 | onClick = { 65 | context.launchView(url) 66 | } 67 | ) 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/OverflowMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2023 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | package com.saulhdev.feeder.ui.components 19 | 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.material3.DropdownMenu 22 | import androidx.compose.material3.Icon 23 | import androidx.compose.material3.IconButton 24 | import androidx.compose.material3.MaterialTheme 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.runtime.MutableState 27 | import androidx.compose.runtime.mutableStateOf 28 | import androidx.compose.runtime.remember 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.unit.DpOffset 31 | import androidx.compose.ui.unit.dp 32 | import com.saulhdev.feeder.R 33 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 34 | import com.saulhdev.feeder.ui.compose.icon.phosphor.DotsThreeVertical 35 | 36 | @Composable 37 | fun OverflowMenu(block: @Composable OverflowMenuScope.() -> Unit) { 38 | val showMenu = remember { mutableStateOf(false) } 39 | val overflowMenuScope = remember { OverflowMenuScopeImpl(showMenu) } 40 | 41 | Box { 42 | IconButton( 43 | onClick = { showMenu.value = true } 44 | ) { 45 | Icon( 46 | imageVector = Phosphor.DotsThreeVertical, 47 | contentDescription = stringResource(id = R.string.title_settings), 48 | tint = MaterialTheme.colorScheme.primary 49 | ) 50 | } 51 | DropdownMenu( 52 | expanded = showMenu.value, 53 | onDismissRequest = { showMenu.value = false }, 54 | offset = DpOffset(x = 8.dp, y = (-4).dp) 55 | ) { 56 | block(overflowMenuScope) 57 | } 58 | } 59 | } 60 | 61 | interface OverflowMenuScope { 62 | fun hideMenu() 63 | } 64 | 65 | private class OverflowMenuScopeImpl(private val showState: MutableState) : 66 | OverflowMenuScope { 67 | override fun hideMenu() { 68 | showState.value = false 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/PreferenceBuilder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.ui.components 20 | 21 | import androidx.compose.runtime.Composable 22 | import com.saulhdev.feeder.data.content.BooleanPref 23 | import com.saulhdev.feeder.data.content.FloatPref 24 | import com.saulhdev.feeder.data.content.StringPref 25 | import com.saulhdev.feeder.data.content.StringSelectionPref 26 | import com.saulhdev.feeder.data.content.StringSetPref 27 | 28 | val PreferenceBuilder = 29 | @Composable { pref: Any, onDialogPref: (Any) -> Unit, index: Int, size: Int -> 30 | when (pref) { 31 | is BooleanPref -> 32 | SwitchPreference(pref = pref, index = index, groupSize = size) 33 | 34 | is StringSetPref -> 35 | StringSetPreference(pref = pref, index = index, groupSize = size) 36 | 37 | is StringPref -> 38 | ActionPreference(pref = pref, index = index, groupSize = size) 39 | 40 | is FloatPref -> 41 | SeekBarPreference(pref = pref, index = index, groupSize = size) 42 | 43 | is StringSelectionPref -> 44 | StringSelectionPreference( 45 | pref = pref, 46 | index = index, 47 | groupSize = size 48 | ) { onDialogPref(pref) } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/PullToRefreshLazyColumn.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.LazyListScope 9 | import androidx.compose.foundation.lazy.LazyListState 10 | import androidx.compose.foundation.lazy.rememberLazyListState 11 | import androidx.compose.material3.ExperimentalMaterial3Api 12 | import androidx.compose.material3.pulltorefresh.PullToRefreshBox 13 | import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.rememberCoroutineScope 19 | import androidx.compose.runtime.setValue 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.unit.dp 22 | import kotlinx.coroutines.delay 23 | import kotlinx.coroutines.launch 24 | 25 | @OptIn(ExperimentalMaterial3Api::class) 26 | @Composable 27 | fun PullToRefreshLazyColumn( 28 | isRefreshing: Boolean = false, 29 | onRefresh: suspend () -> Unit, 30 | content: LazyListScope.() -> Unit, 31 | modifier: Modifier = Modifier, 32 | listState: LazyListState = rememberLazyListState(), 33 | contentPadding: PaddingValues = PaddingValues(8.dp), 34 | ) { 35 | var isRefreshing by remember(isRefreshing) { 36 | mutableStateOf(isRefreshing) 37 | } 38 | val coroutineScope = rememberCoroutineScope() 39 | val pullToRefreshState = rememberPullToRefreshState() 40 | 41 | Box( 42 | modifier = modifier.fillMaxSize(), 43 | ) { 44 | PullToRefreshBox( 45 | isRefreshing = isRefreshing, 46 | state = pullToRefreshState, 47 | onRefresh = { 48 | isRefreshing = true 49 | coroutineScope.launch { 50 | onRefresh() 51 | delay(2_000L) 52 | isRefreshing = false 53 | } 54 | }, 55 | modifier = Modifier.fillMaxSize(), 56 | ) { 57 | LazyColumn( 58 | state = listState, 59 | contentPadding = contentPadding, 60 | modifier = Modifier.fillMaxSize(), 61 | verticalArrangement = Arrangement.spacedBy(8.dp), 62 | content = content 63 | ) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/SeekBarPreference.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.requiredHeight 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Slider 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.mutableFloatStateOf 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.runtime.setValue 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.unit.dp 16 | import com.saulhdev.feeder.data.content.FloatPref 17 | 18 | @Composable 19 | fun SeekBarPreference( 20 | modifier: Modifier = Modifier, 21 | pref: FloatPref, 22 | index: Int = 1, 23 | groupSize: Int = 1, 24 | isEnabled: Boolean = true, 25 | onValueChange: ((Float) -> Unit) = {}, 26 | ) { 27 | var currentValue by remember(pref) { mutableFloatStateOf(pref.getValue()) } 28 | 29 | BasePreference( 30 | modifier = modifier, 31 | titleId = pref.titleId, 32 | summaryId = pref.summaryId, 33 | index = index, 34 | groupSize = groupSize, 35 | isEnabled = isEnabled, 36 | startWidget = { 37 | Icon( 38 | imageVector = pref.icon, 39 | contentDescription = stringResource(id = pref.titleId), 40 | tint = MaterialTheme.colorScheme.onSurface, 41 | ) 42 | }, 43 | bottomWidget = { 44 | Row { 45 | Slider( 46 | modifier = Modifier 47 | .requiredHeight(24.dp) 48 | .weight(1f), 49 | value = currentValue, 50 | valueRange = pref.minValue..pref.maxValue, 51 | onValueChange = { currentValue = it }, 52 | steps = pref.steps, 53 | onValueChangeFinished = { 54 | pref.setValue(currentValue) 55 | onValueChange(currentValue) 56 | }, 57 | enabled = isEnabled 58 | ) 59 | } 60 | } 61 | ) 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/StringSelectionPreference.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components 2 | 3 | import androidx.compose.material3.Icon 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.res.stringResource 8 | import com.saulhdev.feeder.data.content.StringSelectionPref 9 | 10 | @Composable 11 | fun StringSelectionPreference( 12 | modifier: Modifier = Modifier, 13 | pref: StringSelectionPref, 14 | index: Int = 1, 15 | groupSize: Int = 1, 16 | isEnabled: Boolean = true, 17 | onClick: (() -> Unit) = {}, 18 | ) { 19 | BasePreference( 20 | modifier = modifier, 21 | titleId = pref.titleId, 22 | summaryId = pref.summaryId, 23 | summary = pref.entries[pref.getValue()], 24 | index = index, 25 | groupSize = groupSize, 26 | startWidget = { 27 | Icon( 28 | imageVector = pref.icon, 29 | contentDescription = stringResource(id = pref.titleId), 30 | tint = MaterialTheme.colorScheme.onSurface, 31 | ) 32 | }, 33 | isEnabled = isEnabled, 34 | onClick = onClick 35 | ) 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/StringSetPreference.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feeder 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.ui.components 20 | 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Modifier 23 | import com.saulhdev.feeder.data.content.StringSetPref 24 | 25 | @Composable 26 | fun StringSetPreference( 27 | modifier: Modifier = Modifier, 28 | pref: StringSetPref, 29 | index: Int = 1, 30 | groupSize: Int = 1, 31 | isEnabled: Boolean = true, 32 | ) { 33 | BasePreference( 34 | modifier = modifier, 35 | titleId = pref.titleId, 36 | summaryId = pref.summaryId, 37 | index = index, 38 | groupSize = groupSize, 39 | isEnabled = isEnabled 40 | ) 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/SwitchPreference.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.ui.components 20 | 21 | import androidx.compose.foundation.layout.height 22 | import androidx.compose.material3.Icon 23 | import androidx.compose.material3.MaterialTheme 24 | import androidx.compose.material3.Switch 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.res.stringResource 30 | import androidx.compose.ui.unit.dp 31 | import com.saulhdev.feeder.data.content.BooleanPref 32 | 33 | @Composable 34 | fun SwitchPreference( 35 | modifier: Modifier = Modifier, 36 | pref: BooleanPref, 37 | index: Int = 1, 38 | groupSize: Int = 1, 39 | isEnabled: Boolean = true, 40 | onCheckedChange: ((Boolean) -> Unit) = {}, 41 | ) { 42 | val (checked, check) = remember(pref) { mutableStateOf(pref.getValue()) } 43 | BasePreference( 44 | modifier = modifier, 45 | titleId = pref.titleId, 46 | summaryId = pref.summaryId, 47 | index = index, 48 | groupSize = groupSize, 49 | startWidget = { 50 | Icon( 51 | imageVector = pref.icon, 52 | contentDescription = stringResource(id = pref.titleId), 53 | tint = MaterialTheme.colorScheme.onSurface, 54 | ) 55 | }, 56 | isEnabled = isEnabled, 57 | onClick = { 58 | onCheckedChange(!checked) 59 | pref.setValue(!checked) 60 | check(!checked) 61 | }, 62 | endWidget = { 63 | Switch( 64 | modifier = Modifier 65 | .height(24.dp), 66 | checked = checked, 67 | onCheckedChange = { 68 | onCheckedChange(it) 69 | pref.setValue(it) 70 | check(it) 71 | }, 72 | enabled = isEnabled, 73 | ) 74 | } 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/components/dialog/DialogButton.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.components.dialog 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.ButtonDefaults 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.material3.TextButton 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.res.stringResource 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun DialogPositiveButton( 16 | modifier: Modifier = Modifier, 17 | text: String = stringResource(id = android.R.string.ok), 18 | onClick: () -> Unit = {} 19 | ) { 20 | TextButton( 21 | shape = MaterialTheme.shapes.large, 22 | onClick = onClick, 23 | modifier = modifier, 24 | colors = ButtonDefaults.buttonColors( 25 | containerColor = MaterialTheme.colorScheme.primary, 26 | contentColor = MaterialTheme.colorScheme.onPrimary 27 | ) 28 | ) { 29 | Text( 30 | text = text, 31 | fontWeight = FontWeight.ExtraBold, 32 | modifier = Modifier.padding(top = 5.dp, bottom = 5.dp) 33 | ) 34 | } 35 | } 36 | 37 | @Composable 38 | fun DialogNegativeButton( 39 | modifier: Modifier = Modifier, 40 | text: String = stringResource(id = android.R.string.cancel), 41 | onClick: () -> Unit = {} 42 | ) { 43 | TextButton( 44 | shape = MaterialTheme.shapes.large, 45 | onClick = onClick, 46 | modifier = modifier 47 | ) { 48 | Text( 49 | text = text, 50 | fontWeight = FontWeight.ExtraBold, 51 | modifier = Modifier.padding(vertical = 5.dp, horizontal = 8.dp) 52 | ) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/__Phosphor.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon 2 | 3 | object Phosphor 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/ArrowLeft.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.SolidColor 5 | import androidx.compose.ui.graphics.vector.ImageVector 6 | import androidx.compose.ui.graphics.vector.path 7 | import androidx.compose.ui.unit.dp 8 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 9 | 10 | val Phosphor.ArrowLeft: ImageVector 11 | get() { 12 | if (_arrowLeft != null) { 13 | return _arrowLeft!! 14 | } 15 | _arrowLeft = ImageVector.Builder( 16 | name = "ArrowLeft", 17 | defaultWidth = 24.0.dp, 18 | defaultHeight = 24.0.dp, 19 | viewportWidth = 256.0f, 20 | viewportHeight = 256.0f, 21 | ).apply { 22 | path(fill = SolidColor(Color(0xFF000000))) { 23 | moveTo(224f, 128f) 24 | arcToRelative(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, -8f, 8f) 25 | horizontalLineTo(59.31f) 26 | lineToRelative(58.35f, 58.34f) 27 | arcToRelative( 28 | 8f, 29 | 8f, 30 | 0f, 31 | isMoreThanHalf = false, 32 | isPositiveArc = true, 33 | -11.32f, 34 | 11.32f 35 | ) 36 | lineToRelative(-72f, -72f) 37 | arcToRelative(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, 0f, -11.32f) 38 | lineToRelative(72f, -72f) 39 | arcToRelative( 40 | 8f, 41 | 8f, 42 | 0f, 43 | isMoreThanHalf = false, 44 | isPositiveArc = true, 45 | 11.32f, 46 | 11.32f 47 | ) 48 | lineTo(59.31f, 120f) 49 | horizontalLineTo(216f) 50 | arcTo(8f, 8f, 0f, isMoreThanHalf = false, isPositiveArc = true, 224f, 128f) 51 | close() 52 | } 53 | }.build() 54 | 55 | return _arrowLeft!! 56 | } 57 | 58 | private var _arrowLeft: ImageVector? = null 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/ArrowUUpLeft.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | 15 | val Phosphor.ArrowUUpLeft: ImageVector 16 | get() { 17 | if (_ArrowUUpLeft != null) { 18 | return _ArrowUUpLeft!! 19 | } 20 | _ArrowUUpLeft = Builder( 21 | name = "Arrow-u-up-left", 22 | defaultWidth = 24.0.dp, 23 | defaultHeight = 24.0.dp, 24 | viewportWidth = 256.0f, 25 | viewportHeight = 256.0f, 26 | ).apply { 27 | path( 28 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 29 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 30 | pathFillType = NonZero 31 | ) { 32 | moveTo(232.0f, 144.0f) 33 | arcToRelative(64.1f, 64.1f, 0.0f, false, true, -64.0f, 64.0f) 34 | horizontalLineTo(80.0f) 35 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 0.0f, -16.0f) 36 | horizontalLineToRelative(88.0f) 37 | arcToRelative(48.0f, 48.0f, 0.0f, false, false, 0.0f, -96.0f) 38 | horizontalLineTo(51.3f) 39 | lineToRelative(34.4f, 34.3f) 40 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 0.0f, 11.4f) 41 | arcToRelative(8.2f, 8.2f, 0.0f, false, true, -11.4f, 0.0f) 42 | lineToRelative(-48.0f, -48.0f) 43 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 0.0f, -11.4f) 44 | lineToRelative(48.0f, -48.0f) 45 | arcTo(8.1f, 8.1f, 0.0f, false, true, 85.7f, 45.7f) 46 | lineTo(51.3f, 80.0f) 47 | horizontalLineTo(168.0f) 48 | arcTo(64.1f, 64.1f, 0.0f, false, true, 232.0f, 144.0f) 49 | close() 50 | } 51 | } 52 | .build() 53 | return _ArrowUUpLeft!! 54 | } 55 | 56 | private var _ArrowUUpLeft: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Asterisk.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.Asterisk: ImageVector 15 | get() { 16 | if (_asterisk != null) { 17 | return _asterisk!! 18 | } 19 | _asterisk = Builder( 20 | name = "Asterisk", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(211.1f, 176.0f) 32 | arcToRelative(7.7f, 7.7f, 0.0f, false, true, -6.9f, 4.0f) 33 | arcToRelative(7.3f, 7.3f, 0.0f, false, true, -4.0f, -1.1f) 34 | lineToRelative(-64.2f, -37.0f) 35 | verticalLineTo(216.0f) 36 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -16.0f, 0.0f) 37 | verticalLineTo(141.9f) 38 | lineToRelative(-64.2f, 37.0f) 39 | arcToRelative(7.3f, 7.3f, 0.0f, false, true, -4.0f, 1.1f) 40 | arcToRelative(7.7f, 7.7f, 0.0f, false, true, -6.9f, -4.0f) 41 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 2.9f, -10.9f) 42 | lineTo(112.0f, 128.0f) 43 | lineTo(47.8f, 90.9f) 44 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 8.0f, -13.8f) 45 | lineToRelative(64.2f, 37.0f) 46 | verticalLineTo(40.0f) 47 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 16.0f, 0.0f) 48 | verticalLineToRelative(74.1f) 49 | lineToRelative(64.2f, -37.0f) 50 | arcToRelative(8.0f, 8.0f, 0.0f, true, true, 8.0f, 13.8f) 51 | lineTo(144.0f, 128.0f) 52 | lineToRelative(64.2f, 37.1f) 53 | arcTo(8.0f, 8.0f, 0.0f, false, true, 211.1f, 176.0f) 54 | close() 55 | } 56 | } 57 | .build() 58 | return _asterisk!! 59 | } 60 | 61 | private var _asterisk: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/CaretDown.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 7 | import androidx.compose.ui.graphics.SolidColor 8 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 9 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 10 | import androidx.compose.ui.graphics.vector.ImageVector 11 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 12 | import androidx.compose.ui.graphics.vector.path 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 16 | 17 | val Phosphor.CaretDown: ImageVector 18 | get() { 19 | if (_caret_down != null) { 20 | return _caret_down!! 21 | } 22 | _caret_down = Builder( 23 | name = "Caret-down", 24 | defaultWidth = 24.0.dp, 25 | defaultHeight = 24.0.dp, 26 | viewportWidth = 256.0f, 27 | viewportHeight = 256.0f, 28 | ).apply { 29 | path( 30 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 31 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 32 | pathFillType = NonZero 33 | ) { 34 | moveTo(128.0f, 184.0f) 35 | arcToRelative(8.5f, 8.5f, 0.0f, false, true, -5.7f, -2.3f) 36 | lineToRelative(-80.0f, -80.0f) 37 | arcTo(8.1f, 8.1f, 0.0f, false, true, 53.7f, 90.3f) 38 | lineTo(128.0f, 164.7f) 39 | lineToRelative(74.3f, -74.4f) 40 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 11.4f, 11.4f) 41 | lineToRelative(-80.0f, 80.0f) 42 | arcTo(8.5f, 8.5f, 0.0f, false, true, 128.0f, 184.0f) 43 | close() 44 | } 45 | } 46 | .build() 47 | return _caret_down!! 48 | } 49 | 50 | private var _caret_down: ImageVector? = null 51 | 52 | @Preview 53 | @Composable 54 | fun CaretDownPreview() { 55 | Image( 56 | Phosphor.CaretDown, 57 | null 58 | ) 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/CaretUp.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.CaretUp: ImageVector 15 | get() { 16 | if (_caret_up != null) { 17 | return _caret_up!! 18 | } 19 | _caret_up = Builder( 20 | name = "Caret-up", 21 | defaultWidth = 24.dp, 22 | defaultHeight = 24.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(208.0f, 168.0f) 32 | arcToRelative(8.5f, 8.5f, 0.0f, false, true, -5.7f, -2.3f) 33 | lineTo(128.0f, 91.3f) 34 | lineTo(53.7f, 165.7f) 35 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, -11.4f, -11.4f) 36 | lineToRelative(80.0f, -80.0f) 37 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 11.4f, 0.0f) 38 | lineToRelative(80.0f, 80.0f) 39 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 0.0f, 11.4f) 40 | arcTo(8.5f, 8.5f, 0.0f, false, true, 208.0f, 168.0f) 41 | close() 42 | } 43 | } 44 | .build() 45 | return _caret_up!! 46 | } 47 | 48 | private var _caret_up: ImageVector? = null 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Check.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.Check: ImageVector 15 | get() { 16 | if (_check != null) { 17 | return _check!! 18 | } 19 | _check = Builder( 20 | name = "Check", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(104.0f, 192.0f) 32 | arcToRelative(8.5f, 8.5f, 0.0f, false, true, -5.7f, -2.3f) 33 | lineToRelative(-56.0f, -56.0f) 34 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 11.4f, -11.4f) 35 | lineTo(104.0f, 172.7f) 36 | lineTo(210.3f, 66.3f) 37 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, 11.4f, 11.4f) 38 | lineToRelative(-112.0f, 112.0f) 39 | arcTo(8.5f, 8.5f, 0.0f, false, true, 104.0f, 192.0f) 40 | close() 41 | } 42 | } 43 | .build() 44 | return _check!! 45 | } 46 | 47 | private var _check: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/CheckCircle.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.CheckCircle: ImageVector 15 | get() { 16 | if (_check_circle != null) { 17 | return _check_circle!! 18 | } 19 | _check_circle = Builder( 20 | name = "Check-circle", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(177.8f, 98.5f) 32 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -0.3f, 11.3f) 33 | lineToRelative(-58.6f, 56.0f) 34 | arcToRelative(8.1f, 8.1f, 0.0f, false, true, -5.6f, 2.2f) 35 | arcToRelative(7.9f, 7.9f, 0.0f, false, true, -5.5f, -2.2f) 36 | lineToRelative(-29.3f, -28.0f) 37 | arcToRelative(8.0f, 8.0f, 0.0f, true, true, 11.0f, -11.6f) 38 | lineToRelative(23.8f, 22.7f) 39 | lineToRelative(53.2f, -50.7f) 40 | arcTo(8.0f, 8.0f, 0.0f, false, true, 177.8f, 98.5f) 41 | close() 42 | moveTo(232.0f, 128.0f) 43 | arcTo(104.0f, 104.0f, 0.0f, true, true, 128.0f, 24.0f) 44 | arcTo(104.2f, 104.2f, 0.0f, false, true, 232.0f, 128.0f) 45 | close() 46 | moveTo(216.0f, 128.0f) 47 | arcToRelative(88.0f, 88.0f, 0.0f, true, false, -88.0f, 88.0f) 48 | arcTo(88.1f, 88.1f, 0.0f, false, false, 216.0f, 128.0f) 49 | close() 50 | } 51 | } 52 | .build() 53 | return _check_circle!! 54 | } 55 | 56 | private var _check_circle: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Circle.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.Circle: ImageVector 15 | get() { 16 | if (_circle != null) { 17 | return _circle!! 18 | } 19 | _circle = Builder( 20 | name = "Circle", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(128.0f, 232.0f) 32 | arcTo(104.0f, 104.0f, 0.0f, true, true, 232.0f, 128.0f) 33 | arcTo(104.2f, 104.2f, 0.0f, false, true, 128.0f, 232.0f) 34 | close() 35 | moveTo(128.0f, 40.0f) 36 | arcToRelative(88.0f, 88.0f, 0.0f, true, false, 88.0f, 88.0f) 37 | arcTo(88.1f, 88.1f, 0.0f, false, false, 128.0f, 40.0f) 38 | close() 39 | } 40 | } 41 | .build() 42 | return _circle!! 43 | } 44 | 45 | private var _circle: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/DotsThreeVertical.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.SolidColor 5 | import androidx.compose.ui.graphics.vector.ImageVector 6 | import androidx.compose.ui.graphics.vector.path 7 | import androidx.compose.ui.unit.dp 8 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 9 | 10 | val Phosphor.DotsThreeVertical: ImageVector 11 | get() { 12 | if (_dotsThreeVertical != null) { 13 | return _dotsThreeVertical!! 14 | } 15 | _dotsThreeVertical = ImageVector.Builder( 16 | name = "DotsThreeVertical", 17 | defaultWidth = 24.0.dp, 18 | defaultHeight = 24.0.dp, 19 | viewportWidth = 256.0f, 20 | viewportHeight = 256.0f, 21 | ).apply { 22 | path(fill = SolidColor(Color(0xFF000000))) { 23 | moveTo(140f, 128f) 24 | arcToRelative(12f, 12f, 0f, isMoreThanHalf = true, isPositiveArc = true, -12f, -12f) 25 | arcTo(12f, 12f, 0f, isMoreThanHalf = false, isPositiveArc = true, 140f, 128f) 26 | close() 27 | moveTo(128f, 72f) 28 | arcToRelative( 29 | 12f, 30 | 12f, 31 | 0f, 32 | isMoreThanHalf = true, 33 | isPositiveArc = false, 34 | -12f, 35 | -12f 36 | ) 37 | arcTo(12f, 12f, 0f, isMoreThanHalf = false, isPositiveArc = false, 128f, 72f) 38 | close() 39 | moveTo(128f, 184f) 40 | arcToRelative(12f, 12f, 0f, isMoreThanHalf = true, isPositiveArc = false, 12f, 12f) 41 | arcTo(12f, 12f, 0f, isMoreThanHalf = false, isPositiveArc = false, 128f, 184f) 42 | close() 43 | } 44 | }.build() 45 | 46 | return _dotsThreeVertical!! 47 | } 48 | 49 | private var _dotsThreeVertical: ImageVector? = null 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/FunnelSimple.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.FunnelSimple: ImageVector 15 | get() { 16 | if (_funnel_simple != null) { 17 | return _funnel_simple!! 18 | } 19 | _funnel_simple = Builder( 20 | name = "Funnel-simple", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(200.0f, 128.0f) 32 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -8.0f, 8.0f) 33 | lineTo(64.0f, 136.0f) 34 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 0.0f, -16.0f) 35 | lineTo(192.0f, 120.0f) 36 | arcTo(8.0f, 8.0f, 0.0f, false, true, 200.0f, 128.0f) 37 | close() 38 | moveTo(232.0f, 72.0f) 39 | lineTo(24.0f, 72.0f) 40 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, 16.0f) 41 | lineTo(232.0f, 88.0f) 42 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, -16.0f) 43 | close() 44 | moveTo(152.0f, 168.0f) 45 | lineTo(104.0f, 168.0f) 46 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, 16.0f) 47 | horizontalLineToRelative(48.0f) 48 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, -16.0f) 49 | close() 50 | } 51 | } 52 | .build() 53 | return _funnel_simple!! 54 | } 55 | 56 | private var _funnel_simple: ImageVector? = null -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/HeartStraightFill.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | public val Phosphor.HeartStraightFill: ImageVector 15 | get() { 16 | if (_heart_straight_fill != null) { 17 | return _heart_straight_fill!! 18 | } 19 | _heart_straight_fill = Builder( 20 | name = "Heart-straight-fill", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(224.6f, 51.9f) 32 | arcToRelative(59.5f, 59.5f, 0.0f, false, false, -43.0f, -19.9f) 33 | arcToRelative(60.5f, 60.5f, 0.0f, false, false, -44.0f, 17.6f) 34 | lineTo(128.0f, 59.1f) 35 | lineToRelative(-7.5f, -7.4f) 36 | curveTo(97.2f, 28.3f, 59.2f, 26.3f, 35.9f, 47.4f) 37 | arcToRelative(59.9f, 59.9f, 0.0f, false, false, -2.3f, 87.0f) 38 | lineToRelative(83.1f, 83.1f) 39 | arcToRelative(15.9f, 15.9f, 0.0f, false, false, 22.6f, 0.0f) 40 | lineToRelative(81.0f, -81.0f) 41 | curveTo(243.7f, 113.2f, 245.6f, 75.2f, 224.6f, 51.9f) 42 | close() 43 | } 44 | } 45 | .build() 46 | return _heart_straight_fill!! 47 | } 48 | 49 | private var _heart_straight_fill: ImageVector? = null 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Play.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.SolidColor 5 | import androidx.compose.ui.graphics.vector.ImageVector 6 | import androidx.compose.ui.graphics.vector.path 7 | import androidx.compose.ui.unit.dp 8 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 9 | 10 | val Phosphor.Play: ImageVector 11 | get() { 12 | if (_play != null) { 13 | return _play!! 14 | } 15 | _play = ImageVector.Builder( 16 | name = "Play", 17 | defaultWidth = 24.dp, 18 | defaultHeight = 24.dp, 19 | viewportWidth = 256.0f, 20 | viewportHeight = 256.0f, 21 | ).apply { 22 | path(fill = SolidColor(Color(0xFF000000))) { 23 | moveTo(232.4f, 114.49f) 24 | lineTo(88.32f, 26.35f) 25 | arcToRelative( 26 | 16f, 27 | 16f, 28 | 0f, 29 | isMoreThanHalf = false, 30 | isPositiveArc = false, 31 | -16.2f, 32 | -0.3f 33 | ) 34 | arcTo( 35 | 15.86f, 36 | 15.86f, 37 | 0f, 38 | isMoreThanHalf = false, 39 | isPositiveArc = false, 40 | 64f, 41 | 39.87f 42 | ) 43 | verticalLineTo(216.13f) 44 | arcTo(15.94f, 15.94f, 0f, isMoreThanHalf = false, isPositiveArc = false, 80f, 232f) 45 | arcToRelative( 46 | 16.07f, 47 | 16.07f, 48 | 0f, 49 | isMoreThanHalf = false, 50 | isPositiveArc = false, 51 | 8.36f, 52 | -2.35f 53 | ) 54 | lineTo(232.4f, 141.51f) 55 | arcToRelative( 56 | 15.81f, 57 | 15.81f, 58 | 0f, 59 | isMoreThanHalf = false, 60 | isPositiveArc = false, 61 | 0f, 62 | -27f 63 | ) 64 | close() 65 | moveTo(80f, 215.94f) 66 | verticalLineTo(40f) 67 | lineToRelative(143.83f, 88f) 68 | close() 69 | } 70 | }.build() 71 | 72 | return _play!! 73 | } 74 | 75 | private var _play: ImageVector? = null 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Plus.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.Plus: ImageVector 15 | get() { 16 | if (_plus != null) { 17 | return _plus!! 18 | } 19 | _plus = Builder( 20 | name = "Plus", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(224.0f, 128.0f) 32 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -8.0f, 8.0f) 33 | horizontalLineTo(136.0f) 34 | verticalLineToRelative(80.0f) 35 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -16.0f, 0.0f) 36 | verticalLineTo(136.0f) 37 | horizontalLineTo(40.0f) 38 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 0.0f, -16.0f) 39 | horizontalLineToRelative(80.0f) 40 | verticalLineTo(40.0f) 41 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 16.0f, 0.0f) 42 | verticalLineToRelative(80.0f) 43 | horizontalLineToRelative(80.0f) 44 | arcTo(8.0f, 8.0f, 0.0f, false, true, 224.0f, 128.0f) 45 | close() 46 | } 47 | } 48 | .build() 49 | return _plus!! 50 | } 51 | 52 | private var _plus: ImageVector? = null 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/Power.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.Power: ImageVector 15 | get() { 16 | if (_power != null) { 17 | return _power!! 18 | } 19 | _power = Builder( 20 | name = "Power", 21 | defaultWidth = 24.dp, 22 | defaultHeight = 24.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(120.0f, 124.0f) 32 | lineTo(120.0f, 48.0f) 33 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 16.0f, 0.0f) 34 | verticalLineToRelative(76.0f) 35 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, -16.0f, 0.0f) 36 | close() 37 | moveTo(180.4f, 47.5f) 38 | arcToRelative(8.1f, 8.1f, 0.0f, false, false, -11.1f, 2.4f) 39 | arcToRelative(7.9f, 7.9f, 0.0f, false, false, 2.3f, 11.0f) 40 | arcToRelative(80.0f, 80.0f, 0.0f, true, true, -87.2f, 0.0f) 41 | arcToRelative(7.9f, 7.9f, 0.0f, false, false, 2.3f, -11.0f) 42 | arcToRelative(8.1f, 8.1f, 0.0f, false, false, -11.1f, -2.4f) 43 | arcToRelative(96.0f, 96.0f, 0.0f, true, false, 104.8f, 0.0f) 44 | close() 45 | } 46 | } 47 | .build() 48 | return _power!! 49 | } 50 | 51 | private var _power: ImageVector? = null 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/icon/phosphor/TrashSimple.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.icon.phosphor 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType.Companion.NonZero 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap.Companion.Butt 7 | import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.ImageVector.Builder 10 | import androidx.compose.ui.graphics.vector.path 11 | import androidx.compose.ui.unit.dp 12 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 13 | 14 | val Phosphor.TrashSimple: ImageVector 15 | get() { 16 | if (_trash_simple != null) { 17 | return _trash_simple!! 18 | } 19 | _trash_simple = Builder( 20 | name = "Trash-simple", 21 | defaultWidth = 24.0.dp, 22 | defaultHeight = 24.0.dp, 23 | viewportWidth = 256.0f, 24 | viewportHeight = 256.0f, 25 | ).apply { 26 | path( 27 | fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f, 28 | strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f, 29 | pathFillType = NonZero 30 | ) { 31 | moveTo(216.0f, 48.0f) 32 | horizontalLineTo(40.0f) 33 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, 16.0f) 34 | horizontalLineToRelative(8.0f) 35 | verticalLineTo(208.0f) 36 | arcToRelative(16.0f, 16.0f, 0.0f, false, false, 16.0f, 16.0f) 37 | horizontalLineTo(192.0f) 38 | arcToRelative(16.0f, 16.0f, 0.0f, false, false, 16.0f, -16.0f) 39 | verticalLineTo(64.0f) 40 | horizontalLineToRelative(8.0f) 41 | arcToRelative(8.0f, 8.0f, 0.0f, false, false, 0.0f, -16.0f) 42 | close() 43 | moveTo(192.0f, 208.0f) 44 | horizontalLineTo(64.0f) 45 | verticalLineTo(64.0f) 46 | horizontalLineTo(192.0f) 47 | close() 48 | moveTo(80.0f, 24.0f) 49 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 8.0f, -8.0f) 50 | horizontalLineToRelative(80.0f) 51 | arcToRelative(8.0f, 8.0f, 0.0f, false, true, 0.0f, 16.0f) 52 | horizontalLineTo(88.0f) 53 | arcTo(8.0f, 8.0f, 0.0f, false, true, 80.0f, 24.0f) 54 | close() 55 | } 56 | } 57 | .build() 58 | return _trash_simple!! 59 | } 60 | 61 | private var _trash_simple: ImageVector? = null 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Omega Feeder 3 | * Copyright (c) 2022 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.ui.compose.theme 20 | 21 | import androidx.compose.ui.graphics.Color 22 | 23 | // Light Theme 24 | val LightSecondary = Color(0xFF4F6354) 25 | val LightOnSecondary = Color(0xFFFFFFFF) 26 | val LightBackground = Color(0xFFFBFDF8) 27 | val LightOnBackground = Color(0xFF191C1A) 28 | val LightSurface = Color(0xFFE8F5E9) 29 | val LightOnSurface = Color(0xFF191C1A) 30 | val LightPrimary = Color(0xFF009688) 31 | val LightOnPrimary = Color(0xFF434343) 32 | val LightSurfaceVariant = Color(0xFFDCE5DB) 33 | val LightOnSurfaceVariant = Color(0xFF414942) 34 | val LightOutline = Color(0xA46D6D6D) 35 | 36 | // Dark Theme 37 | val DarkSecondary = Color(0xFFB6CCB9) 38 | val DarkOnSecondary = Color(0xFF213527) 39 | val DarkBackground = Color(0xFF191C1A) 40 | val DarkOnBackground = Color(0xFFE1E3DE) 41 | val DarkSurface = Color(0xFF212121) 42 | val DarkOnSurface = Color(0xFFE1E3DE) 43 | val DarkPrimary = Color(0xFF4668D8) 44 | val DarkOnPrimary = Color(0xFF00391E) 45 | val DarkSurfaceVariant = Color(0xFF414942) 46 | val DarkOnSurfaceVariant = Color(0xFFC0C9C0) 47 | val DarkOutline = Color(0xFF8A938B) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/theme/Dimensions.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.theme 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import androidx.compose.ui.unit.Dp 6 | import androidx.compose.ui.unit.dp 7 | 8 | @Immutable 9 | class Dimensions( 10 | /** 11 | * Margin of the navigation button in app bar 12 | */ 13 | val navIconMargin: Dp, 14 | /** 15 | * A gutter is the space between columns that helps separate content. 16 | */ 17 | val gutter: Dp, 18 | /** 19 | * Margins are the space between content and the left and right edges of the screen. 20 | */ 21 | val margin: Dp, 22 | /** 23 | * The max width of the content in case of very wide screens. 24 | */ 25 | val maxContentWidth: Dp, 26 | /** 27 | * The responsive column grid is made up of columns, gutters, and margins, providing a 28 | * convenient structure for the layout of elements within the body region. 29 | * Components, imagery, and text align with the column grid to ensure a logical and 30 | * consistent layout across screen sizes and orientations. 31 | * 32 | * As the size of the body region grows or shrinks, the number of grid columns 33 | * changes in response. 34 | */ 35 | val layoutColumns: Int, 36 | /** 37 | * Number of columns in feed screen 38 | */ 39 | val feedScreenColumns: Int 40 | ) 41 | 42 | val phoneDimensions = Dimensions( 43 | maxContentWidth = 840.dp, 44 | navIconMargin = 16.dp, 45 | margin = 16.dp, 46 | gutter = 16.dp, 47 | layoutColumns = 4, 48 | feedScreenColumns = 1, 49 | ) 50 | 51 | val LocalDimens = staticCompositionLocalOf { 52 | phoneDimensions 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun GroupItemShape(index: Int, lastIndex: Int) = RoundedCornerShape( 9 | topStart = if (index == 0) MaterialTheme.shapes.large.topStart 10 | else MaterialTheme.shapes.extraSmall.topStart, 11 | topEnd = if (index == 0) MaterialTheme.shapes.large.topEnd 12 | else MaterialTheme.shapes.extraSmall.topEnd, 13 | bottomStart = if (index == lastIndex) MaterialTheme.shapes.large.bottomStart 14 | else MaterialTheme.shapes.extraSmall.bottomStart, 15 | bottomEnd = if (index == lastIndex) MaterialTheme.shapes.large.bottomEnd 16 | else MaterialTheme.shapes.extraSmall.bottomEnd 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/theme/Typography.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | import androidx.compose.ui.text.SpanStyle 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.text.font.Font 9 | import androidx.compose.ui.text.font.FontFamily 10 | import androidx.compose.ui.text.font.FontStyle 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.text.style.TextDecoration 13 | import com.saulhdev.feeder.R 14 | 15 | @Composable 16 | fun LinkTextStyle(): TextStyle = 17 | TextStyle( 18 | color = MaterialTheme.colorScheme.primary, 19 | textDecoration = TextDecoration.Underline 20 | ) 21 | 22 | @Composable 23 | fun FeedListItemTitleStyle(): SpanStyle = 24 | FeedListItemTitleTextStyle().toSpanStyle() 25 | 26 | @Composable 27 | fun FeedListItemTitleTextStyle(): TextStyle = 28 | MaterialTheme.typography.titleMedium 29 | 30 | @Composable 31 | fun FeedListItemStyle(): TextStyle = 32 | MaterialTheme.typography.bodyLarge 33 | 34 | @Composable 35 | fun FeedListItemFeedTitleStyle(): TextStyle = 36 | FeedListItemDateStyle() 37 | 38 | @Composable 39 | fun FeedListItemDateStyle(): TextStyle = 40 | MaterialTheme.typography.labelMedium 41 | 42 | @Composable 43 | fun TTSPlayerStyle(): TextStyle = 44 | MaterialTheme.typography.titleMedium 45 | 46 | @Composable 47 | fun CodeInlineStyle(): SpanStyle = 48 | SpanStyle( 49 | background = CodeBlockBackground(), 50 | fontFamily = FontFamily.Monospace 51 | ) 52 | 53 | /** 54 | * Has no background because it is meant to be put over a Surface which has the proper background. 55 | */ 56 | @Composable 57 | fun CodeBlockStyle(): TextStyle = 58 | MaterialTheme.typography.bodyMedium.merge( 59 | SpanStyle( 60 | fontFamily = FontFamily.Monospace 61 | ) 62 | ) 63 | 64 | @Composable 65 | fun CodeBlockBackground(): Color = 66 | MaterialTheme.colorScheme.surfaceVariant 67 | 68 | @Composable 69 | fun BlockQuoteStyle(): SpanStyle = 70 | MaterialTheme.typography.bodyLarge.toSpanStyle().merge( 71 | SpanStyle( 72 | fontWeight = FontWeight.Bold, 73 | fontStyle = FontStyle.Italic, 74 | ) 75 | ) 76 | 77 | val kingthingsPrintingkit = FontFamily( 78 | Font(R.font.kingthings_printingkit) 79 | ) 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/util/KeyEvents.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.saulhdev.feeder.ui.compose.util 17 | 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.input.key.Key 20 | import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp 21 | import androidx.compose.ui.input.key.key 22 | import androidx.compose.ui.input.key.onPreviewKeyEvent 23 | import androidx.compose.ui.input.key.type 24 | 25 | /** 26 | * Intercepts a key event rather than passing it on to children 27 | */ 28 | fun Modifier.interceptKey(key: Key, onKeyEvent: () -> Unit): Modifier { 29 | return this.onPreviewKeyEvent { 30 | if (it.key == key && it.type == KeyUp) { // fire onKeyEvent on KeyUp to prevent duplicates 31 | onKeyEvent() 32 | true 33 | } else it.key == key // only pass the key event to children if it's not the chosen key 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/compose/util/Modifiers.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.util 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.border 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.runtime.Stable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.composed 9 | import androidx.compose.ui.draw.clip 10 | import androidx.compose.ui.draw.shadow 11 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver 12 | import androidx.compose.ui.semantics.semantics 13 | import androidx.compose.ui.unit.dp 14 | 15 | inline fun Modifier.addIf( 16 | condition: Boolean, 17 | crossinline factory: Modifier.() -> Modifier 18 | ): Modifier = 19 | if (condition) factory() else this 20 | 21 | /** 22 | * An object that is immutable means that ‘all publicly accessible properties and fields will not 23 | * change after the instance is constructed’. This characteristic means that Compose can detect 24 | * ‘changes’ between two instances very easily. 25 | * 26 | * Some types - such as List - can't be inferred as stable. 27 | * This class is useful to wrap them to improve performance. 28 | * 29 | * See https://chris.banes.dev/composable-metrics/ 30 | */ 31 | @Stable 32 | data class StableHolder(val item: T) { 33 | override fun toString(): String { 34 | return item.toString() 35 | } 36 | } 37 | 38 | fun Modifier.safeSemantics( 39 | mergeDescendants: Boolean = false, 40 | properties: (SemanticsPropertyReceiver.() -> Unit), 41 | ): Modifier = 42 | semantics(mergeDescendants = mergeDescendants) { 43 | try { 44 | properties() 45 | } catch (_: Exception) { 46 | // Bug in framework? This can be null in any case 47 | } 48 | } 49 | 50 | fun Modifier.blockBorder() = composed { 51 | this 52 | .clip(MaterialTheme.shapes.extraLarge) 53 | .border( 54 | 2.dp, 55 | MaterialTheme.colorScheme.outlineVariant, 56 | MaterialTheme.shapes.extraLarge, 57 | ) 58 | } 59 | 60 | fun Modifier.blockShadow() = 61 | composed { 62 | this 63 | .shadow(elevation = 1.dp, shape = MaterialTheme.shapes.extraLarge) 64 | .background(MaterialTheme.colorScheme.surfaceContainer) 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/feed/FeedAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2024 NeoApplications Team 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.ui.feed 20 | 21 | import android.util.SparseIntArray 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import androidx.recyclerview.widget.RecyclerView 26 | import com.saulhdev.feeder.R 27 | import com.saulhdev.feeder.data.entity.FeedItem 28 | import com.saulhdev.feeder.ui.feed.binders.StoryCardBinder 29 | 30 | class FeedAdapter : RecyclerView.Adapter() { 31 | private var list = listOf() 32 | private lateinit var layoutInflater: LayoutInflater 33 | private var theme: SparseIntArray? = null 34 | 35 | fun replace(new: List) { 36 | if (new != list) { 37 | list = new 38 | notifyDataSetChanged() 39 | } 40 | } 41 | 42 | fun setTheme(theme: SparseIntArray) { 43 | this.theme = theme 44 | notifyDataSetChanged() 45 | } 46 | 47 | override fun getItemCount(): Int { 48 | return list.size 49 | } 50 | 51 | override fun getItemViewType(position: Int): Int { 52 | return (list[position].type.ordinal) 53 | } 54 | 55 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder { 56 | if (!::layoutInflater.isInitialized) layoutInflater = LayoutInflater.from(parent.context) 57 | 58 | val layoutResource = R.layout.feed_card_story_large 59 | 60 | return FeedViewHolder(viewType, layoutInflater.inflate(layoutResource, parent, false)) 61 | } 62 | 63 | override fun onBindViewHolder(holder: FeedViewHolder, position: Int) { 64 | val item = list[position] 65 | StoryCardBinder.bind(theme, item, holder.itemView) 66 | } 67 | 68 | inner class FeedViewHolder(val type: Int, itemView: View) : RecyclerView.ViewHolder(itemView) 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/feed/binders/FeedBinder.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.feed.binders 2 | 3 | import android.util.SparseIntArray 4 | import android.view.View 5 | import com.saulhdev.feeder.data.entity.FeedItem 6 | 7 | interface FeedBinder { 8 | fun bind(theme: SparseIntArray?, item: FeedItem, view: View) 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/navigation/PageItem.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.compose.navigation 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.ui.graphics.vector.ImageVector 5 | import com.saulhdev.feeder.R 6 | import com.saulhdev.feeder.ui.compose.icon.Phosphor 7 | import com.saulhdev.feeder.ui.compose.icon.phosphor.Copyleft 8 | import com.saulhdev.feeder.ui.compose.icon.phosphor.ListDashes 9 | import com.saulhdev.feeder.ui.navigation.NavRoute 10 | 11 | open class PageItem( 12 | @StringRes val titleId: Int, 13 | val icon: ImageVector, 14 | val route: NavRoute, 15 | ) { 16 | companion object { 17 | val AboutLicense = PageItem( 18 | titleId = R.string.about_licenses, 19 | icon = Phosphor.Copyleft, 20 | route = NavRoute.License, 21 | ) 22 | val AboutChangelog = PageItem( 23 | titleId = R.string.about_changelog, 24 | icon = Phosphor.ListDashes, 25 | route = NavRoute.Changelog, 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/ui/pages/MainPage.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.ui.pages 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.foundation.pager.rememberPagerState 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.derivedStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.rememberCoroutineScope 9 | import androidx.compose.ui.Modifier 10 | import com.saulhdev.feeder.ui.components.SlidePager 11 | import com.saulhdev.feeder.ui.navigation.NavItem 12 | import com.saulhdev.feeder.ui.navigation.NeoNavigationSuiteScaffold 13 | import kotlinx.collections.immutable.persistentListOf 14 | import kotlinx.coroutines.launch 15 | 16 | @Composable 17 | fun MainPage(pageIndex: Int = 0) { 18 | val scope = rememberCoroutineScope() 19 | val pages = persistentListOf( 20 | NavItem.Overlay, 21 | NavItem.Settings, 22 | NavItem.Sources, 23 | ) 24 | val pagerState = rememberPagerState(initialPage = pageIndex, pageCount = { pages.size }) 25 | val currentPageIndex = remember { derivedStateOf { pagerState.currentPage } } 26 | 27 | NeoNavigationSuiteScaffold( 28 | pages = pages, 29 | currentState = currentPageIndex, 30 | onItemClick = { index -> 31 | scope.launch { 32 | pagerState.animateScrollToPage(index) 33 | } 34 | } 35 | ) { 36 | SlidePager( 37 | modifier = Modifier 38 | //.padding(paddingValues) 39 | .fillMaxSize(), 40 | pagerState = pagerState, 41 | pageItems = pages, 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/ApplicationCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.SupervisorJob 6 | 7 | class ApplicationCoroutineScope : CoroutineScope { 8 | override val coroutineContext = Dispatchers.Default + SupervisorJob() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/Blob.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | import java.io.File 4 | import java.io.IOException 5 | import java.io.InputStream 6 | import java.io.OutputStream 7 | import java.util.zip.GZIPInputStream 8 | import java.util.zip.GZIPOutputStream 9 | 10 | fun blobFile(itemId: Long, filesDir: File): File = 11 | File(filesDir, "$itemId.txt.gz") 12 | 13 | @Throws(IOException::class) 14 | fun blobInputStream(itemId: Long, filesDir: File): InputStream = 15 | GZIPInputStream(blobFile(itemId = itemId, filesDir = filesDir).inputStream()) 16 | 17 | @Throws(IOException::class) 18 | fun blobOutputStream(itemId: Long, filesDir: File): OutputStream = 19 | GZIPOutputStream(blobFile(itemId = itemId, filesDir = filesDir).outputStream()) 20 | 21 | fun blobFullFile(itemId: Long, filesDir: File): File = 22 | File(filesDir, "$itemId.full.html.gz") 23 | 24 | @Throws(IOException::class) 25 | fun blobFullInputStream(itemId: Long, filesDir: File): InputStream = 26 | GZIPInputStream(blobFullFile(itemId = itemId, filesDir = filesDir).inputStream()) 27 | 28 | @Throws(IOException::class) 29 | fun blobFullOutputStream(itemId: Long, filesDir: File): OutputStream = 30 | GZIPOutputStream(blobFullFile(itemId = itemId, filesDir = filesDir).outputStream()) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/LinearLayoutManagerWrapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2024 NeoApplications Team 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.utils 20 | 21 | import android.content.Context 22 | import android.util.AttributeSet 23 | import androidx.recyclerview.widget.LinearLayoutManager 24 | 25 | class LinearLayoutManagerWrapper : LinearLayoutManager { 26 | constructor(context: Context?) : super(context) {} 27 | constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super( 28 | context, 29 | orientation, 30 | reverseLayout 31 | ) { 32 | } 33 | 34 | constructor( 35 | context: Context?, 36 | attrs: AttributeSet?, 37 | defStyleAttr: Int, 38 | defStyleRes: Int 39 | ) : super(context, attrs, defStyleAttr, defStyleRes) { 40 | } 41 | 42 | override fun supportsPredictiveItemAnimations(): Boolean { 43 | return false 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/OkHttpBuilderExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | import okhttp3.OkHttpClient 4 | import java.security.KeyManagementException 5 | import java.security.NoSuchAlgorithmException 6 | import java.security.cert.X509Certificate 7 | import javax.net.ssl.SSLContext 8 | import javax.net.ssl.TrustManager 9 | import javax.net.ssl.X509TrustManager 10 | 11 | fun OkHttpClient.Builder.trustAllCerts() { 12 | // TODO re-evaluate 13 | try { 14 | val trustManager = object : X509TrustManager { 15 | override fun checkClientTrusted(chain: Array?, authType: String?) { 16 | } 17 | 18 | override fun checkServerTrusted(chain: Array?, authType: String?) { 19 | } 20 | 21 | override fun getAcceptedIssuers(): Array = emptyArray() 22 | } 23 | 24 | val sslContext = SSLContext.getInstance("TLS") 25 | sslContext.init(null, arrayOf(trustManager), null) 26 | val sslSocketFactory = sslContext.socketFactory 27 | 28 | sslSocketFactory(sslSocketFactory, trustManager) 29 | .hostnameVerifier { _, _ -> true } 30 | } catch (e: NoSuchAlgorithmException) { 31 | // ignore 32 | } catch (e: KeyManagementException) { 33 | // ignore 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/OverlayBridge.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | class OverlayBridge { 4 | private var callback: OverlayBridgeCallback? = null 5 | 6 | fun setCallback(callback: OverlayBridgeCallback?) { 7 | this.callback = callback 8 | } 9 | 10 | interface OverlayBridgeCallback { 11 | fun applyNewTheme(value: String) 12 | //fun applyNewCardBg(value: String) 13 | fun applyCompactCard(value: Boolean) 14 | fun applySysColors(value: Boolean) 15 | fun applyNewTransparency(value: Float) 16 | fun onClientMessage(action: String) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/PopupWindowImpl.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.ColorFilter 6 | import android.graphics.PixelFormat 7 | import android.graphics.drawable.Drawable 8 | import android.os.Handler 9 | import android.view.View 10 | import android.widget.PopupWindow 11 | import androidx.annotation.UiThread 12 | 13 | @UiThread 14 | class PopupWindowImpl(context: Context) : PopupWindow(context) { 15 | private val handler = Handler() 16 | private var b: PopupContentAnimator? = null 17 | 18 | init { 19 | isClippingEnabled = false 20 | isFocusable = true 21 | animationStyle = 0 22 | setBackgroundDrawable(object : Drawable() { 23 | override fun draw(canvas: Canvas) { 24 | 25 | } 26 | 27 | override fun setAlpha(alpha: Int) { 28 | 29 | } 30 | 31 | override fun getOpacity(): Int { 32 | return PixelFormat.TRANSPARENT 33 | } 34 | 35 | override fun setColorFilter(colorFilter: ColorFilter?) { 36 | 37 | } 38 | 39 | }) 40 | } 41 | 42 | override fun showAsDropDown(view: View?) { 43 | run { 44 | super.showAsDropDown(view) 45 | } 46 | } 47 | 48 | override fun showAsDropDown(view: View?, i: Int, i2: Int) { 49 | run { 50 | super.showAsDropDown(view, i, i2) 51 | } 52 | } 53 | 54 | override fun showAsDropDown(view: View?, i: Int, i2: Int, i3: Int) { 55 | run { 56 | super.showAsDropDown(view, i, i2, i3) 57 | } 58 | } 59 | 60 | override fun showAtLocation(view: View?, i: Int, i2: Int, i3: Int) { 61 | run { 62 | super.showAtLocation(view, i, i2, i3) 63 | } 64 | } 65 | 66 | private fun run(aVar: () -> Unit) { 67 | this.b = PopupContentAnimator(contentView) 68 | this.b?.b(false) 69 | aVar.invoke() 70 | this.handler.post { 71 | this.b?.a(true) 72 | } 73 | } 74 | 75 | override fun dismiss() { 76 | this.handler.removeCallbacksAndMessages(null) 77 | this.b?.a { 78 | b = null 79 | super.dismiss() 80 | } 81 | this.b?.b(true) 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/VideoTagHunter.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils 2 | 3 | // Example strings 4 | // www.youtube.com/embed/cjxnVO9RpaQ 5 | // www.youtube.com/embed/cjxnVO9RpaQ?feature=oembed 6 | // www.youtube.com/embed/cjxnVO9RpaQ/theoretical_crap 7 | // www.youtube.com/embed/cjxnVO9RpaQ/crap?feature=oembed 8 | internal val YoutubeIdPattern = "youtube.com/embed/([^?/]*)".toRegex() 9 | 10 | fun getVideo(src: String?): Video? { 11 | return src?.let { 12 | YoutubeIdPattern.find(src)?.let { match -> 13 | val videoId = match.groupValues[1] 14 | Video( 15 | src = src, 16 | imageUrl = "http://img.youtube.com/vi/$videoId/hqdefault.jpg", 17 | link = "https://www.youtube.com/watch?v=$videoId" 18 | ) 19 | } 20 | } 21 | } 22 | 23 | data class Video( 24 | val src: String, 25 | val imageUrl: String, 26 | // Youtube needs a different link than embed links 27 | val link: String 28 | ) { 29 | val width: Int 30 | get() = 480 31 | 32 | val height: Int 33 | get() = 360 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/extensions/Koin.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils.extensions 2 | 3 | import android.util.Log 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.LocalActivity 6 | import androidx.compose.runtime.Composable 7 | import androidx.lifecycle.ViewModel 8 | import org.koin.androidx.compose.koinViewModel 9 | import org.koin.core.component.getScopeId 10 | 11 | open class NeoViewModel : ViewModel() { 12 | init { 13 | Log.d(this::class.toString(), "neoviewmodel@koinscope: ${getScopeId()}") 14 | } 15 | } 16 | 17 | @Composable 18 | inline fun koinNeoViewModel() = koinViewModel( 19 | viewModelStoreOwner = LocalActivity.current as ComponentActivity 20 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/extensions/KtExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.saulhdev.feeder.utils.extensions 2 | 3 | import android.graphics.Color 4 | import android.os.Build 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.Window 8 | import androidx.annotation.ColorInt 9 | import androidx.core.graphics.ColorUtils 10 | import androidx.core.view.WindowCompat 11 | 12 | const val LIGHT_BORDER = 0.5f 13 | 14 | fun Int.isLight() = ColorUtils.calculateLuminance(this) > LIGHT_BORDER 15 | fun Int.isDark() = ColorUtils.calculateLuminance(this) < LIGHT_BORDER 16 | 17 | fun Bundle.dump(tag: String) { 18 | keySet().forEach { 19 | val item = get(it) 20 | item ?: return@forEach 21 | Log.d(tag, "[$it] $item") 22 | } 23 | } 24 | 25 | fun Window.setLightFlags() { 26 | val controller = WindowCompat.getInsetsController(this, this.decorView) 27 | controller.isAppearanceLightStatusBars = true 28 | controller.isAppearanceLightNavigationBars = true 29 | } 30 | 31 | fun Window.clearLightFlags() { 32 | val controller = WindowCompat.getInsetsController(this, this.decorView) 33 | controller.isAppearanceLightStatusBars = false 34 | controller.isAppearanceLightNavigationBars = false 35 | } 36 | 37 | @ColorInt 38 | fun Color?.toInt(): Int { 39 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) return Color.BLACK 40 | this ?: Color.BLACK 41 | return Color.rgb(this!!.red(), this.green(), this.blue()) 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/utils/openLinkInCustomTab.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2023 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.utils 20 | 21 | import android.content.ActivityNotFoundException 22 | import android.content.Context 23 | import android.net.Uri 24 | import android.widget.Toast 25 | import androidx.annotation.ColorInt 26 | import androidx.browser.customtabs.CustomTabColorSchemeParams 27 | import androidx.browser.customtabs.CustomTabsIntent 28 | import androidx.core.content.ContextCompat 29 | import com.saulhdev.feeder.R 30 | 31 | fun openLinkInCustomTab( 32 | context: Context, 33 | link: String 34 | ): Boolean { 35 | @ColorInt val colorPrimaryLight = 36 | ContextCompat.getColor(context, R.color.md_theme_primary) 37 | @ColorInt val colorPrimaryDark = 38 | ContextCompat.getColor(context, R.color.md_theme_primary) 39 | try { 40 | val intent = CustomTabsIntent.Builder() 41 | .setShareState(CustomTabsIntent.SHARE_STATE_ON) 42 | .setDefaultColorSchemeParams( 43 | CustomTabColorSchemeParams.Builder() 44 | .setToolbarColor(colorPrimaryLight) 45 | .build() 46 | ) 47 | .setColorSchemeParams( 48 | CustomTabsIntent.COLOR_SCHEME_DARK, CustomTabColorSchemeParams.Builder() 49 | .setToolbarColor(colorPrimaryDark) 50 | .build() 51 | ) 52 | .build() 53 | intent.launchUrl(context, Uri.parse(link)) 54 | } catch (e: ActivityNotFoundException) { 55 | Toast.makeText(context, R.string.app_name, Toast.LENGTH_SHORT).show() 56 | return false 57 | } 58 | return true 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/viewmodels/ArticleViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2025 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.viewmodels 20 | 21 | import androidx.lifecycle.viewModelScope 22 | import com.saulhdev.feeder.data.repository.ArticleRepository 23 | import com.saulhdev.feeder.data.repository.FeedRepository 24 | import com.saulhdev.feeder.data.db.models.Feed 25 | import com.saulhdev.feeder.utils.extensions.NeoViewModel 26 | import kotlinx.coroutines.Dispatchers 27 | import kotlinx.coroutines.flow.Flow 28 | import kotlinx.coroutines.flow.SharingStarted 29 | import kotlinx.coroutines.flow.stateIn 30 | import kotlinx.coroutines.plus 31 | 32 | class ArticleViewModel( 33 | private val articleRepo: ArticleRepository, 34 | private val feedsRepo: FeedRepository, 35 | ) : NeoViewModel() { 36 | private val ioScope = viewModelScope.plus(Dispatchers.IO) 37 | 38 | fun articleById(id: Long) = articleRepo.getArticleById(id) 39 | .stateIn( 40 | ioScope, 41 | SharingStarted.Eagerly, 42 | null 43 | ) 44 | 45 | fun getFeedById(id: Long): Flow { 46 | return feedsRepo.getFeedById(id) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/viewmodels/FeedsViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2025 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.viewmodels 20 | 21 | import androidx.lifecycle.viewModelScope 22 | import com.saulhdev.feeder.data.repository.FeedRepository 23 | import com.saulhdev.feeder.data.db.models.Feed 24 | import com.saulhdev.feeder.utils.extensions.NeoViewModel 25 | import kotlinx.coroutines.Dispatchers 26 | import kotlinx.coroutines.flow.SharingStarted 27 | import kotlinx.coroutines.flow.stateIn 28 | import kotlinx.coroutines.launch 29 | import kotlinx.coroutines.plus 30 | 31 | class FeedsViewModel(private val feedsRepo: FeedRepository) : NeoViewModel() { 32 | private val ioScope = viewModelScope.plus(Dispatchers.IO) 33 | 34 | val allFeeds = feedsRepo.getAllFeeds() 35 | .stateIn( 36 | ioScope, 37 | SharingStarted.Eagerly, 38 | emptyList() 39 | ) 40 | 41 | fun insertFeed(feed: Feed) { 42 | viewModelScope.launch { 43 | feedsRepo.insertFeed(feed) 44 | } 45 | } 46 | 47 | fun deleteFeed(feed: Feed) { 48 | viewModelScope.launch { 49 | feedsRepo.deleteFeed(feed) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saulhdev/feeder/viewmodels/SearchFeedViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Neo Feed 3 | * Copyright (c) 2025 Saul Henriquez 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.saulhdev.feeder.viewmodels 20 | 21 | import android.os.Parcelable 22 | import android.util.Log 23 | import androidx.compose.runtime.Immutable 24 | import com.saulhdev.feeder.manager.models.FeedParser 25 | import com.saulhdev.feeder.utils.extensions.NeoViewModel 26 | import com.saulhdev.feeder.utils.sloppyLinkToStrictURL 27 | import kotlinx.coroutines.Dispatchers 28 | import kotlinx.coroutines.flow.flow 29 | import kotlinx.coroutines.flow.flowOn 30 | import kotlinx.coroutines.flow.mapNotNull 31 | import kotlinx.parcelize.Parcelize 32 | import java.net.URL 33 | 34 | class SearchFeedViewModel : NeoViewModel() { 35 | private val feedParser: FeedParser = FeedParser() 36 | 37 | fun searchForFeeds(url: URL) = 38 | flow { 39 | emit(url) 40 | feedParser.getAlternateFeedLinksAtUrl(url) 41 | .forEach { 42 | emit(sloppyLinkToStrictURL(it.first)) 43 | } 44 | }.mapNotNull { 45 | try { 46 | feedParser.parseFeedUrl(it)?.let { feed -> 47 | SearchResult( 48 | title = feed.title ?: "", 49 | url = feed.feed_url ?: it.toString(), 50 | description = feed.description ?: "", 51 | isError = false 52 | ) 53 | } 54 | } catch (t: Throwable) { 55 | Log.e("searchForFeeds", "Failed to parse", t) 56 | SearchResult( 57 | title = FAILED_TO_PARSE_PLACEHOLDER, 58 | url = it.toString(), 59 | description = t.message ?: "", 60 | isError = true 61 | ) 62 | } 63 | } 64 | .flowOn(Dispatchers.Default) 65 | 66 | companion object { 67 | const val FAILED_TO_PARSE_PLACEHOLDER = "failed_to_parse" 68 | } 69 | } 70 | 71 | @Immutable 72 | @Parcelize 73 | data class SearchResult( 74 | val title: String, 75 | val url: String, 76 | val description: String, 77 | val isError: Boolean, 78 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/vkim_bg_overlay.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/drawable-hdpi/vkim_bg_overlay.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/vkim_bg_overlay.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/drawable-mdpi/vkim_bg_overlay.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/vkim_bg_overlay.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/drawable-xhdpi/vkim_bg_overlay.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/vkim_bg_overlay.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/drawable-xxhdpi/vkim_bg_overlay.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/vkim_bg_overlay.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/drawable-xxxhdpi/vkim_bg_overlay.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_clockwise.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_caret_up.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_arrow_up.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gear.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_fill.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_power.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_trash_simple.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_youtube.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/placeholder_image_article_day.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/kingthings_printingkit.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/font/kingthings_printingkit.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/compose_overlay.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/overlay_header.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 26 | 27 | 37 | 38 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-cs/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nastavení 4 | zítra 5 | O aplikaci 6 | Zdroje dat 7 | v 8 | Kontakty 9 | Licence 10 | Seznam změn 11 | Telegramový kanál 12 | Přidat nebo odebrat zdroje 13 | Nyní 14 | včera 15 | dnes 16 | Knihovny s otevřeným zdrojovým kódem 17 | -------------------------------------------------------------------------------- /app/src/main/res/values-hi/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | डेटा केंद्र 4 | केंद्र जोड़े और हटाएँ 5 | अब 6 | पर 7 | आज 8 | कल 9 | संपर्क 10 | बदलाव 11 | टेलीग्राम चैनल 12 | स्रोत कोड 13 | दिखावा 14 | सेटिंग्स 15 | जानकारी 16 | बीता कल 17 | लाइसेंस 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-lv/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Iestatījumi 4 | Par lietotni 5 | Datu avoti 6 | Pievienot vai noņemt avotus 7 | tikko 8 | pulksten 9 | šodien 10 | rīt 11 | vakar 12 | Kontakti 13 | Dalībnieki 14 | Kanāls 15 | Kopiena 16 | Telegram kopiena 17 | Matrix kopiena 18 | Būvējuma informācija 19 | Izstrādātājs 20 | Licence 21 | Izmaiņu žurnāls 22 | Atvērtā pirmkoda bibliotēkas 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4285f4 4 | #ffffff 5 | #a0a0a0 6 | #D0BCFF 7 | #12200f 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-nn/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Utsjånad 4 | Innstillingar 5 | Legg til eller tak bort kjelder 6 | Om 7 | Datakjelder 8 | no 9 | klokka 10 | i dag 11 | i morgon 12 | i går 13 | Kontaktar 14 | Løyve 15 | Brigdelogg 16 | Telegram-sambandsline 17 | Kjeldekoden 18 | Lysking 19 | Overlagbakgrunnens gjennomsynlegheit 20 | Opne i nettlesar 21 | Samantrengd elementvising 22 | Kortbakgrunn 23 | Overlagsbakgrunn 24 | Opne innstillingane 25 | Ingen tilgjenge gjeve 26 | Trykk her for å hente att 27 | Stadfest at du har gjeve løyve til «merknadstilgjenge» og freist om att. 28 | Nytta systemets bakgrunnsbileteletar 29 | HomeFeeder tarv «merknadstilgjenge» for å visa merknadar oppå andre appar. 30 | -------------------------------------------------------------------------------- /app/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | час назад 5 | два часа назад 6 | три часа назад 7 | 8 | 9 | 10 | Янв 11 | Фев 12 | Мар 13 | Апр 14 | Мая 15 | Июн 16 | Июл 17 | Авг 18 | Сен 19 | Окт 20 | Ноя 21 | Дек 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %,d минут назад 5 | %,d минуту назад 6 | %,d минуты назад 7 | 8 | 9 | %,d секунд назад 10 | %,d секунду назад 11 | %,d секунды назад 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 設定 4 | 關於 5 | 數據源 6 | 增加或刪除 7 | 現在 8 | 今天 9 | 昨天 10 | 變更日誌 11 | 開源庫 12 | 電報頻道 13 | 調試 14 | 背景過度不透明度 15 | 疊加主題 16 | 在瀏覽器中打開 17 | 執照 18 | 明天 19 | 聯絡方式 20 | 源代碼 21 | 外觀 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | an hour ago 5 | two hours ago 6 | three hours ago 7 | 8 | 9 | 10 | Jan 11 | Feb 12 | Mar 13 | Apr 14 | May 15 | Jun 16 | Jul 17 | Aug 18 | Sep 19 | Oct 20 | Nov 21 | Dec 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4285f4 4 | #000000 5 | #707070 6 | #6750A4 7 | #F9FCF6 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/plurals.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %,d minutes ago 5 | %,d minute ago 6 | %,d minutes ago 7 | 8 | 9 | %,d seconds ago 10 | %,d second ago 11 | %,d seconds ago 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 17 | 18 | 21 | 22 | 27 | 28 | 31 | 32 | 39 | 40 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | file:///android_asset/ 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /badge_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/badge_github.png -------------------------------------------------------------------------------- /badge_izzy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/badge_izzy.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) apply false 3 | alias(libs.plugins.kotlin.android) apply false 4 | alias(libs.plugins.kotlin.compose) apply false 5 | alias(libs.plugins.kotlin.kapt) apply false 6 | alias(libs.plugins.kotlin.parcelize) apply false 7 | alias(libs.plugins.kotlin.serialization) apply false 8 | alias(libs.plugins.ksp) apply false 9 | } 10 | 11 | tasks.register("clean", Delete::class.java) { 12 | delete(layout.buildDirectory) 13 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1700.txt: -------------------------------------------------------------------------------- 1 | Add: Pref to remove duplicate articles 2 | Add: Main pager with Feed, Settings & Feeds 3 | Add: Share button as action to articles 4 | Fix: Applying updated Overlay theme 5 | Update: App icon 6 | Update: Migrate DI from KodeIn to Koin 7 | Update: Revamp preferences, articles and feed layouts 8 | + more in more than 120 commits & 10 translation contributions -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1701.txt: -------------------------------------------------------------------------------- 1 | Add: Black themes 2 | Fix: Crashing offline reader on certain systems 3 | Update: Selection dialog layout 4 | TargetSDK 34 5 | 1 translation contribution -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1703.txt: -------------------------------------------------------------------------------- 1 | Add: Parsing most and improve existing HTML-tags 2 | Add: Option to open feed in WebView 3 | Fix: Placeholder icons visibility on black backgrounds 4 | Fix: Applying dark/light system bars 5 | Update: Re-sync source after relevant update 6 | Update: Revamp About page 7 | + more in +30 commits & 5 translation contributions 8 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1800.txt: -------------------------------------------------------------------------------- 1 | Add: SortFilter sheet to the articles page 2 | Fix: Back handling on edit and add feed pages 3 | Update: Make UI wide screens friendly & navigation adaptive 4 | Update: Replace removed MD icons with Phosphor icons 5 | Update: Revamp viewmodels and repositories applying separation of concerns 6 | TargetSDK 35 7 | + more in +40 commits & 10 translation contributions 8 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/50.txt: -------------------------------------------------------------------------------- 1 | Add: Bookmarking 2 | Add: Share & open-in-browser buttons 3 | Add: Dynamic colors 4 | Add: Monochrome app icon 5 | Update: Revamp all layouts 6 | Fix: Editing feeds 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/55.txt: -------------------------------------------------------------------------------- 1 | Replace: bookmark button with favorite button -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/57.txt: -------------------------------------------------------------------------------- 1 | Replace: ComposeWebView articles with the CustomTabsIntent -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -- 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use feature qw(say); 6 | 7 | # read from __DATA__ section in this file. 8 | local $/ = undef; 9 | my $text = ; 10 | 11 | ############################################ 12 | # change spaces and line feeds to single space, 13 | # it's required for F-droid android client. 14 | # also remove head/tail spaces in whole text. 15 | 16 | $text =~ s/[\x00-\x20]+/ /g; 17 | $text =~ s/\A //; 18 | $text =~ s/ \z//; 19 | 20 | ############################################ 21 | # trim spaces before/after open/close block tags. also
,
,
22 | 23 | # HTML block elements and "br". joined with '|' 24 | my $blockElements = join "|", qw( 25 | address article aside blockquote canvas dd div dl dt 26 | fieldset figcaption figure footer form 27 | h1 h2 h3 h4 h5 h6 header hr li 28 | main nav noscript ol p pre section table tfoot ul video 29 | br 30 | ); 31 | 32 | # RegEx for block tag that may have attributes, and spaces before/after tag. 33 | my $trimElementRe = qr!\s*(/"]+|"[^"]*")*/?>)\s*!i; 34 | 35 | ## verbose debugging. 36 | #say $trimElementsRe; 37 | #while( $text =~ /$trimElementRe/g){ 38 | # next if $& eq $1; 39 | # say "[$&] => [$1]"; 40 | #} 41 | 42 | $text =~ s/$trimElementRe/$1/g; 43 | 44 | ############################################ 45 | 46 | # write to .txt file. $0 means path of the this script file. 47 | my $file = $0; 48 | $file =~ s/\.pl$/\.txt/ or die "can't make output filename. $0"; 49 | open(my $fh,">:utf8",$file) or die "$file $!"; 50 | say $fh $text; 51 | close($fh) or die "$file $!"; 52 | 53 | # apt-cyg install tidy libtidy5 54 | system qq(tidy -q -e $file); 55 | 56 | __DATA__ 57 | 58 |

Neo Feed is a custom Google Now Feed replacement for launchers. It's actually a normal RSS-reader that can be embedded in place of Google's „Discover“ feature, to be pinned as a widget to the left of your home screen for easy access, and lets you add your own RSS feeds. At this moment it's an „early bird“ version and not yet considered a „public release“.

59 |

While primarily focused on Neo-Launcher, Neo Feed currently supports a.o. the following launchers:

60 |
    61 |
  • Neo Launcher
  • 62 |
  • LibreChair
  • 63 |
  • Shade Launcher
  • 64 |
  • And any launcher with custom feed provider support.
  • 65 |
66 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 |

Neo Feed is a custom Google Now Feed replacement for launchers. It's actually a normal RSS-reader that can be embedded in place of Google's „Discover“ feature, to be pinned as a widget to the left of your home screen for easy access, and lets you add your own RSS feeds. At this moment it's an „early bird“ version and not yet considered a „public release“.

While primarily focused on Neo-Launcher, Neo Feed currently supports a.o. the following launchers:

  • Neo Launcher
  • LibreChair
  • Shade Launcher
  • And any launcher with custom feed provider support.
2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | a custom Google Now Feed alternative for launchers -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Sat May 20 18:45:21 CST 2023 14 | android.enableJetifier=false 15 | android.useAndroidX=true 16 | kotlin.code.style=official 17 | org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 18 | org.gradle.unsafe.configuration-cache=true 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 18 15:48:52 CEST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /neo_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoApplications/Neo-Feed/182acef4f22361037bfc8928d6c88ceee4393805/neo_banner.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | maven(url = "https://jitpack.io") 13 | } 14 | } 15 | dependencyResolutionManagement { 16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 17 | repositories { 18 | google() 19 | mavenCentral() 20 | maven(url = "https://jitpack.io") 21 | } 22 | } 23 | 24 | rootProject.name = "Neo Feed" 25 | include(":app") --------------------------------------------------------------------------------