├── modules ├── dailywallpaper │ ├── android │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nzran │ │ │ │ └── dailywallpaper │ │ │ │ ├── DailyWallpaperModule.kt │ │ │ │ ├── BackgroundWorker.kt │ │ │ │ └── Utils.kt │ │ └── build.gradle │ ├── expo-module.config.json │ ├── ios │ │ ├── DailyWallpaperView.swift │ │ ├── DailyWallpaper.podspec │ │ └── DailyWallpaperModule.swift │ ├── src │ │ └── DailyWallpaperModule.ts │ └── index.ts ├── download-manager │ ├── android │ │ ├── src │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nzran │ │ │ │ └── downloadmanager │ │ │ │ └── DownloadManagerModule.kt │ │ └── build.gradle │ ├── src │ │ ├── DownloadManager.types.ts │ │ └── DownloadManagerModule.ts │ ├── expo-module.config.json │ ├── ios │ │ ├── DownloadManagerView.swift │ │ ├── DownloadManager.podspec │ │ └── DownloadManagerModule.swift │ └── index.ts └── wallpaper-manager │ ├── android │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── nzran │ │ │ └── wallpapermanager │ │ │ └── WallpaperManagerModule.kt │ └── build.gradle │ ├── src │ ├── WallpaperManager.types.ts │ └── WallpaperManagerModule.ts │ ├── expo-module.config.json │ ├── ios │ ├── WallpaperManagerView.swift │ ├── WallpaperManager.podspec │ └── WallpaperManagerModule.swift │ └── index.ts ├── assets ├── db │ └── mysqlite.db ├── images │ ├── icon.png │ ├── favicon.png │ ├── splash.png │ ├── adaptive-icon.png │ ├── tail.svg │ └── icon.svg ├── fonts │ ├── DMSans │ │ ├── DMSans-Bold.ttf │ │ ├── DMSans-Thin.ttf │ │ ├── DMSans-Black.ttf │ │ ├── DMSans-Light.ttf │ │ ├── DMSans-Medium.ttf │ │ ├── DMSans-Regular.ttf │ │ └── DMSans-SemiBold.ttf │ └── SpaceMono-Regular.ttf └── icons │ └── play_store.svg ├── press ├── screenshots.png └── play_store_badge.png ├── images.d.ts ├── lib ├── icons │ ├── Check.tsx │ ├── ChevronUp.tsx │ ├── ChevronDown.tsx │ └── iconWithClassName.ts ├── utils │ ├── cn.ts │ ├── uuid.ts │ ├── time_since.ts │ ├── sql.ts │ └── process_reddit_post.ts ├── animations │ ├── show_hide_topbar.ts │ └── fading_pulse.ts └── services │ ├── send_error_logs.ts │ ├── wallpaper_type.ts │ ├── get_black_percentage.ts │ ├── search_wallpapers.ts │ └── get_wallpapers.ts ├── nativewind-env.d.ts ├── babel.config.js ├── .prettierrc.js ├── constants ├── sort_options.ts ├── colors.ts └── wallpaper_options.ts ├── tsconfig.json ├── hooks └── useDebounce.ts ├── .gitignore ├── eas.json ├── app ├── +not-found.tsx ├── +html.tsx ├── _layout.tsx ├── (tabs) │ ├── _layout.tsx │ ├── settings.tsx │ ├── index.tsx │ ├── search.tsx │ └── downloaded.tsx └── downloaded_viewer.tsx ├── components ├── ui │ ├── Text.tsx │ ├── LoadingSpinner.tsx │ ├── TopBar.tsx │ ├── Input.tsx │ ├── Select.tsx │ ├── Button.tsx │ └── Switch.tsx ├── SearchTopBar.tsx ├── ParallaxScrollView.tsx ├── PrivacyPolicyDialog.tsx ├── ChangeLog.tsx └── OnlineWallpaperGridItem.tsx ├── appconfig.ts ├── apptheme.js ├── metro.config.js ├── styles └── global.css ├── tailwind.config.js ├── README.md ├── app.json ├── package.json └── store ├── downloaded_wallpapers.ts └── settings.ts /modules/dailywallpaper/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /modules/download-manager/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/db/mysqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/db/mysqlite.db -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /press/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/press/screenshots.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /press/play_store_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/press/play_store_badge.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/DMSans/DMSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gauravjot/amoledbackgrounds-app/HEAD/assets/fonts/DMSans/DMSans-SemiBold.ttf -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png"; 2 | declare module "*.jpg"; 3 | declare module "*.jpeg"; 4 | declare module "*.svg"; 5 | declare module "*.gif"; 6 | -------------------------------------------------------------------------------- /lib/icons/Check.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from 'lucide-react-native'; 2 | import { iconWithClassName } from './iconWithClassName'; 3 | iconWithClassName(Check); 4 | export { Check }; -------------------------------------------------------------------------------- /nativewind-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind. -------------------------------------------------------------------------------- /lib/icons/ChevronUp.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronUp } from 'lucide-react-native'; 2 | import { iconWithClassName } from './iconWithClassName'; 3 | iconWithClassName(ChevronUp); 4 | export { ChevronUp }; -------------------------------------------------------------------------------- /modules/download-manager/src/DownloadManager.types.ts: -------------------------------------------------------------------------------- 1 | export type ChangeEventPayload = { 2 | value: string; 3 | }; 4 | 5 | export type DownloadManagerViewProps = { 6 | name: string; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/icons/ChevronDown.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDown } from 'lucide-react-native'; 2 | import { iconWithClassName } from './iconWithClassName'; 3 | iconWithClassName(ChevronDown); 4 | export { ChevronDown }; -------------------------------------------------------------------------------- /lib/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/src/WallpaperManager.types.ts: -------------------------------------------------------------------------------- 1 | export type ChangeEventPayload = { 2 | success: boolean; 3 | path: string; 4 | }; 5 | 6 | export type WallpaperManagerViewProps = { 7 | name: string; 8 | }; 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: [ 5 | ["babel-preset-expo", { jsxImportSource: "nativewind" }], 6 | "nativewind/babel" 7 | ], 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: "avoid", 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: false, 6 | trailingComma: "all", 7 | printWidth: 120, 8 | singleAttributePerLine: false, 9 | }; 10 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["ios", "android"], 3 | "ios": { 4 | "modules": ["WallpaperManagerModule"] 5 | }, 6 | "android": { 7 | "modules": ["com.nzran.wallpapermanager.WallpaperManagerModule"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /constants/sort_options.ts: -------------------------------------------------------------------------------- 1 | // sort options enum 2 | export enum SortOptions { 3 | Hot = "Hot", 4 | New = "New", 5 | "Top 24h" = "Top 24h", 6 | "Top Week" = "Top Week", 7 | "Top Month" = "Top Month", 8 | "Top Year" = "Top Year", 9 | "Top All" = "Top All", 10 | } 11 | -------------------------------------------------------------------------------- /lib/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | export default function generateUUID() { 2 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 3 | const r = (Math.random() * 16) | 0; 4 | const v = c === "x" ? r : (r & 0x3) | 0x8; 5 | return v.toString(16); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /modules/dailywallpaper/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["ios", "tvos", "android", "web"], 3 | "ios": { 4 | "modules": ["DailyWallpaperModule"] 5 | }, 6 | "android": { 7 | "modules": ["com.nzran.dailywallpaper.DailyWallpaperModule"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/download-manager/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["ios", "tvos", "android", "web"], 3 | "ios": { 4 | "modules": ["DownloadManagerModule"] 5 | }, 6 | "android": { 7 | "modules": ["com.nzran.downloadmanager.DownloadManagerModule"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/dailywallpaper/ios/DailyWallpaperView.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | // This view will be used as a native component. Make sure to inherit from `ExpoView` 4 | // to apply the proper styling (e.g. border radius and shadows). 5 | class DailyWallpaperView: ExpoView { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /modules/download-manager/ios/DownloadManagerView.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | // This view will be used as a native component. Make sure to inherit from `ExpoView` 4 | // to apply the proper styling (e.g. border radius and shadows). 5 | class DownloadManagerView: ExpoView { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/ios/WallpaperManagerView.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | 3 | // This view will be used as a native component. Make sure to inherit from `ExpoView` 4 | // to apply the proper styling (e.g. border radius and shadows). 5 | class WallpaperManagerView: ExpoView { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": ["./*"] 7 | } 8 | }, 9 | "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "nativewind-env.d.ts", "images.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /constants/colors.ts: -------------------------------------------------------------------------------- 1 | export const Colors = { 2 | accent_rgb: "rgb(75, 87, 155)", 3 | accent_foreground_rgb: "rgb(117, 138, 255)", 4 | background_rgb: "rgb(0,0,0)", 5 | foreground_rgb: "rgb(250, 250, 250)", 6 | secondary_rgb: "rgb(39, 39, 42)", 7 | secondary_foreground_rgb: "rgb(217, 217, 217)", 8 | }; 9 | -------------------------------------------------------------------------------- /modules/dailywallpaper/src/DailyWallpaperModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | // It loads the native module object from the JSI or falls back to 4 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 5 | export default requireNativeModule('DailyWallpaper'); 6 | -------------------------------------------------------------------------------- /modules/download-manager/src/DownloadManagerModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | // It loads the native module object from the JSI or falls back to 4 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 5 | export default requireNativeModule('DownloadManager'); 6 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/src/WallpaperManagerModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | // It loads the native module object from the JSI or falls back to 4 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 5 | export default requireNativeModule('WallpaperManager'); 6 | -------------------------------------------------------------------------------- /lib/icons/iconWithClassName.ts: -------------------------------------------------------------------------------- 1 | import type { LucideIcon } from 'lucide-react-native'; 2 | import { cssInterop } from 'nativewind'; 3 | 4 | export function iconWithClassName(icon: LucideIcon) { 5 | cssInterop(icon, { 6 | className: { 7 | target: 'style', 8 | nativeStyleToProp: { 9 | color: true, 10 | opacity: true, 11 | }, 12 | }, 13 | }); 14 | } -------------------------------------------------------------------------------- /hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from "react"; 2 | 3 | export default function useDebounce(value: T, delay?: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500); 8 | 9 | return () => { 10 | clearTimeout(timer); 11 | }; 12 | }, [value, delay]); 13 | 14 | return debouncedValue; 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | /android 13 | local.properties 14 | build/ 15 | .idea 16 | .gradle 17 | .vscode/ 18 | # macOS 19 | .DS_Store 20 | .env*.local 21 | 22 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 23 | # The following patterns were generated by expo-cli 24 | 25 | expo-env.d.ts 26 | # @end expo-cli 27 | 28 | package-lock.json 29 | -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 9.1.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal" 12 | }, 13 | "production": { 14 | "env": { 15 | "EXPO_PUBLIC_BUILD_NAME": "PRODUCTION", 16 | "EXPO_PUBLIC_LOGS_SERVER": "https://bigsur.nzran.com" 17 | } 18 | } 19 | }, 20 | "submit": { 21 | "production": {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /constants/wallpaper_options.ts: -------------------------------------------------------------------------------- 1 | import {WALLPAPERS_POST_LIMIT} from "@/appconfig"; 2 | 3 | export const WALLPAPERS_URL = "https://www.reddit.com/r/Amoledbackgrounds"; 4 | export const SearchURL = (query: string, page: number, after: string | undefined) => 5 | `https://www.reddit.com/r/Amoledbackgrounds/search.json?q=${query}&count=${page * WALLPAPERS_POST_LIMIT}&after=${ 6 | after || "" 7 | }&restrict_sr=1`; 8 | export const CommentsURL = (postId: string) => `https://www.reddit.com/r/Amoledbackgrounds/comments/${postId}.json`; 9 | -------------------------------------------------------------------------------- /app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from "@/components/ui/Text"; 2 | import { Link, Stack } from "expo-router"; 3 | import { View } from "react-native"; 4 | 5 | export default function NotFoundScreen() { 6 | return ( 7 | <> 8 | 9 | 10 | This screen doesn't exist. 11 | 12 | Go to home screen! 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/ui/Text.tsx: -------------------------------------------------------------------------------- 1 | import {cn} from "@/lib/utils/cn"; 2 | import * as React from "react"; 3 | import {Text as RNText} from "react-native"; 4 | 5 | const Text = React.forwardRef< 6 | React.ElementRef, 7 | React.ComponentPropsWithoutRef 8 | >(({className, ...props}, ref) => { 9 | return ( 10 | 15 | ); 16 | }); 17 | Text.displayName = "Text"; 18 | 19 | export {Text}; 20 | -------------------------------------------------------------------------------- /lib/animations/show_hide_topbar.ts: -------------------------------------------------------------------------------- 1 | import {useAnimatedStyle, withTiming} from "react-native-reanimated"; 2 | 3 | const hideTopBar = (duration: number) => 4 | useAnimatedStyle(() => { 5 | return { 6 | top: withTiming(-200, { 7 | duration: duration, 8 | }), 9 | }; 10 | }); 11 | 12 | const showTopBar = (duration: number) => 13 | useAnimatedStyle(() => { 14 | return { 15 | top: withTiming(0, { 16 | duration: duration, 17 | }), 18 | }; 19 | }); 20 | 21 | export {hideTopBar, showTopBar}; 22 | -------------------------------------------------------------------------------- /appconfig.ts: -------------------------------------------------------------------------------- 1 | export const WALLPAPERS_POST_LIMIT = 50; 2 | export const WALLPAPER_MIN_ALLOWED_WIDTH = 600; 3 | export const WALLPAPER_MIN_ALLOWED_HEIGHT = 1400; 4 | 5 | // Search history limit 6 | export const SEARCH_HISTORY_LIMIT = 10; 7 | 8 | export const PRIVACY_POLICY_URL = "https://droidheat.nzran.com/amoledbackgrounds/privacy_policy_20241117.html"; 9 | export const PRIVACY_POLICY_VERSION = "20241117"; 10 | export const PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.droidheat.amoledbackgrounds"; 11 | export const SEND_ERROR_LOGS_URL = process.env.EXPO_PUBLIC_LOGS_SERVER + "/api/error_logger/bulk_report/"; 12 | -------------------------------------------------------------------------------- /apptheme.js: -------------------------------------------------------------------------------- 1 | const {withAndroidStyles} = require("expo/config-plugins"); 2 | 3 | function withCustomAppTheme(config) { 4 | return withAndroidStyles(config, config => { 5 | let modified = false; 6 | const styles = config.modResults; 7 | styles.resources.style.map(style => { 8 | if (style.$.name === "AppTheme") { 9 | if (!modified) { 10 | style.$.parent = "Theme.AppCompat.NoActionBar"; 11 | modified = true; 12 | } else { 13 | styles.resources.style.splice(styles.resources.style.indexOf(style), 1); 14 | } 15 | } 16 | }); 17 | return config; 18 | }); 19 | } 20 | module.exports = withCustomAppTheme; 21 | -------------------------------------------------------------------------------- /lib/animations/fading_pulse.ts: -------------------------------------------------------------------------------- 1 | import {useAnimatedStyle, withRepeat, withSequence, withTiming} from "react-native-reanimated"; 2 | 3 | // Animations 4 | const fadingPulseAnimation = (duration: number) => 5 | useAnimatedStyle(() => { 6 | return { 7 | opacity: withRepeat( 8 | withSequence( 9 | withTiming(0.5, { 10 | duration: duration / 3, 11 | }), 12 | withTiming(1, { 13 | duration: duration / 3, 14 | }), 15 | withTiming(0.5, { 16 | duration: duration / 3, 17 | }), 18 | ), 19 | -1, 20 | ), 21 | }; 22 | }); 23 | 24 | export {fadingPulseAnimation}; 25 | -------------------------------------------------------------------------------- /modules/dailywallpaper/ios/DailyWallpaper.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DailyWallpaper' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platforms = { :ios => '13.4', :tvos => '13.4' } 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const {getDefaultConfig} = require("expo/metro-config"); 3 | const {withNativeWind} = require("nativewind/metro"); 4 | 5 | module.exports = (() => { 6 | const config = getDefaultConfig(__dirname); 7 | 8 | const {transformer, resolver} = config; 9 | 10 | config.transformer = { 11 | ...transformer, 12 | babelTransformerPath: require.resolve("react-native-svg-transformer/expo"), 13 | }; 14 | config.resolver = { 15 | ...resolver, 16 | assetExts: resolver.assetExts.filter(ext => ext !== "svg"), 17 | sourceExts: [...resolver.sourceExts, "svg", "cjs"], 18 | }; 19 | 20 | return withNativeWind(config, {input: "./styles/global.css"}); 21 | })(); 22 | -------------------------------------------------------------------------------- /modules/download-manager/ios/DownloadManager.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DownloadManager' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platforms = { :ios => '13.4', :tvos => '13.4' } 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/ios/WallpaperManager.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'WallpaperManager' 3 | s.version = '1.0.0' 4 | s.summary = 'A sample project summary' 5 | s.description = 'A sample project description' 6 | s.author = '' 7 | s.homepage = 'https://docs.expo.dev/modules/' 8 | s.platforms = { :ios => '13.4', :tvos => '13.4' } 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /modules/wallpaper-manager/index.ts: -------------------------------------------------------------------------------- 1 | // Import the native module. On web, it will be resolved to WallpaperManager.web.ts 2 | // and on native platforms to WallpaperManager.ts 3 | import WallpaperManagerModule from "./src/WallpaperManagerModule"; 4 | import {ChangeEventPayload} from "./src/WallpaperManager.types"; 5 | 6 | export const Module = WallpaperManagerModule; 7 | 8 | export async function setWallpaper(path: string): Promise { 9 | return WallpaperManagerModule.setWallpaper(path); 10 | } 11 | 12 | export async function deleteWallpaper(path: string): Promise { 13 | return WallpaperManagerModule.deleteWallpaper(path); 14 | } 15 | 16 | /* 17 | * Events 18 | */ 19 | 20 | export type ChangeEventType = ChangeEventPayload; 21 | export const ChangeEvent = "onChange"; 22 | -------------------------------------------------------------------------------- /lib/services/send_error_logs.ts: -------------------------------------------------------------------------------- 1 | import {SEND_ERROR_LOGS_URL} from "@/appconfig"; 2 | import axios from "axios"; 3 | import * as SqlUtility from "@/lib/utils/sql"; 4 | 5 | export default async function SendErrorLogs(isSendingEnabled: boolean): Promise { 6 | if (!isSendingEnabled) { 7 | return false; 8 | } 9 | const logs = await SqlUtility.getAllErrorLogs(); 10 | if (logs.length > 0) { 11 | try { 12 | const result = await axios.post(SEND_ERROR_LOGS_URL, logs, { 13 | headers: { 14 | "Content-Type": "application/json", 15 | }, 16 | }); 17 | if (result.status === 204) { 18 | await SqlUtility.deleteAllErrorLogs(); 19 | } 20 | return true; 21 | } catch (_) { 22 | return false; 23 | } 24 | } 25 | return false; 26 | } 27 | -------------------------------------------------------------------------------- /components/SearchTopBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import TopBar from "./ui/TopBar"; 3 | import {Search, View} from "lucide-react-native"; 4 | import useDebounce from "@/hooks/useDebounce"; 5 | import {TextInput} from "react-native"; 6 | import {Text} from "./ui/Text"; 7 | import {Input} from "./ui/Input"; 8 | 9 | export default function SearchTopBar({ 10 | onQueryChanged, 11 | }: { 12 | hide?: boolean; 13 | showLoader?: boolean; 14 | onQueryChanged: (query: string) => void; 15 | }) { 16 | const [query, setQuery] = React.useState(""); 17 | const debouncedQuery = useDebounce(query, 1000); 18 | 19 | React.useEffect(() => { 20 | onQueryChanged(debouncedQuery); 21 | }, [debouncedQuery]); 22 | 23 | return ( 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/ui/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from "react"; 2 | import {Loader2} from "lucide-react-native"; 3 | import Animated, {useAnimatedStyle, useSharedValue, withRepeat, withTiming} from "react-native-reanimated"; 4 | 5 | export default function LoadingSpinner({size = 32, color = "white"}: {size?: number; color?: string}) { 6 | const rotation = useSharedValue(0); 7 | 8 | useEffect(() => { 9 | rotation.value = withRepeat(withTiming(1, {duration: 1000}), -1, false); 10 | }, [rotation]); 11 | 12 | const animatedStyle = useAnimatedStyle(() => { 13 | return { 14 | transform: [ 15 | { 16 | rotate: `${rotation.value * 2 * Math.PI}rad`, 17 | }, 18 | ], 19 | }; 20 | }); 21 | 22 | return ( 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /lib/services/wallpaper_type.ts: -------------------------------------------------------------------------------- 1 | export type WallpaperPostType = { 2 | id: string; 3 | image: WallpaperImageType; 4 | flair: string; 5 | title: string; 6 | created_utc: Date; 7 | domain?: string; 8 | score: number; 9 | over_18?: boolean; 10 | author: string; 11 | author_flair?: string; 12 | postlink: string; 13 | comments: string; 14 | comments_link: string; 15 | }; 16 | 17 | export type WallpaperImageType = { 18 | url: string; 19 | preview_url?: string; 20 | preview_small_url?: string; 21 | width: number; 22 | height: number; 23 | }; 24 | 25 | export type PaginationType = { 26 | page_number: number; 27 | before?: string; 28 | after?: string; 29 | }; 30 | 31 | export type RedditCommentType = { 32 | id: string; 33 | author: string; 34 | body: string; 35 | score: number; 36 | author_flair: string; 37 | comment_link: string; 38 | parent_id: string; 39 | created_utc: string; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/services/get_black_percentage.ts: -------------------------------------------------------------------------------- 1 | import {CommentsURL} from "@/constants/wallpaper_options"; 2 | import axios from "axios"; 3 | 4 | /** 5 | * Get the black percentage from the comments of a post by AmoledBot 6 | * @param post_id 7 | * @returns 8 | */ 9 | export const getBlackPercentage = async (post_id: string) => { 10 | return await axios.get(CommentsURL(post_id)).then(response => { 11 | const comments = response.data[1].data.children; 12 | let black_percentage = ""; 13 | 14 | for (let i = 0; i < comments.length; i++) { 15 | const author_id = comments[i].data.author_fullname; 16 | if (author_id === "t2_ezs32dqs") { 17 | // Percentage format is 00.00% 18 | const body = comments[i].data.body; 19 | const percentage = body.match(/\d+\.\d+/); 20 | if (percentage) { 21 | black_percentage = percentage[0]; 22 | } 23 | break; 24 | } 25 | } 26 | return black_percentage; 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /modules/dailywallpaper/index.ts: -------------------------------------------------------------------------------- 1 | // Import the native module. On web, it will be resolved to DailyWallpaper.web.ts 2 | // and on native platforms to DailyWallpaper.ts 3 | import DailyWallpaperModule from "./src/DailyWallpaperModule"; 4 | 5 | export const Module = DailyWallpaperModule; 6 | 7 | // 8 | export async function registerDailyWallpaperService( 9 | type: "online" | "downloaded", 10 | sort: String | null, 11 | ): Promise { 12 | return await DailyWallpaperModule.registerService(type, sort); 13 | } 14 | 15 | export async function unregisterDailyWallpaperService(): Promise { 16 | return await DailyWallpaperModule.unregisterService(); 17 | } 18 | 19 | export function isDailyWallpaperServiceEnabled() { 20 | return DailyWallpaperModule.isServiceEnabled(); 21 | } 22 | 23 | export function changeDailyWallpaperType(type: "online" | "downloaded") { 24 | return DailyWallpaperModule.changeType(type); 25 | } 26 | 27 | export function changeDailyWallpaperSort(sort: String) { 28 | return DailyWallpaperModule.changeSort(sort); 29 | } 30 | -------------------------------------------------------------------------------- /components/ui/TopBar.tsx: -------------------------------------------------------------------------------- 1 | import {LinearGradient} from "expo-linear-gradient"; 2 | import React from "react"; 3 | import {View} from "react-native"; 4 | import LoadingSpinner from "./LoadingSpinner"; 5 | import TailIcon from "@/assets/images/tail.svg"; 6 | import {Text} from "./Text"; 7 | 8 | export interface TopBarProps { 9 | children?: React.ReactNode; 10 | showLoader?: boolean; 11 | title?: string; 12 | } 13 | 14 | const TopBar = React.forwardRef, TopBarProps>( 15 | ({title, showLoader, children, ...props}, ref) => { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | {showLoader ? : } 23 | 24 | {title && {title}} 25 | {children ?? <>} 26 | 27 | 28 | 29 | 30 | ); 31 | }, 32 | ); 33 | 34 | export default TopBar; 35 | -------------------------------------------------------------------------------- /lib/utils/time_since.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Take a date and return a string representing the time since that date 3 | * @param date 4 | */ 5 | export function timeSince(date: Date): string { 6 | const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); 7 | 8 | let interval = Math.floor(seconds / 31536000); 9 | if (interval > 1) { 10 | return `${interval} years ago`; 11 | } else if (interval === 1) { 12 | return "an year ago"; 13 | } 14 | interval = Math.floor(seconds / 2592000); 15 | if (interval > 1) { 16 | return `${interval} months ago`; 17 | } else if (interval === 1) { 18 | return "a month ago"; 19 | } 20 | 21 | interval = Math.floor(seconds / 86400); 22 | if (interval > 1) { 23 | return `${interval} days ago`; 24 | } else if (interval === 1) { 25 | return "a day ago"; 26 | } 27 | 28 | interval = Math.floor(seconds / 3600); 29 | if (interval > 1) { 30 | return `${interval} hours ago`; 31 | } else if (interval === 1) { 32 | return "an hour ago"; 33 | } 34 | 7; 35 | 36 | interval = Math.floor(seconds / 60); 37 | if (interval > 1) { 38 | return `${interval} minutes ago`; 39 | } else if (interval === 1) { 40 | return "a minute ago"; 41 | } 42 | 43 | if (seconds < 1) { 44 | return "just now"; 45 | } 46 | return `${Math.floor(seconds)} seconds ago`; 47 | } 48 | -------------------------------------------------------------------------------- /components/ui/Input.tsx: -------------------------------------------------------------------------------- 1 | import {cn} from "@/lib/utils/cn"; 2 | import * as React from "react"; 3 | import {TextInput, View} from "react-native"; 4 | import {Button, ButtonText} from "./Button"; 5 | import {X} from "lucide-react-native"; 6 | 7 | interface InputProps extends React.ComponentPropsWithoutRef { 8 | showClearButton?: boolean; 9 | } 10 | 11 | const Input = React.forwardRef, InputProps>( 12 | ({className, showClearButton, ...props}, ref) => { 13 | return ( 14 | 15 | 25 | {showClearButton && props.value && props.value.length > 0 && ( 26 | 36 | )} 37 | 38 | ); 39 | }, 40 | ); 41 | 42 | Input.displayName = "Input"; 43 | 44 | export {Input}; 45 | -------------------------------------------------------------------------------- /modules/download-manager/android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | group = 'com.nzran.downloadmanager' 4 | version = '0.6.0' 5 | 6 | def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") 7 | apply from: expoModulesCorePlugin 8 | applyKotlinExpoModulesCorePlugin() 9 | useCoreDependencies() 10 | useExpoPublishing() 11 | 12 | // If you want to use the managed Android SDK versions from expo-modules-core, set this to true. 13 | // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. 14 | // Most of the time, you may like to manage the Android SDK versions yourself. 15 | def useManagedAndroidSdkVersions = false 16 | if (useManagedAndroidSdkVersions) { 17 | useDefaultAndroidSdkVersions() 18 | } else { 19 | buildscript { 20 | // Simple helper that allows the root project to override versions declared by this library. 21 | ext.safeExtGet = { prop, fallback -> 22 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 23 | } 24 | } 25 | project.android { 26 | compileSdkVersion safeExtGet("compileSdkVersion", 34) 27 | defaultConfig { 28 | minSdkVersion safeExtGet("minSdkVersion", 21) 29 | targetSdkVersion safeExtGet("targetSdkVersion", 34) 30 | } 31 | } 32 | } 33 | 34 | android { 35 | namespace "com.nzran.downloadmanager" 36 | defaultConfig { 37 | versionCode 1 38 | versionName "0.6.0" 39 | } 40 | lintOptions { 41 | abortOnError false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/+html.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollViewStyleReset } from 'expo-router/html'; 2 | import { type PropsWithChildren } from 'react'; 3 | 4 | /** 5 | * This file is web-only and used to configure the root HTML for every web page during static rendering. 6 | * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. 7 | */ 8 | export default function Root({ children }: PropsWithChildren) { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | {/* 17 | Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 18 | However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 19 | */} 20 | 21 | 22 | {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 23 |