├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── kotlinScripting.xml
├── kotlinc.xml
├── ktlint.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.de-DE.md
├── README.ja-JP.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── schemas
│ └── app.suhasdissa.memerize.backend.database.MemeDatabase
│ │ ├── 4.json
│ │ ├── 5.json
│ │ └── 6.json
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── app
│ │ └── suhasdissa
│ │ └── memerize
│ │ ├── AppContainer.kt
│ │ ├── Destination.kt
│ │ ├── MainActivity.kt
│ │ ├── MemerizeApplication.kt
│ │ ├── NavHost.kt
│ │ ├── backend
│ │ ├── apis
│ │ │ ├── FileDownloadApi.kt
│ │ │ ├── LemmyApi.kt
│ │ │ ├── RedditApi.kt
│ │ │ └── RedditVideoApi.kt
│ │ ├── database
│ │ │ ├── MemeDatabase.kt
│ │ │ ├── dao
│ │ │ │ ├── CommunityDAO.kt
│ │ │ │ ├── LemmyMemeDAO.kt
│ │ │ │ ├── RedditMemeDao.kt
│ │ │ │ └── SubredditDAO.kt
│ │ │ └── entity
│ │ │ │ ├── AboutCommunity.kt
│ │ │ │ ├── LemmyCommunity.kt
│ │ │ │ ├── LemmyMeme.kt
│ │ │ │ ├── Meme.kt
│ │ │ │ ├── RedditCommunity.kt
│ │ │ │ └── RedditMeme.kt
│ │ ├── model
│ │ │ ├── LemmyAbout.kt
│ │ │ ├── LemmyResponse.kt
│ │ │ ├── RedditAboutResponse.kt
│ │ │ ├── RedditResponse.kt
│ │ │ └── Sort.kt
│ │ ├── repositories
│ │ │ ├── CommunityRepository.kt
│ │ │ ├── LemmyCommunityRepository.kt
│ │ │ ├── LemmyMemeRepository.kt
│ │ │ ├── MemeRepository.kt
│ │ │ ├── RedditCommunityRepository.kt
│ │ │ └── RedditMemeRepository.kt
│ │ └── viewmodels
│ │ │ ├── CheckUpdateViewModel.kt
│ │ │ ├── LemmyCommunityViewModel.kt
│ │ │ ├── LemmyViewModel.kt
│ │ │ ├── PhotoViewModel.kt
│ │ │ ├── PlayerViewModel.kt
│ │ │ ├── RedditCommunityViewModel.kt
│ │ │ ├── RedditViewModel.kt
│ │ │ └── state
│ │ │ ├── AboutCommunityState.kt
│ │ │ └── MemeUiState.kt
│ │ ├── ui
│ │ ├── MemerizeApp.kt
│ │ ├── components
│ │ │ ├── CacheSizeDialog.kt
│ │ │ ├── ErrorScreen.kt
│ │ │ ├── HighlightCard.kt
│ │ │ ├── ImageCard.kt
│ │ │ ├── LoadingScreen.kt
│ │ │ ├── MemeCard.kt
│ │ │ ├── MemeGrid.kt
│ │ │ ├── NavDrawerContent.kt
│ │ │ ├── RetryScreen.kt
│ │ │ ├── SettingItem.kt
│ │ │ ├── SortBottomSheet.kt
│ │ │ ├── SubredditCard.kt
│ │ │ └── VideoCard.kt
│ │ ├── screens
│ │ │ ├── home
│ │ │ │ ├── CommunityScreen.kt
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ └── SubredditScreen.kt
│ │ │ ├── primary
│ │ │ │ ├── LemmyMemeScreen.kt
│ │ │ │ └── RedditMemeScreen.kt
│ │ │ ├── secondary
│ │ │ │ ├── MemeFeedView.kt
│ │ │ │ ├── PhotoView.kt
│ │ │ │ └── VideoView.kt
│ │ │ └── settings
│ │ │ │ ├── AboutScreen.kt
│ │ │ │ └── SettingsScreen.kt
│ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── utils
│ │ ├── CheckUpdate.kt
│ │ ├── OpenBrowser.kt
│ │ ├── PlayerState.kt
│ │ ├── Preferences.kt
│ │ ├── RedditVideoDownloader.kt
│ │ └── ShareUrl.kt
│ ├── res
│ ├── drawable
│ │ ├── ic_broken_image.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_launcher_monochrome.xml
│ │ ├── loading_img.xml
│ │ └── reddit_placeholder.xml
│ ├── mipmap-anydpi-v26
│ │ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ ├── values-de-rDE
│ │ └── strings.xml
│ ├── values-ja
│ │ └── strings.xml
│ ├── values
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── xml
│ │ └── provider_paths.xml
│ └── values-night
│ └── themes.xml
├── build.gradle.kts
├── crowdin.yml
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ └── 5.png
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── key.jks
├── logo.svg
└── settings.gradle
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Create Apk
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | paths-ignore:
7 | - "README*.md"
8 | - "app/src/main/res/**"
9 | - ".github/**"
10 | push:
11 | paths-ignore:
12 | - "README*.md"
13 | - "app/src/main/res/**"
14 | - ".github/**"
15 |
16 | jobs:
17 | apk:
18 | name: Generate APK
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v1
23 | - name: Validate Gradle Wrapper
24 | uses: gradle/wrapper-validation-action@v1
25 | - name: Setup JDK
26 | uses: actions/setup-java@v3
27 | with:
28 | distribution: 'temurin'
29 | java-version: 17
30 | cache: "gradle"
31 | - name: Build APK
32 | run: bash ./gradlew assembleReleaseGithub --stacktrace
33 | - name: Upload APK
34 | uses: actions/upload-artifact@v1
35 | with:
36 | name: release
37 | path: app/build/outputs/apk/releaseGithub/
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Memerize
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.idea/kotlinScripting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 2147483647
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/ktlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | false
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.de-DE.md:
--------------------------------------------------------------------------------
1 | [日本語](README.ja-JP.md)
2 |
3 |
4 |
5 |
Memerize
6 |
Memerize is a handy meme viewer app for Reddit and Lemmy
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ---
21 |
22 |
23 | Screenshots
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ## Features
36 | - Material you dynamic theme with dark mode support
37 | - Add/Remove subreddits and lemmy communities as you like
38 | - Support catching memes for offline browsing.
39 | - Sort Top memes by time period (Today, This Week, This Month)
40 | - Supports sharing memes from the app
41 | - Allows custom download location
42 |
43 | ## Installation
44 |
45 | [ ](https://github.com/SuhasDissa/MemerizeApp/releases/latest)
48 |
49 | ## Useful Links
50 |
51 |
52 |
--------------------------------------------------------------------------------
/README.ja-JP.md:
--------------------------------------------------------------------------------
1 | [English](README.md)
2 |
3 |
4 |
5 |
Memerize
6 |
Memerize はRedditとLemmy用の便利なミーム閲覧アプリです
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ---
20 |
21 |
22 | スクリーンショット
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## 特徴
35 | - ダークモードをサポートしたMaterial you dynamic theme
36 | - Subredditsやlemmyコミュニティを自由に追加/削除できます
37 | - オフライン閲覧のためのミームキャッチをサポート。
38 | - 人気のミームを期間ごとに並べ替えます(今日、今週、今月)
39 | - アプリからのミームの共有をサポート
40 | - ダウンロード場所のカスタマイズが可能
41 |
42 | ## インストール
43 |
44 | [ ](https://github.com/SuhasDissa/MemerizeApp/releases/latest)
47 |
48 | ## 役立つリンク
49 |
50 |
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [日本語](README.ja-JP.md)
2 |
3 |
4 |
5 |
Memerize
6 |
Memerize is a handy meme viewer app for Reddit and Lemmy
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ---
20 |
21 |
22 | Screenshots
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## Features
35 | - Material you dynamic theme with dark mode support
36 | - Add/Remove subreddits and lemmy communities as you like
37 | - Support catching memes for offline browsing.
38 | - Sort Top memes by time period (Today, This Week, This Month)
39 | - Supports sharing memes from the app
40 | - Allows custom download location
41 |
42 | ## Installation
43 |
44 | [ ](https://github.com/SuhasDissa/MemerizeApp/releases/latest)
47 |
48 | ## Useful Links
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release/
3 | release
4 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | kotlin("android")
4 | id("com.google.devtools.ksp")
5 | id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
6 | }
7 |
8 | android {
9 | namespace = "app.suhasdissa.memerize"
10 | compileSdk = 34
11 |
12 | defaultConfig {
13 | applicationId = "app.suhasdissa.memerize"
14 | minSdk = 24
15 | targetSdk = 34
16 | versionCode = 24
17 | versionName = "2.4"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | vectorDrawables {
21 | useSupportLibrary = true
22 | }
23 | ksp {
24 | arg("room.schemaLocation", "$projectDir/schemas")
25 | }
26 | }
27 | signingConfigs {
28 | create("release") {
29 | storeFile = file("../key.jks")
30 | storePassword = "lolcat"
31 | keyAlias = "key0"
32 | keyPassword = "lolcat"
33 | }
34 | }
35 | buildTypes {
36 | getByName("release") {
37 | isMinifyEnabled = true
38 | isShrinkResources = true
39 | proguardFiles(
40 | getDefaultProguardFile("proguard-android-optimize.txt"),
41 | "proguard-rules.pro"
42 | )
43 | }
44 | getByName("debug") {
45 | applicationIdSuffix = ".debug"
46 | isDebuggable = true
47 | }
48 | create("releaseGithub") {
49 | isMinifyEnabled = true
50 | isShrinkResources = true
51 | signingConfig = signingConfigs.getByName("release")
52 | proguardFiles(
53 | getDefaultProguardFile("proguard-android-optimize.txt"),
54 | "proguard-rules.pro"
55 | )
56 | }
57 | }
58 | compileOptions {
59 | sourceCompatibility = JavaVersion.VERSION_17
60 | targetCompatibility = JavaVersion.VERSION_17
61 | }
62 | kotlinOptions {
63 | jvmTarget = "17"
64 | }
65 | buildFeatures {
66 | compose = true
67 | buildConfig = true
68 | }
69 | composeOptions {
70 | kotlinCompilerExtensionVersion = "1.4.7"
71 | }
72 | packaging {
73 | resources {
74 | excludes.add("/META-INF/{AL2.0,LGPL2.1}")
75 | }
76 | }
77 | }
78 |
79 | dependencies {
80 | implementation("androidx.core:core-ktx:1.12.0")
81 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
82 | implementation("androidx.activity:activity-compose:1.8.0")
83 | implementation(platform("androidx.compose:compose-bom:2023.10.00"))
84 | implementation("androidx.compose.ui:ui")
85 | implementation("androidx.compose.ui:ui-graphics")
86 | implementation("androidx.compose.ui:ui-tooling-preview")
87 | implementation("androidx.compose.material3:material3")
88 |
89 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
90 | implementation("androidx.navigation:navigation-compose:2.7.4")
91 |
92 | implementation("androidx.compose.material:material-icons-extended:1.5.3")
93 |
94 | testImplementation("junit:junit:4.13.2")
95 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
96 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
97 | androidTestImplementation(platform("androidx.compose:compose-bom:2022.10.00"))
98 | androidTestImplementation("androidx.compose.ui:ui-test-junit4")
99 | debugImplementation("androidx.compose.ui:ui-tooling")
100 | debugImplementation("androidx.compose.ui:ui-test-manifest")
101 |
102 | implementation("androidx.documentfile:documentfile:1.0.1")
103 |
104 | implementation("io.coil-kt:coil-compose:2.4.0")
105 |
106 | val media3_version = "1.1.1"
107 |
108 | // For media playback using ExoPlayer
109 | implementation("androidx.media3:media3-exoplayer:$media3_version")
110 | // For HLS playback support with ExoPlayer
111 | implementation("androidx.media3:media3-exoplayer-dash:$media3_version")
112 | // For building media playback UIs
113 | implementation("androidx.media3:media3-ui:$media3_version")
114 |
115 | implementation("androidx.media3:media3-session:$media3_version")
116 |
117 | val roomVersion = "2.5.2"
118 |
119 | implementation("androidx.room:room-runtime:$roomVersion")
120 | implementation("androidx.room:room-ktx:$roomVersion")
121 | annotationProcessor("androidx.room:room-compiler:$roomVersion")
122 | ksp("androidx.room:room-compiler:$roomVersion")
123 |
124 | implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6")
125 |
126 | implementation("com.squareup.retrofit2:retrofit:2.9.0")
127 | implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
128 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
129 | implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
130 | }
131 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
2 |
3 | -keep class retrofit2.** { *; }
4 | -keepattributes *Annotation*
5 | -keep class com.squareup.okhttp.** { *; }
6 | -keep interface com.squareup.okhttp.** { *; }
7 | -keep class okhttp3.** { *; }
8 | -keep interface okhttp3.** { *; }
9 |
10 | -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
11 |
12 | -dontwarn okhttp3.internal.platform.**
13 | -dontwarn org.conscrypt.**
14 | -dontwarn org.bouncycastle.**
15 | -dontwarn org.openjsse.**
--------------------------------------------------------------------------------
/app/schemas/app.suhasdissa.memerize.backend.database.MemeDatabase/5.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 5,
5 | "identityHash": "e2fc7824a14602ba5a702fa14f828362",
6 | "entities": [
7 | {
8 | "tableName": "reddit_table",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL DEFAULT '', `is_video` INTEGER NOT NULL, `preview` TEXT NOT NULL DEFAULT '', `subreddit` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
10 | "fields": [
11 | {
12 | "fieldPath": "id",
13 | "columnName": "id",
14 | "affinity": "TEXT",
15 | "notNull": true
16 | },
17 | {
18 | "fieldPath": "url",
19 | "columnName": "url",
20 | "affinity": "TEXT",
21 | "notNull": true
22 | },
23 | {
24 | "fieldPath": "title",
25 | "columnName": "title",
26 | "affinity": "TEXT",
27 | "notNull": true,
28 | "defaultValue": "''"
29 | },
30 | {
31 | "fieldPath": "isVideo",
32 | "columnName": "is_video",
33 | "affinity": "INTEGER",
34 | "notNull": true
35 | },
36 | {
37 | "fieldPath": "preview",
38 | "columnName": "preview",
39 | "affinity": "TEXT",
40 | "notNull": true,
41 | "defaultValue": "''"
42 | },
43 | {
44 | "fieldPath": "subreddit",
45 | "columnName": "subreddit",
46 | "affinity": "TEXT",
47 | "notNull": true,
48 | "defaultValue": "''"
49 | }
50 | ],
51 | "primaryKey": {
52 | "autoGenerate": false,
53 | "columnNames": [
54 | "id"
55 | ]
56 | },
57 | "indices": [],
58 | "foreignKeys": []
59 | },
60 | {
61 | "tableName": "subreddit",
62 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `icon_url` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
63 | "fields": [
64 | {
65 | "fieldPath": "id",
66 | "columnName": "id",
67 | "affinity": "TEXT",
68 | "notNull": true
69 | },
70 | {
71 | "fieldPath": "iconUrl",
72 | "columnName": "icon_url",
73 | "affinity": "TEXT",
74 | "notNull": false
75 | },
76 | {
77 | "fieldPath": "name",
78 | "columnName": "name",
79 | "affinity": "TEXT",
80 | "notNull": true
81 | }
82 | ],
83 | "primaryKey": {
84 | "autoGenerate": false,
85 | "columnNames": [
86 | "id"
87 | ]
88 | },
89 | "indices": [],
90 | "foreignKeys": []
91 | },
92 | {
93 | "tableName": "lemmy_table",
94 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL DEFAULT '', `is_video` INTEGER NOT NULL, `preview` TEXT NOT NULL DEFAULT '', `community` TEXT NOT NULL DEFAULT '', `instance` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
95 | "fields": [
96 | {
97 | "fieldPath": "id",
98 | "columnName": "id",
99 | "affinity": "TEXT",
100 | "notNull": true
101 | },
102 | {
103 | "fieldPath": "url",
104 | "columnName": "url",
105 | "affinity": "TEXT",
106 | "notNull": true
107 | },
108 | {
109 | "fieldPath": "title",
110 | "columnName": "title",
111 | "affinity": "TEXT",
112 | "notNull": true,
113 | "defaultValue": "''"
114 | },
115 | {
116 | "fieldPath": "isVideo",
117 | "columnName": "is_video",
118 | "affinity": "INTEGER",
119 | "notNull": true
120 | },
121 | {
122 | "fieldPath": "preview",
123 | "columnName": "preview",
124 | "affinity": "TEXT",
125 | "notNull": true,
126 | "defaultValue": "''"
127 | },
128 | {
129 | "fieldPath": "community",
130 | "columnName": "community",
131 | "affinity": "TEXT",
132 | "notNull": true,
133 | "defaultValue": "''"
134 | },
135 | {
136 | "fieldPath": "instance",
137 | "columnName": "instance",
138 | "affinity": "TEXT",
139 | "notNull": true,
140 | "defaultValue": "''"
141 | }
142 | ],
143 | "primaryKey": {
144 | "autoGenerate": false,
145 | "columnNames": [
146 | "id"
147 | ]
148 | },
149 | "indices": [],
150 | "foreignKeys": []
151 | },
152 | {
153 | "tableName": "community",
154 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`community` TEXT NOT NULL, `instance` TEXT NOT NULL, `icon_url` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`community`, `instance`))",
155 | "fields": [
156 | {
157 | "fieldPath": "id",
158 | "columnName": "community",
159 | "affinity": "TEXT",
160 | "notNull": true
161 | },
162 | {
163 | "fieldPath": "instance",
164 | "columnName": "instance",
165 | "affinity": "TEXT",
166 | "notNull": true
167 | },
168 | {
169 | "fieldPath": "iconUrl",
170 | "columnName": "icon_url",
171 | "affinity": "TEXT",
172 | "notNull": false
173 | },
174 | {
175 | "fieldPath": "name",
176 | "columnName": "name",
177 | "affinity": "TEXT",
178 | "notNull": true
179 | }
180 | ],
181 | "primaryKey": {
182 | "autoGenerate": false,
183 | "columnNames": [
184 | "community",
185 | "instance"
186 | ]
187 | },
188 | "indices": [],
189 | "foreignKeys": []
190 | }
191 | ],
192 | "views": [],
193 | "setupQueries": [
194 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
195 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2fc7824a14602ba5a702fa14f828362')"
196 | ]
197 | }
198 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
13 |
14 |
19 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/AppContainer.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/25/22, 7:00 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize
9 |
10 | import app.suhasdissa.memerize.backend.apis.LemmyApi
11 | import app.suhasdissa.memerize.backend.apis.RedditApi
12 | import app.suhasdissa.memerize.backend.database.MemeDatabase
13 | import app.suhasdissa.memerize.backend.repositories.LemmyCommunityRepository
14 | import app.suhasdissa.memerize.backend.repositories.LemmyCommunityRepositoryImpl
15 | import app.suhasdissa.memerize.backend.repositories.LemmyMemeRepository
16 | import app.suhasdissa.memerize.backend.repositories.LemmyMemeRepositoryImpl
17 | import app.suhasdissa.memerize.backend.repositories.RedditCommunityRepository
18 | import app.suhasdissa.memerize.backend.repositories.RedditCommunityRepositoryImpl
19 | import app.suhasdissa.memerize.backend.repositories.RedditMemeRepository
20 | import app.suhasdissa.memerize.backend.repositories.RedditMemeRepositoryImpl
21 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
22 | import kotlinx.serialization.json.Json
23 | import okhttp3.MediaType.Companion.toMediaType
24 | import retrofit2.Retrofit
25 |
26 | interface AppContainer {
27 | val redditApi: RedditApi
28 | val lemmyApi: LemmyApi
29 | val redditMemeRepository: RedditMemeRepository
30 | val lemmyMemeRepository: LemmyMemeRepository
31 | val lemmyCommunityRepository: LemmyCommunityRepository
32 | val redditCommunityRepository: RedditCommunityRepository
33 | }
34 |
35 | class DefaultAppContainer(database: MemeDatabase) : AppContainer {
36 | override val redditMemeRepository: RedditMemeRepository by lazy {
37 | RedditMemeRepositoryImpl(database.redditMemeDao(), redditApi)
38 | }
39 | override val lemmyMemeRepository: LemmyMemeRepository by lazy {
40 | LemmyMemeRepositoryImpl(database.lemmyMemeDao(), lemmyApi)
41 | }
42 | override val lemmyCommunityRepository: LemmyCommunityRepository by lazy {
43 | LemmyCommunityRepositoryImpl(database.communityDao(), lemmyApi)
44 | }
45 | override val redditCommunityRepository: RedditCommunityRepository by lazy {
46 | RedditCommunityRepositoryImpl(database.subredditDao(), redditApi)
47 | }
48 |
49 | private val json = Json { ignoreUnknownKeys = true }
50 |
51 | private val redditRetrofit = Retrofit.Builder()
52 | .baseUrl("https://www.reddit.com/")
53 | .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
54 | .build()
55 |
56 | private val lemmyRetrofit = Retrofit.Builder()
57 | .baseUrl("https://lemmy.ml/")
58 | .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
59 | .build()
60 |
61 | override val redditApi: RedditApi by lazy {
62 | redditRetrofit.create(RedditApi::class.java)
63 | }
64 |
65 | override val lemmyApi: LemmyApi by lazy {
66 | lemmyRetrofit.create(LemmyApi::class.java)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/Destination.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize
9 |
10 | import androidx.navigation.NavType
11 | import androidx.navigation.navArgument
12 |
13 | sealed class Destination(val route: String) {
14 | object Home : Destination("home")
15 | object RedditMemeView : Destination("reddit_memeview")
16 | object LemmyMemeView : Destination("lemmy_memeview")
17 | object Settings : Destination("settings")
18 | object Subreddits : Destination("subreddits")
19 | object Communities : Destination("communities")
20 | object About : Destination("about")
21 | object RedditFeed : Destination("reddit_feed") {
22 | val routeWithArgs = "$route/{id}"
23 | val arguments = listOf(navArgument("id") { type = NavType.IntType })
24 | }
25 |
26 | object LemmyFeed : Destination("lemmy_feed") {
27 | val routeWithArgs = "$route/{id}"
28 | val arguments = listOf(navArgument("id") { type = NavType.IntType })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize
9 |
10 | import android.os.Bundle
11 | import androidx.activity.ComponentActivity
12 | import androidx.activity.compose.setContent
13 | import app.suhasdissa.memerize.ui.MemerizeApp
14 | import app.suhasdissa.memerize.ui.theme.MemerizeTheme
15 |
16 | class MainActivity : ComponentActivity() {
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContent {
20 | MemerizeTheme {
21 | MemerizeApp()
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/MemerizeApplication.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/25/22, 6:27 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize
9 |
10 | import android.app.Application
11 | import app.suhasdissa.memerize.backend.database.MemeDatabase
12 | import app.suhasdissa.memerize.utils.UpdateUtil
13 | import app.suhasdissa.memerize.utils.defaultImageCacheSize
14 | import app.suhasdissa.memerize.utils.imageCacheKey
15 | import app.suhasdissa.memerize.utils.preferences
16 | import coil.ImageLoader
17 | import coil.ImageLoaderFactory
18 | import coil.disk.DiskCache
19 |
20 | class MemerizeApplication : Application(), ImageLoaderFactory {
21 | private val database by lazy { MemeDatabase.getDatabase(this) }
22 | lateinit var container: AppContainer
23 |
24 | override fun onCreate() {
25 | super.onCreate()
26 | container = DefaultAppContainer(database)
27 | UpdateUtil.getCurrentVersion(this.applicationContext)
28 | }
29 |
30 | override fun newImageLoader(): ImageLoader {
31 | return ImageLoader.Builder(this)
32 | .crossfade(true)
33 | .respectCacheHeaders(false)
34 | .diskCache(
35 | DiskCache.Builder()
36 | .directory(cacheDir.resolve("image_cache"))
37 | .maxSizeBytes(
38 | preferences.getInt(imageCacheKey, defaultImageCacheSize) * 1024 * 1024L
39 | )
40 | .build()
41 | ).build()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/NavHost.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize
9 |
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.navigation.NavHostController
13 | import androidx.navigation.compose.NavHost
14 | import androidx.navigation.compose.composable
15 | import app.suhasdissa.memerize.ui.screens.home.CommunityScreen
16 | import app.suhasdissa.memerize.ui.screens.home.HomeScreen
17 | import app.suhasdissa.memerize.ui.screens.home.SubredditScreen
18 | import app.suhasdissa.memerize.ui.screens.primary.LemmyMemeScreen
19 | import app.suhasdissa.memerize.ui.screens.primary.RedditMemeScreen
20 | import app.suhasdissa.memerize.ui.screens.secondary.LemmyMemeFeed
21 | import app.suhasdissa.memerize.ui.screens.secondary.RedditMemeFeed
22 | import app.suhasdissa.memerize.ui.screens.settings.AboutScreen
23 | import app.suhasdissa.memerize.ui.screens.settings.SettingsScreen
24 |
25 | @Composable
26 | fun AppNavHost(
27 | navController: NavHostController,
28 | onDrawerOpen: () -> Unit,
29 | modifier: Modifier = Modifier
30 | ) {
31 | NavHost(
32 | navController = navController,
33 | startDestination = Destination.Home.route,
34 | modifier = modifier
35 | ) {
36 | composable(route = Destination.Home.route) {
37 | HomeScreen(
38 | onNavigate = { destination ->
39 | navController.navigateTo(destination.route)
40 | },
41 | onDrawerOpen
42 | )
43 | }
44 | composable(route = Destination.Settings.route) {
45 | SettingsScreen(
46 | onDrawerOpen,
47 | onAboutClick = {
48 | navController.navigateTo(Destination.About.route)
49 | }
50 | )
51 | }
52 | composable(route = Destination.Subreddits.route) {
53 | SubredditScreen(onDrawerOpen)
54 | }
55 | composable(route = Destination.Communities.route) {
56 | CommunityScreen(onDrawerOpen)
57 | }
58 | composable(route = Destination.About.route) {
59 | AboutScreen()
60 | }
61 | composable(
62 | route = Destination.RedditMemeView.route
63 | ) {
64 | RedditMemeScreen(
65 | onClickCard = { id ->
66 | navController.navigateTo("${Destination.RedditFeed.route}/$id")
67 | }
68 | )
69 | }
70 | composable(
71 | route = Destination.LemmyMemeView.route
72 | ) {
73 | LemmyMemeScreen(
74 | onClickCard = { id ->
75 | navController.navigateTo("${Destination.LemmyFeed.route}/$id")
76 | }
77 | )
78 | }
79 | composable(
80 | route = Destination.RedditFeed.routeWithArgs,
81 | arguments = Destination.RedditFeed.arguments
82 | ) {
83 | val id = it.arguments?.getInt("id")
84 | if (id != null) {
85 | RedditMemeFeed(initialPage = id)
86 | }
87 | }
88 |
89 | composable(
90 | route = Destination.LemmyFeed.routeWithArgs,
91 | arguments = Destination.LemmyFeed.arguments
92 | ) {
93 | val id = it.arguments?.getInt("id")
94 | if (id != null) {
95 | LemmyMemeFeed(initialPage = id)
96 | }
97 | }
98 | }
99 | }
100 |
101 | fun NavHostController.navigateTo(route: String) = this.navigate(route) {
102 | launchSingleTop = true
103 | restoreState = true
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/apis/FileDownloadApi.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/7/23, 9:34 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.apis
9 |
10 | import okhttp3.ResponseBody
11 | import retrofit2.Call
12 | import retrofit2.http.GET
13 | import retrofit2.http.Url
14 |
15 | interface FileDownloadApi {
16 | @GET
17 | fun downloadFile(@Url fileUrl: String): Call
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/apis/LemmyApi.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/3/23, 4:40 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.apis
9 |
10 | import app.suhasdissa.memerize.backend.model.LemmyAbout
11 | import app.suhasdissa.memerize.backend.model.LemmyResponse
12 | import retrofit2.http.GET
13 | import retrofit2.http.Headers
14 | import retrofit2.http.Path
15 | import retrofit2.http.Query
16 |
17 | private const val header =
18 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" // ktlint-disable max-line-length
19 |
20 | interface LemmyApi {
21 | /**
22 | * @param community The name of the community.
23 | * @param sort "Active", "Hot", "MostComments", "New", "NewComments",
24 | * "Old", "TopAll", "TopDay", "TopMonth", "TopWeek",
25 | * "TopYear".
26 | */
27 | @Headers(header)
28 | @GET("https://{instance}/api/v3/post/list")
29 | suspend fun getLemmyData(
30 | @Path("instance") instance: String,
31 | @Query("community_name") community: String,
32 | @Query("sort") sort: String
33 | ): LemmyResponse
34 |
35 | @Headers(header)
36 | @GET("https://{instance}/api/v3/community")
37 | suspend fun getCommunity(
38 | @Path("instance") instance: String,
39 | @Query("name") name: String
40 | ): LemmyAbout
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/apis/RedditApi.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.apis
9 |
10 | import app.suhasdissa.memerize.backend.model.Reddit
11 | import app.suhasdissa.memerize.backend.model.RedditAboutResponse
12 | import retrofit2.http.GET
13 | import retrofit2.http.Headers
14 | import retrofit2.http.Path
15 | import retrofit2.http.Query
16 |
17 | private const val header =
18 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" // ktlint-disable max-line-length
19 |
20 | interface RedditApi {
21 | @Headers(header)
22 | @GET("r/{subreddit}/{sort}.json")
23 | suspend fun getRedditData(
24 | @Path("subreddit") subreddit: String,
25 | @Path("sort") sort: String,
26 | @Query("t") time: String? = null
27 | ): Reddit
28 |
29 | @Headers(header)
30 | @GET("r/{subreddit}/about.json")
31 | suspend fun getAboutSubreddit(
32 | @Path("subreddit") subreddit: String
33 | ): RedditAboutResponse
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/apis/RedditVideoApi.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/7/23, 6:41 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.apis
9 |
10 | import retrofit2.http.GET
11 | import retrofit2.http.Headers
12 | import retrofit2.http.Url
13 |
14 | private const val header =
15 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" // ktlint-disable max-line-length
16 |
17 | interface RedditVideoApi {
18 | @Headers(header)
19 | @GET
20 | suspend fun getRedditData(
21 | @Url url: String
22 | ): String
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/MemeDatabase.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.database
2 |
3 | import android.content.Context
4 | import androidx.room.AutoMigration
5 | import androidx.room.Database
6 | import androidx.room.Room
7 | import androidx.room.RoomDatabase
8 | import androidx.sqlite.db.SupportSQLiteDatabase
9 | import app.suhasdissa.memerize.backend.database.dao.CommunityDAO
10 | import app.suhasdissa.memerize.backend.database.dao.LemmyMemeDAO
11 | import app.suhasdissa.memerize.backend.database.dao.RedditMemeDao
12 | import app.suhasdissa.memerize.backend.database.dao.SubredditDAO
13 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
14 | import app.suhasdissa.memerize.backend.database.entity.LemmyMeme
15 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
16 | import app.suhasdissa.memerize.backend.database.entity.RedditMeme
17 |
18 | @Database(
19 | entities = [RedditMeme::class, RedditCommunity::class, LemmyMeme::class, LemmyCommunity::class],
20 | version = 6,
21 | exportSchema = true,
22 | autoMigrations = [
23 | AutoMigration(from = 4, to = 5),
24 | AutoMigration(from = 5, to = 6)
25 | ]
26 | )
27 | abstract class MemeDatabase : RoomDatabase() {
28 |
29 | abstract fun redditMemeDao(): RedditMemeDao
30 | abstract fun subredditDao(): SubredditDAO
31 | abstract fun communityDao(): CommunityDAO
32 | abstract fun lemmyMemeDao(): LemmyMemeDAO
33 |
34 | companion object {
35 | @Volatile
36 | private var INSTANCE: MemeDatabase? = null
37 |
38 | fun getDatabase(context: Context): MemeDatabase {
39 | return INSTANCE ?: synchronized(this) {
40 | val instance = Room.databaseBuilder(
41 | context.applicationContext,
42 | MemeDatabase::class.java,
43 | "meme_database"
44 | ).allowMainThreadQueries()
45 | .addCallback(initSubreddits())
46 | .build()
47 | INSTANCE = instance
48 | instance
49 | }
50 | }
51 |
52 | class initSubreddits : Callback() {
53 | val redditList = listOf(
54 | RedditCommunity(
55 | "maybemaybemaybe",
56 | "https://styles.redditmedia.com/t5_38e1l/styles/communityIcon_hcpveq6pu5p41.png",
57 | "Maybe Maybe Maybe"
58 | ),
59 | RedditCommunity(
60 | "holup",
61 | "https://styles.redditmedia.com/t5_qir9n/styles/communityIcon_yvasg0bnblaa1.png",
62 | "HolUP"
63 | ),
64 | RedditCommunity(
65 | "funny",
66 | "https://a.thumbs.redditmedia.com/kIpBoUR8zJLMQlF8azhN-kSBsjVUidHjvZNLuHDONm8.png",
67 | "Funny"
68 | ),
69 | RedditCommunity(
70 | "facepalm",
71 | "https://styles.redditmedia.com/t5_2r5rp/styles/communityIcon_qzjxzx1g08z91.jpg",
72 | "FacePalm"
73 | ),
74 | RedditCommunity(
75 | "memes",
76 | "https://styles.redditmedia.com/t5_2qjpg/styles/communityIcon_uzvo7sibvc3a1.jpg",
77 | "Memes"
78 | ),
79 | RedditCommunity(
80 | "dankmemes",
81 | "https://styles.redditmedia.com/t5_2zmfe/styles/communityIcon_g5xoywnpe2l91.png",
82 | "Dank Memes"
83 | )
84 | )
85 |
86 | override fun onCreate(db: SupportSQLiteDatabase) {
87 | redditList.forEach {
88 | db.execSQL(
89 | "INSERT INTO subreddit (id, icon_url, name) " +
90 | "VALUES ('${it.id}', '${it.iconUrl}', '${it.name}');"
91 | )
92 | }
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/dao/CommunityDAO.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/3/23, 7:58 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.dao
9 |
10 | import androidx.room.Dao
11 | import androidx.room.Delete
12 | import androidx.room.Insert
13 | import androidx.room.OnConflictStrategy
14 | import androidx.room.Query
15 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
16 | import kotlinx.coroutines.flow.Flow
17 |
18 | @Dao
19 | interface CommunityDAO {
20 | @Query("SELECT * FROM community")
21 | fun getAll(): Flow>
22 |
23 | @Insert(onConflict = OnConflictStrategy.REPLACE)
24 | fun insertAll(community: List)
25 |
26 | @Insert(onConflict = OnConflictStrategy.REPLACE)
27 | fun insert(community: LemmyCommunity)
28 |
29 | @Delete
30 | fun delete(community: LemmyCommunity)
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/dao/LemmyMemeDAO.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/3/23, 8:01 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.dao
9 |
10 | import androidx.room.Dao
11 | import androidx.room.Insert
12 | import androidx.room.OnConflictStrategy
13 | import androidx.room.Query
14 | import app.suhasdissa.memerize.backend.database.entity.LemmyMeme
15 |
16 | @Dao
17 | interface LemmyMemeDAO {
18 | @Query("SELECT * FROM lemmy_table WHERE id=:community AND instance=:instance")
19 | fun getAll(community: String, instance: String): List
20 |
21 | @Insert(onConflict = OnConflictStrategy.REPLACE)
22 | fun insertAll(memes: List)
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/dao/RedditMemeDao.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/29/23, 8:14 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.dao
9 |
10 | import androidx.room.Dao
11 | import androidx.room.Delete
12 | import androidx.room.Insert
13 | import androidx.room.OnConflictStrategy
14 | import androidx.room.Query
15 | import app.suhasdissa.memerize.backend.database.entity.RedditMeme
16 |
17 | @Dao
18 | interface RedditMemeDao {
19 | @Query("SELECT * FROM reddit_table WHERE subreddit=:subreddit")
20 | fun getAll(subreddit: String): List
21 |
22 | @Insert(onConflict = OnConflictStrategy.IGNORE)
23 | fun insertAll(memes: List)
24 |
25 | @Delete
26 | fun delete(meme: RedditMeme)
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/dao/SubredditDAO.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 12:36 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.dao
9 |
10 | import androidx.room.Dao
11 | import androidx.room.Delete
12 | import androidx.room.Insert
13 | import androidx.room.OnConflictStrategy
14 | import androidx.room.Query
15 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
16 | import kotlinx.coroutines.flow.Flow
17 |
18 | @Dao
19 | interface SubredditDAO {
20 | @Query("SELECT * FROM subreddit")
21 | fun getAll(): Flow>
22 |
23 | @Insert(onConflict = OnConflictStrategy.REPLACE)
24 | fun insertAll(subreddits: List)
25 |
26 | @Insert(onConflict = OnConflictStrategy.REPLACE)
27 | fun insert(subreddits: RedditCommunity)
28 |
29 | @Delete
30 | fun delete(subreddit: RedditCommunity)
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/AboutCommunity.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 9:20 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.entity
9 |
10 | interface AboutCommunity {
11 | val id: String
12 | val name: String
13 | val iconUrl: String?
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/LemmyCommunity.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 12:30 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.entity
9 |
10 | import androidx.room.ColumnInfo
11 | import androidx.room.Entity
12 |
13 | @Entity(tableName = "community", primaryKeys = ["community", "instance"])
14 | data class LemmyCommunity(
15 | @ColumnInfo(name = "community") override val id: String,
16 | @ColumnInfo(name = "instance") val instance: String,
17 | @ColumnInfo(name = "icon_url") override val iconUrl: String? = null,
18 | @ColumnInfo(name = "name") override val name: String = ""
19 | ) : AboutCommunity
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/LemmyMeme.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.database.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "lemmy_table")
8 | data class LemmyMeme(
9 | @PrimaryKey override val id: String,
10 | @ColumnInfo(name = "url") override val url: String,
11 | @ColumnInfo(name = "title", defaultValue = "") override val title: String,
12 | @ColumnInfo(name = "is_video") override val isVideo: Boolean,
13 | @ColumnInfo(name = "preview", defaultValue = "") override val preview: String,
14 | @ColumnInfo(name = "community", defaultValue = "") val community: String,
15 | @ColumnInfo(name = "instance", defaultValue = "") val instance: String,
16 | @ColumnInfo(name = "post_link", defaultValue = "NULL") override val postLink: String?
17 | ) : Meme
18 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/Meme.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.database.entity
2 |
3 | interface Meme {
4 | val id: String
5 | val url: String
6 | val title: String
7 | val isVideo: Boolean
8 | val preview: String
9 | val postLink: String?
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/RedditCommunity.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 12:30 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.database.entity
9 |
10 | import androidx.room.ColumnInfo
11 | import androidx.room.Entity
12 | import androidx.room.PrimaryKey
13 |
14 | @Entity(tableName = "subreddit")
15 | data class RedditCommunity(
16 | @PrimaryKey override val id: String,
17 | @ColumnInfo(name = "icon_url") override val iconUrl: String? = null,
18 | @ColumnInfo(name = "name") override val name: String = ""
19 | ) : AboutCommunity
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/database/entity/RedditMeme.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.database.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "reddit_table")
8 | data class RedditMeme(
9 | @PrimaryKey override val id: String,
10 | @ColumnInfo(name = "url") override val url: String,
11 | @ColumnInfo(name = "title", defaultValue = "") override val title: String,
12 | @ColumnInfo(name = "is_video") override val isVideo: Boolean,
13 | @ColumnInfo(name = "preview", defaultValue = "") override val preview: String,
14 | @ColumnInfo(name = "subreddit", defaultValue = "") val subreddit: String,
15 | @ColumnInfo(name = "post_link", defaultValue = "NULL") override val postLink: String?
16 | ) : Meme
17 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/model/LemmyAbout.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class LemmyAbout(
8 | @SerialName("community_view") val communityView: CommunityView? = CommunityView()
9 | )
10 |
11 | @Serializable
12 | data class CommunityView(
13 | @SerialName("community") val community: Community? = Community()
14 | )
15 |
16 | @Serializable
17 | data class Community(
18 | @SerialName("id") val id: Int? = null,
19 | @SerialName("name") val name: String? = null,
20 | @SerialName("title") val title: String? = null,
21 | @SerialName("icon") val icon: String? = null
22 | )
23 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/model/LemmyResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class LemmyResponse(
8 | @SerialName("posts") val posts: ArrayList = arrayListOf()
9 | )
10 |
11 | @Serializable
12 | data class Posts(
13 | @SerialName("post") val post: Post? = Post()
14 | )
15 |
16 | @Serializable
17 | data class Post(
18 | @SerialName("id") val id: Int? = null,
19 | @SerialName("name") val name: String? = null,
20 | @SerialName("url") val url: String? = null,
21 | @SerialName("thumbnail_url") val thumbnailUrl: String? = null,
22 | @SerialName("ap_id") val postLink: String? = null
23 | )
24 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/model/RedditAboutResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class RedditAboutResponse(
8 | @SerialName("data") val data: AboutData? = AboutData()
9 | )
10 |
11 | @Serializable
12 | data class AboutData(
13 | @SerialName("community_icon") val communityIcon: String? = null,
14 | @SerialName("display_name") val displayName: String? = null,
15 | @SerialName("display_name_prefixed") val displayNamePrefixed: String? = null
16 | ) {
17 | val communityIconUrl
18 | get() = communityIcon?.replace("&", "&")
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/model/RedditResponse.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Reddit(
8 | @SerialName("data") val data: Data? = Data()
9 | )
10 |
11 | @Serializable
12 | data class Data(
13 | @SerialName("children") val children: ArrayList? = arrayListOf()
14 | )
15 |
16 | @Serializable
17 | data class Children(
18 | @SerialName("data") val childdata: ChildData? = ChildData()
19 | )
20 |
21 | @Serializable
22 | data class ChildData(
23 | @SerialName("title") val title: String? = null,
24 | @SerialName("secure_media") val secure_media: SecureMedia? = SecureMedia(),
25 | @SerialName("url") val url: String? = null,
26 | @SerialName("permalink") val permalink: String? = null,
27 | @SerialName("preview") val preview: Preview? = Preview()
28 |
29 | )
30 |
31 | @Serializable
32 | data class SecureMedia(
33 | @SerialName("reddit_video") val reddit_video: RedditVideo? = RedditVideo()
34 | )
35 |
36 | @Serializable
37 | data class RedditVideo(
38 | @SerialName("dash_url") val dash_url: String? = null
39 | )
40 |
41 | @Serializable
42 | data class Preview(
43 | @SerialName("images") val images: ArrayList = arrayListOf(),
44 | @SerialName("reddit_video_preview") val redditVideo: RedditVideo? = null
45 | )
46 |
47 | @Serializable
48 | data class Images(
49 | @SerialName("source") val source: Source? = Source()
50 | )
51 |
52 | @Serializable
53 | data class Source(
54 | @SerialName("url") val url: String? = null
55 | )
56 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/model/Sort.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 6:12 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.model
9 |
10 | import app.suhasdissa.memerize.R
11 |
12 | sealed class Sort(open val name: Int, val redditSort: String, open val lemmySort: String) {
13 | object Hot : Sort(R.string.hot, "hot", "Hot")
14 | object New : Sort(R.string.sort_new, "new", "New")
15 | object Rising : Sort(R.string.rising, "rising", "Active")
16 | sealed class Top(val redditT: String) : Sort(R.string.top, "top", "") {
17 | object Today : Top("today") {
18 | override val name = R.string.reddit_today_btn
19 | override val lemmySort = "TopDay"
20 | }
21 |
22 | object Week : Top("week") {
23 | override val name = R.string.reddit_week_btn
24 | override val lemmySort = "TopWeek"
25 | }
26 |
27 | object Month : Top("month") {
28 | override val name = R.string.reddit_month_btn
29 | override val lemmySort = "TopMonth"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/CommunityRepository.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 9:44 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.repositories
9 |
10 | import app.suhasdissa.memerize.backend.database.entity.AboutCommunity
11 | import kotlinx.coroutines.flow.Flow
12 |
13 | interface CommunityRepository {
14 | fun getCommunities(): Flow>
15 | suspend fun getCommunityInfo(community: T): T?
16 | suspend fun insertCommunity(community: T)
17 | suspend fun removeCommunity(community: T)
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/LemmyCommunityRepository.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 10:15 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.repositories
9 |
10 | import android.util.Log
11 | import app.suhasdissa.memerize.backend.apis.LemmyApi
12 | import app.suhasdissa.memerize.backend.database.dao.CommunityDAO
13 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
14 | import kotlinx.coroutines.flow.Flow
15 |
16 | interface LemmyCommunityRepository : CommunityRepository
17 | class LemmyCommunityRepositoryImpl(
18 | private val communityDAO: CommunityDAO,
19 | private val lemmyApi: LemmyApi
20 | ) : LemmyCommunityRepository {
21 |
22 | override fun getCommunities(): Flow> = communityDAO.getAll()
23 |
24 | override suspend fun getCommunityInfo(community: LemmyCommunity): LemmyCommunity? {
25 | return try {
26 | val comm =
27 | lemmyApi.getCommunity(community.instance, community.id).communityView?.community
28 | ?: return null
29 |
30 | return community.copy(name = comm.name ?: community.id, iconUrl = comm.icon)
31 | } catch (e: Exception) {
32 | Log.e("Lemmy Repository", e.toString())
33 | null
34 | }
35 | }
36 |
37 | override suspend fun insertCommunity(community: LemmyCommunity) = communityDAO.insert(community)
38 |
39 | override suspend fun removeCommunity(community: LemmyCommunity) = communityDAO.delete(community)
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/LemmyMemeRepository.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.repositories
2 |
3 | import android.util.Log
4 | import androidx.annotation.WorkerThread
5 | import app.suhasdissa.memerize.backend.apis.LemmyApi
6 | import app.suhasdissa.memerize.backend.database.dao.LemmyMemeDAO
7 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
8 | import app.suhasdissa.memerize.backend.database.entity.LemmyMeme
9 | import app.suhasdissa.memerize.backend.model.Sort
10 |
11 | interface LemmyMemeRepository : MemeRepository
12 | class LemmyMemeRepositoryImpl(
13 | private val lemmyDAO: LemmyMemeDAO,
14 | private val lemmyApi: LemmyApi
15 | ) : LemmyMemeRepository {
16 |
17 | override suspend fun getOnlineData(
18 | community: LemmyCommunity,
19 | sort: Sort
20 | ): List? {
21 | return try {
22 | val memesList = getNetworkData(community, sort.lemmySort)
23 | Thread {
24 | insertMemes(memesList)
25 | }.start()
26 | memesList
27 | } catch (e: Exception) {
28 | Log.e("Lemmy Repository", e.toString())
29 | null
30 | }
31 | }
32 |
33 | private suspend fun getNetworkData(
34 | community: LemmyCommunity,
35 | time: String
36 | ): List {
37 | val memeList: ArrayList = arrayListOf()
38 | val lemmyData = lemmyApi.getLemmyData(
39 | instance = community.instance,
40 | community = community.id,
41 | sort = time
42 | ).posts
43 | lemmyData.forEach { post ->
44 | val url = post.post?.url ?: ""
45 | val title = post.post?.name ?: ""
46 | if (url.endsWith("jpg") || url.endsWith("jpeg") || url.endsWith("png")) {
47 | val id = url.hashCode().toString()
48 | memeList.add(
49 | LemmyMeme(
50 | id,
51 | url,
52 | title,
53 | false,
54 | url,
55 | community.name,
56 | community.instance,
57 | post.post?.postLink
58 | )
59 | )
60 | }
61 | }
62 | return memeList
63 | }
64 |
65 | override suspend fun getLocalData(community: LemmyCommunity): List =
66 | lemmyDAO.getAll(community.id, community.instance)
67 |
68 | @WorkerThread
69 | private fun insertMemes(memes: List) {
70 | lemmyDAO.insertAll(memes)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/MemeRepository.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.repositories
9 |
10 | import app.suhasdissa.memerize.backend.database.entity.AboutCommunity
11 | import app.suhasdissa.memerize.backend.database.entity.Meme
12 | import app.suhasdissa.memerize.backend.model.Sort
13 |
14 | interface MemeRepository {
15 | suspend fun getOnlineData(community: C, sort: Sort): List?
16 | suspend fun getLocalData(community: C): List
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/RedditCommunityRepository.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 10:15 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.repositories
9 |
10 | import app.suhasdissa.memerize.backend.apis.RedditApi
11 | import app.suhasdissa.memerize.backend.database.dao.SubredditDAO
12 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
13 | import kotlinx.coroutines.flow.Flow
14 |
15 | interface RedditCommunityRepository : CommunityRepository
16 | class RedditCommunityRepositoryImpl(
17 | private val subredditDAO: SubredditDAO,
18 | private val redditApi: RedditApi
19 | ) : RedditCommunityRepository {
20 |
21 | override fun getCommunities(): Flow> = subredditDAO.getAll()
22 |
23 | override suspend fun getCommunityInfo(community: RedditCommunity): RedditCommunity? {
24 | return try {
25 | val info = redditApi.getAboutSubreddit(community.id).data ?: return null
26 | community.copy(iconUrl = info.communityIconUrl, name = info.displayName ?: community.id)
27 | } catch (_: Exception) {
28 | null
29 | }
30 | }
31 |
32 | override suspend fun insertCommunity(community: RedditCommunity) =
33 | subredditDAO.insert(community)
34 |
35 | override suspend fun removeCommunity(community: RedditCommunity) =
36 | subredditDAO.delete(community)
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/repositories/RedditMemeRepository.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.repositories
2 |
3 | import android.util.Log
4 | import androidx.annotation.WorkerThread
5 | import app.suhasdissa.memerize.backend.apis.RedditApi
6 | import app.suhasdissa.memerize.backend.database.dao.RedditMemeDao
7 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
8 | import app.suhasdissa.memerize.backend.database.entity.RedditMeme
9 | import app.suhasdissa.memerize.backend.model.Sort
10 |
11 | interface RedditMemeRepository : MemeRepository
12 |
13 | class RedditMemeRepositoryImpl(
14 | private val redditMemeDao: RedditMemeDao,
15 | private val redditApi: RedditApi
16 | ) : RedditMemeRepository {
17 |
18 | private val imageRegex = Regex("^.+\\.(jpg|jpeg|png|webp)\$")
19 | override suspend fun getOnlineData(
20 | community: RedditCommunity,
21 | sort: Sort
22 | ): List? {
23 | val srt = when (sort) {
24 | is Sort.Top -> sort.redditSort to sort.redditT
25 | else -> sort.redditSort to null
26 | }
27 | return try {
28 | val memesList = getNetworkData(community.id, srt.first, srt.second)
29 | Thread {
30 | insertMemes(memesList)
31 | }.start()
32 | memesList
33 | } catch (e: Exception) {
34 | Log.e("Reddit Repository", e.message, e)
35 | null
36 | }
37 | }
38 |
39 | override suspend fun getLocalData(community: RedditCommunity): List =
40 | redditMemeDao.getAll(community.id)
41 |
42 | private suspend fun getNetworkData(
43 | subreddit: String,
44 | sort: String,
45 | time: String?
46 | ): List {
47 | val memeList: ArrayList = arrayListOf()
48 | val redditData =
49 | redditApi.getRedditData(subreddit, sort, time).data?.children ?: return emptyList()
50 | redditData.forEach { child ->
51 | val url = child.childdata?.url
52 | if (url?.matches(imageRegex) == true) {
53 | val id = url.hashCode().toString()
54 | memeList.add(
55 | RedditMeme(
56 | id,
57 | url,
58 | child.childdata.title ?: "",
59 | false,
60 | "",
61 | subreddit,
62 | child.childdata.permalink?.let { "https://www.reddit.com$it" }
63 | )
64 | )
65 | } else if (url?.contains("v.redd.it") == true || child.childdata?.preview?.redditVideo?.dash_url != null) {
66 | val dashUrl = child.childdata.secure_media?.reddit_video?.dash_url
67 | ?: child.childdata.preview?.redditVideo?.dash_url
68 | val previewUrl = child.childdata.preview?.images?.get(0)?.source?.url
69 | if (dashUrl != null && previewUrl != null) {
70 | val id = url.hashCode().toString()
71 | memeList.add(
72 | RedditMeme(
73 | id,
74 | dashUrl,
75 | child.childdata.title ?: "",
76 | true,
77 | previewUrl.replace("&", "&"),
78 | subreddit,
79 | child.childdata.permalink?.let { "https://www.reddit.com$it" }
80 | )
81 | )
82 | }
83 | }
84 | }
85 | return memeList
86 | }
87 |
88 | @WorkerThread
89 | private fun insertMemes(memes: List) {
90 | redditMemeDao.insertAll(memes)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/CheckUpdateViewModel.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/9/23, 3:34 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels
9 |
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.viewModelScope
15 | import app.suhasdissa.memerize.utils.UpdateUtil
16 | import kotlinx.coroutines.launch
17 |
18 | class CheckUpdateViewModel : ViewModel() {
19 | var latestVersion: Float? by mutableStateOf(null)
20 | val currentVersion = UpdateUtil.currentVersion
21 |
22 | init {
23 | getLatestRelease()
24 | }
25 |
26 | private fun getLatestRelease() {
27 | viewModelScope.launch {
28 | latestVersion = UpdateUtil.getLatestVersion()
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/LemmyCommunityViewModel.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 2:18 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels
9 |
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
16 | import androidx.lifecycle.viewModelScope
17 | import androidx.lifecycle.viewmodel.initializer
18 | import androidx.lifecycle.viewmodel.viewModelFactory
19 | import app.suhasdissa.memerize.MemerizeApplication
20 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
21 | import app.suhasdissa.memerize.backend.repositories.LemmyCommunityRepository
22 | import app.suhasdissa.memerize.backend.viewmodels.state.AboutCommunityState
23 | import kotlinx.coroutines.flow.SharingStarted
24 | import kotlinx.coroutines.flow.stateIn
25 | import kotlinx.coroutines.launch
26 |
27 | class LemmyCommunityViewModel(private val lemmyRepository: LemmyCommunityRepository) :
28 | ViewModel() {
29 |
30 | val communities = lemmyRepository.getCommunities().stateIn(
31 | viewModelScope,
32 | started = SharingStarted.WhileSubscribed(5000L),
33 | initialValue = listOf()
34 | )
35 |
36 | var aboutCommutnityState: AboutCommunityState by mutableStateOf(
37 | AboutCommunityState.Loading(
38 | LemmyCommunity("", "")
39 | )
40 | )
41 |
42 | fun removeCommunity(community: LemmyCommunity) {
43 | viewModelScope.launch {
44 | lemmyRepository.removeCommunity(community)
45 | }
46 | }
47 |
48 | fun getInfo(instance: String, community: String) {
49 | viewModelScope.launch {
50 | aboutCommutnityState = AboutCommunityState.Loading(LemmyCommunity(community, instance))
51 | val lemmyInfo = lemmyRepository.getCommunityInfo(LemmyCommunity(community, instance))
52 | if (lemmyInfo == null) {
53 | aboutCommutnityState =
54 | AboutCommunityState.Error(LemmyCommunity(community, instance))
55 | } else {
56 | aboutCommutnityState = AboutCommunityState.Success(lemmyInfo)
57 | lemmyRepository.insertCommunity(lemmyInfo)
58 | }
59 | }
60 | }
61 |
62 | companion object {
63 | val Factory: ViewModelProvider.Factory = viewModelFactory {
64 | initializer {
65 | val application = (this[APPLICATION_KEY] as MemerizeApplication)
66 | val lemmyRepository = application.container.lemmyCommunityRepository
67 | LemmyCommunityViewModel(lemmyRepository)
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/LemmyViewModel.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels
9 |
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
16 | import androidx.lifecycle.viewModelScope
17 | import androidx.lifecycle.viewmodel.initializer
18 | import androidx.lifecycle.viewmodel.viewModelFactory
19 | import app.suhasdissa.memerize.MemerizeApplication
20 | import app.suhasdissa.memerize.backend.database.entity.LemmyCommunity
21 | import app.suhasdissa.memerize.backend.model.Sort
22 | import app.suhasdissa.memerize.backend.repositories.LemmyMemeRepository
23 | import app.suhasdissa.memerize.backend.viewmodels.state.MemeUiState
24 | import kotlinx.coroutines.launch
25 |
26 | class LemmyViewModel(private val lemmyRepository: LemmyMemeRepository) :
27 | ViewModel() {
28 | var memeUiState: MemeUiState by mutableStateOf(MemeUiState.Loading)
29 | private set
30 |
31 | var currentCommunity: LemmyCommunity? = null
32 | private set
33 | var currentSortTime: Sort = Sort.Top.Today
34 | private set
35 |
36 | fun getMemePhotos(
37 | community: LemmyCommunity? = currentCommunity,
38 | sort: Sort = Sort.Top.Today
39 | ) {
40 | currentCommunity = community!!
41 | currentSortTime = sort
42 | viewModelScope.launch {
43 | memeUiState = MemeUiState.Loading
44 |
45 | memeUiState = when (val data = lemmyRepository.getOnlineData(community, sort)) {
46 | null -> {
47 | MemeUiState.Error("")
48 | }
49 |
50 | else -> {
51 | MemeUiState.Success(data)
52 | }
53 | }
54 | }
55 | }
56 |
57 | fun getLocalMemes(community: LemmyCommunity = currentCommunity!!) {
58 | viewModelScope.launch {
59 | memeUiState = MemeUiState.Loading
60 |
61 | memeUiState = MemeUiState.Success(lemmyRepository.getLocalData(community))
62 | }
63 | }
64 |
65 | companion object {
66 | val Factory: ViewModelProvider.Factory = viewModelFactory {
67 | initializer {
68 | val application = (this[APPLICATION_KEY] as MemerizeApplication)
69 | val lemmyRepository = application.container.lemmyMemeRepository
70 | LemmyViewModel(lemmyRepository)
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/PhotoViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.viewmodels
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.graphics.Bitmap
6 | import android.net.Uri
7 | import android.os.Environment
8 | import android.util.Log
9 | import android.widget.Toast
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.core.content.FileProvider
14 | import androidx.core.graphics.drawable.toBitmap
15 | import androidx.documentfile.provider.DocumentFile
16 | import androidx.lifecycle.ViewModel
17 | import androidx.lifecycle.viewModelScope
18 | import app.suhasdissa.memerize.BuildConfig
19 | import app.suhasdissa.memerize.backend.database.entity.Meme
20 | import app.suhasdissa.memerize.utils.SaveDirectoryKey
21 | import app.suhasdissa.memerize.utils.preferences
22 | import coil.ImageLoader
23 | import coil.request.ImageRequest
24 | import coil.request.SuccessResult
25 | import java.io.File
26 | import java.io.FileOutputStream
27 | import java.util.UUID
28 | import kotlinx.coroutines.Dispatchers
29 | import kotlinx.coroutines.launch
30 | import kotlinx.coroutines.withContext
31 |
32 | class PhotoViewModel : ViewModel() {
33 |
34 | var downloadState: DownloadState by mutableStateOf(DownloadState.NotStarted)
35 |
36 | private suspend fun getBitmapFromUrl(url: String, context: Context): Bitmap? {
37 | val imageLoader = ImageLoader.Builder(context).build()
38 | val request = ImageRequest.Builder(context)
39 | .data(url)
40 | .build()
41 | val result = imageLoader.execute(request)
42 |
43 | if (result is SuccessResult) {
44 | return result.drawable.toBitmap()
45 | }
46 | return null
47 | }
48 |
49 | fun savePhotoToDisk(meme: Meme, context: Context) {
50 | viewModelScope.launch(Dispatchers.IO) {
51 | withContext(Dispatchers.Main) {
52 | downloadState = DownloadState.Loading
53 | }
54 | val bitmap = getBitmapFromUrl(meme.url, context)
55 | val prefDir =
56 | context.preferences.getString(SaveDirectoryKey, null)
57 |
58 | val saveDir = when {
59 | prefDir.isNullOrBlank() -> {
60 | val dir =
61 | Environment.getExternalStoragePublicDirectory(
62 | Environment.DIRECTORY_DOWNLOADS
63 | )
64 | DocumentFile.fromFile(dir)
65 | }
66 |
67 | else -> DocumentFile.fromTreeUri(context, Uri.parse(prefDir))!!
68 | }
69 | val outputFile =
70 | saveDir.createFile(
71 | "image/jpg",
72 | "${meme.title.take(64)}-${
73 | UUID.randomUUID().toString().take(8)
74 | }.jpg".replace("[\\\\/:*?\"<>|]".toRegex(), "")
75 | )
76 | if (outputFile == null) {
77 | withContext(Dispatchers.Main) {
78 | downloadState = DownloadState.Error
79 | }
80 | withContext(Dispatchers.Main) {
81 | Toast.makeText(context, "Failed to create file", Toast.LENGTH_LONG).show()
82 | }
83 | return@launch
84 | }
85 | if (bitmap != null) {
86 | try {
87 | val outputStream = context.contentResolver.openOutputStream(outputFile.uri)!!
88 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
89 | outputStream.flush()
90 | outputStream.close()
91 | withContext(Dispatchers.Main) {
92 | Toast.makeText(context, "Download Finished", Toast.LENGTH_LONG).show()
93 | downloadState = DownloadState.NotStarted
94 | }
95 | } catch (e: Exception) {
96 | Log.e("Photo save", e.toString())
97 | withContext(Dispatchers.Main) {
98 | Toast.makeText(context, "Download Failed", Toast.LENGTH_LONG).show()
99 | downloadState = DownloadState.Error
100 | }
101 | }
102 | } else {
103 | withContext(Dispatchers.Main) {
104 | downloadState = DownloadState.Error
105 | }
106 | }
107 | }
108 | }
109 |
110 | fun shareImage(url: String, context: Context) {
111 | viewModelScope.launch(Dispatchers.IO) {
112 | val bitmap = getBitmapFromUrl(url, context)
113 | if (bitmap != null) {
114 | try {
115 | val outputFile = File(
116 | context.cacheDir,
117 | "${UUID.randomUUID()}.jpg"
118 | )
119 | val outputStream = FileOutputStream(outputFile)
120 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
121 | outputStream.flush()
122 | outputStream.close()
123 | val sendIntent: Intent = Intent().apply {
124 | action = Intent.ACTION_SEND
125 | putExtra(
126 | Intent.EXTRA_STREAM,
127 | FileProvider.getUriForFile(
128 | context,
129 | BuildConfig.APPLICATION_ID + ".provider",
130 | outputFile
131 | )
132 | )
133 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
134 | type = "image/jpg"
135 | }
136 | val shareIntent = Intent.createChooser(sendIntent, "Send Photo to..")
137 | context.startActivity(shareIntent)
138 | } catch (e: Exception) {
139 | Log.e("Share Image", e.toString())
140 | }
141 | }
142 | }
143 | }
144 | }
145 |
146 | sealed interface DownloadState {
147 | object NotStarted : DownloadState
148 | object Success : DownloadState
149 | object Error : DownloadState
150 | object Loading : DownloadState
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/PlayerViewModel.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.backend.viewmodels
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.widget.Toast
6 | import androidx.annotation.RequiresApi
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.mutableStateOf
9 | import androidx.compose.runtime.setValue
10 | import androidx.lifecycle.ViewModel
11 | import androidx.lifecycle.viewModelScope
12 | import androidx.media3.common.Player
13 | import app.suhasdissa.memerize.backend.database.entity.Meme
14 | import app.suhasdissa.memerize.utils.RedditVideoDownloader
15 | import java.util.UUID
16 | import kotlinx.coroutines.launch
17 |
18 | class PlayerViewModel() : ViewModel() {
19 | var downloadState: DownloadState by mutableStateOf(DownloadState.NotStarted)
20 |
21 | var muted by mutableStateOf(false)
22 |
23 | @RequiresApi(Build.VERSION_CODES.O)
24 | fun downloadVideo(context: Context, meme: Meme) {
25 | val fileName = "${meme.title.take(64)}-${
26 | UUID.randomUUID().toString().take(8)
27 | }".replace("[\\\\/:*?\"<>|]".toRegex(), "")
28 | viewModelScope.launch {
29 | downloadState = DownloadState.Loading
30 | val downloader = RedditVideoDownloader()
31 | val result =
32 | downloader.downloadRedditVideo(context.applicationContext, meme.url, fileName)
33 | downloadState = if (result) {
34 | Toast.makeText(context, "Download Finished", Toast.LENGTH_LONG).show()
35 | DownloadState.NotStarted
36 | } else {
37 | Toast.makeText(context, "Download Failed", Toast.LENGTH_LONG).show()
38 | DownloadState.Error
39 | }
40 | }
41 | }
42 | }
43 |
44 | fun Player.playPause() {
45 | if (isPlaying) pause() else play()
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/RedditCommunityViewModel.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 2:18 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels
9 |
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
16 | import androidx.lifecycle.viewModelScope
17 | import androidx.lifecycle.viewmodel.initializer
18 | import androidx.lifecycle.viewmodel.viewModelFactory
19 | import app.suhasdissa.memerize.MemerizeApplication
20 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
21 | import app.suhasdissa.memerize.backend.repositories.RedditCommunityRepository
22 | import app.suhasdissa.memerize.backend.viewmodels.state.AboutCommunityState
23 | import kotlinx.coroutines.flow.SharingStarted
24 | import kotlinx.coroutines.flow.stateIn
25 | import kotlinx.coroutines.launch
26 |
27 | class RedditCommunityViewModel(private val redditRepository: RedditCommunityRepository) :
28 | ViewModel() {
29 |
30 | val communities = redditRepository.getCommunities().stateIn(
31 | viewModelScope,
32 | started = SharingStarted.WhileSubscribed(5000L),
33 | initialValue = listOf()
34 | )
35 |
36 | var aboutCommunityState: AboutCommunityState by mutableStateOf(
37 | AboutCommunityState.Loading(
38 | RedditCommunity("")
39 | )
40 | )
41 |
42 | fun removeSubreddit(subreddit: RedditCommunity) {
43 | viewModelScope.launch {
44 | redditRepository.removeCommunity(subreddit)
45 | }
46 | }
47 |
48 | fun getSubredditInfo(subreddit: String) {
49 | viewModelScope.launch {
50 | aboutCommunityState = AboutCommunityState.Loading(RedditCommunity(subreddit))
51 | val subredditInfo = redditRepository.getCommunityInfo(RedditCommunity(subreddit))
52 | if (subredditInfo == null) {
53 | aboutCommunityState = AboutCommunityState.Error(RedditCommunity(subreddit))
54 | } else {
55 | aboutCommunityState = AboutCommunityState.Success(subredditInfo)
56 | redditRepository.insertCommunity(subredditInfo)
57 | }
58 | }
59 | }
60 |
61 | companion object {
62 | val Factory: ViewModelProvider.Factory = viewModelFactory {
63 | initializer {
64 | val application = (this[APPLICATION_KEY] as MemerizeApplication)
65 | val redditRepository = application.container.redditCommunityRepository
66 | RedditCommunityViewModel(redditRepository = redditRepository)
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/RedditViewModel.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels
9 |
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.setValue
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
16 | import androidx.lifecycle.viewModelScope
17 | import androidx.lifecycle.viewmodel.initializer
18 | import androidx.lifecycle.viewmodel.viewModelFactory
19 | import app.suhasdissa.memerize.MemerizeApplication
20 | import app.suhasdissa.memerize.backend.database.entity.RedditCommunity
21 | import app.suhasdissa.memerize.backend.database.entity.RedditMeme
22 | import app.suhasdissa.memerize.backend.model.Sort
23 | import app.suhasdissa.memerize.backend.repositories.RedditMemeRepository
24 | import app.suhasdissa.memerize.backend.viewmodels.state.MemeUiState
25 | import kotlinx.coroutines.async
26 | import kotlinx.coroutines.awaitAll
27 | import kotlinx.coroutines.launch
28 |
29 | class RedditViewModel(private val redditRepository: RedditMemeRepository) :
30 | ViewModel() {
31 | var memeUiState: MemeUiState by mutableStateOf(MemeUiState.Loading)
32 | private set
33 |
34 | var currentSubreddit: RedditCommunity? = null
35 | private set
36 |
37 | var currentSortTime: Sort = Sort.Top.Today
38 | private set
39 |
40 | fun getMemePhotos(
41 | subreddit: RedditCommunity? = currentSubreddit,
42 | sort: Sort = Sort.Top.Today
43 | ) {
44 | currentSubreddit = subreddit!!
45 | currentSortTime = sort
46 | viewModelScope.launch {
47 | memeUiState = MemeUiState.Loading
48 |
49 | memeUiState = when (
50 | val data =
51 | redditRepository.getOnlineData(subreddit, sort)
52 | ) {
53 | null -> {
54 | MemeUiState.Error("")
55 | }
56 |
57 | else -> {
58 | MemeUiState.Success(data)
59 | }
60 | }
61 | }
62 | }
63 |
64 | fun getLocalMemes(subreddit: RedditCommunity = currentSubreddit!!) {
65 | viewModelScope.launch {
66 | memeUiState = MemeUiState.Loading
67 |
68 | memeUiState =
69 | MemeUiState.Success(redditRepository.getLocalData(subreddit))
70 | }
71 | }
72 |
73 | fun getMultiMemes(communities: List) {
74 | viewModelScope.launch {
75 | currentSubreddit = null
76 | memeUiState = MemeUiState.Loading
77 | val results = communities.map {
78 | async { redditRepository.getOnlineData(it, Sort.Top.Today) }
79 | }.awaitAll()
80 | val memeList: List = results.filterNotNull().flatten().shuffled()
81 | memeUiState = if (memeList.isEmpty()) {
82 | MemeUiState.Error("")
83 | } else {
84 | MemeUiState.Success(memeList)
85 | }
86 | }
87 | }
88 |
89 | companion object {
90 | val Factory: ViewModelProvider.Factory = viewModelFactory {
91 | initializer {
92 | val application = (this[APPLICATION_KEY] as MemerizeApplication)
93 | val redditRepository = application.container.redditMemeRepository
94 | RedditViewModel(redditRepository = redditRepository)
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/state/AboutCommunityState.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 9:26 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels.state
9 |
10 | import app.suhasdissa.memerize.backend.database.entity.AboutCommunity
11 |
12 | sealed interface AboutCommunityState {
13 | data class Success(val community: AboutCommunity) : AboutCommunityState
14 | data class Error(val community: AboutCommunity) : AboutCommunityState
15 | data class Loading(val community: AboutCommunity) : AboutCommunityState
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/backend/viewmodels/state/MemeUiState.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 12:13 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.backend.viewmodels.state
9 |
10 | import app.suhasdissa.memerize.backend.database.entity.Meme
11 |
12 | sealed interface MemeUiState {
13 | data class Success(val memes: List) : MemeUiState
14 | data class Error(val error: String) : MemeUiState
15 | object Loading : MemeUiState
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/MemerizeApp.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/25/22, 6:10 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.material3.DrawerValue
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.ModalNavigationDrawer
15 | import androidx.compose.material3.Surface
16 | import androidx.compose.material3.rememberDrawerState
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.getValue
19 | import androidx.compose.runtime.mutableStateOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.runtime.rememberCoroutineScope
22 | import androidx.compose.runtime.setValue
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.platform.LocalView
25 | import androidx.navigation.compose.rememberNavController
26 | import app.suhasdissa.memerize.AppNavHost
27 | import app.suhasdissa.memerize.Destination
28 | import app.suhasdissa.memerize.navigateTo
29 | import app.suhasdissa.memerize.ui.components.NavDrawerContent
30 | import kotlinx.coroutines.launch
31 |
32 | @Composable
33 | fun MemerizeApp() {
34 | val navController = rememberNavController()
35 | val drawerState = rememberDrawerState(DrawerValue.Closed)
36 | val scope = rememberCoroutineScope()
37 | var currentDestination by remember {
38 | mutableStateOf(Destination.Home)
39 | }
40 | val view = LocalView.current
41 | ModalNavigationDrawer(
42 | drawerState = drawerState,
43 | gesturesEnabled = drawerState.isOpen,
44 | drawerContent = {
45 | NavDrawerContent(currentDestination = currentDestination, onDestinationSelected = {
46 | scope.launch {
47 | drawerState.close()
48 | }
49 | navController.navigateTo(it.route)
50 | currentDestination = it
51 | })
52 | }
53 | ) {
54 | Surface(
55 | modifier = Modifier
56 | .fillMaxSize(),
57 | color = MaterialTheme.colorScheme.surface
58 | ) {
59 | AppNavHost(
60 | navController = navController,
61 | onDrawerOpen = {
62 | view.playSoundEffect(SoundEffectConstants.CLICK)
63 | scope.launch {
64 | drawerState.open()
65 | }
66 | },
67 | modifier = Modifier.fillMaxSize()
68 | )
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/CacheSizeDialog.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/9/23, 9:56 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.layout.Arrangement
11 | import androidx.compose.foundation.lazy.grid.GridCells
12 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13 | import androidx.compose.foundation.lazy.grid.items
14 | import androidx.compose.material3.AlertDialog
15 | import androidx.compose.material3.Button
16 | import androidx.compose.material3.ExperimentalMaterial3Api
17 | import androidx.compose.material3.FilterChip
18 | import androidx.compose.material3.Text
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.runtime.setValue
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.unit.dp
24 | import app.suhasdissa.memerize.R
25 | import app.suhasdissa.memerize.utils.defaultImageCacheSize
26 | import app.suhasdissa.memerize.utils.imageCacheKey
27 | import app.suhasdissa.memerize.utils.rememberPreference
28 |
29 | @OptIn(ExperimentalMaterial3Api::class)
30 | @Composable
31 | fun CacheSizeDialog(onDismissRequest: () -> Unit) {
32 | val cacheSizes = listOf(16, 32, 64, 128, 256, 512, 1024, 2048)
33 | var prefSize by rememberPreference(key = imageCacheKey, defaultValue = defaultImageCacheSize)
34 | AlertDialog(
35 | onDismissRequest,
36 | title = { Text(stringResource(R.string.change_image_cache_size)) },
37 | confirmButton = {
38 | Button(onClick = {
39 | onDismissRequest.invoke()
40 | }) {
41 | Text(text = stringResource(R.string.ok))
42 | }
43 | },
44 | text = {
45 | LazyVerticalGrid(
46 | columns = GridCells.Fixed(3),
47 | verticalArrangement = Arrangement.spacedBy(8.dp),
48 | horizontalArrangement = Arrangement.spacedBy(8.dp)
49 | ) {
50 | items(items = cacheSizes) {
51 | FilterChip(
52 | selected = prefSize == it,
53 | onClick = { prefSize = it },
54 | label = {
55 | Text("$it MB")
56 | }
57 | )
58 | }
59 | }
60 | }
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/ErrorScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.Text
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.res.stringResource
19 | import app.suhasdissa.memerize.R
20 |
21 | @Composable
22 | fun ErrorScreen(memeUiState: String, modifier: Modifier = Modifier) {
23 | Box(
24 | contentAlignment = Alignment.Center,
25 | modifier = modifier.fillMaxSize()
26 | ) {
27 | Column {
28 | Text(
29 | stringResource(R.string.loading_failed),
30 | style = MaterialTheme.typography.bodyLarge
31 | )
32 | Text(memeUiState, color = MaterialTheme.colorScheme.error)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/HighlightCard.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.ExperimentalFoundationApi
12 | import androidx.compose.foundation.combinedClickable
13 | import androidx.compose.foundation.layout.Arrangement
14 | import androidx.compose.foundation.layout.Row
15 | import androidx.compose.foundation.layout.aspectRatio
16 | import androidx.compose.foundation.layout.fillMaxSize
17 | import androidx.compose.foundation.layout.fillMaxWidth
18 | import androidx.compose.foundation.layout.height
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.foundation.layout.size
21 | import androidx.compose.foundation.shape.CircleShape
22 | import androidx.compose.material3.CardDefaults
23 | import androidx.compose.material3.ElevatedCard
24 | import androidx.compose.material3.MaterialTheme
25 | import androidx.compose.material3.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.draw.clip
30 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType
31 | import androidx.compose.ui.layout.ContentScale
32 | import androidx.compose.ui.platform.LocalHapticFeedback
33 | import androidx.compose.ui.platform.LocalView
34 | import androidx.compose.ui.res.painterResource
35 | import androidx.compose.ui.text.style.TextAlign
36 | import androidx.compose.ui.tooling.preview.Preview
37 | import androidx.compose.ui.unit.dp
38 | import app.suhasdissa.memerize.R
39 | import coil.compose.AsyncImage
40 |
41 | @OptIn(ExperimentalFoundationApi::class)
42 | @Composable
43 | fun HighlightCard(
44 | onClick: () -> Unit,
45 | name: String,
46 | thumbnail_url: String? = null,
47 | highlighted: Boolean = false,
48 | onLongClick: () -> Unit = {}
49 | ) {
50 | val view = LocalView.current
51 | val haptic = LocalHapticFeedback.current
52 | ElevatedCard(
53 | modifier = Modifier
54 | .fillMaxWidth()
55 | .height(128.dp)
56 | .padding(8.dp),
57 | colors = if (highlighted) {
58 | CardDefaults.elevatedCardColors(
59 | containerColor = MaterialTheme.colorScheme.primary,
60 | contentColor = MaterialTheme.colorScheme.onPrimary
61 | )
62 | } else {
63 | CardDefaults.elevatedCardColors()
64 | }
65 | ) {
66 | Row(
67 | modifier = Modifier
68 | .combinedClickable(
69 | onClick = {
70 | view.playSoundEffect(SoundEffectConstants.CLICK)
71 | onClick.invoke()
72 | },
73 | onLongClick = {
74 | haptic.performHapticFeedback(HapticFeedbackType.LongPress)
75 | onLongClick.invoke()
76 | }
77 | )
78 | .fillMaxSize()
79 | .padding(16.dp),
80 | verticalAlignment = Alignment.CenterVertically,
81 | horizontalArrangement = Arrangement.SpaceEvenly
82 | ) {
83 | if (thumbnail_url != null) {
84 | AsyncImage(
85 | model = thumbnail_url,
86 | contentDescription = null,
87 | modifier = Modifier
88 | .size(90.dp)
89 | .aspectRatio(1f)
90 | .clip(CircleShape),
91 | contentScale = ContentScale.Crop,
92 | error = painterResource(R.drawable.reddit_placeholder),
93 | placeholder = painterResource(R.drawable.reddit_placeholder)
94 | )
95 | }
96 | Text(
97 | text = name,
98 | style = MaterialTheme.typography.titleMedium,
99 | modifier = Modifier.weight(1f),
100 | textAlign = TextAlign.Center
101 | )
102 | }
103 | }
104 | }
105 |
106 | @Preview()
107 | @Composable
108 | fun HighlightCardPreview() {
109 | HighlightCard(onClick = {}, name = "Preview")
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/ImageCard.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.clickable
12 | import androidx.compose.foundation.layout.Column
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.fillMaxWidth
15 | import androidx.compose.foundation.layout.height
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.material3.ElevatedCard
18 | import androidx.compose.material3.MaterialTheme
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.layout.ContentScale
24 | import androidx.compose.ui.platform.LocalView
25 | import androidx.compose.ui.res.painterResource
26 | import androidx.compose.ui.res.stringResource
27 | import androidx.compose.ui.text.style.TextAlign
28 | import androidx.compose.ui.unit.dp
29 | import app.suhasdissa.memerize.R
30 | import coil.compose.AsyncImage
31 |
32 | @Composable
33 | fun ImageCard(
34 | clickAction: () -> Unit,
35 | photoUrl: String,
36 | title: String
37 | ) {
38 | val view = LocalView.current
39 | ElevatedCard(
40 | modifier = Modifier
41 | .padding(8.dp)
42 | .fillMaxWidth()
43 | .clickable {
44 | view.playSoundEffect(SoundEffectConstants.CLICK)
45 | clickAction()
46 | }
47 | ) {
48 | Column(
49 | modifier = Modifier.fillMaxWidth(),
50 | horizontalAlignment = Alignment.CenterHorizontally
51 | ) {
52 | Text(
53 | text = title,
54 | modifier = Modifier.padding(horizontal = 8.dp),
55 | style = MaterialTheme.typography.titleLarge,
56 | color = MaterialTheme.colorScheme.primary,
57 | textAlign = TextAlign.Center
58 | )
59 | Spacer(modifier = Modifier.height(20.dp))
60 | AsyncImage(
61 | model = photoUrl,
62 | contentDescription = stringResource(R.string.meme_photo),
63 | contentScale = ContentScale.FillWidth,
64 | modifier = Modifier.fillMaxWidth(),
65 | error = painterResource(R.drawable.ic_broken_image),
66 | placeholder = painterResource(R.drawable.loading_img)
67 | )
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/LoadingScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.material3.CircularProgressIndicator
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 |
17 | @Composable
18 | fun LoadingScreen(modifier: Modifier = Modifier) {
19 | Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
20 | CircularProgressIndicator()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/MemeCard.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 12/20/22, 8:56 AM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.runtime.Composable
11 |
12 | @Composable
13 | fun MemeCard(
14 | onClickMeme: () -> Unit,
15 | photo: String,
16 | title: String
17 | ) {
18 | ImageCard({ onClickMeme.invoke() }, photo, title)
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/MemeGrid.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 12:09 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.PaddingValues
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.foundation.layout.fillMaxWidth
15 | import androidx.compose.foundation.layout.padding
16 | import androidx.compose.foundation.lazy.grid.GridCells
17 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
18 | import androidx.compose.foundation.lazy.grid.itemsIndexed
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Alignment
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.unit.dp
26 | import app.suhasdissa.memerize.R
27 | import app.suhasdissa.memerize.backend.database.entity.Meme
28 |
29 | @Composable
30 | fun MemeGrid(
31 | memes: List,
32 | onClickCard: (id: Int) -> Unit
33 | ) {
34 | Column(
35 | modifier = Modifier
36 | .fillMaxSize()
37 | .padding(horizontal = 8.dp),
38 | horizontalAlignment = Alignment.CenterHorizontally
39 | ) {
40 | if (memes.isNotEmpty()) {
41 | LazyVerticalGrid(
42 | columns = GridCells.Adaptive(375.dp),
43 | modifier = Modifier.fillMaxWidth(),
44 | contentPadding = PaddingValues(8.dp)
45 | ) {
46 | itemsIndexed(items = memes) { index, meme ->
47 | if (meme.isVideo) {
48 | VideoCard(onClickVideo = {
49 | onClickCard.invoke(index)
50 | }, meme.title, meme.preview, Modifier)
51 | } else {
52 | MemeCard(onClickMeme = {
53 | onClickCard.invoke(index)
54 | }, meme.url, meme.title)
55 | }
56 | }
57 | }
58 | } else {
59 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
60 | Text(
61 | stringResource(R.string.no_memes_here),
62 | style = MaterialTheme.typography.bodyLarge,
63 | color = MaterialTheme.colorScheme.tertiary
64 | )
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/NavDrawerContent.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 12:15 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.Image
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.fillMaxWidth
15 | import androidx.compose.foundation.layout.height
16 | import androidx.compose.foundation.layout.size
17 | import androidx.compose.foundation.layout.width
18 | import androidx.compose.material.icons.Icons
19 | import androidx.compose.material.icons.filled.Group
20 | import androidx.compose.material.icons.filled.Home
21 | import androidx.compose.material.icons.filled.Settings
22 | import androidx.compose.material3.Icon
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.ModalDrawerSheet
25 | import androidx.compose.material3.NavigationDrawerItem
26 | import androidx.compose.material3.Text
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.platform.LocalView
31 | import androidx.compose.ui.res.painterResource
32 | import androidx.compose.ui.res.stringResource
33 | import androidx.compose.ui.unit.dp
34 | import app.suhasdissa.memerize.Destination
35 | import app.suhasdissa.memerize.R
36 |
37 | @Composable
38 | fun NavDrawerContent(
39 | currentDestination: Destination,
40 | onDestinationSelected: (Destination) -> Unit
41 | ) {
42 | val view = LocalView.current
43 | ModalDrawerSheet(modifier = Modifier.width(250.dp)) {
44 | Spacer(Modifier.height(48.dp))
45 | Row(
46 | modifier = Modifier
47 | .fillMaxWidth(),
48 | verticalAlignment = Alignment.CenterVertically
49 | ) {
50 | Image(
51 | modifier = Modifier.size(96.dp),
52 | painter = painterResource(id = R.drawable.ic_launcher_foreground),
53 | contentDescription = null
54 | )
55 | Text(
56 | stringResource(id = R.string.app_name),
57 | color = MaterialTheme.colorScheme.primary,
58 | style = MaterialTheme.typography.headlineMedium
59 | )
60 | }
61 | Spacer(Modifier.height(16.dp))
62 | NavigationDrawerItem(
63 | icon = {
64 | Icon(
65 | imageVector = Icons.Default.Home,
66 | contentDescription = null
67 | )
68 | },
69 | label = { Text(text = stringResource(id = R.string.home)) },
70 | selected = currentDestination == Destination.Home,
71 | onClick = {
72 | view.playSoundEffect(SoundEffectConstants.CLICK)
73 | onDestinationSelected(Destination.Home)
74 | }
75 | )
76 | Spacer(Modifier.height(16.dp))
77 | NavigationDrawerItem(
78 | icon = {
79 | Icon(
80 | painter = painterResource(id = R.drawable.reddit_placeholder),
81 | contentDescription = null
82 | )
83 | },
84 | label = { Text(text = stringResource(id = R.string.subreddits)) },
85 | selected = currentDestination == Destination.Subreddits,
86 | onClick = {
87 | view.playSoundEffect(SoundEffectConstants.CLICK)
88 | onDestinationSelected(Destination.Subreddits)
89 | }
90 | )
91 | Spacer(Modifier.height(16.dp))
92 | NavigationDrawerItem(
93 | icon = {
94 | Icon(
95 | imageVector = Icons.Default.Group,
96 | contentDescription = null
97 | )
98 | },
99 | label = { Text(text = stringResource(id = R.string.lemmy_communities)) },
100 | selected = currentDestination == Destination.Communities,
101 | onClick = {
102 | view.playSoundEffect(SoundEffectConstants.CLICK)
103 | onDestinationSelected(Destination.Communities)
104 | }
105 | )
106 | Spacer(Modifier.height(16.dp))
107 | NavigationDrawerItem(
108 | icon = {
109 | Icon(
110 | imageVector = Icons.Default.Settings,
111 | contentDescription = null
112 | )
113 | },
114 | label = { Text(text = stringResource(id = R.string.settings_title)) },
115 | selected = false,
116 | onClick = {
117 | view.playSoundEffect(SoundEffectConstants.CLICK)
118 | onDestinationSelected(Destination.Settings)
119 | }
120 | )
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/RetryScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 5/10/23, 8:57 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.layout.Box
12 | import androidx.compose.foundation.layout.Column
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.material3.Button
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.platform.LocalView
21 |
22 | @Composable
23 | fun RetryScreen(
24 | message: String,
25 | btnText: String,
26 | modifier: Modifier = Modifier,
27 | onRetry: () -> Unit
28 | ) {
29 | val view = LocalView.current
30 | Box(
31 | contentAlignment = Alignment.Center,
32 | modifier = modifier.fillMaxSize()
33 | ) {
34 | Column(horizontalAlignment = Alignment.CenterHorizontally) {
35 | Text(
36 | message,
37 | color = MaterialTheme.colorScheme.error,
38 | style = MaterialTheme.typography.bodyLarge
39 | )
40 | Button(onClick = {
41 | view.playSoundEffect(SoundEffectConstants.CLICK)
42 | onRetry()
43 | }) {
44 | Text(btnText)
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/SettingItem.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.ui.components
2 |
3 | import android.view.SoundEffectConstants
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Surface
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.vector.ImageVector
18 | import androidx.compose.ui.platform.LocalView
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 |
22 | @Composable
23 | fun SettingItem(title: String, description: String, icon: ImageVector?, onClick: () -> Unit) {
24 | val view = LocalView.current
25 | Surface(
26 | modifier = Modifier.clickable {
27 | view.playSoundEffect(SoundEffectConstants.CLICK)
28 | onClick()
29 | }
30 | ) {
31 | Row(
32 | modifier = Modifier
33 | .fillMaxWidth()
34 | .padding(16.dp, 24.dp),
35 | verticalAlignment = Alignment.CenterVertically
36 | ) {
37 | icon?.let {
38 | Icon(
39 | imageVector = icon,
40 | contentDescription = null,
41 | modifier = Modifier
42 | .padding(start = 8.dp, end = 16.dp)
43 | .size(24.dp),
44 | tint = MaterialTheme.colorScheme.secondary
45 | )
46 | }
47 | Column(
48 | modifier = Modifier
49 | .weight(1f)
50 | .padding(start = if (icon == null) 16.dp else 0.dp)
51 | ) {
52 | Text(
53 | text = title,
54 | maxLines = 1,
55 | style = MaterialTheme.typography.titleLarge,
56 | color = MaterialTheme.colorScheme.onSurface
57 | )
58 | Text(
59 | text = description,
60 | color = MaterialTheme.colorScheme.onSurfaceVariant,
61 | maxLines = 1,
62 | style = MaterialTheme.typography.bodyMedium
63 | )
64 | }
65 | }
66 | }
67 | }
68 |
69 | @Preview
70 | @Composable
71 | fun SettingItemPreview() {
72 | SettingItem(title = "Setting Item", description = "Description", onClick = {}, icon = null)
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/SortBottomSheet.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/14/23, 1:10 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.layout.Arrangement
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.fillMaxWidth
14 | import androidx.compose.foundation.layout.padding
15 | import androidx.compose.material3.ExperimentalMaterial3Api
16 | import androidx.compose.material3.FilterChip
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.material3.ModalBottomSheet
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.remember
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.res.stringResource
24 | import androidx.compose.ui.unit.dp
25 | import app.suhasdissa.memerize.R
26 | import app.suhasdissa.memerize.backend.model.Sort
27 |
28 | @OptIn(ExperimentalMaterial3Api::class)
29 | @Composable
30 | fun SortBottomSheet(currentSort: Sort, onSelect: (Sort) -> Unit, onDismissRequest: () -> Unit) {
31 | val options = remember { listOf(Sort.Hot, Sort.New, Sort.Rising) }
32 | val topOptions = remember { listOf(Sort.Top.Today, Sort.Top.Week, Sort.Top.Month) }
33 |
34 | ModalBottomSheet(onDismissRequest) {
35 | Column(
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .padding(16.dp)
39 | ) {
40 | Text(
41 | text = stringResource(R.string.sort_by),
42 | style = MaterialTheme.typography.titleLarge
43 | )
44 | Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
45 | options.forEach {
46 | SortFilterChip(selected = currentSort == it, sort = it, onSelect)
47 | }
48 | }
49 | Text(
50 | text = stringResource(id = R.string.top),
51 | style = MaterialTheme.typography.titleMedium
52 | )
53 | Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
54 | topOptions.forEach {
55 | SortFilterChip(selected = currentSort == it, sort = it, onSelect)
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | @OptIn(ExperimentalMaterial3Api::class)
63 | @Composable
64 | fun SortFilterChip(selected: Boolean, sort: Sort, onSelect: (Sort) -> Unit) {
65 | FilterChip(
66 | selected,
67 | onClick = { onSelect(sort) },
68 | label = { Text(stringResource(id = sort.name)) }
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/SubredditCard.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/30/23, 1:22 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import android.view.SoundEffectConstants
11 | import androidx.compose.foundation.clickable
12 | import androidx.compose.foundation.layout.Column
13 | import androidx.compose.foundation.layout.Row
14 | import androidx.compose.foundation.layout.aspectRatio
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.shape.CircleShape
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Alignment
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.draw.clip
25 | import androidx.compose.ui.layout.ContentScale
26 | import androidx.compose.ui.platform.LocalView
27 | import androidx.compose.ui.text.style.TextOverflow
28 | import androidx.compose.ui.unit.dp
29 | import coil.compose.AsyncImage
30 |
31 | @Composable
32 | fun SubredditCardCompact(
33 | thumbnail: String?,
34 | title: String,
35 | onClickCard: () -> Unit,
36 | TrailingContent: @Composable () -> Unit,
37 | modifier: Modifier = Modifier
38 | ) {
39 | val view = LocalView.current
40 | Row(
41 | modifier
42 | .fillMaxWidth()
43 | .clickable {
44 | view.playSoundEffect(SoundEffectConstants.CLICK)
45 | onClickCard()
46 | },
47 | verticalAlignment = Alignment.CenterVertically
48 | ) {
49 | AsyncImage(
50 | modifier = Modifier
51 | .size(64.dp)
52 | .padding(8.dp)
53 | .aspectRatio(1f)
54 | .clip(CircleShape),
55 | model = thumbnail,
56 | contentDescription = null,
57 | contentScale = ContentScale.Crop
58 | )
59 | Column(
60 | Modifier
61 | .weight(1f)
62 | .padding(8.dp)
63 | ) {
64 | Text(
65 | title,
66 | style = MaterialTheme.typography.titleMedium,
67 | maxLines = 1,
68 | overflow = TextOverflow.Ellipsis
69 | )
70 | }
71 |
72 | Column(
73 | Modifier
74 | .padding(8.dp)
75 | ) {
76 | TrailingContent()
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/components/VideoCard.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 12/20/22, 8:56 AM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.components
9 |
10 | import androidx.compose.foundation.clickable
11 | import androidx.compose.foundation.layout.Box
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.shape.CircleShape
15 | import androidx.compose.material.icons.Icons
16 | import androidx.compose.material.icons.filled.PlayCircle
17 | import androidx.compose.material3.Card
18 | import androidx.compose.material3.Icon
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.tooling.preview.Preview
24 | import androidx.compose.ui.unit.dp
25 |
26 | @Composable
27 | fun VideoCard(
28 | onClickVideo: () -> Unit,
29 | title: String,
30 | preview: String,
31 | modifier: Modifier = Modifier
32 | ) {
33 | Box(
34 | contentAlignment = Alignment.Center,
35 | modifier = modifier
36 | .fillMaxSize()
37 | ) {
38 | ImageCard({ onClickVideo.invoke() }, preview, title)
39 | Card(modifier.clickable(onClick = { onClickVideo.invoke() }), shape = CircleShape) {
40 | Icon(
41 | modifier = modifier.size(64.dp),
42 | imageVector = Icons.Default.PlayCircle,
43 | contentDescription = stringResource(
44 | app.suhasdissa.memerize.R.string.play_video_hint
45 | )
46 | )
47 | }
48 | }
49 | }
50 |
51 | @Preview
52 | @Composable
53 | fun VideoCardPreview() {
54 | VideoCard(onClickVideo = {}, title = "Preview", preview = "")
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/screens/primary/LemmyMemeScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 11:03 AM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.screens.primary
9 |
10 | import androidx.activity.ComponentActivity
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.aspectRatio
15 | import androidx.compose.foundation.layout.fillMaxSize
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.foundation.shape.CircleShape
20 | import androidx.compose.material.icons.Icons
21 | import androidx.compose.material.icons.filled.FilterList
22 | import androidx.compose.material3.ExperimentalMaterial3Api
23 | import androidx.compose.material3.Icon
24 | import androidx.compose.material3.IconButton
25 | import androidx.compose.material3.MaterialTheme
26 | import androidx.compose.material3.Scaffold
27 | import androidx.compose.material3.Text
28 | import androidx.compose.material3.TopAppBar
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.runtime.getValue
31 | import androidx.compose.runtime.mutableStateOf
32 | import androidx.compose.runtime.remember
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Alignment
35 | import androidx.compose.ui.Modifier
36 | import androidx.compose.ui.draw.clip
37 | import androidx.compose.ui.layout.ContentScale
38 | import androidx.compose.ui.platform.LocalContext
39 | import androidx.compose.ui.res.stringResource
40 | import androidx.compose.ui.unit.dp
41 | import androidx.lifecycle.viewmodel.compose.viewModel
42 | import app.suhasdissa.memerize.R
43 | import app.suhasdissa.memerize.backend.viewmodels.LemmyViewModel
44 | import app.suhasdissa.memerize.backend.viewmodels.state.MemeUiState
45 | import app.suhasdissa.memerize.ui.components.LoadingScreen
46 | import app.suhasdissa.memerize.ui.components.MemeGrid
47 | import app.suhasdissa.memerize.ui.components.RetryScreen
48 | import app.suhasdissa.memerize.ui.components.SortBottomSheet
49 | import coil.compose.AsyncImage
50 | import coil.request.ImageRequest
51 |
52 | @OptIn(ExperimentalMaterial3Api::class)
53 | @Composable
54 | fun LemmyMemeScreen(
55 | modifier: Modifier = Modifier,
56 | lemmyViewModel: LemmyViewModel = viewModel(
57 | LocalContext.current as ComponentActivity,
58 | factory = LemmyViewModel.Factory
59 | ),
60 | onClickCard: (Int) -> Unit
61 | ) {
62 | var showFilterButtons by remember { mutableStateOf(false) }
63 | Scaffold(
64 | modifier = Modifier.fillMaxSize(),
65 | topBar = {
66 | TopAppBar(title = {
67 | Row(verticalAlignment = Alignment.CenterVertically) {
68 | lemmyViewModel.currentCommunity?.let {
69 | AsyncImage(
70 | modifier = Modifier
71 | .size(36.dp)
72 | .aspectRatio(1f)
73 | .clip(CircleShape),
74 | model = ImageRequest.Builder(context = LocalContext.current)
75 | .data(it.iconUrl).crossfade(true).build(),
76 | contentDescription = null,
77 | contentScale = ContentScale.Crop
78 | )
79 | }
80 | Spacer(Modifier.width(8.dp))
81 | Column {
82 | Text(
83 | stringResource(R.string.lemmy)
84 | )
85 | lemmyViewModel.currentCommunity?.let {
86 | Text(it.name, style = MaterialTheme.typography.bodySmall)
87 | }
88 | }
89 | }
90 | }, actions = {
91 | lemmyViewModel.currentCommunity?.let {
92 | IconButton(onClick = { showFilterButtons = !showFilterButtons }) {
93 | Icon(
94 | imageVector = Icons.Default.FilterList,
95 | contentDescription = stringResource(R.string.filter_by_time)
96 | )
97 | }
98 | }
99 | })
100 | }
101 | ) { paddingValues ->
102 | Column(Modifier.padding(paddingValues)) {
103 | when (val memeDataState = lemmyViewModel.memeUiState) {
104 | is MemeUiState.Loading -> LoadingScreen(modifier)
105 | is MemeUiState.Error -> RetryScreen(
106 | stringResource(R.string.error_loading_online_memes),
107 | stringResource(R.string.show_offline_memes),
108 | modifier,
109 | onRetry = { lemmyViewModel.getLocalMemes() }
110 | )
111 |
112 | is MemeUiState.Success -> MemeGrid(
113 | memeDataState.memes,
114 | onClickCard
115 | )
116 | }
117 | }
118 | }
119 | if (showFilterButtons) {
120 | SortBottomSheet(currentSort = lemmyViewModel.currentSortTime, onSelect = {
121 | showFilterButtons = false
122 | lemmyViewModel.getMemePhotos(sort = it)
123 | }, onDismissRequest = { showFilterButtons = false })
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/screens/primary/RedditMemeScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.screens.primary
9 |
10 | import androidx.activity.ComponentActivity
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.aspectRatio
15 | import androidx.compose.foundation.layout.fillMaxSize
16 | import androidx.compose.foundation.layout.padding
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.foundation.shape.CircleShape
20 | import androidx.compose.material.icons.Icons
21 | import androidx.compose.material.icons.filled.FilterList
22 | import androidx.compose.material3.ExperimentalMaterial3Api
23 | import androidx.compose.material3.Icon
24 | import androidx.compose.material3.IconButton
25 | import androidx.compose.material3.MaterialTheme
26 | import androidx.compose.material3.Scaffold
27 | import androidx.compose.material3.Text
28 | import androidx.compose.material3.TopAppBar
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.runtime.getValue
31 | import androidx.compose.runtime.mutableStateOf
32 | import androidx.compose.runtime.remember
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Alignment
35 | import androidx.compose.ui.Modifier
36 | import androidx.compose.ui.draw.clip
37 | import androidx.compose.ui.layout.ContentScale
38 | import androidx.compose.ui.platform.LocalContext
39 | import androidx.compose.ui.res.stringResource
40 | import androidx.compose.ui.unit.dp
41 | import androidx.lifecycle.viewmodel.compose.viewModel
42 | import app.suhasdissa.memerize.R
43 | import app.suhasdissa.memerize.backend.viewmodels.RedditViewModel
44 | import app.suhasdissa.memerize.backend.viewmodels.state.MemeUiState
45 | import app.suhasdissa.memerize.ui.components.LoadingScreen
46 | import app.suhasdissa.memerize.ui.components.MemeGrid
47 | import app.suhasdissa.memerize.ui.components.RetryScreen
48 | import app.suhasdissa.memerize.ui.components.SortBottomSheet
49 | import coil.compose.AsyncImage
50 | import coil.request.ImageRequest
51 |
52 | @OptIn(ExperimentalMaterial3Api::class)
53 | @Composable
54 | fun RedditMemeScreen(
55 | modifier: Modifier = Modifier,
56 | redditViewModel: RedditViewModel = viewModel(
57 | LocalContext.current as ComponentActivity,
58 | factory = RedditViewModel.Factory
59 | ),
60 | onClickCard: (Int) -> Unit
61 | ) {
62 | var showFilterButtons by remember { mutableStateOf(false) }
63 | Scaffold(
64 | modifier = Modifier.fillMaxSize(),
65 | topBar = {
66 | TopAppBar(title = {
67 | Row(verticalAlignment = Alignment.CenterVertically) {
68 | redditViewModel.currentSubreddit?.let {
69 | AsyncImage(
70 | modifier = Modifier
71 | .size(36.dp)
72 | .aspectRatio(1f)
73 | .clip(CircleShape),
74 | model = ImageRequest.Builder(context = LocalContext.current)
75 | .data(it.iconUrl).crossfade(true).build(),
76 | contentDescription = null,
77 | contentScale = ContentScale.Crop
78 | )
79 | }
80 | Spacer(Modifier.width(8.dp))
81 | Column {
82 | Text(
83 | stringResource(R.string.reddit)
84 | )
85 | redditViewModel.currentSubreddit?.let {
86 | Text(it.name, style = MaterialTheme.typography.bodySmall)
87 | }
88 | }
89 | }
90 | }, actions = {
91 | redditViewModel.currentSubreddit?.let {
92 | IconButton(onClick = { showFilterButtons = !showFilterButtons }) {
93 | Icon(
94 | imageVector = Icons.Default.FilterList,
95 | contentDescription = stringResource(R.string.filter_by_time)
96 | )
97 | }
98 | }
99 | })
100 | }
101 | ) { paddingValues ->
102 | Column(Modifier.padding(paddingValues)) {
103 | when (val memeDataState = redditViewModel.memeUiState) {
104 | is MemeUiState.Loading -> LoadingScreen(modifier)
105 | is MemeUiState.Error -> RetryScreen(
106 | stringResource(R.string.error_loading_online_memes),
107 | stringResource(R.string.show_offline_memes),
108 | modifier,
109 | onRetry = { redditViewModel.getLocalMemes() }
110 | )
111 |
112 | is MemeUiState.Success -> MemeGrid(
113 | memeDataState.memes,
114 | onClickCard
115 | )
116 | }
117 | }
118 | }
119 | if (showFilterButtons) {
120 | SortBottomSheet(currentSort = redditViewModel.currentSortTime, onSelect = {
121 | showFilterButtons = false
122 | redditViewModel.getMemePhotos(sort = it)
123 | }, onDismissRequest = { showFilterButtons = false })
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/screens/secondary/MemeFeedView.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/15/23, 12:48 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.screens.secondary
9 |
10 | import androidx.activity.ComponentActivity
11 | import androidx.compose.foundation.ExperimentalFoundationApi
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.foundation.pager.VerticalPager
14 | import androidx.compose.foundation.pager.rememberPagerState
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.platform.LocalContext
18 | import androidx.lifecycle.viewmodel.compose.viewModel
19 | import app.suhasdissa.memerize.backend.database.entity.Meme
20 | import app.suhasdissa.memerize.backend.viewmodels.LemmyViewModel
21 | import app.suhasdissa.memerize.backend.viewmodels.RedditViewModel
22 | import app.suhasdissa.memerize.backend.viewmodels.state.MemeUiState
23 |
24 | @Composable
25 | fun RedditMemeFeed(
26 | initialPage: Int,
27 | redditViewModel: RedditViewModel = viewModel(
28 | LocalContext.current as ComponentActivity,
29 | factory = RedditViewModel.Factory
30 | )
31 | ) {
32 | when (val state = redditViewModel.memeUiState) {
33 | is MemeUiState.Success -> MemeFeedView(initialPage, memes = state.memes)
34 | else -> {}
35 | }
36 | }
37 |
38 | @Composable
39 | fun LemmyMemeFeed(
40 | initialPage: Int,
41 | lemmyViewModel: LemmyViewModel = viewModel(
42 | LocalContext.current as ComponentActivity,
43 | factory = LemmyViewModel.Factory
44 | )
45 | ) {
46 | when (val state = lemmyViewModel.memeUiState) {
47 | is MemeUiState.Success -> MemeFeedView(initialPage, memes = state.memes)
48 | else -> {}
49 | }
50 | }
51 |
52 | @OptIn(ExperimentalFoundationApi::class)
53 | @Composable
54 | private fun MemeFeedView(initialPage: Int, memes: List) {
55 | val pagerState = rememberPagerState(
56 | initialPage = initialPage,
57 | initialPageOffsetFraction = 0f
58 | ) {
59 | memes.size
60 | }
61 | VerticalPager(modifier = Modifier.fillMaxSize(), state = pagerState) {
62 | with(memes[it]) {
63 | if (isVideo) {
64 | VideoView(this, playWhenReady = (it == pagerState.currentPage))
65 | } else {
66 | PhotoView(this)
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/screens/settings/AboutScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 7/9/23, 3:39 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.screens.settings
9 |
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.lazy.LazyColumn
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.filled.ContactSupport
15 | import androidx.compose.material.icons.filled.Description
16 | import androidx.compose.material.icons.filled.Info
17 | import androidx.compose.material.icons.filled.NewReleases
18 | import androidx.compose.material3.CenterAlignedTopAppBar
19 | import androidx.compose.material3.ExperimentalMaterial3Api
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Scaffold
22 | import androidx.compose.material3.Text
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.platform.LocalContext
26 | import androidx.compose.ui.res.stringResource
27 | import androidx.compose.ui.tooling.preview.Preview
28 | import androidx.lifecycle.viewmodel.compose.viewModel
29 | import app.suhasdissa.memerize.R
30 | import app.suhasdissa.memerize.backend.viewmodels.CheckUpdateViewModel
31 | import app.suhasdissa.memerize.ui.components.SettingItem
32 | import app.suhasdissa.memerize.utils.openBrowser
33 |
34 | @OptIn(ExperimentalMaterial3Api::class)
35 | @Composable
36 | fun AboutScreen(
37 | modifier: Modifier = Modifier,
38 | updateViewModel: CheckUpdateViewModel = viewModel()
39 | ) {
40 | val context = LocalContext.current
41 | val githubRepo = "https://github.com/SuhasDissa/MemerizeApp"
42 |
43 | Scaffold(
44 | modifier = Modifier.fillMaxSize(),
45 | topBar = {
46 | CenterAlignedTopAppBar(title = {
47 | Text(
48 | stringResource(R.string.about),
49 | color = MaterialTheme.colorScheme.primary
50 | )
51 | })
52 | }
53 | ) { paddingValues ->
54 | LazyColumn(
55 | modifier
56 | .fillMaxSize()
57 | .padding(paddingValues)
58 | ) {
59 | item {
60 | SettingItem(
61 | title = stringResource(R.string.readme),
62 | description = stringResource(R.string.check_repo_and_readme),
63 | onClick = { openBrowser(context, githubRepo) },
64 | icon = Icons.Default.Description
65 | )
66 | }
67 | item {
68 | SettingItem(
69 | title = stringResource(R.string.latest_release),
70 | description = "${updateViewModel.latestVersion}",
71 | onClick = {
72 | openBrowser(
73 | context,
74 | "$githubRepo/releases/latest"
75 | )
76 | },
77 | icon = Icons.Default.NewReleases
78 | )
79 | }
80 | item {
81 | SettingItem(
82 | title = stringResource(R.string.github_issue),
83 | description = stringResource(R.string.github_issue_description),
84 | onClick = {
85 | openBrowser(
86 | context,
87 | "$githubRepo/issues"
88 | )
89 | },
90 | icon = Icons.Default.ContactSupport
91 | )
92 | }
93 | item {
94 | SettingItem(
95 | title = stringResource(R.string.current_version),
96 | description = "${updateViewModel.currentVersion}",
97 | onClick = {},
98 | icon = Icons.Default.Info
99 | )
100 | }
101 | }
102 | }
103 | }
104 |
105 | @Composable
106 | @Preview
107 | fun AboutScreenPreview() {
108 | AboutScreen()
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/screens/settings/SettingsScreen.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.screens.settings
9 |
10 | import android.content.Intent
11 | import android.net.Uri
12 | import android.util.Log
13 | import androidx.activity.compose.rememberLauncherForActivityResult
14 | import androidx.activity.result.contract.ActivityResultContracts
15 | import androidx.compose.foundation.layout.Arrangement
16 | import androidx.compose.foundation.layout.fillMaxSize
17 | import androidx.compose.foundation.layout.padding
18 | import androidx.compose.foundation.lazy.LazyColumn
19 | import androidx.compose.material.icons.Icons
20 | import androidx.compose.material.icons.filled.Menu
21 | import androidx.compose.material.icons.filled.Storage
22 | import androidx.compose.material.icons.outlined.Folder
23 | import androidx.compose.material.icons.outlined.Info
24 | import androidx.compose.material3.CenterAlignedTopAppBar
25 | import androidx.compose.material3.ExperimentalMaterial3Api
26 | import androidx.compose.material3.Icon
27 | import androidx.compose.material3.IconButton
28 | import androidx.compose.material3.MaterialTheme
29 | import androidx.compose.material3.Scaffold
30 | import androidx.compose.material3.Text
31 | import androidx.compose.runtime.Composable
32 | import androidx.compose.runtime.getValue
33 | import androidx.compose.runtime.mutableStateOf
34 | import androidx.compose.runtime.remember
35 | import androidx.compose.runtime.setValue
36 | import androidx.compose.ui.Modifier
37 | import androidx.compose.ui.platform.LocalContext
38 | import androidx.compose.ui.res.stringResource
39 | import androidx.compose.ui.unit.dp
40 | import androidx.core.content.edit
41 | import app.suhasdissa.memerize.R
42 | import app.suhasdissa.memerize.ui.components.CacheSizeDialog
43 | import app.suhasdissa.memerize.ui.components.SettingItem
44 | import app.suhasdissa.memerize.utils.SaveDirectoryKey
45 | import app.suhasdissa.memerize.utils.preferences
46 |
47 | @OptIn(ExperimentalMaterial3Api::class)
48 | @Composable
49 | fun SettingsScreen(
50 | onDrawerOpen: () -> Unit,
51 | onAboutClick: () -> Unit
52 | ) {
53 | val context = LocalContext.current
54 | val directoryPicker =
55 | rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
56 | it ?: return@rememberLauncherForActivityResult
57 | context.contentResolver.takePersistableUriPermission(
58 | it,
59 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
60 | )
61 | Log.d("FIle path", it.toString())
62 | context.preferences.edit { putString(SaveDirectoryKey, it.toString()) }
63 | }
64 | var showImageCacheDialog by remember { mutableStateOf(false) }
65 | Scaffold(modifier = Modifier.fillMaxSize(), topBar = {
66 | CenterAlignedTopAppBar(navigationIcon = {
67 | IconButton(onClick = {
68 | onDrawerOpen.invoke()
69 | }) {
70 | Icon(
71 | imageVector = Icons.Default.Menu,
72 | contentDescription = stringResource(R.string.open_navigation_drawer)
73 | )
74 | }
75 | }, title = {
76 | Text(
77 | stringResource(R.string.settings),
78 | color = MaterialTheme.colorScheme.primary
79 | )
80 | })
81 | }) { paddingValues ->
82 | LazyColumn(
83 | Modifier.fillMaxSize().padding(paddingValues),
84 | verticalArrangement = Arrangement.spacedBy(24.dp)
85 | ) {
86 | item {
87 | SettingItem(
88 | title = stringResource(R.string.download_location),
89 | description = stringResource(R.string.select_meme_download_location),
90 | onClick = {
91 | val lastDir = context.preferences.getString(SaveDirectoryKey, null)
92 | .takeIf { !it.isNullOrBlank() }
93 | directoryPicker.launch(lastDir?.let { Uri.parse(it) })
94 | },
95 | icon = Icons.Outlined.Folder
96 | )
97 | }
98 | item {
99 | SettingItem(
100 | title = stringResource(R.string.image_cache_limit),
101 | description = stringResource(R.string.set_image_cache_limit),
102 | onClick = {
103 | showImageCacheDialog = true
104 | },
105 | icon = Icons.Default.Storage
106 | )
107 | }
108 | item {
109 | SettingItem(
110 | title = stringResource(R.string.about),
111 | description = stringResource(R.string.developer_contact),
112 | onClick = { onAboutClick() },
113 | icon = Icons.Outlined.Info
114 | )
115 | }
116 | }
117 | }
118 |
119 | if (showImageCacheDialog) {
120 | CacheSizeDialog {
121 | showImageCacheDialog = false
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.theme
9 |
10 | import androidx.compose.ui.graphics.Color
11 |
12 | val md_theme_light_primary = Color(0xFF984717)
13 | val md_theme_light_onPrimary = Color(0xFFFFFFFF)
14 | val md_theme_light_primaryContainer = Color(0xFFFFDBCB)
15 | val md_theme_light_onPrimaryContainer = Color(0xFF341100)
16 | val md_theme_light_secondary = Color(0xFF765849)
17 | val md_theme_light_onSecondary = Color(0xFFFFFFFF)
18 | val md_theme_light_secondaryContainer = Color(0xFFFFDBCB)
19 | val md_theme_light_onSecondaryContainer = Color(0xFF2C160B)
20 | val md_theme_light_tertiary = Color(0xFF655F31)
21 | val md_theme_light_onTertiary = Color(0xFFFFFFFF)
22 | val md_theme_light_tertiaryContainer = Color(0xFFECE4AA)
23 | val md_theme_light_onTertiaryContainer = Color(0xFF1F1C00)
24 | val md_theme_light_error = Color(0xFFBA1A1A)
25 | val md_theme_light_errorContainer = Color(0xFFFFDAD6)
26 | val md_theme_light_onError = Color(0xFFFFFFFF)
27 | val md_theme_light_onErrorContainer = Color(0xFF410002)
28 | val md_theme_light_background = Color(0xFFFFFBFF)
29 | val md_theme_light_onBackground = Color(0xFF201A18)
30 | val md_theme_light_surface = Color(0xFFFFFBFF)
31 | val md_theme_light_onSurface = Color(0xFF201A18)
32 | val md_theme_light_surfaceVariant = Color(0xFFF4DED5)
33 | val md_theme_light_onSurfaceVariant = Color(0xFF52443D)
34 | val md_theme_light_outline = Color(0xFF85736C)
35 | val md_theme_light_inverseOnSurface = Color(0xFFFBEEE9)
36 | val md_theme_light_inverseSurface = Color(0xFF362F2C)
37 | val md_theme_light_inversePrimary = Color(0xFFFFB692)
38 | val md_theme_light_surfaceTint = Color(0xFF984717)
39 | val md_theme_light_outlineVariant = Color(0xFFD7C2B9)
40 | val md_theme_light_scrim = Color(0xFF000000)
41 |
42 | val md_theme_dark_primary = Color(0xFFFFB692)
43 | val md_theme_dark_onPrimary = Color(0xFF562000)
44 | val md_theme_dark_primaryContainer = Color(0xFF793000)
45 | val md_theme_dark_onPrimaryContainer = Color(0xFFFFDBCB)
46 | val md_theme_dark_secondary = Color(0xFFE6BEAC)
47 | val md_theme_dark_onSecondary = Color(0xFF432A1E)
48 | val md_theme_dark_secondaryContainer = Color(0xFF5C4033)
49 | val md_theme_dark_onSecondaryContainer = Color(0xFFFFDBCB)
50 | val md_theme_dark_tertiary = Color(0xFFD0C890)
51 | val md_theme_dark_onTertiary = Color(0xFF353107)
52 | val md_theme_dark_tertiaryContainer = Color(0xFF4C481C)
53 | val md_theme_dark_onTertiaryContainer = Color(0xFFECE4AA)
54 | val md_theme_dark_error = Color(0xFFFFB4AB)
55 | val md_theme_dark_errorContainer = Color(0xFF93000A)
56 | val md_theme_dark_onError = Color(0xFF690005)
57 | val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
58 | val md_theme_dark_background = Color(0xFF201A18)
59 | val md_theme_dark_onBackground = Color(0xFFEDE0DB)
60 | val md_theme_dark_surface = Color(0xFF201A18)
61 | val md_theme_dark_onSurface = Color(0xFFEDE0DB)
62 | val md_theme_dark_surfaceVariant = Color(0xFF52443D)
63 | val md_theme_dark_onSurfaceVariant = Color(0xFFD7C2B9)
64 | val md_theme_dark_outline = Color(0xFFA08D85)
65 | val md_theme_dark_inverseOnSurface = Color(0xFF201A18)
66 | val md_theme_dark_inverseSurface = Color(0xFFEDE0DB)
67 | val md_theme_dark_inversePrimary = Color(0xFF984717)
68 | val md_theme_dark_surfaceTint = Color(0xFFFFB692)
69 | val md_theme_dark_outlineVariant = Color(0xFF52443D)
70 | val md_theme_dark_scrim = Color(0xFF000000)
71 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.theme
9 |
10 | import android.os.Build
11 | import androidx.compose.foundation.isSystemInDarkTheme
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.darkColorScheme
14 | import androidx.compose.material3.dynamicDarkColorScheme
15 | import androidx.compose.material3.dynamicLightColorScheme
16 | import androidx.compose.material3.lightColorScheme
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.platform.LocalContext
19 |
20 | private val LightColorScheme = lightColorScheme(
21 | primary = md_theme_light_primary,
22 | onPrimary = md_theme_light_onPrimary,
23 | primaryContainer = md_theme_light_primaryContainer,
24 | onPrimaryContainer = md_theme_light_onPrimaryContainer,
25 | secondary = md_theme_light_secondary,
26 | onSecondary = md_theme_light_onSecondary,
27 | secondaryContainer = md_theme_light_secondaryContainer,
28 | onSecondaryContainer = md_theme_light_onSecondaryContainer,
29 | tertiary = md_theme_light_tertiary,
30 | onTertiary = md_theme_light_onTertiary,
31 | tertiaryContainer = md_theme_light_tertiaryContainer,
32 | onTertiaryContainer = md_theme_light_onTertiaryContainer,
33 | error = md_theme_light_error,
34 | errorContainer = md_theme_light_errorContainer,
35 | onError = md_theme_light_onError,
36 | onErrorContainer = md_theme_light_onErrorContainer,
37 | background = md_theme_light_background,
38 | onBackground = md_theme_light_onBackground,
39 | surface = md_theme_light_surface,
40 | onSurface = md_theme_light_onSurface,
41 | surfaceVariant = md_theme_light_surfaceVariant,
42 | onSurfaceVariant = md_theme_light_onSurfaceVariant,
43 | outline = md_theme_light_outline,
44 | inverseOnSurface = md_theme_light_inverseOnSurface,
45 | inverseSurface = md_theme_light_inverseSurface,
46 | inversePrimary = md_theme_light_inversePrimary,
47 | surfaceTint = md_theme_light_surfaceTint,
48 | outlineVariant = md_theme_light_outlineVariant,
49 | scrim = md_theme_light_scrim
50 | )
51 |
52 | private val DarkColorScheme = darkColorScheme(
53 | primary = md_theme_dark_primary,
54 | onPrimary = md_theme_dark_onPrimary,
55 | primaryContainer = md_theme_dark_primaryContainer,
56 | onPrimaryContainer = md_theme_dark_onPrimaryContainer,
57 | secondary = md_theme_dark_secondary,
58 | onSecondary = md_theme_dark_onSecondary,
59 | secondaryContainer = md_theme_dark_secondaryContainer,
60 | onSecondaryContainer = md_theme_dark_onSecondaryContainer,
61 | tertiary = md_theme_dark_tertiary,
62 | onTertiary = md_theme_dark_onTertiary,
63 | tertiaryContainer = md_theme_dark_tertiaryContainer,
64 | onTertiaryContainer = md_theme_dark_onTertiaryContainer,
65 | error = md_theme_dark_error,
66 | errorContainer = md_theme_dark_errorContainer,
67 | onError = md_theme_dark_onError,
68 | onErrorContainer = md_theme_dark_onErrorContainer,
69 | background = md_theme_dark_background,
70 | onBackground = md_theme_dark_onBackground,
71 | surface = md_theme_dark_surface,
72 | onSurface = md_theme_dark_onSurface,
73 | surfaceVariant = md_theme_dark_surfaceVariant,
74 | onSurfaceVariant = md_theme_dark_onSurfaceVariant,
75 | outline = md_theme_dark_outline,
76 | inverseOnSurface = md_theme_dark_inverseOnSurface,
77 | inverseSurface = md_theme_dark_inverseSurface,
78 | inversePrimary = md_theme_dark_inversePrimary,
79 | surfaceTint = md_theme_dark_surfaceTint,
80 | outlineVariant = md_theme_dark_outlineVariant,
81 | scrim = md_theme_dark_scrim
82 | )
83 |
84 | @Composable
85 | fun MemerizeTheme(
86 | darkTheme: Boolean = isSystemInDarkTheme(),
87 | // Dynamic color is available on Android 12+
88 | dynamicColor: Boolean = true,
89 | content: @Composable () -> Unit
90 | ) {
91 | val colorScheme = when {
92 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
93 | val context = LocalContext.current
94 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
95 | }
96 |
97 | darkTheme -> DarkColorScheme
98 | else -> LightColorScheme
99 | }
100 |
101 | MaterialTheme(
102 | colorScheme = colorScheme,
103 | typography = Typography,
104 | content = content
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.ui.theme
9 |
10 | import androidx.compose.material3.Typography
11 | import androidx.compose.ui.text.TextStyle
12 | import androidx.compose.ui.text.font.FontFamily
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.unit.sp
15 |
16 | // Set of Material typography styles to start with
17 | val Typography = Typography(
18 | bodyLarge = TextStyle(
19 | fontFamily = FontFamily.Default,
20 | fontWeight = FontWeight.Normal,
21 | fontSize = 16.sp,
22 | lineHeight = 24.sp,
23 | letterSpacing = 0.5.sp
24 | )
25 | /* Other default text styles to override
26 | titleLarge = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Normal,
29 | fontSize = 22.sp,
30 | lineHeight = 28.sp,
31 | letterSpacing = 0.sp
32 | ),
33 | labelSmall = TextStyle(
34 | fontFamily = FontFamily.Default,
35 | fontWeight = FontWeight.Medium,
36 | fontSize = 11.sp,
37 | lineHeight = 16.sp,
38 | letterSpacing = 0.5.sp
39 | )
40 | */
41 | )
42 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/utils/CheckUpdate.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.utils
9 |
10 | import android.content.Context
11 | import android.content.pm.PackageManager
12 | import android.os.Build
13 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
14 | import kotlinx.serialization.SerialName
15 | import kotlinx.serialization.Serializable
16 | import kotlinx.serialization.json.Json
17 | import okhttp3.MediaType.Companion.toMediaType
18 | import retrofit2.Retrofit
19 | import retrofit2.http.GET
20 | import retrofit2.http.Headers
21 | import java.util.regex.Pattern
22 |
23 | object UpdateUtil {
24 | var currentVersion = 0f
25 |
26 | private suspend fun getLatestRelease(): LatestRelease? {
27 | return try {
28 | UpdateApi.retrofitService.getLatestRelease()
29 | } catch (e: Exception) {
30 | null
31 | }
32 | }
33 |
34 | suspend fun getLatestVersion(): Float? {
35 | return getLatestRelease()?.let {
36 | it.tagName.toVersion()
37 | }
38 | }
39 |
40 | fun getCurrentVersion(context: Context) {
41 | currentVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
42 | context.packageManager.getPackageInfo(
43 | context.packageName,
44 | PackageManager.PackageInfoFlags.of(0)
45 | ).versionName.toFloat()
46 | } else {
47 | context.packageManager.getPackageInfo(
48 | context.packageName,
49 | 0
50 | ).versionName.toFloat()
51 | }
52 | }
53 |
54 | private val pattern = Pattern.compile("""v(.+)""")
55 |
56 | private fun String?.toVersion(): Float = this?.run {
57 | val matcher = pattern.matcher(this)
58 | if (matcher.find()) {
59 | matcher.group(1)?.toFloat() ?: 0f
60 | } else {
61 | 0f
62 | }
63 | } ?: 0f
64 | }
65 |
66 | @Serializable
67 | data class LatestRelease(
68 | @SerialName("tag_name") val tagName: String? = null
69 | )
70 |
71 | private val jsonFormat = Json { ignoreUnknownKeys = true }
72 |
73 | private val retrofitVideo = Retrofit.Builder()
74 | .baseUrl("https://api.github.com/")
75 | .addConverterFactory(jsonFormat.asConverterFactory("application/json".toMediaType()))
76 | .build()
77 |
78 | interface UpdateApiService {
79 | @Headers(
80 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
81 | )
82 | @GET("repos/SuhasDissa/MemerizeApp/releases/latest")
83 | suspend fun getLatestRelease(): LatestRelease
84 | }
85 |
86 | object UpdateApi {
87 | val retrofitService: UpdateApiService by lazy {
88 | retrofitVideo.create(UpdateApiService::class.java)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/utils/OpenBrowser.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.utils
9 |
10 | import android.content.Context
11 | import android.content.Intent
12 | import android.net.Uri
13 |
14 | fun openBrowser(context: Context, url: String) {
15 | val viewIntent: Intent = Intent().apply {
16 | action = Intent.ACTION_VIEW
17 | data = Uri.parse(url)
18 | }
19 | context.startActivity(viewIntent)
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/utils/PlayerState.kt:
--------------------------------------------------------------------------------
1 | package app.suhasdissa.memerize.utils
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.produceState
6 | import androidx.media3.common.MediaItem
7 | import androidx.media3.common.Player
8 | import kotlinx.coroutines.delay
9 | import kotlinx.coroutines.isActive
10 | import kotlinx.coroutines.launch
11 |
12 | @Composable
13 | fun Player.positionAndDurationState(): State> {
14 | return produceState(
15 | initialValue = (currentPosition to duration.let { if (it < 0) null else it }),
16 | this
17 | ) {
18 | var isSeeking = false
19 | val listener = object : Player.Listener {
20 | override fun onPlaybackStateChanged(playbackState: Int) {
21 | if (playbackState == Player.STATE_READY) {
22 | isSeeking = false
23 | }
24 | }
25 |
26 | override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
27 | value = currentPosition to value.second
28 | }
29 |
30 | override fun onPositionDiscontinuity(
31 | oldPosition: Player.PositionInfo,
32 | newPosition: Player.PositionInfo,
33 | reason: Int
34 | ) {
35 | if (reason == Player.DISCONTINUITY_REASON_SEEK) {
36 | isSeeking = true
37 | value = currentPosition to duration.let { if (it < 0) null else it }
38 | }
39 | }
40 | }
41 | addListener(listener)
42 |
43 | val pollJob = launch {
44 | while (isActive) {
45 | if (!isSeeking) {
46 | value = currentPosition to duration.let { if (it < 0) null else it }
47 | }
48 | delay(500)
49 | }
50 | }
51 | if (!isActive) {
52 | pollJob.cancel()
53 | removeListener(listener)
54 | }
55 | }
56 | }
57 |
58 | enum class PlayerState {
59 | Buffer, Play, Pause
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/utils/Preferences.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 8/4/23, 2:32 PM
3 | Copyright (c) 2023
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.utils
9 |
10 | import android.content.Context
11 | import android.content.SharedPreferences
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.MutableState
14 | import androidx.compose.runtime.SnapshotMutationPolicy
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.platform.LocalContext
18 | import androidx.core.content.edit
19 |
20 | const val SaveDirectoryKey = "saveDirectory"
21 | const val imageCacheKey = "imageCacheLimit"
22 |
23 | const val defaultImageCacheSize: Int = 256
24 |
25 | inline fun > SharedPreferences.getEnum(
26 | key: String,
27 | defaultValue: T
28 | ): T =
29 | getString(key, null)?.let {
30 | try {
31 | enumValueOf(it)
32 | } catch (e: IllegalArgumentException) {
33 | null
34 | }
35 | } ?: defaultValue
36 |
37 | inline fun > SharedPreferences.Editor.putEnum(
38 | key: String,
39 | value: T
40 | ): SharedPreferences.Editor =
41 | putString(key, value.name)
42 |
43 | val Context.preferences: SharedPreferences
44 | get() = getSharedPreferences("preferences", Context.MODE_PRIVATE)
45 |
46 | @Composable
47 | fun rememberPreference(key: String, defaultValue: Boolean): MutableState {
48 | val context = LocalContext.current
49 | return remember {
50 | mutableStatePreferenceOf(context.preferences.getBoolean(key, defaultValue)) {
51 | context.preferences.edit { putBoolean(key, it) }
52 | }
53 | }
54 | }
55 |
56 | @Composable
57 | fun rememberPreference(key: String, defaultValue: Int): MutableState {
58 | val context = LocalContext.current
59 | return remember {
60 | mutableStatePreferenceOf(context.preferences.getInt(key, defaultValue)) {
61 | context.preferences.edit { putInt(key, it) }
62 | }
63 | }
64 | }
65 |
66 | @Composable
67 | fun rememberPreference(key: String, defaultValue: String): MutableState {
68 | val context = LocalContext.current
69 | return remember {
70 | mutableStatePreferenceOf(context.preferences.getString(key, null) ?: defaultValue) {
71 | context.preferences.edit { putString(key, it) }
72 | }
73 | }
74 | }
75 |
76 | @Composable
77 | inline fun > rememberPreference(key: String, defaultValue: T): MutableState {
78 | val context = LocalContext.current
79 | return remember {
80 | mutableStatePreferenceOf(context.preferences.getEnum(key, defaultValue)) {
81 | context.preferences.edit { putEnum(key, it) }
82 | }
83 | }
84 | }
85 |
86 | inline fun mutableStatePreferenceOf(
87 | value: T,
88 | crossinline onStructuralInequality: (newValue: T) -> Unit
89 | ) =
90 | mutableStateOf(
91 | value = value,
92 | policy = object : SnapshotMutationPolicy {
93 | override fun equivalent(a: T, b: T): Boolean {
94 | val areEquals = a == b
95 | if (!areEquals) onStructuralInequality(b)
96 | return areEquals
97 | }
98 | }
99 | )
100 |
--------------------------------------------------------------------------------
/app/src/main/java/app/suhasdissa/memerize/utils/ShareUrl.kt:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | Created By Suhas Dissanayake on 11/23/22, 4:16 PM
3 | Copyright (c) 2022
4 | https://github.com/SuhasDissa/
5 | All Rights Reserved
6 | ******************************************************************************/
7 |
8 | package app.suhasdissa.memerize.utils
9 |
10 | import android.content.Context
11 | import android.content.Intent
12 |
13 | fun shareUrl(context: Context, url: String) {
14 | var shareurl = url
15 | if (url.contains("v.redd.it")) {
16 | shareurl = url.split("/").slice(0..3).joinToString("/")
17 | }
18 | val sendIntent: Intent = Intent().apply {
19 | action = Intent.ACTION_SEND
20 | putExtra(Intent.EXTRA_TEXT, shareurl)
21 | type = "text/plain"
22 | }
23 | val shareIntent = Intent.createChooser(sendIntent, "Send Photo to..")
24 | context.startActivity(shareIntent)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_broken_image.xml:
--------------------------------------------------------------------------------
1 |
16 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/loading_img.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
28 |
34 |
40 |
46 |
52 |
58 |
64 |
70 |
76 |
82 |
88 |
94 |
95 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/reddit_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de-rDE/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Failed to load
4 | Meme Photo
5 | Today
6 | This Week
7 | This Month
8 | Play Video
9 | Settings
10 | README
11 | Check repository and README
12 | Latest Release
13 | Current Version
14 | Github Issue
15 | Submit bug reports and feature requests
16 | Home
17 | Subreddits
18 | Pause
19 | Play
20 | Lemmy Communities
21 | Change Image Cache Size
22 | OK
23 | No Memes Here
24 | Add new community
25 | Open Navigation Drawer
26 | Remove subreddit
27 | Add new RedditCommunity
28 | Save
29 | Cancel
30 | Instance Url
31 | Community Name
32 | Failed to fetch Community info
33 | Multi Reddit Feed
34 | Add new subreddit
35 | Remove Community
36 | Subreddit name from url
37 | Failed to fetch subreddit Info
38 | Filter by time
39 | Error Loading Online Memes
40 | Show Offline Memes
41 | Download Photo
42 | Share Photo
43 | Download Video
44 | Share Video
45 | Mute Sound
46 | Un-mute Sound
47 | Turn off repeat
48 | Turn on repeat
49 | About
50 | Settings
51 | Download Location
52 | Select Meme download location
53 | Image Cache Limit
54 | Set Image Cache Limit
55 | Developer Contact
56 | Hot
57 | Rising
58 | New
59 | Top
60 | Sort By
61 | Open post
62 | Show more options
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Memerize
3 | 読み込みに失敗しました
4 | ミーム写真
5 | 今日
6 | 今週
7 | 今月
8 | 動画を再生
9 | 設定
10 | README
11 | リポジトリとREADMEを確認してください
12 | 最新のリリース
13 | 現在のバージョン
14 | Github Issue
15 | バグレポートや機能リクエストを送信
16 | ホーム
17 | Subreddits
18 | 一時停止
19 | 再生
20 | レミーコミュニティ
21 | 画像キャッシュサイズの変更
22 | OK
23 | ここにはミームはありません
24 | 新しいコミュニティを追加する
25 | ナビゲーションドロワーを開く
26 | subredditを削除する
27 | 新しいRedditコミュニティを追加
28 | Save
29 | キャンセル
30 | インスタンスURL
31 | コミュニティ名
32 | コミュニティ情報の取得に失敗しました
33 | マルチ Reddit フィード
34 | 新しいsubredditを追加する
35 | コミュニティを削除する
36 | URLからサブレディット名を取得
37 | subreddit情報の取得に失敗しました
38 | 時間による絞り込み
39 | オンラインミームの読み込みエラー
40 | オフラインでミームを表示
41 | Reddit
42 | 写真をダウンロード
43 | 写真を共有
44 | 動画をダウンロード
45 | 動画を共有
46 | 消音
47 | 消音を解除
48 | リピートをオフにする
49 | リピートをオンにする
50 | 詳細
51 | 設定
52 | ダウンロード場所
53 | ミームのダウンロード場所を選択してください
54 | 画像キャッシュの制限
55 | 画像キャッシュの制限を設定する
56 | 開発者の連絡先
57 | Lemmy
58 | ホット
59 | 上昇
60 | 新しい
61 | トップ
62 | 並べ替え
63 | オープンポスト
64 | さらにオプションを表示
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFB700
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Memerize
3 | Failed to load
4 | Meme Photo
5 | Today
6 | This Week
7 | This Month
8 | Play Video
9 | Settings
10 | README
11 | Check repository and README
12 | Latest Release
13 | Current Version
14 | Github Issue
15 | Submit bug reports and feature requests
16 | Home
17 | Subreddits
18 | Pause
19 | Play
20 | Lemmy Communities
21 | Change Image Cache Size
22 | OK
23 | No Memes Here
24 | Add new community
25 | Open Navigation Drawer
26 | Remove subreddit
27 | Add new RedditCommunity
28 | Save
29 | Cancel
30 | Instance Url
31 | Community Name
32 | Failed to fetch Community info
33 | Multi Reddit Feed
34 | Add new subreddit
35 | Remove Community
36 | Subreddit name from url
37 | Failed to fetch subreddit Info
38 | Filter by time
39 | Error Loading Online Memes
40 | Show Offline Memes
41 | Reddit
42 | Download Photo
43 | Share Photo
44 | Download Video
45 | Share Video
46 | Mute Sound
47 | Un-mute Sound
48 | Turn off repeat
49 | Turn on repeat
50 | About
51 | Settings
52 | Download Location
53 | Select Meme download location
54 | Image Cache Limit
55 | Set Image Cache Limit
56 | Developer Contact
57 | Lemmy
58 | Hot
59 | Rising
60 | New
61 | Top
62 | Sort By
63 | Open post
64 | Show more options
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - @android:color/transparent
6 | - true
7 | - @android:color/transparent
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - @android:color/transparent
6 | - false
7 | - @android:color/transparent
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.1.1" apply false
4 | id("com.android.library") version "8.1.1" apply false
5 | id("org.jetbrains.kotlin.android") version "1.8.21" apply false
6 | id("com.google.devtools.ksp") version "1.8.21-1.0.11" apply false
7 | }
8 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /app/src/main/res/values/strings.xml
3 | translation: /app/src/main/res/values-%android_code%/strings.xml
4 | - source: /README.md
5 | translation: /README.%locale%.md
6 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Memerize is a handy meme viewer app. It fetches memes from various subreddits. You can add/remove subreddits as you wish.
2 | This app caches memes as you browse through them. So you can browse them later without internet.
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Memerize is a handy meme viewer app. Uses Reddit to get the latest memes.
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 05 17:32:31 IST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SuhasDissa/MemerizeApp/17ffdf78ce0b8e85aef31d31f3d8c4bd5f72b7c0/key.jks
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Memerize"
16 | include ':app'
17 |
--------------------------------------------------------------------------------