├── .github └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── peterchege │ │ └── aiimagegenerator │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── peterchege │ │ │ └── aiimagegenerator │ │ │ ├── MainActivity.kt │ │ │ ├── data │ │ │ ├── api │ │ │ │ ├── NetworkResult.kt │ │ │ │ ├── NetworkUtil.kt │ │ │ │ └── OpenAIApi.kt │ │ │ ├── di │ │ │ │ ├── AppModule.kt │ │ │ │ └── ImageGeneratorApp.kt │ │ │ └── repository │ │ │ │ └── ImageRepositoryImpl.kt │ │ │ ├── domain │ │ │ ├── downloader │ │ │ │ ├── AndroidDownloader.kt │ │ │ │ ├── DownloadCompletedReceiver.kt │ │ │ │ └── Downloader.kt │ │ │ ├── models │ │ │ │ ├── ImageResponse.kt │ │ │ │ └── RequestBody.kt │ │ │ └── repository │ │ │ │ └── ImageRepository.kt │ │ │ ├── ui │ │ │ ├── AppNavigation.kt │ │ │ ├── components │ │ │ │ ├── DropdownMenu.kt │ │ │ │ └── PagerIndicator.kt │ │ │ ├── screens │ │ │ │ ├── HistoryScreen.kt │ │ │ │ └── HomeScreen.kt │ │ │ ├── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── viewModels │ │ │ │ ├── HistoryScreenViewModel.kt │ │ │ │ └── HomeScreenViewModel.kt │ │ │ └── util │ │ │ ├── Constants.kt │ │ │ ├── ImageSizes.kt │ │ │ ├── Resource.kt │ │ │ ├── Screens.kt │ │ │ └── UiEvent.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── peterchege │ └── aiimagegenerator │ ├── ExampleUnitTest.kt │ ├── MainDispatchersRule.kt │ └── ui │ └── viewModels │ └── HomeScreenViewModelTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── spotless └── LICENSE.txt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: AI Image Generator App CI/CD WorkFlow 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Create Local Properties File 14 | run: cat /home/runner/work/AIImageGeneratorApp/AIImageGeneratorApp/local.properties | base64 15 | - name: Putting data 16 | env: 17 | DATA: ${{ secrets.PROPERTIES_CONTENT }} 18 | run: echo $DATA > /home/runner/work/AIImageGeneratorApp/AIImageGeneratorApp/local.properties 19 | 20 | - name: Set up JDK 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: 11 24 | 25 | - name: Cache gradle 26 | uses: actions/cache@v1 27 | with: 28 | path: ~/.gradle/caches 29 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 30 | restore-keys: | 31 | ${{ runner.os }}-gradle- 32 | 33 | - name: Build App with Gradle 34 | run: ./gradlew assembleDebug 35 | 36 | - name: Run Unit Tests with Gradle 37 | run: ./gradlew test 38 | 39 | - name: Upload a Build Artifact (APK) 40 | uses: actions/upload-artifact@v2.2.4 41 | with: 42 | name: app 43 | path: app/build/outputs/apk/debug/app-debug.apk 44 | -------------------------------------------------------------------------------- /.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 | AI Image Generator -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Image Generator App 2 | 3 | 4 | This is an android AI image generator app that consumes the OPEN AI API 5 | to generate images based on a given prompt 6 | 7 | To run this code make sure you get your own API key and add it to the 8 | local.properties file for the code to run smoothly 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties 2 | 3 | plugins { 4 | id ("com.android.application") 5 | id ("org.jetbrains.kotlin.android") 6 | 7 | } 8 | 9 | 10 | val key: String = gradleLocalProperties(rootDir).getProperty("OPEN_AI_API_KEY") 11 | android { 12 | namespace ="com.peterchege.aiimagegenerator" 13 | compileSdk =34 14 | 15 | defaultConfig { 16 | applicationId ="com.peterchege.aiimagegenerator" 17 | minSdk = 21 18 | targetSdk = 34 19 | versionCode= 1 20 | versionName = "1.0" 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | vectorDrawables { 24 | useSupportLibrary = true 25 | } 26 | 27 | } 28 | 29 | buildTypes { 30 | getByName("debug"){ 31 | buildConfigField("String", "OPEN_AI_API_KEY", key) 32 | } 33 | getByName("release") { 34 | buildConfigField("String", "OPEN_AI_API_KEY", key) 35 | isMinifyEnabled =false 36 | proguardFiles (getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 37 | } 38 | } 39 | compileOptions { 40 | sourceCompatibility = JavaVersion.VERSION_1_8 41 | targetCompatibility = JavaVersion.VERSION_1_8 42 | } 43 | kotlinOptions { 44 | jvmTarget = "1.8" 45 | } 46 | buildFeatures { 47 | compose= true 48 | } 49 | composeOptions { 50 | kotlinCompilerExtensionVersion ="1.5.3" 51 | } 52 | packagingOptions { 53 | resources { 54 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 55 | } 56 | } 57 | 58 | 59 | } 60 | 61 | dependencies { 62 | 63 | 64 | implementation ("androidx.core:core-ktx:1.10.1") 65 | implementation ("androidx.compose.ui:ui:1.5.0-beta01") 66 | implementation ("androidx.compose.material:material:1.5.0-beta01") 67 | implementation ("androidx.compose.ui:ui-tooling-preview:1.5.0-beta01") 68 | implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") 69 | implementation ("androidx.activity:activity-compose:1.7.2") 70 | testImplementation ("junit:junit:4.13.2") 71 | androidTestImplementation ("androidx.test.ext:junit:1.1.5") 72 | androidTestImplementation ("androidx.test.espresso:espresso-core:3.5.1") 73 | androidTestImplementation ("androidx.compose.ui:ui-test-junit4:1.5.0-beta01") 74 | debugImplementation ("androidx.compose.ui:ui-tooling:1.5.0-beta01") 75 | 76 | 77 | implementation ("androidx.constraintlayout:constraintlayout-compose:1.0.1") 78 | 79 | // retrofit 80 | implementation("com.squareup.retrofit2:retrofit:2.9.0") 81 | implementation("com.squareup.retrofit2:converter-moshi:2.9.0") 82 | implementation ("com.squareup.okhttp3:okhttp:5.0.0-alpha.2") 83 | implementation ("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2") 84 | implementation("androidx.navigation:navigation-compose:2.7.1") 85 | 86 | // view model 87 | implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") 88 | implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") 89 | implementation ("androidx.lifecycle:lifecycle-runtime-compose:2.6.1") 90 | 91 | 92 | //coroutines 93 | implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") 94 | implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") 95 | 96 | // dagger hilt 97 | implementation ("com.google.dagger:hilt-android:2.48") 98 | ksp("com.google.dagger:dagger-compiler:2.48") // Dagger compiler 99 | ksp("com.google.dagger:hilt-compiler:2.48") // Hilt compiler 100 | implementation ("androidx.hilt:hilt-navigation-compose:1.0.0") 101 | // coil 102 | implementation ("io.coil-kt:coil-compose:2.4.0") 103 | 104 | 105 | //pager 106 | implementation ("com.google.accompanist:accompanist-pager:0.30.1") 107 | implementation ("com.google.accompanist:accompanist-pager-indicators:0.30.1") 108 | 109 | testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") 110 | testImplementation("io.mockk:mockk:1.13.5") 111 | androidTestImplementation( "io.mockk:mockk-android:1.13.5") 112 | debugImplementation ("com.github.chuckerteam.chucker:library:4.0.0") 113 | releaseImplementation ("com.github.chuckerteam.chucker:library-no-op:4.0.0") 114 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/peterchege/aiimagegenerator/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator 17 | 18 | import androidx.test.platform.app.InstrumentationRegistry 19 | import androidx.test.ext.junit.runners.AndroidJUnit4 20 | 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | 24 | import org.junit.Assert.* 25 | 26 | /** 27 | * Instrumented test, which will execute on an Android device. 28 | * 29 | * See [testing documentation](http://d.android.com/tools/testing). 30 | */ 31 | @RunWith(AndroidJUnit4::class) 32 | class ExampleInstrumentedTest { 33 | @Test 34 | fun useAppContext() { 35 | // Context of the app under test. 36 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 37 | assertEquals("com.peterchege.aiimagegenerator", appContext.packageName) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator 17 | 18 | import android.os.Bundle 19 | import androidx.activity.ComponentActivity 20 | import androidx.activity.compose.setContent 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.material.MaterialTheme 23 | import androidx.compose.material.Surface 24 | import androidx.compose.material.Text 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.tooling.preview.Preview 28 | import androidx.navigation.compose.rememberNavController 29 | import com.peterchege.aiimagegenerator.ui.AppNavigation 30 | import com.peterchege.aiimagegenerator.ui.theme.AIImageGeneratorTheme 31 | import dagger.hilt.android.AndroidEntryPoint 32 | 33 | 34 | @AndroidEntryPoint 35 | class MainActivity : ComponentActivity() { 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | setContent { 39 | AIImageGeneratorTheme { 40 | // A surface container using the 'background' color from the theme 41 | Surface( 42 | modifier = Modifier.fillMaxSize(), 43 | color = MaterialTheme.colors.background 44 | ) { 45 | val navController = rememberNavController() 46 | AppNavigation(navController = navController) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/api/NetworkResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.api 17 | 18 | sealed class NetworkResult { 19 | class Success(val data: T) : NetworkResult() 20 | class Error(val code: Int, val message: String?) : NetworkResult() 21 | class Exception(val e: Throwable) : NetworkResult() 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/api/NetworkUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.api 17 | 18 | import retrofit2.HttpException 19 | import retrofit2.Response 20 | 21 | 22 | suspend fun handleApi( 23 | execute: suspend () -> Response 24 | ): NetworkResult { 25 | return try { 26 | val response = execute() 27 | val body = response.body() 28 | if (response.isSuccessful && body != null) { 29 | NetworkResult.Success(body) 30 | } else { 31 | NetworkResult.Error(code = response.code(), message = response.message()) 32 | } 33 | } catch (e: HttpException) { 34 | NetworkResult.Error(code = e.code(), message = e.message()) 35 | } catch (e: Throwable) { 36 | NetworkResult.Exception(e) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/api/OpenAIApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.api 17 | 18 | 19 | import com.peterchege.aiimagegenerator.BuildConfig 20 | import com.peterchege.aiimagegenerator.domain.models.ImageResponse 21 | import com.peterchege.aiimagegenerator.domain.models.RequestBody 22 | import com.peterchege.aiimagegenerator.util.Constants 23 | import retrofit2.Response 24 | import retrofit2.http.* 25 | 26 | interface OpenAIApi { 27 | 28 | @POST("generations") 29 | suspend fun generateImages(@Body requestBody: RequestBody): Response 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.di 17 | 18 | import android.content.Context 19 | import com.chuckerteam.chucker.api.ChuckerCollector 20 | import com.chuckerteam.chucker.api.ChuckerInterceptor 21 | import com.chuckerteam.chucker.api.RetentionManager 22 | import com.peterchege.aiimagegenerator.BuildConfig 23 | import com.peterchege.aiimagegenerator.data.api.OpenAIApi 24 | import com.peterchege.aiimagegenerator.data.repository.ImageRepositoryImpl 25 | import com.peterchege.aiimagegenerator.domain.repository.ImageRepository 26 | import com.peterchege.aiimagegenerator.util.Constants 27 | import dagger.Module 28 | import dagger.Provides 29 | import dagger.hilt.InstallIn 30 | import dagger.hilt.android.qualifiers.ApplicationContext 31 | import dagger.hilt.components.SingletonComponent 32 | import okhttp3.Interceptor 33 | import okhttp3.OkHttpClient 34 | import retrofit2.Retrofit 35 | import retrofit2.converter.moshi.MoshiConverterFactory 36 | import javax.inject.Singleton 37 | 38 | @Module 39 | @InstallIn(SingletonComponent::class) 40 | object AppModule { 41 | 42 | 43 | @Provides 44 | @Singleton 45 | fun provideChuckerCollector( 46 | @ApplicationContext context: Context 47 | ):ChuckerCollector{ 48 | return ChuckerCollector( 49 | context = context, 50 | showNotification = true, 51 | retentionPeriod = RetentionManager.Period.ONE_HOUR 52 | ) 53 | } 54 | 55 | @Provides 56 | @Singleton 57 | fun provideChuckerInterceptor( 58 | chuckerCollector: ChuckerCollector, 59 | @ApplicationContext context: Context 60 | ):ChuckerInterceptor{ 61 | return ChuckerInterceptor.Builder(context) 62 | .collector(collector = chuckerCollector) 63 | .maxContentLength(250_000L) 64 | .alwaysReadResponseBody(enable = true) 65 | .build() 66 | 67 | } 68 | 69 | @Provides 70 | @Singleton 71 | fun provideOkhttpClient( 72 | chuckerInterceptor: ChuckerInterceptor, 73 | ):OkHttpClient{ 74 | return OkHttpClient.Builder() 75 | .addInterceptor(OAuthInterceptor("Bearer", BuildConfig.OPEN_AI_API_KEY)) 76 | .addInterceptor(chuckerInterceptor) 77 | .build() 78 | } 79 | 80 | 81 | 82 | 83 | @Provides 84 | @Singleton 85 | fun provideOpenAIApi( 86 | client:OkHttpClient 87 | ): OpenAIApi { 88 | return Retrofit.Builder() 89 | .addConverterFactory(MoshiConverterFactory.create()) 90 | .baseUrl(Constants.BASE_URL) 91 | .client(client) 92 | .build() 93 | .create(OpenAIApi::class.java) 94 | } 95 | 96 | @Provides 97 | @Singleton 98 | fun provideImageRepository(api:OpenAIApi): ImageRepository { 99 | return ImageRepositoryImpl(api = api) 100 | } 101 | 102 | } 103 | 104 | class OAuthInterceptor(private val tokenType: String, private val acceessToken: String): 105 | Interceptor { 106 | 107 | override fun intercept(chain: Interceptor.Chain): okhttp3.Response { 108 | var request = chain.request() 109 | request = request.newBuilder().header("Authorization", "$tokenType $acceessToken").build() 110 | 111 | return chain.proceed(request) 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/di/ImageGeneratorApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.di 17 | 18 | import android.app.Application 19 | import dagger.hilt.android.HiltAndroidApp 20 | 21 | 22 | @HiltAndroidApp 23 | class ImageGeneratorApp : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/data/repository/ImageRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.data.repository 17 | 18 | import com.peterchege.aiimagegenerator.data.api.NetworkResult 19 | import com.peterchege.aiimagegenerator.data.api.OpenAIApi 20 | import com.peterchege.aiimagegenerator.data.api.handleApi 21 | import com.peterchege.aiimagegenerator.domain.models.ImageResponse 22 | import com.peterchege.aiimagegenerator.domain.models.RequestBody 23 | import com.peterchege.aiimagegenerator.domain.repository.ImageRepository 24 | import javax.inject.Inject 25 | 26 | class ImageRepositoryImpl @Inject constructor( 27 | private val api:OpenAIApi 28 | ):ImageRepository { 29 | 30 | 31 | override suspend fun generateImages(requestBody: RequestBody): NetworkResult { 32 | return handleApi { api.generateImages(requestBody = requestBody) } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/downloader/AndroidDownloader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.downloader 17 | 18 | import android.app.DownloadManager 19 | import android.content.Context 20 | import android.os.Build 21 | import android.os.Environment 22 | import androidx.annotation.RequiresApi 23 | import androidx.core.net.toUri 24 | 25 | class AndroidDownloader( 26 | context: Context 27 | ): Downloader { 28 | 29 | @RequiresApi(Build.VERSION_CODES.M) 30 | private val downloadManager = context.getSystemService(DownloadManager::class.java) 31 | 32 | @RequiresApi(Build.VERSION_CODES.M) 33 | override fun downloadFile(url: String): Long { 34 | val request = DownloadManager.Request(url.toUri()) 35 | .setMimeType("image/png") 36 | .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) 37 | .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) 38 | .setTitle("${getRandomString(10)}.png") 39 | .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "image.png") 40 | 41 | return downloadManager.enqueue(request) 42 | } 43 | private fun getRandomString(length: Int) : String { 44 | val charset = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789" 45 | return (1..length) 46 | .map { charset.random() } 47 | .joinToString("") 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/downloader/DownloadCompletedReceiver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.downloader 17 | 18 | import android.app.DownloadManager 19 | import android.content.BroadcastReceiver 20 | import android.content.Context 21 | import android.content.Intent 22 | 23 | class DownloadCompletedReceiver: BroadcastReceiver() { 24 | 25 | override fun onReceive(context: Context?, intent: Intent?) { 26 | if(intent?.action == "android.intent.action.DOWNLOAD_COMPLETE") { 27 | val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L) 28 | if(id != -1L) { 29 | println("Download with ID $id finished!") 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/downloader/Downloader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.downloader 17 | 18 | interface Downloader { 19 | fun downloadFile(url: String): Long 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/models/ImageResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.models 17 | 18 | import com.squareup.moshi.Json 19 | 20 | data class ImageResponse ( 21 | @field:Json(name = "created") 22 | val created:String, 23 | @field:Json(name = "data") 24 | val data:List 25 | ) 26 | 27 | data class ImageItem( 28 | @field:Json(name = "url") 29 | val url:String 30 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/models/RequestBody.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.models 17 | 18 | data class RequestBody ( 19 | val prompt:String, 20 | val n:Int, 21 | val size:String, 22 | 23 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/domain/repository/ImageRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.domain.repository 17 | 18 | import com.peterchege.aiimagegenerator.data.api.NetworkResult 19 | import com.peterchege.aiimagegenerator.domain.models.ImageResponse 20 | import com.peterchege.aiimagegenerator.domain.models.RequestBody 21 | 22 | interface ImageRepository { 23 | 24 | suspend fun generateImages(requestBody: RequestBody):NetworkResult 25 | 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/AppNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.navigation.NavController 20 | import androidx.navigation.NavHostController 21 | import androidx.navigation.compose.NavHost 22 | import androidx.navigation.compose.composable 23 | import com.peterchege.aiimagegenerator.ui.screens.HistoryScreen 24 | import com.peterchege.aiimagegenerator.ui.screens.HomeScreen 25 | import com.peterchege.aiimagegenerator.util.Screens 26 | 27 | 28 | @Composable 29 | fun AppNavigation ( 30 | navController:NavHostController 31 | ){ 32 | NavHost( 33 | navController = navController, 34 | startDestination = Screens.HOME_SCREEN){ 35 | composable( 36 | route = Screens.HOME_SCREEN 37 | ){ 38 | HomeScreen(navController = navController) 39 | } 40 | composable( 41 | route = Screens.HISTORY_SCREEN 42 | ){ 43 | HistoryScreen(navController = navController) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/components/DropdownMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.components 17 | 18 | import android.util.Log 19 | import androidx.compose.foundation.background 20 | import androidx.compose.foundation.border 21 | import androidx.compose.foundation.clickable 22 | import androidx.compose.foundation.layout.* 23 | import androidx.compose.material.* 24 | import androidx.compose.material.icons.Icons 25 | import androidx.compose.material.icons.filled.ArrowDropDown 26 | import androidx.compose.runtime.* 27 | import androidx.compose.ui.Alignment 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.unit.dp 30 | import androidx.constraintlayout.compose.ConstraintLayout 31 | import androidx.constraintlayout.compose.Dimension 32 | import com.peterchege.aiimagegenerator.util.imageSizes 33 | 34 | @Composable 35 | fun MyCustomDropDownMenu( 36 | selectedIndex:Int, 37 | listItems:List, 38 | onChangeSelectedIndex:(Int) -> Unit, 39 | width:Float 40 | ){ 41 | var menuListExpanded by remember { mutableStateOf(false) } 42 | Box( 43 | modifier = Modifier 44 | .fillMaxSize() 45 | .fillMaxWidth() 46 | .padding(5.dp), 47 | contentAlignment = Alignment.CenterStart 48 | ) { 49 | ComposeMenu( 50 | menuItems = listItems, 51 | menuExpandedState = menuListExpanded, 52 | seletedIndex = selectedIndex, 53 | updateMenuExpandStatus = { 54 | menuListExpanded = true 55 | }, 56 | onDismissMenuView = { 57 | menuListExpanded = false 58 | }, 59 | onMenuItemclick = { index-> 60 | Log.d("Index",index.toString()) 61 | onChangeSelectedIndex(index) 62 | menuListExpanded = false 63 | }, 64 | setWidth = width 65 | ) 66 | } 67 | } 68 | 69 | 70 | @Composable 71 | fun ComposeMenu( 72 | menuItems: List, 73 | menuExpandedState: Boolean, 74 | seletedIndex : Int, 75 | updateMenuExpandStatus : () -> Unit, 76 | onDismissMenuView : () -> Unit, 77 | onMenuItemclick : (Int) -> Unit, 78 | setWidth: Float, 79 | ) { 80 | Box( 81 | modifier = Modifier 82 | .fillMaxSize() 83 | .wrapContentSize(Alignment.TopStart) 84 | .padding(top = 3.dp) 85 | .border(0.5.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.5f)) 86 | .clickable( 87 | onClick = { 88 | updateMenuExpandStatus() 89 | }, 90 | ), 91 | 92 | ) { 93 | 94 | ConstraintLayout( 95 | modifier = Modifier 96 | .fillMaxWidth() 97 | .padding(10.dp) 98 | ) { 99 | 100 | val (lable, iconView) = createRefs() 101 | 102 | Text( 103 | text= menuItems[seletedIndex], 104 | modifier = Modifier 105 | .fillMaxWidth() 106 | .constrainAs(lable) { 107 | top.linkTo(parent.top) 108 | bottom.linkTo(parent.bottom) 109 | start.linkTo(parent.start) 110 | end.linkTo(iconView.start) 111 | width = Dimension.fillToConstraints 112 | } 113 | ) 114 | Icon( 115 | Icons.Filled.ArrowDropDown, 116 | contentDescription = null, 117 | modifier = Modifier 118 | .size(20.dp, 20.dp) 119 | .constrainAs(iconView) { 120 | end.linkTo(parent.end) 121 | top.linkTo(parent.top) 122 | bottom.linkTo(parent.bottom) 123 | }, 124 | tint = MaterialTheme.colors.onSurface 125 | ) 126 | 127 | DropdownMenu( 128 | expanded = menuExpandedState, 129 | onDismissRequest = { onDismissMenuView() }, 130 | modifier = Modifier 131 | .fillMaxWidth(fraction = setWidth) 132 | .background(MaterialTheme.colors.surface) 133 | ) { 134 | menuItems.forEachIndexed { index, title -> 135 | DropdownMenuItem( 136 | onClick = { 137 | onMenuItemclick(index) 138 | }) { 139 | Text(text = title) 140 | } 141 | } 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/components/PagerIndicator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.components 17 | 18 | import androidx.compose.foundation.background 19 | import androidx.compose.foundation.clickable 20 | import androidx.compose.foundation.layout.* 21 | import androidx.compose.foundation.lazy.LazyColumn 22 | import androidx.compose.foundation.lazy.LazyListScope 23 | import androidx.compose.foundation.lazy.LazyRow 24 | import androidx.compose.foundation.lazy.rememberLazyListState 25 | import androidx.compose.foundation.shape.CircleShape 26 | import androidx.compose.material.MaterialTheme 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.LaunchedEffect 29 | import androidx.compose.runtime.derivedStateOf 30 | import androidx.compose.runtime.remember 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.draw.clip 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.graphics.Shape 35 | import androidx.compose.ui.graphics.graphicsLayer 36 | import androidx.compose.ui.platform.LocalDensity 37 | import androidx.compose.ui.unit.Dp 38 | import androidx.compose.ui.unit.dp 39 | import com.google.accompanist.pager.ExperimentalPagerApi 40 | import com.google.accompanist.pager.PagerState 41 | import androidx.compose.runtime.* 42 | 43 | 44 | 45 | @OptIn(ExperimentalPagerApi::class) 46 | @Composable 47 | fun PagerIndicator( 48 | modifier: Modifier = Modifier, 49 | pagerState: PagerState, 50 | indicatorCount: Int = 5, 51 | indicatorSize: Dp = 8.dp, 52 | indicatorShape: Shape = CircleShape, 53 | space: Dp = 6.dp, 54 | activeColor: Color = Color.Blue, 55 | inActiveColor: Color = Color.LightGray, 56 | orientation: IndicatorOrientation = IndicatorOrientation.Horizontal, 57 | onClick: ((Int) -> Unit)? = null 58 | ) { 59 | 60 | val listState = rememberLazyListState() 61 | 62 | val totalWidth: Dp = indicatorSize * indicatorCount + space * (indicatorCount - 1) 63 | val widthInPx = LocalDensity.current.run { indicatorSize.toPx() } 64 | 65 | val currentItem by remember { 66 | derivedStateOf { 67 | pagerState.currentPage 68 | } 69 | } 70 | 71 | val itemCount = pagerState.pageCount 72 | 73 | LaunchedEffect(key1 = currentItem) { 74 | val viewportSize = listState.layoutInfo.viewportSize 75 | if (orientation == IndicatorOrientation.Horizontal) { 76 | listState.animateScrollToItem( 77 | currentItem, 78 | (widthInPx / 2 - viewportSize.width / 2).toInt() 79 | ) 80 | } else { 81 | listState.animateScrollToItem( 82 | currentItem, 83 | (widthInPx / 2 - viewportSize.height / 2).toInt() 84 | ) 85 | } 86 | 87 | } 88 | 89 | if (orientation == IndicatorOrientation.Horizontal) { 90 | LazyRow( 91 | modifier = modifier.width(totalWidth), 92 | state = listState, 93 | contentPadding = PaddingValues(vertical = space), 94 | horizontalArrangement = Arrangement.spacedBy(space), 95 | userScrollEnabled = false 96 | ) { 97 | indicatorItems( 98 | itemCount, 99 | currentItem, 100 | indicatorCount, 101 | indicatorShape, 102 | activeColor, 103 | inActiveColor, 104 | indicatorSize, 105 | onClick 106 | ) 107 | } 108 | } else { 109 | LazyColumn( 110 | modifier = modifier.height(totalWidth), 111 | state = listState, 112 | contentPadding = PaddingValues(horizontal = space), 113 | verticalArrangement = Arrangement.spacedBy(space), 114 | userScrollEnabled = false 115 | ) { 116 | indicatorItems( 117 | itemCount, 118 | currentItem, 119 | indicatorCount, 120 | indicatorShape, 121 | activeColor, 122 | inActiveColor, 123 | indicatorSize, 124 | onClick 125 | ) 126 | } 127 | } 128 | 129 | } 130 | 131 | private fun LazyListScope.indicatorItems( 132 | itemCount: Int, 133 | currentItem: Int, 134 | indicatorCount: Int, 135 | indicatorShape: Shape, 136 | activeColor: Color, 137 | inActiveColor: Color, 138 | indicatorSize: Dp, 139 | onClick: ((Int) -> Unit)? 140 | ) { 141 | items(itemCount) { index -> 142 | 143 | val isSelected = (index == currentItem) 144 | 145 | // Index of item in center when odd number of indicators are set 146 | // for 5 indicators this is 2nd indicator place 147 | val centerItemIndex = indicatorCount / 2 148 | 149 | val right1 = 150 | (currentItem < centerItemIndex && 151 | index >= indicatorCount - 1) 152 | 153 | val right2 = 154 | (currentItem >= centerItemIndex && 155 | index >= currentItem + centerItemIndex && 156 | index < itemCount - centerItemIndex + 1) 157 | val isRightEdgeItem = right1 || right2 158 | 159 | // Check if this item's distance to center item is smaller than half size of 160 | // the indicator count when current indicator at the center or 161 | // when we reach the end of list. End of the list only one item is on edge 162 | // with 10 items and 7 indicators 163 | // 7-3= 4th item can be the first valid left edge item and 164 | val isLeftEdgeItem = 165 | index <= currentItem - centerItemIndex && 166 | currentItem > centerItemIndex && 167 | index < itemCount - indicatorCount + 1 168 | 169 | Box( 170 | modifier = Modifier 171 | .graphicsLayer { 172 | val scale = if (isSelected) { 173 | 1f 174 | } else if (isLeftEdgeItem || isRightEdgeItem) { 175 | .5f 176 | } else { 177 | .8f 178 | } 179 | scaleX = scale 180 | scaleY = scale 181 | 182 | } 183 | .clip(indicatorShape) 184 | .size(indicatorSize) 185 | .background( 186 | if (isSelected) activeColor else inActiveColor, 187 | indicatorShape 188 | ) 189 | .then( 190 | if (onClick != null) { 191 | Modifier 192 | .clickable { 193 | onClick.invoke(index) 194 | } 195 | } else Modifier 196 | ) 197 | ) 198 | } 199 | } 200 | 201 | enum class IndicatorOrientation { 202 | Horizontal, Vertical 203 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/screens/HistoryScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.screens 17 | 18 | import androidx.compose.runtime.Composable 19 | import androidx.navigation.NavController 20 | 21 | @Composable 22 | fun HistoryScreen( 23 | navController: NavController 24 | 25 | ) { 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/screens/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.screens 17 | 18 | import android.annotation.SuppressLint 19 | import android.os.Build 20 | import android.util.Log 21 | import androidx.annotation.RequiresApi 22 | import androidx.compose.foundation.background 23 | import androidx.compose.foundation.layout.* 24 | import androidx.compose.foundation.shape.RoundedCornerShape 25 | import androidx.compose.material.* 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.runtime.LaunchedEffect 28 | import androidx.compose.runtime.getValue 29 | import androidx.compose.runtime.rememberCoroutineScope 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.draw.clip 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.layout.ContentScale 35 | import androidx.compose.ui.platform.LocalContext 36 | import androidx.compose.ui.text.style.TextAlign 37 | import androidx.compose.ui.unit.dp 38 | import androidx.compose.ui.unit.sp 39 | import androidx.hilt.navigation.compose.hiltViewModel 40 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 41 | import androidx.navigation.NavController 42 | import coil.compose.SubcomposeAsyncImage 43 | import com.google.accompanist.pager.ExperimentalPagerApi 44 | import com.google.accompanist.pager.HorizontalPager 45 | import com.google.accompanist.pager.rememberPagerState 46 | import com.peterchege.aiimagegenerator.domain.downloader.AndroidDownloader 47 | import com.peterchege.aiimagegenerator.ui.components.MyCustomDropDownMenu 48 | import com.peterchege.aiimagegenerator.ui.components.PagerIndicator 49 | import com.peterchege.aiimagegenerator.ui.viewModels.FormState 50 | import com.peterchege.aiimagegenerator.ui.viewModels.HomeScreenUiState 51 | import com.peterchege.aiimagegenerator.ui.viewModels.HomeScreenViewModel 52 | import com.peterchege.aiimagegenerator.util.UiEvent 53 | import com.peterchege.aiimagegenerator.util.imageCounts 54 | import com.peterchege.aiimagegenerator.util.imageSizes 55 | import kotlinx.coroutines.flow.SharedFlow 56 | import kotlinx.coroutines.flow.collectLatest 57 | import kotlinx.coroutines.launch 58 | 59 | @RequiresApi(Build.VERSION_CODES.M) 60 | @Composable 61 | fun HomeScreen( 62 | navController:NavController, 63 | viewModel:HomeScreenViewModel = hiltViewModel() 64 | ){ 65 | val uiState by viewModel.uiState.collectAsStateWithLifecycle() 66 | val formState by viewModel.formState.collectAsStateWithLifecycle() 67 | HomeScreenContent( 68 | uiState = uiState, 69 | formState = formState, 70 | eventFlow = viewModel.eventFlow, 71 | onChangePrompt = { viewModel.onChangePrompt(it) }, 72 | onChangeSize = { viewModel.onChangeSelectedImageSizeIndex(it) }, 73 | onChangeImageCount = { viewModel.onChangeSelectedImageCountIndex(it) }, 74 | onSubmit = { viewModel.generateImages() } 75 | ) 76 | 77 | } 78 | 79 | 80 | @RequiresApi(Build.VERSION_CODES.M) 81 | @OptIn(ExperimentalPagerApi::class) 82 | @SuppressLint("UnusedMaterialScaffoldPaddingParameter") 83 | @Composable 84 | fun HomeScreenContent( 85 | uiState: HomeScreenUiState, 86 | formState:FormState, 87 | eventFlow:SharedFlow, 88 | onChangePrompt:(String) -> Unit, 89 | onChangeSize:(Int) -> Unit, 90 | onChangeImageCount: (Int) -> Unit, 91 | onSubmit:() -> Unit, 92 | ) { 93 | val context = LocalContext.current 94 | val scaffoldState = rememberScaffoldState() 95 | 96 | LaunchedEffect(key1 = true) { 97 | eventFlow.collectLatest { event -> 98 | when (event) { 99 | is UiEvent.ShowSnackbar -> { 100 | scaffoldState.snackbarHostState.showSnackbar( 101 | message = event.uiText 102 | ) 103 | } 104 | is UiEvent.Navigate -> { 105 | 106 | } 107 | } 108 | } 109 | } 110 | 111 | Scaffold( 112 | scaffoldState = scaffoldState, 113 | modifier = Modifier.fillMaxSize(), 114 | topBar = { 115 | TopAppBar( 116 | title = { 117 | Row( 118 | modifier = Modifier 119 | .fillMaxSize() 120 | .padding(end = 15.dp), 121 | verticalAlignment = Alignment.CenterVertically, 122 | horizontalArrangement = Arrangement.SpaceBetween, 123 | ) { 124 | Text(text = "AI Image Generator") 125 | 126 | } 127 | } 128 | ) 129 | }, 130 | ) { 131 | 132 | Box(modifier = Modifier.fillMaxSize()){ 133 | Column( 134 | modifier = Modifier 135 | .fillMaxSize() 136 | .padding(10.dp), 137 | verticalArrangement = Arrangement.Top, 138 | horizontalAlignment = Alignment.CenterHorizontally, 139 | ) { 140 | TextField( 141 | modifier = Modifier.fillMaxWidth(), 142 | label = { 143 | Text(text = "Enter Image Description") 144 | }, 145 | value = formState.prompt, 146 | onValueChange = { 147 | onChangePrompt(it) 148 | 149 | }) 150 | Spacer(modifier = Modifier.height(10.dp)) 151 | Row( 152 | modifier = Modifier 153 | .fillMaxWidth() 154 | .height(110.dp), 155 | verticalAlignment = Alignment.CenterVertically, 156 | horizontalArrangement = Arrangement.SpaceBetween, 157 | ) { 158 | Column( 159 | modifier = Modifier 160 | .fillMaxWidth(fraction = 0.4f) 161 | .fillMaxHeight() 162 | 163 | ) { 164 | Text( 165 | text = "Select Image Size", 166 | fontSize = 14.sp 167 | ) 168 | MyCustomDropDownMenu( 169 | listItems = imageSizes, 170 | selectedIndex = imageSizes.indexOf(formState.size) , 171 | onChangeSelectedIndex = { 172 | onChangeSize(it) 173 | }, 174 | width = 0.4f, 175 | ) 176 | } 177 | Column( 178 | modifier = Modifier 179 | .fillMaxWidth(fraction = 0.4f) 180 | .fillMaxHeight() 181 | 182 | ) { 183 | Text( 184 | text = "No. of Images", 185 | fontSize = 14.sp 186 | ) 187 | MyCustomDropDownMenu( 188 | listItems = imageCounts.map { it.toString() }, 189 | selectedIndex = imageCounts.indexOf(formState.imageCount) , 190 | onChangeSelectedIndex = { 191 | onChangeImageCount(it) 192 | }, 193 | width = 0.4f, 194 | ) 195 | } 196 | Column( 197 | verticalArrangement = Arrangement.Center, 198 | horizontalAlignment = Alignment.CenterHorizontally, 199 | ) { 200 | Button(onClick = { 201 | onSubmit() 202 | 203 | }) { 204 | Text(text = "Generate") 205 | } 206 | } 207 | 208 | } 209 | 210 | when(uiState){ 211 | is HomeScreenUiState.Idle -> { 212 | Box(modifier = Modifier.fillMaxSize()){ 213 | Text( 214 | text = "Start generating images", 215 | modifier = Modifier.align(Alignment.Center) 216 | ) 217 | } 218 | } 219 | is HomeScreenUiState.Loading -> { 220 | Box(modifier = Modifier.fillMaxSize()){ 221 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) 222 | } 223 | } 224 | is HomeScreenUiState.Error -> { 225 | Box(modifier = Modifier.fillMaxSize()){ 226 | Text( 227 | text = "An unexpected error", 228 | modifier = Modifier.align(Alignment.Center) 229 | ) 230 | } 231 | } 232 | is HomeScreenUiState.Success -> { 233 | val images = uiState.images 234 | val pagerState1 = rememberPagerState(initialPage = 0) 235 | val coroutineScope = rememberCoroutineScope() 236 | HorizontalPager( 237 | count = images.size, 238 | state = pagerState1 239 | ) { image -> 240 | Box( 241 | modifier = Modifier.fillMaxWidth() 242 | ){ 243 | SubcomposeAsyncImage( 244 | model = images[image].url, 245 | loading = { 246 | Box(modifier = Modifier.fillMaxSize()) { 247 | CircularProgressIndicator( 248 | modifier = Modifier.align( 249 | Alignment.Center 250 | ) 251 | ) 252 | } 253 | }, 254 | contentScale = ContentScale.Crop, 255 | modifier = Modifier 256 | .fillMaxWidth() 257 | .height(300.dp), 258 | contentDescription = "Generated Images" 259 | ) 260 | Box( 261 | modifier = Modifier 262 | .padding(10.dp) 263 | .width(45.dp) 264 | .align(Alignment.TopEnd) 265 | .height(25.dp) 266 | .clip(RoundedCornerShape(15.dp)) 267 | .background(Color.White) 268 | 269 | ){ 270 | Text( 271 | modifier = Modifier 272 | .align(Alignment.Center) 273 | .padding(horizontal = 3.dp), 274 | textAlign = TextAlign.Start, 275 | fontSize = 17.sp, 276 | text = "${image + 1}/${images.size}" 277 | ) 278 | } 279 | } 280 | } 281 | Box( 282 | modifier = Modifier 283 | .fillMaxWidth() 284 | .height(20.dp) 285 | ) { 286 | PagerIndicator( 287 | modifier = Modifier.align(Alignment.Center), 288 | pagerState = pagerState1 289 | ) { 290 | coroutineScope.launch { 291 | pagerState1.scrollToPage(it) 292 | } 293 | } 294 | } 295 | 296 | Text( 297 | fontSize = 13.sp, 298 | textAlign = TextAlign.Center, 299 | modifier = Modifier.fillMaxWidth(), 300 | text = "NB: The images might load slowly because of the API so please be patient enough for them to load ") 301 | Row( 302 | modifier = Modifier 303 | .fillMaxWidth() 304 | .height(90.dp), 305 | verticalAlignment = Alignment.CenterVertically, 306 | horizontalArrangement = Arrangement.Center, 307 | ){ 308 | Button( 309 | onClick = { 310 | val downloader = AndroidDownloader(context = context) 311 | images.map { 312 | downloader.downloadFile(url = it.url) 313 | } 314 | 315 | } 316 | ){ 317 | Text(text = "Download Images") 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | } 325 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.theme 17 | 18 | import androidx.compose.ui.graphics.Color 19 | 20 | val Purple200 = Color(0xFFBB86FC) 21 | val Purple500 = Color(0xFF6200EE) 22 | val Purple700 = Color(0xFF3700B3) 23 | val Teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.theme 17 | 18 | import androidx.compose.foundation.shape.RoundedCornerShape 19 | import androidx.compose.material.Shapes 20 | import androidx.compose.ui.unit.dp 21 | 22 | val Shapes = Shapes( 23 | small = RoundedCornerShape(4.dp), 24 | medium = RoundedCornerShape(4.dp), 25 | large = RoundedCornerShape(0.dp) 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.theme 17 | 18 | import androidx.compose.foundation.isSystemInDarkTheme 19 | import androidx.compose.material.MaterialTheme 20 | import androidx.compose.material.darkColors 21 | import androidx.compose.material.lightColors 22 | import androidx.compose.runtime.Composable 23 | 24 | private val DarkColorPalette = darkColors( 25 | primary = Purple200, 26 | primaryVariant = Purple700, 27 | secondary = Teal200 28 | ) 29 | 30 | private val LightColorPalette = lightColors( 31 | primary = Purple500, 32 | primaryVariant = Purple700, 33 | secondary = Teal200 34 | 35 | /* Other default colors to override 36 | background = Color.White, 37 | surface = Color.White, 38 | onPrimary = Color.White, 39 | onSecondary = Color.Black, 40 | onBackground = Color.Black, 41 | onSurface = Color.Black, 42 | */ 43 | ) 44 | 45 | @Composable 46 | fun AIImageGeneratorTheme( 47 | darkTheme: Boolean = isSystemInDarkTheme(), 48 | content: @Composable () -> Unit 49 | ) { 50 | val colors = if (darkTheme) { 51 | DarkColorPalette 52 | } else { 53 | LightColorPalette 54 | } 55 | 56 | MaterialTheme( 57 | colors = colors, 58 | typography = Typography, 59 | shapes = Shapes, 60 | content = content 61 | ) 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.theme 17 | 18 | import androidx.compose.material.Typography 19 | import androidx.compose.ui.text.TextStyle 20 | import androidx.compose.ui.text.font.FontFamily 21 | import androidx.compose.ui.text.font.FontWeight 22 | import androidx.compose.ui.unit.sp 23 | 24 | // Set of Material typography styles to start with 25 | val Typography = Typography( 26 | body1 = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Normal, 29 | fontSize = 16.sp 30 | ) 31 | /* Other default text styles to override 32 | button = TextStyle( 33 | fontFamily = FontFamily.Default, 34 | fontWeight = FontWeight.W500, 35 | fontSize = 14.sp 36 | ), 37 | caption = TextStyle( 38 | fontFamily = FontFamily.Default, 39 | fontWeight = FontWeight.Normal, 40 | fontSize = 12.sp 41 | ) 42 | */ 43 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/viewModels/HistoryScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.viewModels 17 | 18 | import androidx.lifecycle.ViewModel 19 | import dagger.hilt.android.lifecycle.HiltViewModel 20 | import javax.inject.Inject 21 | 22 | 23 | @HiltViewModel 24 | class HistoryScreenViewModel @Inject constructor( 25 | 26 | ) :ViewModel() { 27 | 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/ui/viewModels/HomeScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.viewModels 17 | 18 | 19 | import androidx.compose.runtime.State 20 | import androidx.compose.runtime.mutableStateOf 21 | import androidx.lifecycle.ViewModel 22 | import androidx.lifecycle.viewModelScope 23 | import com.peterchege.aiimagegenerator.data.api.NetworkResult 24 | import com.peterchege.aiimagegenerator.domain.models.ImageItem 25 | import com.peterchege.aiimagegenerator.domain.models.RequestBody 26 | import com.peterchege.aiimagegenerator.domain.repository.ImageRepository 27 | import com.peterchege.aiimagegenerator.util.Resource 28 | import com.peterchege.aiimagegenerator.util.UiEvent 29 | import com.peterchege.aiimagegenerator.util.imageCounts 30 | import com.peterchege.aiimagegenerator.util.imageSizes 31 | import dagger.hilt.android.lifecycle.HiltViewModel 32 | import kotlinx.coroutines.flow.MutableSharedFlow 33 | import kotlinx.coroutines.flow.MutableStateFlow 34 | import kotlinx.coroutines.flow.asSharedFlow 35 | import kotlinx.coroutines.flow.asStateFlow 36 | import kotlinx.coroutines.flow.launchIn 37 | import kotlinx.coroutines.flow.onEach 38 | import kotlinx.coroutines.launch 39 | import javax.inject.Inject 40 | 41 | data class FormState( 42 | val prompt:String = "", 43 | val size:String = "1024x1024", 44 | val imageCount:Int = 1 45 | ) 46 | 47 | sealed interface HomeScreenUiState { 48 | object Idle : HomeScreenUiState 49 | object Loading : HomeScreenUiState 50 | data class Error(val message: String) : HomeScreenUiState 51 | data class Success(val images: List, ) : HomeScreenUiState 52 | } 53 | 54 | @HiltViewModel 55 | class HomeScreenViewModel @Inject constructor( 56 | private val repository: ImageRepository, 57 | ) : ViewModel() { 58 | 59 | private val _formState = MutableStateFlow(FormState()) 60 | val formState = _formState.asStateFlow() 61 | 62 | 63 | private val _uiState = MutableStateFlow(HomeScreenUiState.Idle) 64 | val uiState = _uiState.asStateFlow() 65 | 66 | 67 | private val _eventFlow = MutableSharedFlow() 68 | val eventFlow = _eventFlow.asSharedFlow() 69 | 70 | 71 | fun onChangeSelectedImageSizeIndex(index: Int) { 72 | _formState.value = _formState.value.copy(size = imageSizes[index]) 73 | 74 | } 75 | fun onChangeSelectedImageCountIndex(index: Int) { 76 | _formState.value = _formState.value.copy(imageCount = imageCounts[index]) 77 | } 78 | 79 | fun onChangePrompt(text: String) { 80 | _formState.value = _formState.value.copy(prompt = text) 81 | } 82 | 83 | fun generateImages() { 84 | _uiState.value = HomeScreenUiState.Loading 85 | 86 | val requestBody = RequestBody( 87 | prompt = _formState.value.prompt, 88 | n = _formState.value.imageCount, 89 | size = _formState.value.size 90 | ) 91 | viewModelScope.launch { 92 | when (val response = repository.generateImages(requestBody = requestBody)) { 93 | is NetworkResult.Success -> { 94 | _uiState.value = HomeScreenUiState.Success(images = response.data.data) 95 | } 96 | is NetworkResult.Error -> { 97 | _uiState.value = HomeScreenUiState.Error( 98 | message = response.message ?: "An unexpected error occurred") 99 | _eventFlow.emit(UiEvent.ShowSnackbar(uiText = "${response.code} ${response.message}")) 100 | } 101 | is NetworkResult.Exception -> { 102 | _uiState.value = HomeScreenUiState.Error( 103 | message = response.e.message ?: "An unexpected error occurred") 104 | _eventFlow.emit(UiEvent.ShowSnackbar(uiText = "${response.e.message}")) 105 | } 106 | } 107 | } 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/util/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.util 17 | 18 | import com.peterchege.aiimagegenerator.BuildConfig 19 | 20 | 21 | 22 | object Constants { 23 | var OPEN_AI_API_KEY: String = BuildConfig.OPEN_AI_API_KEY 24 | 25 | const val BASE_URL = "https://api.openai.com/v1/images/" 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/util/ImageSizes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.util 17 | 18 | 19 | 20 | 21 | val imageSizes = listOf("1024x1024","512x512","256x256") 22 | val imageCounts = listOf(1,2,3,4,5,6,7,8,9,10) -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/util/Resource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.util 17 | 18 | import com.peterchege.aiimagegenerator.data.api.NetworkResult 19 | import com.peterchege.aiimagegenerator.domain.models.ImageResponse 20 | 21 | sealed class Resource( 22 | val data: T? = null, 23 | val message: String? = null 24 | ) { 25 | class Success(data: T) : Resource(data) 26 | class Error(message: String, data: T? = null) : Resource(data, message) 27 | class Loading : Resource() 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/util/Screens.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.util 17 | 18 | object Screens { 19 | const val HOME_SCREEN= "HOME_SCREEN" 20 | const val HISTORY_SCREEN= "HISTORY_SCREEN" 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peterchege/aiimagegenerator/util/UiEvent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.util 17 | 18 | abstract class Event 19 | 20 | sealed class UiEvent: Event() { 21 | data class ShowSnackbar(val uiText: String) : UiEvent() 22 | data class Navigate(val route: String) : UiEvent() 23 | 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AI Image Generator 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/peterchege/aiimagegenerator/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator 17 | 18 | import org.junit.Test 19 | 20 | import org.junit.Assert.* 21 | 22 | /** 23 | * Example local unit test, which will execute on the development machine (host). 24 | * 25 | * See [testing documentation](http://d.android.com/tools/testing). 26 | */ 27 | class ExampleUnitTest { 28 | @Test 29 | fun addition_isCorrect() { 30 | assertEquals(4, 2 + 2) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/test/java/com/peterchege/aiimagegenerator/MainDispatchersRule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator 17 | 18 | import kotlinx.coroutines.Dispatchers 19 | import kotlinx.coroutines.ExperimentalCoroutinesApi 20 | import kotlinx.coroutines.test.TestDispatcher 21 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 22 | import kotlinx.coroutines.test.resetMain 23 | import kotlinx.coroutines.test.setMain 24 | import org.junit.rules.TestWatcher 25 | import org.junit.runner.Description 26 | 27 | class MainDispatcherRule( 28 | val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), 29 | ) : TestWatcher() { 30 | @OptIn(ExperimentalCoroutinesApi::class) 31 | override fun starting(description: Description) { 32 | Dispatchers.setMain(testDispatcher) 33 | } 34 | 35 | @OptIn(ExperimentalCoroutinesApi::class) 36 | override fun finished(description: Description) { 37 | Dispatchers.resetMain() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/test/java/com/peterchege/aiimagegenerator/ui/viewModels/HomeScreenViewModelTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.peterchege.aiimagegenerator.ui.viewModels 17 | 18 | import com.peterchege.aiimagegenerator.MainDispatcherRule 19 | import com.peterchege.aiimagegenerator.data.api.NetworkResult 20 | import com.peterchege.aiimagegenerator.domain.models.ImageItem 21 | import com.peterchege.aiimagegenerator.domain.models.ImageResponse 22 | import com.peterchege.aiimagegenerator.domain.repository.ImageRepository 23 | import io.mockk.coEvery 24 | import io.mockk.mockk 25 | import kotlinx.coroutines.ExperimentalCoroutinesApi 26 | import kotlinx.coroutines.test.* 27 | import org.junit.Assert.* 28 | 29 | import org.junit.Before 30 | import org.junit.Rule 31 | import org.junit.Test 32 | 33 | val fakeImages = ('a'..'z').map { c -> 34 | ImageItem( 35 | url = c.toString() 36 | ) 37 | } 38 | 39 | class HomeScreenViewModelTest { 40 | lateinit var homeScreenViewModel: HomeScreenViewModel 41 | val imageRepository = mockk(relaxed = true) 42 | 43 | @get:Rule 44 | val mainDispatcherRule = MainDispatcherRule() 45 | 46 | @Before 47 | fun setUp() { 48 | homeScreenViewModel = HomeScreenViewModel(repository = imageRepository) 49 | } 50 | 51 | @Test 52 | fun `When app is launched the state is idle `(){ 53 | assert(homeScreenViewModel.uiState.value is HomeScreenUiState.Idle) 54 | } 55 | 56 | 57 | @Test 58 | fun `When the an error occurs, then an error state is loaded`() = runTest { 59 | coEvery { imageRepository.generateImages(any()) } returns 60 | NetworkResult.Error(code = 4000 ,message = "An unexpected error occurred") 61 | homeScreenViewModel.generateImages() 62 | assert(homeScreenViewModel.uiState.value is HomeScreenUiState.Error) 63 | 64 | } 65 | @Test 66 | fun `When the request is successful the images are loaded into state`() = runTest { 67 | coEvery { imageRepository.generateImages(any()) } returns 68 | NetworkResult.Success(data = ImageResponse(created = "", data = fakeImages)) 69 | 70 | homeScreenViewModel.generateImages() 71 | assert(homeScreenViewModel.uiState.value is HomeScreenUiState.Success) 72 | } 73 | 74 | } 75 | 76 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.diffplug.spotless") version "5.3.0" 3 | id ("org.jetbrains.kotlin.jvm") version "1.9.0" 4 | id("com.google.devtools.ksp") version "1.9.10-1.0.13" 5 | 6 | } 7 | 8 | subprojects{ 9 | apply(plugin = "com.google.devtools.ksp") 10 | apply(plugin = "dagger.hilt.android.plugin") 11 | } 12 | buildscript { 13 | 14 | repositories { 15 | google() 16 | mavenCentral() 17 | 18 | } 19 | dependencies { 20 | classpath ("com.android.tools.build:gradle:7.4.2") 21 | classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10") 22 | classpath ("com.google.dagger:hilt-android-gradle-plugin:2.48") 23 | } 24 | } 25 | 26 | apply(plugin = "com.diffplug.spotless") 27 | spotless { 28 | kotlin { 29 | target("**/*.kt") 30 | licenseHeaderFile( 31 | rootProject.file("${project.rootDir}/spotless/LICENSE.txt"), 32 | "^(package|object|import|interface)" 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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/chege4179/AIImageGeneratorApp/c87ec4f2fa0dfeda90faaa0ac04ac513bc3af5e2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 22 00:21:17 EAT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 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 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 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 = "AI Image Generator" 16 | include (":app") 17 | -------------------------------------------------------------------------------- /spotless/LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright $YEAR AI Image Generator App Peter Chege Mwangi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ --------------------------------------------------------------------------------